Model Context Protocol (MCP)
Learn how to connect to Model Context Protocol (MCP) servers and use their tools with AI SDK Core.
The AI SDK supports connecting to Model Context Protocol (MCP) servers to access their tools, resources, and prompts. This enables your AI applications to discover and use capabilities across various services through a standardized interface.
If you're using OpenAI's Responses API, you can also use the built-in
openai.tools.mcp tool, which provides direct MCP server integration without
needing to convert tools. See the OpenAI provider
documentation for details.
Initializing an MCP Client
We recommend using HTTP transport (like StreamableHTTPClientTransport) for production deployments. The stdio transport should only be used for connecting to local servers as it cannot be deployed to production environments.
Create an MCP client using one of the following transport options:
- HTTP transport (Recommended): Either configure HTTP directly via the client using
transport: { type: 'http', ... }, or use MCP's official TypeScript SDKStreamableHTTPClientTransport - SSE (Server-Sent Events): An alternative HTTP-based transport
stdio: For local development only. Uses standard input/output streams for local MCP servers
The AI SDK MCP client supports both legacy initialization-based protocol
versions and stateless MCP 2026-07-28. The built-in stdio transport probes
with server/discover and automatically falls back to the legacy
initialize handshake for older servers. Custom transports can opt into this
negotiation with supportsProtocolVersionDiscovery: true.
HTTP Transport (Recommended)
For production deployments, we recommend using the HTTP transport. You can configure it directly on the client:
If a legacy MCP server uses Streamable HTTP sessions, you can reattach to a
saved session by restoring both the previous session id and initialize result.
MCP 2026-07-28 is stateless and does not use these options:
When initialInitializeResult is provided, createMCPClient reuses the cached
initialize metadata and does not send another initialize request. When
onSessionExpired is called, the transport has already cleared the session id
and the request still fails with the underlying HTTP error. Retry by creating a
fresh client without initialSessionId or initialInitializeResult.
Set terminateSessionOnClose to false when closing only the local client but
keeping the MCP session available for a later reattach.
Alternatively, you can use StreamableHTTPClientTransport from MCP's official TypeScript SDK:
SSE Transport
SSE provides an alternative HTTP-based transport option. Configure it with a type and url property. You can also provide an authProvider for OAuth:
Stdio Transport (Local Servers)
The stdio transport should only be used for local servers.
The Stdio transport can be imported from either the MCP SDK or the AI SDK:
Custom Transport
You can also bring your own transport by implementing the MCPTransport interface for specific requirements not covered by the standard transports.
The client returned by the createMCPClient function is a
lightweight client intended for use in tool conversion. It currently does not
support all features of the full MCP client, such as automatic session
persistence, resumable streams, and receiving notifications.
Authorization via OAuth is supported when using the AI SDK MCP HTTP or SSE
transports by providing an authProvider.
Shared OAuth Credential Storage
When multiple MCP clients or replicas share OAuth credentials, a rotating refresh
token can be used concurrently. One refresh may save new tokens while another
receives invalid_grant for the old refresh token.
The optional OAuthClientProvider.invalidateCredentials method receives a
second argument identifying the tokens used by a failed refresh:
For scope === 'tokens' with a context, delete the credential only if the stored
access and refresh tokens still match context.tokens. The comparison and
deletion must be atomic, using a conditional database operation or a transaction.
If they no longer match, leave the newer tokens in storage. The SDK retries
authorization and reads the stored tokens again.
The SDK also provides this context when invalidating stored tokens that lack an authorization server pin. Other invalidations can omit the context.
If an authorization-code exchange returns invalid_grant, the SDK propagates
the error without invalidating stored credentials or retrying the rejected code.
A rejected authorization code does not indicate that existing access or refresh
tokens are invalid.
OAuth Authorization Server Validation
When using MCP OAuth in server-side applications, the MCP server can advertise
the OAuth authorization server to use. If you connect to MCP servers outside
your control, implement validateAuthorizationServerURL on your
authProvider to allow only the authorization server origins you trust:
This hook is called before the SDK fetches authorization server metadata, so a rejected URL is not requested. It is optional and does not change existing OAuth flows unless you implement it.
OAuth Callback Issuer Validation
When completing an OAuth authorization callback, pass the callback's iss
parameter to auth as callbackIssuer:
When iss is present, it must exactly match the discovered authorization
server issuer. The authorization code is not exchanged when it does not match.
Dynamic client registration requests include an OAuth application_type. The
client infers native for loopback, localhost, and custom-scheme redirects,
and web for remote HTTP(S) redirects. You can override this by setting
application_type in the provider's clientMetadata.
Dynamic Client Credential Recovery
To let the SDK replace invalid credentials created through dynamic client
registration, implement isClientInformationDynamicallyRegistered on the
OAuth client provider. Return true only when the current client information
came from a dynamic registration response. Persist this provenance alongside
the credentials when the provider is recreated between requests.
When this method is omitted or returns false, client information is treated
as pre-registered and is preserved after invalid_client and
unauthorized_client errors. The SDK does not infer provenance from fields such
as redirect_uris, because pre-registered clients can contain the same
metadata as dynamically registered clients.
Retrying Transient Tool Failures
MCP tool calls can fail for transient transport reasons, such as rate limits,
temporary overload, or gateway timeouts. You can opt into automatic retries for
tools/call requests by passing maxRetries when creating the client:
Retries are disabled by default. Built-in retry matching is intended for
transient HTTP and network failures only. JSON-RPC application errors, such as
invalid tool arguments, are surfaced immediately without retrying. Successful
MCP tool responses with isError: true are also returned to the model without
retrying.
maxRetries applies to tools/call requests, not to client initialization.
The following example identifies potentially transient HTTP initialization
failures using the exported MCPClientError type guard:
This example only considers HTTP statuses. A missing statusCode does not
establish that a failure is transient: configuration errors, protocol errors,
and cancelled requests can also lack an HTTP status. Network and timeout
retries require a separate application policy. OAuth errors such as
UnauthorizedError and MCPClientOAuthError are rethrown by this example.
Only enable retries for MCP tools where retrying is safe. Retrying non-idempotent tools, such as tools that send emails or create records, can duplicate side effects.
Closing the MCP Client
After initialization, you should close the MCP client based on your usage pattern:
- For short-lived usage (e.g., single requests), close the client when the response is finished
- For long-running clients (e.g., command line apps), keep the client open but ensure it's closed when the application terminates
When streaming responses, you can close the client when the LLM response has finished. For example, when using streamText, you should use the onEnd callback:
When generating responses without streaming, you can use try/finally or cleanup functions in your framework:
Using MCP Tools
The client's tools method acts as an adapter between MCP tools and AI SDK tools. It supports two approaches for working with tool schemas:
Schema Discovery
With schema discovery, all tools offered by the server are automatically listed, and input parameter types are inferred based on the schemas provided by the server:
This approach is simpler to implement and automatically stays in sync with server changes. However, you won't have TypeScript type safety during development, and all tools from the server will be loaded
Tool Annotations and Approval
MCP servers can describe tool behavior with annotations such as
readOnlyHint, destructiveHint, idempotentHint, and openWorldHint. The
MCP client exposes these annotations on each tool's metadata.annotations and
on the resulting tool call's toolMetadata.annotations.
Annotations are untrusted, server-provided hints. The MCP client does not turn
them into an approval policy automatically. Applications should combine them
with deterministic controls such as tool allowlists, scoped credentials, and
their own toolApproval policy.
The following conservative policy allows tools to run automatically only when
the server explicitly marks them as read-only. Tools with readOnlyHint: false
or no readOnlyHint require user approval:
See the local tool annotations example for a complete annotated MCP server and application-layer approval configuration. Start the server and client in separate terminals:
Ask the client to read, delete, or create a note. The read-only tool executes
immediately. The destructive and unannotated tools prompt for approval; after
you enter y or n, the client sends the decision back and prints the model's
response.
Schema Definition
For better type safety and control, you can define the tools and their input schemas explicitly in your client code:
This approach provides full TypeScript type safety and IDE autocompletion, letting you catch parameter mismatches during development. When you define schemas, the client only pulls the explicitly defined tools, keeping your application focused on the tools it needs
Typed Tool Outputs
When MCP servers return structuredContent (per the MCP specification), you can define an outputSchema to get typed tool results:
When outputSchema is provided:
- The client extracts
structuredContentfrom the tool result - The output is validated against your schema at runtime
- You get full TypeScript type safety for the result
If the server doesn't return structuredContent, the client falls back to parsing JSON from the text content. If neither is available or validation fails, an error is thrown.
If a server returns structuredContent without the backwards-compatible content field, the client adds a text content block containing the serialized JSON before returning the result. This compatibility behavior handles servers that omit the text mirror recommended by the MCP specification.
Without outputSchema, the tool returns the raw CallToolResult object
containing content and optional isError fields.
Using MCP Resources
According to the MCP specification, resources are application-driven data sources that provide context to the model. Unlike tools (which are model-controlled), your application decides when to fetch and pass resources as context.
The MCP client provides three methods for working with resources:
Listing Resources
List all available resources from the MCP server:
Reading Resource Contents
Read the contents of a specific resource by its URI:
Listing Resource Templates
Resource templates are dynamic URI patterns that allow flexible queries. List all available templates:
Using MCP Completions
MCP servers can provide autocompletion suggestions for prompt arguments and
resource template variables when they advertise the completions capability.
Use complete to ask the server for suggestions based on the current partial
argument value:
For resource templates or prompts with multiple arguments, pass already resolved
values through context.arguments:
If the connected server does not advertise capabilities.completions, the
client throws an MCPClientError.
Using MCP Prompts
MCP Prompts is an experimental feature and may change in the future.
According to the MCP specification, prompts are user-controlled templates that servers expose for clients to list and retrieve with optional arguments.
Listing Prompts
Getting a Prompt
Retrieve prompt messages, optionally passing arguments defined by the server:
Handling Elicitation Requests
Elicitation is a mechanism where MCP servers can request additional information from the client during tool execution. For example, a server might need user input to complete a registration form or confirmation for a sensitive operation.
It is up to the client application to handle elicitation requests properly. The MCP client simply surfaces these requests from the server to your application code.
Enabling Elicitation Support
To enable elicitation, you need to advertise the capability when creating the MCP client:
Registering an Elicitation Handler
Use the onElicitationRequest method to register a handler that will be called when the server requests input:
Elicitation Response Actions
Your handler must return an object with an action field that can be one of:
'accept': User provided the requested information. Must includecontentwith the data.'decline': User chose not to provide the information.'cancel': User cancelled the operation entirely.
Detecting tool-definition drift ("rug pull")
An MCP server sends tool definitions (name, description, input schema) when your
app first connects, and you typically review and approve them at that point.
Nothing in the protocol prevents the server from later serving a different
definition for the same tool name — for example a description carrying injected
instructions, or an input schema widened with an extra field. Because the SDK
uses whatever tools you pass on each call, a mutated definition returned by a
later mcpClient.tools() fetch would be used without any comparison to what was
approved. This is the MCP "rug pull"
class of attack.
The AI SDK provides two functions to pin the approved definitions and detect
changes. fingerprintTools digests the server-controlled, security-relevant
fields of each tool (string description, resolved input schema, and title)
into a stable map of tool name to digest. detectToolDrift diffs two such maps.
Your app owns baseline storage and the response to drift (block, force
re-approval, or alert):
This detects mutation of a tool's description, input schema, or title — the prompt-injection and schema-widening vectors. It cannot detect a behavior/endpoint swap where the name, description, and schema are all unchanged, because the tool runs remotely on the MCP server and that change is invisible to the client. Core stays unopinionated: it does not persist baselines or block calls — those are your app's responsibility.
Examples
You can see MCP in action in the following examples: