Menu

AI

Vercel AI SDK MCP Client: Connect MCP Tools to generateText and streamText

Use @ai-sdk/mcp to load MCP server tools into the Vercel AI SDK. Covers HTTP and stdio transports, multi-step calls, approvals from annotations and closing clients.

By: Irina ShvayaNovember 2, 2026

Vercel AI SDK MCP Client: Connect MCP Tools to generateText and streamText

This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.

The Vercel AI SDK talks to MCP servers through createMCPClient from the @ai-sdk/mcp package. You create a client, call tools(), and pass the result to generateText or streamText like any other tool set. The client also reads resources and prompts.

This tutorial connects a local server, runs a multi-step tool call, adds an approval rule based on MCP tool annotations, and covers the closing and transport rules that cause most bugs. Code follows the AI SDK's MCP docs. On 2 November 2026 the client snippets below ran locally with @ai-sdk/mcp 2.0.89, ai 7.0.161 and Node.js 24 against the official @modelcontextprotocol/server-everything 2.1.0 test server. The model call was checked against the docs only.

Key Takeaways

  • Install ai and @ai-sdk/mcp; create a client with createMCPClient({ transport }) and get AI SDK tools with await mcpClient.tools().
  • Use the HTTP transport in production; the docs say stdio "should only be used for connecting to local servers."
  • The client supports both legacy initialize servers and the stateless 2026-07-28 protocol, probing with server/discover first on stdio.
  • Tool annotations such as readOnlyHint are exposed on each tool, but the SDK does not turn them into approvals; you write that policy.
  • Always close the client: in onEnd for streamText, or in finally for generateText.

Prerequisites

  • Node.js (the run above used Node.js 24).
  • A model provider. The docs' examples use plain model strings such as "anthropic/claude-sonnet-5.5"; use whichever model setup your AI SDK project already has.
  • An MCP server. To follow along with zero setup, install the reference server: npm install @modelcontextprotocol/server-everything.

Install

npm install ai @ai-sdk/mcp

Both are on npm: ai and @ai-sdk/mcp.

The docs also mention a shortcut for OpenAI's Responses API: the provider's built-in openai.tools.mcp tool lets OpenAI call a server directly. This tutorial uses @ai-sdk/mcp, which works with any model provider.

Minimal working example

This lists every tool on a local stdio server and then closes the client:

import { createMCPClient } from '@ai-sdk/mcp';
import { StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';

const mcpClient = await createMCPClient({
  transport: new StdioMCPTransport({
    command: 'node',
    args: ['node_modules/@modelcontextprotocol/server-everything/dist/index.js', 'stdio'],
  }),
});

try {
  const tools = await mcpClient.tools();
  console.log(Object.keys(tools));
} finally {
  await mcpClient.close();
}

Run it with node list.mjs. In the local run it printed 22 tool names, starting with echo, get-annotated-message and get-env. You can also call a tool directly, without a model:

const result = await tools['echo'].execute(
  { message: 'hello' },
  { messages: [], toolCallId: 'test-1' },
);

That returned a standard MCP result whose text content was Echo: hello.

Let the model use the tools

Pass the tools to generateText and allow more than one step. Without stopWhen, the tool calling docs say a call defaults to isStepCount(1), so the model can call a tool but never sees the result.

import { generateText, isStepCount } from 'ai';
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';

let mcpClient: MCPClient | undefined;

try {
  mcpClient = await createMCPClient({
    transport: { type: 'http', url: 'https://your-server.com/mcp' },
  });
  const tools = await mcpClient.tools();

  const { text } = await generateText({
    model: 'anthropic/claude-sonnet-5.5',
    tools,
    stopWhen: isStepCount(5),
    prompt: 'Use the available tools to answer: what does the welcome note say?',
  });
  console.log(text);
} finally {
  await mcpClient?.close();
}

Pick a transport

TransportHowWhen
HTTP (recommended)transport: { type: 'http', url, headers }Production and any remote server
SSEtransport: { type: 'sse', url }Legacy servers only
stdionew StdioMCPTransport({ command, args })Local development only

The docs recommend HTTP "for production deployments" and say stdio "cannot be deployed to production environments." That matters on Vercel and other serverless hosts, where you cannot keep a child process alive between requests. Our SSE vs Streamable HTTP page explains why SSE is legacy.

The HTTP transport takes optional headers, an OAuth authProvider, and a redirect setting. Redirects default to 'error' "to prevent SSRF," so only set 'follow' for servers you trust.

Protocol versions

The docs say the client "supports both legacy initialization-based protocol versions and stateless MCP 2026-07-28." The stdio transport probes with server/discover and falls back to initialize for older servers. If an endpoint needs the legacy handshake, set protocolVersionDiscovery: false on createMCPClient. Our spec version explorer shows what changed between versions.

Add a tool

Tools come from the server. Add one there and the next tools() call returns it, because schema discovery lists everything the server offers.

If you want type safety, define schemas on the client instead:

import { z } from 'zod';

const tools = await mcpClient.tools({
  schemas: {
    'get-data': {
      inputSchema: z.object({
        query: z.string().describe('The data query'),
        format: z.enum(['json', 'text']).optional(),
      }),
    },
  },
});

The docs note that with schemas, "the client only pulls the explicitly defined tools." That doubles as an allow list. You can also add an outputSchema to validate a tool's structuredContent at runtime.

Approvals from tool annotations

MCP servers can mark tools with hints like readOnlyHint and destructiveHint. In the local run, the echo tool's metadata carried readOnlyHint: true and destructiveHint: false.

The docs are firm: annotations "are untrusted, server-provided hints," and the client "does not turn them into an approval policy automatically." Their conservative example auto-runs only tools marked read-only:

import { streamText } from 'ai';
import { type McpProviderMetadata } from '@ai-sdk/mcp';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  tools,
  toolApproval: ({ toolCall }) => {
    const annotations = (
      toolCall.toolMetadata as McpProviderMetadata | undefined
    )?.annotations;

    return annotations?.readOnlyHint === true
      ? 'not-applicable'
      : {
          type: 'user-approval',
          reason: 'The MCP server does not mark this tool as read-only.',
        };
  },
  prompt,
  onEnd: async () => {
    await mcpClient.close();
  },
});

