Skip to main content

Model Context Protocol (MCP)

The AI SDK supports connecting to Model Context Protocol (MCP) servers to access their tools, resources, and prompts. MCP enables your AI applications to discover and use capabilities across various services through a standardized interface.

What is MCP?

Model Context Protocol (MCP) is an open standard that allows AI applications to:
  • Discover tools from external servers dynamically
  • Access resources like files, databases, and APIs
  • Use prompts defined by service providers
  • Integrate services without custom integrations

When to Use MCP

MCP is best suited for:
  • Rapid development: Quickly prototype with external tools
  • User-provided tools: Allow users to bring their own MCP servers
  • Dynamic tool discovery: Tools that change frequently
  • Third-party integrations: Connect to services with MCP support

AI SDK Tools vs MCP Tools

For production applications, prefer AI SDK tools for full control and type safety:
AspectAI SDK ToolsMCP Tools
Type SafetyFull static typingRuntime discovery
PerformanceSame process (fast)Network overhead
ControlFull control over prompts/schemasServer-controlled
Best ForProduction appsDevelopment, user tools

Installation

npm install @ai-sdk/mcp
Optionally install the MCP SDK for additional transports:
npm install @modelcontextprotocol/sdk

Creating an MCP Client

For production deployments, use HTTP transport:
import { createMCPClient } from '@ai-sdk/mcp';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
    
    // Optional: Add authentication
    headers: {
      Authorization: 'Bearer your-api-key',
    },
  },
});

With OAuth

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

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

SSE Transport

Server-Sent Events provide an alternative HTTP-based transport:
import { createMCPClient } from '@ai-sdk/mcp';

const mcpClient = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'https://my-server.com/sse',
    headers: {
      Authorization: 'Bearer my-api-key',
    },
  },
});

Stdio Transport (Local Only)

Use stdio only for local development. It cannot be deployed to production.
import { createMCPClient } from '@ai-sdk/mcp';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

const mcpClient = await createMCPClient({
  transport: new StdioClientTransport({
    command: 'node',
    args: ['path/to/server.js'],
  }),
});

Using MCP Tools

Schema Discovery

Automatically load all tools from the MCP server:
import { createMCPClient } from '@ai-sdk/mcp';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://weather-mcp.example.com',
  },
});

const tools = await mcpClient.tools();

const result = await generateText({
  model: openai('gpt-5'),
  tools,
  prompt: 'What is the weather in Brooklyn, New York?',
});

console.log(result.text);

await mcpClient.close();

Schema Definition

Define schemas explicitly for type safety:
import { createMCPClient } from '@ai-sdk/mcp';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://weather-mcp.example.com',
  },
});

const tools = await mcpClient.tools({
  schemas: {
    'get-weather': {
      inputSchema: z.object({
        location: z.string().describe('City and country'),
        units: z.enum(['celsius', 'fahrenheit']).optional(),
      }),
    },
  },
});

const result = await generateText({
  model: openai('gpt-5'),
  tools,
  prompt: 'What is the weather in Tokyo?',
});

console.log(result.text);

await mcpClient.close();

Typed Tool Outputs

Define output schemas for type-safe results:
import { createMCPClient } from '@ai-sdk/mcp';
import { z } from 'zod';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://weather-mcp.example.com',
  },
});

const tools = await mcpClient.tools({
  schemas: {
    'get-weather': {
      inputSchema: z.object({
        location: z.string(),
      }),
      // Define output schema for typed results
      outputSchema: z.object({
        temperature: z.number(),
        condition: z.string(),
        humidity: z.number(),
      }),
    },
  },
});

// Execute tool directly with typed result
const result = await tools['get-weather'].execute(
  { location: 'New York' },
  { messages: [], toolCallId: 'weather-1' }
);

console.log(`Temperature: ${result.temperature}°C`);
console.log(`Condition: ${result.condition}`);
console.log(`Humidity: ${result.humidity}%`);
Without outputSchema, the tool returns the raw CallToolResult object. With outputSchema, you get validated, typed data.

Closing the Client

Short-Lived Usage

Close after the response completes:
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

const mcpClient = await createMCPClient({ /* ... */ });
const tools = await mcpClient.tools();

const result = streamText({
  model: openai('gpt-5'),
  tools,
  prompt: 'What is the weather?',
  onFinish: async () => {
    await mcpClient.close();
  },
});

Long-Lived Usage

Use try/finally for non-streaming:
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

let mcpClient: MCPClient | undefined;

