Connecting to an MCP Server
To use MCP tools, you first need to create an MCP client using themcpclient.NewClient function.:
Parameters
ctx: The context for the connectionendpoint: The SSE endpoint URL of the MCP server (e.g.,"http://localhost:9001/sse")
Custom Headers
You can also configure the MCP Client to send custom HTTP headers while interacting with the MCP server.Filtering Tools
You can optionally restrict which tools are exposed to the agent using theWithToolFilter(...) option. Only the named tools are surfaced; all others are dropped.
Deferred (Lazy-Loaded) Tools
UseWithDeferredTools(...) to mark tools as deferred. Deferred tools are not added to the LLM’s tool list upfront; instead they are discovered and activated on demand via the ToolSearch meta-tool. This keeps the prompt small when an MCP server exposes a large number of tools.
"*" to defer every tool the server exposes, which saves keeping a list in sync with a server whose catalog changes:
Schema Caching
By default, the client connects to the MCP server to fetch tool schemas on everyListTools call. You can cache schemas to avoid repeated round-trips:
WithCacheTTL(ttl time.Duration)sets how long cached schemas remain valid.WithSchemaCache(cache SchemaCache)injects aSchemaCacheimplementation. When set,ListTools()checks the cache before connecting to the MCP server. Back it with Redis (or a similar shared store) to share cached schemas across multiple pods.
Approval-Required Tools
UseWithApprovalRequiredTools(...) to mark specific MCP tools as requiring human approval before they run. See the Human in the Loop page for the full approval flow.
Transport
You can set the transport to be used for the connection. Supported transports areSSE and Streamable HTTP. If not specified it defaults to SSE
streamable-http, the client also opens the post-init server→client SSE stream. Some servers don’t support that standalone GET stream, and the client then hangs waiting on a stream that never opens. Turn it off for those servers:
Tool Annotations
MCP tools carry whatever behavioural hints their server declared — read-only, destructive, idempotent, open-world — and the SDK converts them into the sameagents.ToolAnnotations shape a local function tool uses. Nothing extra is needed to pick them up, and one permission policy can read both:
readOnlyHint and idempotentHint as bare booleans, so an absent hint arrives as false — which is the spec’s default for both. destructiveHint and openWorldHint stay unset when the server said nothing, leaving the conservative defaults (destructive, open world) to the Is* helpers.
See Tool Annotations for the full table and how to declare them on your own tools.
Progress Notifications
When a tool call is streaming, the client attaches a progress token so the server can sendnotifications/progress for the duration of that call. Those notifications arrive on the run’s chunk stream as tool.progress chunks, exactly like progress emitted by a function tool:
CallID ties each update back to the in-flight call. Total is optional — 0 means unknown. Progress is a live side stream: under the durable runtimes it is published directly rather than journaled, so it is never replayed.
Elicitation
An MCP server can ask the user for something mid-call rather than answering: a form to fill in, or a URL to visit. The SDK maps that onto the same pause the agent already uses for human-in-the-loop approvals — the run ends with an interrupt, and the answer arrives on a later request. The pause is surfaced as anInterrupt whose Mode says what is being asked:
Elicitations carries the server’s own request — its message, and for a form the RequestedSchema describing the fields to collect.
Answering a form
Resume the same way as an approval, but attach the filled form as the resolution’sContent. It must match the RequestedSchema the pause advertised:
accept; a rejection becomes a decline, and its content is dropped rather than delivered — a rejected form has no answer to pass on.
The request id and the server’s own opaque state are carried across the pause in thread state, so the resuming call answers the server’s original question rather than provoking a fresh one. That is what lets a server resume from where it left off instead of redoing the work that preceded the question.