> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nimbleway.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mastra

> Give a Mastra agent deep research on the live web. Start a Nimble Web Search Agent run in one turn, collect the cited answer in another, from any process.

## Overview

Give a Mastra agent the ability to research the live web. Ask a question in one sentence. Get back a written answer with sources, per-claim citations, and a confidence grade.

The [`@nimble-way/mastra`](https://www.npmjs.com/package/@nimble-way/mastra) package brings [Nimble Web Search Agent](/nimble-sdk/web-search-agents/overview) research into [Mastra](https://mastra.ai) as three typed tools. Nimble plans the research, searches, and opens pages. It reads JavaScript-heavy sites, cross-checks what it finds, and returns a cited answer. Your agent sends a task and collects the result.

That research takes minutes, not milliseconds. A `high` effort run takes 5 to 15 minutes, far longer than a single agent turn. So by default none of the three tools block: the run keeps going server-side and the `runId` is the only handle needed to collect it. Bounded waiting is opt-in, and the `wait` option is the one way to make the result tool block.

* **A cited answer from the live open web**: prose or JSON, with sources, per-claim citations with verbatim excerpts, and confidence grades, read at run time.
* **Gets cheaper the more you run it**: a reused Web Search Agent remembers which sources worked. Recurring research runs up to 50% cheaper.
* **Start now, collect later**: start the run in one turn, read the result in another turn, another process, or another day.
* **The model never sees your credential**: the API key is resolved server-side and sent only as a request header.

### Which path to take

Two ways to get Nimble research into a Mastra agent. Pick one in ten seconds.

<CardGroup cols={2}>
  <Card title="Nimble MCP Server" icon="plug" href="#using-the-nimble-mcp-server-instead">
    The fastest start. No package to install. Tools arrive over the wire and the model calls them. Gives you the full Nimble tool surface, not just research.
  </Card>

  <Card title="This package" icon="cube" href="#quick-start">
    Typed tools, a developer-controlled effort ceiling, server-side output schemas, and explicit create-failure semantics. Best when research is the job and cost control matters.
  </Card>
</CardGroup>

## Prerequisites

<AccordionGroup>
  <Accordion title="Node.js 22.13 or later" icon="node-js">
    The package declares `engines.node >= 22.13.0`.
  </Accordion>

  <Accordion title="@mastra/core 1.56.0 or later, and zod" icon="cube">
    Peer dependencies, supplied by your project: `@mastra/core` `^1.56.0` and `zod` `^3.25.1 || ^4.0.0`.

    The `zod` floor is `3.25.1` rather than `3.25.0` for a reason. `3.25.0` declares a `./v4` export whose target is missing from its published tarball, and Mastra's schema layer resolves that path, so the combination fails at import. The declared range excludes it, so npm refuses the install rather than letting it fail at run time.

    Verified against the published `0.1.1`: `zod` `3.25.1`, `3.25.76`, and `4.0.0` all work with `@mastra/core` `1.56.0`, as does `4.5.4` with `1.64.0`.
  </Accordion>

  <Accordion title="NIMBLE_API_KEY (server-side only)" icon="key">
    Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys). See [Handling the API key](#handling-the-api-key) for the rules that keep it out of model input.
  </Accordion>

  <Accordion title="A model provider credential" icon="brain">
    Mastra resolves the agent's `model` at run time, so the provider needs its own key in the environment. The examples here use `anthropic/claude-sonnet-4-6`, which reads `ANTHROPIC_API_KEY`.

    This is separate from `NIMBLE_API_KEY`. Nimble does the research; your model decides when to ask for it.
  </Accordion>

  <Accordion title="A Web Search Agent ID" icon="robot">
    Create a Web Search Agent once, then reuse it. Its ID looks like `wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9`.

    A reused agent keeps its memory, so it gets faster, more accurate, and up to 50% cheaper on recurring work. Create one in the [dashboard](https://online.nimbleway.com) or with `POST /v2/agents`.

    The agent is developer configuration, not model input. The model can never choose which agent to run.
  </Accordion>
</AccordionGroup>

## Quick Start

<Steps>
  <Step title="Install">
    ```bash theme={"system"}
    npm install @nimble-way/mastra@0.1.1 @mastra/core zod
    ```

    `0.1.1` is the current release. It is published from CI over OIDC and carries a SLSA provenance attestation, so the artifact on npm can be traced to the commit and workflow that built it.
  </Step>

  <Step title="Set the environment">
    ```bash theme={"system"}
    export NIMBLE_API_KEY="your-key"
    export NIMBLE_AGENT_ID="wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9"
    ```

    Both are read server-side at execute time. Never put either in a committed file.
  </Step>

  <Step title="Attach the tools to an agent">
    ```ts src/agents/researcher.ts theme={"system"}
    import { Agent } from "@mastra/core/agent";
    import { createNimbleAgentTools } from "@nimble-way/mastra";

    export const researcher = new Agent({
      id: "researcher",
      name: "Researcher",
      instructions:
        "For deep research questions, start a Nimble agent run, tell the user the " +
        "runId, and fetch the result when they ask for it later.",
      model: "anthropic/claude-sonnet-4-6",
      tools: createNimbleAgentTools(),
    });
    ```

    `createNimbleAgentTools()` returns all three tools sharing one credential and one agent configuration. Pass `{ agentId, apiKey }` to override the environment.
  </Step>

  <Step title="Ask it something">
    ```ts theme={"system"}
    const result = await researcher.generate(
      "Summarize how the EU AI Act is being enforced in 2026, with sources.",
    );

    console.log(result.text);
    ```

    The model calls `nimble-agent-start-run` and answers in seconds with a `task_run_…` ID, which it reports in `result.text`. The research keeps going server-side for minutes. Collect it later with the same ID.
  </Step>
</Steps>

### Example Response

A completed run, as the result tool returns it. This is the prose shape. An agent with a stored output schema returns `"type": "json"` with a `json` field instead of `text`, and its claims carry `path` rather than `callout`:

```json theme={"system"}
{
  "ready": true,
  "runId": "task_run_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
  "agentId": "wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "status": "completed",
  "effort": "low",
  "createdAt": "2026-09-08T10:00:00Z",
  "startedAt": "2026-09-08T10:00:01Z",
  "completedAt": "2026-09-08T10:02:00Z",
  "output": {
    "type": "text",
    "text": "The answer, with citation markers [1].",
    "trust": {
      "confidence": "high",
      "reasoning": "Two primary sources agree.",
      "sources": [
        {
          "url": "https://example.com/a",
          "type": "primary",
          "source_category": "official",
          "title": "Official doc"
        }
      ],
      "claims": [
        {
          "callout": 1,
          "confidence": "high",
          "reasoning": "Stated directly in the source.",
          "citations": [
            {
              "url": "https://example.com/a",
              "excerpts": ["verbatim excerpt here"],
              "source_type": "primary"
            }
          ]
        }
      ]
    }
  }
}
```

## How it works

<Steps>
  <Step title="The model starts a run">
    `nimble-agent-start-run` sends `POST /v2/agents/{agent_id}/runs` and returns immediately with a real `task_run_…` ID. The turn is never blocked for the run's duration.
  </Step>

  <Step title="The run proceeds server-side">
    Nimble plans the research, searches, opens pages, and cross-checks findings. Minutes, not milliseconds.
  </Step>

  <Step title="Anything with the runId can check it">
    `nimble-agent-run-status` returns an instant snapshot. `nimble-agent-run-result` returns the answer, or `{ ready: false }` while the run is still working.
  </Step>

  <Step title="The answer arrives with its evidence">
    A completed run returns prose or structured JSON, plus `trust`: sources, per-claim citations with verbatim excerpts, and confidence grades.
  </Step>
</Steps>

### The three tools

| Tool id                   | Model-facing input                     | What it returns                                                      |
| ------------------------- | -------------------------------------- | -------------------------------------------------------------------- |
| `nimble-agent-start-run`  | `task` (required), `effort` (optional) | `runId`, `agentId`, `interactionId`, `status`, `effort`, `createdAt` |
| `nimble-agent-run-status` | `runId`                                | `status`, `isActive`, and run timestamps. Instant, never waits       |
| `nimble-agent-run-result` | `runId`                                | The answer with `trust`, or `{ ready: false }` while still working   |

The factory returns them keyed `nimbleAgentStartRun`, `nimbleAgentRunStatus`, and `nimbleAgentRunResult`.

Those three inputs are the complete model-facing surface. The credential, the agent, the effort ceiling, the output schema, and the source boundary are all developer configuration.

### The resumable lifecycle

The `runId` is the only handle. Any process configured with the same agent can check or collect a run it did not start.

```ts theme={"system"}
// Turn 1 - the agent starts a run and answers immediately:
//   start  -> { runId: "task_run_…", status: "queued", … }

// Turn 2 (seconds or hours later, same or a different process):
//   status -> { status: "running", isActive: true, … }

// Turn 3:
//   result -> { ready: false, status: "running", … }   // still working, not an error
//   result -> { ready: true, status: "completed", output: { type: "text", text, trust } }
```

## Parameters

<AccordionGroup>
  <Accordion title="agentId" icon="robot">
    The Web Search Agent to run, for example `wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9`. Defaults to `process.env.NIMBLE_AGENT_ID`. Resolved at execute time, so the model can never choose the agent.

    Missing it raises `NimbleConfigError: Missing Nimble agent id`.
  </Accordion>

  <Accordion title="apiKey" icon="key">
    Nimble API key. Defaults to `process.env.NIMBLE_API_KEY`. Read server-side at execute time and sent only as a request header.

    Missing it raises `NimbleConfigError: Missing Nimble API key`.
  </Accordion>

  <Accordion title="effort and effortCap" icon="gauge">
    `effort` is the tier used when the model does not choose one. Leave it unset and the agent instance's own configured default applies, which is the recommendation.

    `effortCap` is the ceiling on what the *model* may request. It defaults to `high`, so a model cannot unilaterally reach the `x-high` and `max` cost tiers. A developer-set `effort` is not clamped.

    See [effort tiers](/nimble-sdk/web-search-agents/efforts) for what each tier does and costs.

    Verified behaviour, reading the request body the package sends:

    | Configuration                          | Model requests | Sent to the API  |
    | -------------------------------------- | -------------- | ---------------- |
    | default cap (`high`)                   | `max`          | `high`           |
    | default cap (`high`)                   | `x-high`       | `high`           |
    | `effortCap: "medium"`                  | `max`          | `medium`         |
    | `effortCap: "medium"`                  | `low`          | `low`            |
    | `effort: "max"`, `effortCap: "medium"` | nothing        | `max`            |
    | nothing set                            | nothing        | `effort` omitted |

    Those are the request bodies the package sends. `max` is a custom-budget tier that is [coming soon](/nimble-sdk/web-search-agents/efforts), so the API may reject it even though the package forwards it. Ask for `x-high` if you want the top available tier now.
  </Accordion>

  <Accordion title="outputSchema" icon="brackets-curly">
    A JSON Schema sent as the Agent API `output_schema`. Use it when the run must return structured data.

    Keep this in developer configuration. Do not ask the model to author JSON, source URLs, or excerpts in its prompt.

    ```ts theme={"system"}
    import { nimbleAgentStartRunTool } from "@nimble-way/mastra";

    const startResearch = nimbleAgentStartRunTool({
      outputSchema: {
        type: "object",
        required: ["facts", "recommendation"],
        properties: {
          facts: { type: "array", items: { type: "object" } },
          recommendation: { type: "string" },
        },
      },
    });
    ```

    A completed run then returns `output.type: "json"` with `output.json` instead of `output.text`.
  </Accordion>

  <Accordion title="sources" icon="filter">
    Server-enforced source guidance, sent as the Agent API `sources`.

    ```ts theme={"system"}
    import { nimbleAgentStartRunTool } from "@nimble-way/mastra";

    const startResearch = nimbleAgentStartRunTool({
      sources: {
        allow: [
          { title: "Official documentation", domains: ["docs.example.com"] },
          { title: "Official repository", domains: ["github.com"] },
        ],
      },
    });
    ```

    Rejected before the run is created: an empty `allow`, a group with a blank `title`, a group with an empty `domains` array, and a blank domain string.
  </Accordion>

  <Accordion title="wait" icon="clock">
    Opt-in bounded waiting on the result tool. Absent, which is the default, the tool never blocks.

    ```ts theme={"system"}
    import { nimbleAgentRunResultTool } from "@nimble-way/mastra";

    const result = nimbleAgentRunResultTool({
      wait: { pollIntervalMs: 10_000, timeoutMs: 600_000 },
    });
    ```

    Defaults are `timeoutMs: 300_000` and `pollIntervalMs: 10_000`, with a 100 ms floor. Runs take minutes, so polling faster only spends requests. Waiting respects Mastra's per-call `AbortSignal`.

    When the timeout elapses the tool returns `{ ready: false }` and the run keeps going server-side. A bounded wait never invents a terminal status: it returns the last authoritative one. If the initial status request itself reaches the deadline, `status` is `unknown` and activity is omitted, because no lifecycle snapshot was received.
  </Accordion>

  <Accordion title="clientOptions" icon="sliders">
    Options forwarded to the Nimble client: `baseURL`, `fetch`, `timeout`, and `maxRetries`.

    `maxRetries` applies to status and result reads only. Run creation is always sent with `maxRetries: 0`, whatever this value is.
  </Accordion>
</AccordionGroup>

### Per-tool configuration

Individual factories are exported when the three tools need different settings:

```ts theme={"system"}
import {
  nimbleAgentStartRunTool,
  nimbleAgentRunStatusTool,
  nimbleAgentRunResultTool,
} from "@nimble-way/mastra";

const tools = {
  startResearch: nimbleAgentStartRunTool({ effortCap: "medium" }),
  researchStatus: nimbleAgentRunStatusTool(),
  researchResult: nimbleAgentRunResultTool({ wait: { timeoutMs: 600_000 } }),
};
```

## A long-running research example

This is the shape the package is built for. The run outlives the turn that started it.

```ts src/research.ts theme={"system"}
import { Agent } from "@mastra/core/agent";
import { createNimbleAgentTools } from "@nimble-way/mastra";

export const researcher = new Agent({
  id: "researcher",
  name: "Researcher",
  instructions:
    "Start a Nimble run for deep research questions and report the runId to the " +
    "user. When the user asks for the answer, fetch the result by runId. If the " +
    "result is not ready, say so and keep the runId. Never start a second run " +
    "for a task that already has a runId. If starting a run fails without " +
    "returning a runId, report the failure and stop. Do not try again.",
  model: "anthropic/claude-sonnet-4-6",
  tools: createNimbleAgentTools({ effortCap: "high" }),
});

// Turn 1: the model starts the run and answers in seconds.
const started = await researcher.generate(
  "Summarize how the EU AI Act is being enforced in 2026, with sources.",
);
console.log(started.text); // carries the task_run_… id  persist it

// Turn 2, minutes later, in the same process or a different one:
const collected = await researcher.generate(
  "Fetch the result for run task_run_9f8e7d6c5b4a39281706f5e4d3c2b1a0.",
);
console.log(collected.text);
```

Persist the `runId` wherever your application keeps state. It is the only handle needed, and it stays valid across processes and restarts.

<Warning>
  Keep the "never start a second run" clause in the instructions. A bounded `wait` can expire before a `high` run completes, and the result tool returns `{ ready: false }` whenever it does. That is expected, not a failure. Without the clause, restarting is an available response to a not-ready result, and each restart is a new billed run. Nothing in `effortCap` limits how many runs a model may create.
</Warning>

<Note>
  The three tools are built for the model to call. To drive the lifecycle yourself, server-side and without a model in the loop, use the exported `createNimbleClient(apiKey)` and call `nimble.agents.runs.create`, `.get`, and `.result` directly.
</Note>

## Citations and trust metadata

Every completed run carries a `trust` object:

* `confidence` and `reasoning`: the run's own grade of its answer, and why.
* `sources`: every source used, with `type` and `source_category`.
* `claims`: per-claim confidence, plus `citations` carrying the `url` and the **verbatim excerpts** the claim rests on. In a prose answer, a `callout` number ties the claim to its marker in the text. Under an [output schema](#parameters) there is no prose, so claims carry a `path` instead: the JSON path of the value they support. Read whichever is present, not `callout` alone.

`trust` is passed through verbatim. `snake_case` field names are preserved and unknown future fields are kept, so nothing is silently dropped and prose citation markers stay aligned with the text.

<Warning>
  Trust-provided source URLs, claims, citations, and excerpts are the authoritative record. Render them from `trust`. Never ask the model to restate them as its own output fields, and never treat a model-authored URL as a citation.
</Warning>

## Handling the API key

`NIMBLE_API_KEY` is a server-side credential. The package resolves it at execute time and sends it only as a request header.

Verified against the published package, reading the requests it actually sends: the key appears in `Authorization: Bearer …` and nowhere else. It is absent from all three tool input schemas, absent from tool outputs, and scrubbed from error messages.

<Warning>
  The key must never appear in a model schema, a tool input, a tool output, client state, a log line, a prompt, or an example. The model's entire input surface is `task`, `effort`, and `runId`. Keep it that way.

  Do not pass the key through a URL. A credential in a URL leaks into server access logs, browser history, and `Referer` headers sent to third parties.
</Warning>

If you inject your own client with `client`, attribution headers and credential handling become your concern. Pass the matching `apiKey` too, or server error details are withheld, because the package cannot prove they are safe to expose.

## Failure and not-ready behaviour

An active run is never an error. Two cases matter, and the package treats them differently on purpose.

<AccordionGroup>
  <Accordion title="A run that is still working" icon="hourglass">
    The result tool returns `{ ready: false }` with the run's `status` and `isActive`. Not an error. Check again later.

    **Branch on `ready`, never on `status`.** A not-ready payload can carry any status, including a terminal one, and it never carries `output`. The same HTTP 409 on a completed run reports differently depending on `wait`:

    | Configuration | Returned                                                              |
    | ------------- | --------------------------------------------------------------------- |
    | no `wait`     | `ready: false`, `status: "running"`, `isActive: true`                 |
    | with `wait`   | `ready: false`, `status: "completed"`, `isActive: false`, no `output` |

    So `if (status === "completed") render(output.text)` throws on a payload that is entirely legal. A `status` reported after a `wait` deadline is also the last snapshot taken rather than a fresh read, so a run that failed after that snapshot can still report `running`. Confirm with the status tool, which never waits, before treating a run as alive.
  </Accordion>

  <Accordion title="A run that terminally failed or was cancelled" icon="triangle-exclamation">
    Throws `NimbleAgentRunError` with `reason: "failed"`, the server's message, and the `runId` preserved so the run can still be inspected or reported.

    Verified message shape: `Nimble agent run task_run_… failed: <server message>`.
  </Accordion>

  <Accordion title="Run creation failed, definitely" icon="circle-xmark">
    A definite 4xx such as 429 gives `createOutcome: "not-created"` and `reason: "request"`. No run exists. Retry only after the underlying condition clears.
  </Accordion>

  <Accordion title="Run creation failed, ambiguously" icon="circle-question">
    A timeout, 408, 409, 5xx, or a dropped connection gives `createOutcome: "unknown"`. The run **may** have been created server-side.

    **An ambiguous create is never retried automatically.** Run creation is billed and not idempotent, so the POST is sent once with `maxRetries: 0`. The transient-retry policy is disabled for creation only. Status and result reads keep normal retries.

    Reconcile before creating again: list recent runs for the agent, or check the dashboard. An automatic retry here bills a second run.

    The thrown error carries **no `runId`**, because none was returned, so a run that was created cannot be reconciled by ID. Reconcile by agent and timestamp instead.

    `maxRetries: 0` only removes the *transport* retry. A model that is told to retry a failed start reinstates it one layer up, and the package cannot tell that apart from a new task. Keep the "do not try again" clause in the [example instructions](#a-long-running-research-example).
  </Accordion>

  <Accordion title="An out-of-contract payload" icon="file-circle-exclamation">
    Surfaces as `reason: "protocol"` on `NimbleAgentRunError`, not a `TypeError`.
  </Accordion>
</AccordionGroup>

## Mastra background tasks

Mastra can run a tool call as a [background task](https://mastra.ai/docs/long-running-agents/background-tasks), so the agent turn returns before the tool finishes. This is optional. By default the Nimble tools already return immediately. Reach for it when you want Mastra to hold the thread open and re-invoke the agent once the answer lands.

Enable it on the Mastra instance, then opt the tool in:

```ts theme={"system"}
import { Mastra } from "@mastra/core";

export const mastra = new Mastra({
  agents: { researcher },
  backgroundTasks: {
    enabled: true,
    globalConcurrency: 10,
    perAgentConcurrency: 5,
  },
});
```

With `untilIdle`, the stream stays open until the background task completes and the agent is re-invoked with the result:

```ts theme={"system"}
const stream = await researcher.stream("Research the EU AI Act enforcement record", {
  memory: { thread: "t1", resource: "u1" },
  untilIdle: true,
});
```

You can also look a task up directly:

```ts theme={"system"}
const task = await mastra.backgroundTaskManager?.getTask(taskId);
const listed = await mastra.backgroundTaskManager?.listTasks({
  status: "running",
  agentId: "researcher",
});
const tasks = listed?.tasks ?? [];
```

<Warning>
  Mastra's `taskId` and Nimble's `runId` are different identifiers for different things. The `taskId` tracks Mastra's local execution of the tool call. The `runId` is the research run itself, and it is the only handle that survives a restart, a redeploy, or a move to another process.

  Persist and surface the `runId`. A Mastra `taskId` cannot collect a Nimble run, and a task that fails or is evicted does not stop or reclaim the run it started.
</Warning>

## Nimble research or Mastra's built-in web search

Mastra ships its own `webSearchTool`. It is not an alternative to Nimble research, and the difference is worth ten seconds of thought.

|                  | Mastra `webSearchTool`                      | Nimble Web Search Agent run                                            |
| ---------------- | ------------------------------------------- | ---------------------------------------------------------------------- |
| Who searches     | The active model provider                   | Nimble, independent of your model                                      |
| Provider support | OpenAI, Anthropic, Google, and xAI only     | Any model, including local and open-weight                             |
| Shape            | One in-turn search, milliseconds to seconds | A planned multi-step run, minutes                                      |
| Evidence         | Whatever the provider returns               | Sources, per-claim citations with verbatim excerpts, confidence grades |
| Reuse            | None                                        | A reused agent keeps memory across runs                                |
| Control          | Provider-defined                            | Developer-set effort ceiling, `output_schema`, source allowlist        |

`webSearchTool` resolves the active model at runtime and delegates to that provider's own search. When the provider has none, it throws a `MastraError` with the id `WEB_SEARCH_UNSUPPORTED_PROVIDER`.

Reach for `webSearchTool` for a quick in-turn lookup on a supported provider. Reach for a Nimble run when the answer has to be researched rather than recalled, has to carry its evidence, or has to be reproducible across models. They coexist: attach both and let the instructions route between them.

## Using the Nimble MCP Server instead

If you want Nimble tools in a Mastra agent without adding a dependency, connect the [Nimble MCP Server](/integrations/mcp-server/mcp-server) over Streamable HTTP:

```ts theme={"system"}
import { MCPClient } from "@mastra/mcp";

const mcp = new MCPClient({
  servers: {
    nimble: {
      url: new URL("https://mcp.nimbleway.com/mcp"),
      requestInit: {
        headers: { Authorization: `Bearer ${process.env.NIMBLE_API_KEY}` },
      },
    },
  },
});

const tools = await mcp.listTools();
```

<Warning>
  Pass the credential in the `Authorization` header, through `requestInit`, never in the URL. A key in a URL leaks into server access logs, browser history, and `Referer` headers sent to third parties. Header values are not logged the same way.
</Warning>

This is the shortest path, and it gives you the whole Nimble tool surface rather than research alone. What it does not give you is the package's developer-side controls. Those are the effort ceiling that stops a model reaching `max`, the server-enforced output schema and source allowlist, and the explicit `not-created` against `unknown` distinction on a failed create.

Choose the MCP server for an interactive agent that needs breadth. Choose the package when research is the job and you need those controls.

The MCP server page covers the full transport options, the authentication options including OAuth, and the complete tool table. This section does not repeat them.

## Attribution

Every request the package makes carries `X-Client-Source: mastra`. The package sets this itself, on run creation and on every status and result read. There is nothing to configure, and nothing for you to set.

The value is a constant. It carries no credential and no user-identifying data.

The one exception is an injected `client`. The package then makes no requests of its own, so attribution travels only if your client sets the header.

## Next Steps

<CardGroup cols={2}>
  <Card title="Web Search Agent" icon="robot" href="/nimble-sdk/web-search-agents/overview">
    What a Web Search Agent is, how effort tiers work, and how agent memory makes recurring research cheaper.
  </Card>

  <Card title="Nimble MCP Server" icon="plug" href="/integrations/mcp-server/mcp-server">
    Transports, the full authentication options including OAuth, and the complete tool table.
  </Card>

  <Card title="Trust" icon="shield-check" href="/nimble-sdk/web-search-agents/trust">
    How sources, per-claim citations, excerpts, and confidence grades are produced.
  </Card>

  <Card title="Package on npm" icon="npm" href="https://www.npmjs.com/package/@nimble-way/mastra">
    Release notes, the typed exports, and the changelog.
  </Card>
</CardGroup>