try {
  mcpClient = await createMCPClient({ /* ... */ });
  const tools = await mcpClient.tools();
  
  const result = await generateText({
    model: openai('gpt-5'),
    tools,
    prompt: 'What is the weather?',
  });
  
  console.log(result.text);
} finally {
  await mcpClient?.close();
}

MCP Resources

Resources are application-driven data sources that provide context to the model.

Listing Resources

const resources = await mcpClient.listResources();

for (const resource of resources) {
  console.log('Resource:', resource.name);
  console.log('URI:', resource.uri);
  console.log('Description:', resource.description);
}

Reading Resources

const resourceData = await mcpClient.readResource({
  uri: 'file:///example/document.txt',
});

console.log(resourceData.content);

Resource Templates

Resource templates allow dynamic URI patterns:
const templates = await mcpClient.listResourceTemplates();

for (const template of templates) {
  console.log('Template:', template.name);
  console.log('URI pattern:', template.uriTemplate);
}

MCP Prompts

MCP Prompts is an experimental feature and may change.

Listing Prompts

const prompts = await mcpClient.experimental_listPrompts();

for (const prompt of prompts) {
  console.log('Prompt:', prompt.name);
  console.log('Description:', prompt.description);
}

Getting a Prompt

const prompt = await mcpClient.experimental_getPrompt({
  name: 'code_review',
  arguments: {
    code: 'function add(a, b) { return a + b; }',
    language: 'javascript',
  },
});

console.log(prompt.messages);

Elicitation Requests

Elicitation allows MCP servers to request additional information during tool execution.

Enable Elicitation

const mcpClient = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'https://your-server.com/sse',
  },
  capabilities: {
    elicitation: {},
  },
});

Handle Elicitation Requests

import { ElicitationRequestSchema } from '@ai-sdk/mcp';

mcpClient.onElicitationRequest(ElicitationRequestSchema, async (request) => {
  // request.params.message: Description of needed input
  // request.params.requestedSchema: Expected input structure
  
  console.log('Server needs:', request.params.message);
  
  // Get input from user
  const userInput = await getUserInput(
    request.params.message,
    request.params.requestedSchema
  );
  
  // Return response
  return {
    action: 'accept', // or 'decline' or 'cancel'
    content: userInput,
  };
});

Elicitation Actions

accept
string
User provided the requested information (include content)
decline
string
User chose not to provide the information
cancel
string
User cancelled the operation entirely

Complete Example

import { createMCPClient } from '@ai-sdk/mcp';
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

let mcpClient;

try {
  // Create MCP client
  mcpClient = await createMCPClient({
    transport: {
      type: 'http',
      url: 'https://weather-mcp.example.com',
      headers: {
        'Authorization': 'Bearer your-api-key',
      },
    },
  });
  
  // Load tools with schemas
  const tools = await mcpClient.tools({
    schemas: {
      'get-weather': {
        inputSchema: z.object({
          location: z.string(),
          units: z.enum(['celsius', 'fahrenheit']).optional(),
        }),
        outputSchema: z.object({
          temperature: z.number(),
          condition: z.string(),
        }),
      },
    },
  });
  
  // Use with generateText
  const result = await generateText({
    model: openai('gpt-5'),
    tools,
    stopWhen: stepCountIs(5),
    prompt: 'What should I wear in San Francisco today?',
  });
  
  console.log(result.text);
  
  // Access tool results
  for (const toolResult of result.toolResults) {
    if (toolResult.toolName === 'get-weather') {
      console.log('Temperature:', toolResult.output.temperature);
      console.log('Condition:', toolResult.output.condition);
    }
  }
  
} finally {
  await mcpClient?.close();
}

Best Practices

Use HTTP Transport for Production

HTTP and SSE transports work in all deployment environments:
// Good: Works everywhere
const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
  },
});

// Bad: Only works locally
const mcpClient = await createMCPClient({
  transport: new StdioClientTransport({ /* ... */ }),
});

Define Schemas for Type Safety

// Good: Type-safe with IDE autocomplete
const tools = await mcpClient.tools({
  schemas: {
    'get-weather': {
      inputSchema: z.object({ location: z.string() }),
      outputSchema: z.object({ temperature: z.number() }),
    },
  },
});

// Acceptable: No type safety
const tools = await mcpClient.tools();

Close Clients Properly

// Good: Cleanup in finally block
try {
  const mcpClient = await createMCPClient({ /* ... */ });
  // Use client
} finally {
  await mcpClient?.close();
}

// Good: Cleanup in callback
const result = streamText({
  tools: await mcpClient.tools(),
  onFinish: async () => {
    await mcpClient.close();
  },
});

Next Steps

Tool Calling

Learn about AI SDK tools

Prompt Engineering

Tips for effective tool usage