Combine this with a short allow list and scoped credentials. Our tool annotations guide explains why a hostile server could mislabel a tool.

Retries

Set maxRetries on createMCPClient to retry tools/call requests on transient HTTP failures. Retries are off by default. The docs warn that retrying "non-idempotent tools, such as tools that send emails or create records, can duplicate side effects," so enable it only where repeats are safe.

Test with the MCP Inspector

Check the server on its own before you debug the app:

npx @modelcontextprotocol/inspector --cli https://your-server.com/mcp --transport http --method tools/list

Our MCP Inspector lesson shows tool calls, exit codes and CI checks.

Deploy notes

  • Close clients. For streamText, close in onEnd. For generateText, use try/finally. Leaking clients leaks connections.
  • Short-lived vs long-lived. The docs suggest closing after each response for request handlers, and keeping one client open for the life of a CLI app.
  • OAuth on servers you do not control. Implement validateAuthorizationServerURL on your authProvider to allow only authorization servers you trust.
  • Hosting your own server. If the MCP server also lives on Vercel, see our Vercel deployment guide.

Pitfalls

  • No stopWhen. The model calls a tool and the run ends without an answer.
  • stdio in production. Works on your laptop, fails on serverless.
  • Old experimental_createMCPClient imports. createMCPClient is the documented name; the experimental alias still exported in 2.0.89, but follow the docs.
  • Expecting full client features. The docs call this "a lightweight client intended for use in tool conversion" without automatic session persistence, resumable streams or notifications.

Next step

If MCP is new to you, the free MCP course covers the protocol from first principles. If you need an MCP server built for your AI SDK app, with OAuth and safe tool design, see our MCP development service.

More in the MCP Guide

Free tools: server.json Validator · MCP Error Code Lookup

Latest research: State of MCP: October 2026

Frequently asked questions

Is the AI SDK MCP client stable?

Yes. createMCPClient is exported from @ai-sdk/mcp without the experimental_ prefix, and the docs use that name throughout. The package was at version 2.0.89 on 2 November 2026.

Can I use the official MCP TypeScript SDK transport instead?

Yes. The docs show passing StreamableHTTPClientTransport from @modelcontextprotocol/sdk/client/streamableHttp.js to createMCPClient, and StdioClientTransport for stdio.

Does the AI SDK MCP client support resources and prompts?

Yes. The client has listResources(), readResource({ uri }) and listResourceTemplates(). Resources are application-driven, so your code decides when to fetch them and pass them to the model.

Why does my tool call fail with an issuer mismatch during OAuth?

Pre-registered confidential clients must set issuer on the credentials returned by clientInformation(), and it must exactly match the discovered issuer. The SDK rejects a mismatch before sending credentials.

You Might Also like to Read

Get a FREE Website Audit

Enter your website and get a 20–30 page SEO, GEO, AEO & CRO audit within 24 hours. Completely free.

Don't have a site yet? Get in touch →

Ready to speak with a marketing expert?
Give us a call! (916) 866-7893

Websites Launched
1407
Websites Launched
Marketing Experts
29
Marketing Experts
Hours of Dedicated Work
204533
Hours of Dedicated Work
Irina S.Benjamin G.Jeanette M.David G.