Skip to main content

Bring your own provider

provider takes an AIProvider instance as well as a name, which is how an agent runs against an adapter you wrote - or against a deterministic stand-in in tests, with no API key:
See Providers for the AIProvider contract, including how a tool declaration reaches the model and how tool calls come back.

Attaching an external MCP server

mcpServers gives an agent tools you did not write. The kit connects to the server, reads its tool list, and hands those tools to the model alongside your own - each call running as an ordinary durable step.
string
required
Namespaces this server’s tools. “orders” turns the remote “lookup-order” into “orders__lookup-order”, so two servers offering the same tool stay distinguishable.
McpTransport
required
How to reach the server. { kind: “http”, url } speaks Streamable HTTP.
Record<string, string> | (() => Promise<Record<string, string>>)
Sent with every request. The function form runs inside the durable step, which is where credentials.resolve() is legal.
string[]
Remote tool names to attach, unprefixed. Omitted, the agent gets everything the server offers.
(tool: McpTool) => boolean | ApprovalRule | undefined
Which of this server’s tools park on a human first. A predicate, because you did not author these tools and cannot annotate them one by one. Return a rule to say how the gate is raised as well as whether; return nothing for a tool and it falls to the agent’s own approval default.
The predicate answers per tool, and what it returns places that tool in the same two levels a declared tool goes through: The last row is why the default exists. These are the tools you did not write, attached here and discovered at runtime, so there is no declaration to annotate and no list to enumerate ahead of time. Silence about a tool you did not write is not a decision that it is harmless. Install the MCP SDK alongside the kit - it is an optional peer, so an agent that attaches no server never pulls it:

What it records

Discovery is a durable step because the tool list decides what the model was offered. A server that adds or drops a tool mid-run cannot change what a replay sees, and a fully replayed run opens no connection at all.
Name the tools you want. An allow-list is what stops a server you do not control from widening your agent’s reach by adding a tool - and asking for one the server does not offer fails the run rather than quietly attaching fewer tools than you asked for.

Credentials, approval and bad arguments

A remote tool is still a tool, so everything the kit already does applies to it:
  • Credentials resolve inside the step, so a stored credential works:
  • Approval parks the run before the call, and a denial comes back to the model as that tool’s result. A destructiveHint in the server’s own annotations never gates on its own - hints inform a default, requiresApproval is the boundary.
  • Guardrails validate the model’s arguments against the server’s inputSchema, so a schema you did not write still stops a malformed call reaching a third party.
A remote tool that answers with MCP’s isError fails its step rather than returning the error text to the model. The step then retries under your workflow’s policy and the run records why; memoizing a broken call as a successful result would leave no replay able to get past it.

Listing tools before they run

By default the engine has no record of a tool until a run calls it. toolManifest() projects your tools and mcpServers arrays into workflow’s advisory tools field, so a tool - its name, description, and parameter schema - is listable before it has ever executed:
One array, two readers - toolManifest() reads the same tools/mcpServers you pass to agent(), so the manifest can never silently disagree with what the model actually sees. An attached MCP server appears as an unexpanded group (its name only) rather than a tool list: its tools are discovered at run time, not registration time, so listing them here would mean contacting the server before a run even starts - reintroducing the replay drift durable discovery exists to prevent. Full field reference and the advisory guarantees are in Tool manifest.

Exposing the same tools over MCP

A tool is one definition, and the kit emits it in whichever dialect a surface needs. agent() uses the function-calling dialect; createMcpEmitter() produces the same tools as Model Context Protocol definitions, so the tools your agent uses are the tools an MCP client sees - one source, not two lists that drift.
The emitter produces the definitions a server advertises - the name, description, input schema, and the outputSchema and annotations when a tool declares them. What a call then does is the server’s decision, and stays yours to write.
requiresApproval is never emitted. Approval is Duraton parking your run on your reviewer; it is not something a client on the other end of an MCP connection can honour, and advertising it would read as a promise the protocol cannot keep. Use the low-level tools/list handler as above rather than registerTool: registerTool takes a Zod schema for its input, while a kit tool declares a JSON Schema, which is what tools/list carries on the wire.

Extension points

Two seams, each a closed set of adapters. A new one is a new adapter, not a change to the existing ones.