Skip to content

Commit c9ed3c4

Browse files
authoredMar 18, 2025··
feat(ai): allow custom mcp transports (#5209)
1 parent 640f418 commit c9ed3c4

25 files changed

+541
-294
lines changed
 

‎.changeset/cold-apples-design.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'next-openai': patch
3+
'ai': patch
4+
---
5+
6+
feat: enable custom mcp transports
7+
breaking change: remove internal stdio transport creation

‎content/cookbook/05-node/54-mcp-tools.mdx

+18-6
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ The AI SDK supports Model Context Protocol (MCP) tools by offering a lightweight
1010

1111
```ts
1212
import { experimental_createMCPClient, generateText } from 'ai';
13+
import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio';
1314
import { openai } from '@ai-sdk/openai';
1415

1516
let clientOne;
1617
let clientTwo;
18+
let clientThree;
1719

1820
try {
1921
// Initialize an MCP client to connect to a `stdio` MCP server:
22+
const transport = new Experimental_StdioMCPTransport({
23+
command: 'node',
24+
args: ['src/stdio/dist/server.js'],
25+
});
2026
clientOne = await experimental_createMCPClient({
21-
transport: {
22-
type: 'stdio',
23-
command: 'node server.js',
24-
},
27+
transport,
2528
});
2629

2730
// Alternatively, you can connect to a Server-Sent Events (SSE) MCP server:
@@ -32,12 +35,21 @@ try {
3235
},
3336
});
3437

38+
// Similarly to the stdio example, you can pass in your own custom transport as long as it implements the `MCPTransport` interface:
39+
const transport = new MyCustomTransport({
40+
// ...
41+
});
42+
clientThree = await experimental_createMCPClient({
43+
transport,
44+
});
45+
3546
const toolSetOne = await clientOne.tools();
3647
const toolSetTwo = await clientTwo.tools();
37-
48+
const toolSetThree = await clientThree.tools();
3849
const tools = {
3950
...toolSetOne,
40-
...toolSetTwo, // note: this overrides tools from toolSetOne when there are naming collisions
51+
...toolSetTwo,
52+
...toolSetThree, // note: this approach causes subsequent tool sets to override tools with the same name
4153
};
4254

4355
const response = await generateText({

‎content/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx

+16-5
Original file line numberDiff line numberDiff line change
@@ -639,15 +639,18 @@ Create an MCP client using either:
639639

640640
- `stdio`: Uses standard input and output streams for communication, ideal for local tool servers running on the same machine (like CLI tools or local services)
641641
- `SSE` (Server-Sent Events): Uses HTTP-based real-time communication, better suited for remote servers that need to send data over the network
642+
- Custom transport: Bring your own transport by implementing the `MCPTransport` interface
642643

643644
```typescript
645+
import { experimental_createMCPClient as createMCPClient } from 'ai';
646+
import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio';
647+
644648
// Example of using MCP with stdio
645649
const mcpClient = await createMCPClient({
646-
transport: {
647-
type: 'stdio',
650+
transport: new StdioMCPTransport({
648651
command: 'node',
649652
args: ['src/stdio/dist/server.js'],
650-
},
653+
}),
651654
});
652655

653656
// Example of using MCP with SSE
@@ -657,17 +660,25 @@ const mcpClient = await createMCPClient({
657660
url: 'https://my-server.com/sse',
658661
},
659662
});
663+
664+
// Example of using a custom transport
665+
const transport = new MyCustomTransport({
666+
// ...
667+
});
668+
const mcpClient = await createMCPClient({
669+
transport,
670+
});
660671
```
661672

662673
After initialization, always close the MCP client when you're done to prevent resource leaks. Use try/finally or cleanup functions in your framework:
663674

664675
```typescript
665676
try {
666-
const mcpClient = await createMCPClient({...});
677+
const mcpClient = await experimental_createMCPClient({...});
667678
const tools = await mcpClient.tools();
668679
// use tools...
669680
} finally {
670-
await client?.close();
681+
await mcpClient?.close();
671682
}
672683
```
673684

‎content/docs/07-reference/01-ai-sdk-core/21-create-mcp-client.mdx

+25-25
Original file line numberDiff line numberDiff line change
@@ -34,47 +34,47 @@ This feature is experimental and may change or be removed in the future.
3434
parameters: [
3535
{
3636
name: 'transport',
37-
type: 'TransportConfig = McpStdioServerConfig | McpSSEServerConfig',
37+
type: 'TransportConfig = MCPTransport | McpSSEServerConfig',
3838
description: 'Configuration for the message transport layer.',
3939
properties: [
4040
{
41-
type: 'McpStdioServerConfig',
41+
type: 'MCPTransport',
42+
description:
43+
'A client transport instance, used explicitly for stdio or custom transports',
4244
parameters: [
4345
{
44-
name: 'type',
45-
type: "'stdio'",
46-
description:
47-
'Use standard input/output streams for communication',
46+
name: 'start',
47+
type: '() => Promise<void>',
48+
description: 'A method that starts the transport',
4849
},
4950
{
50-
name: 'command',
51-
type: 'string',
52-
description: 'Command to start the server',
51+
name: 'send',
52+
type: '(message: JSONRPCMessage) => Promise<void>',
53+
description:
54+
'A method that sends a message through the transport',
5355
},
5456
{
55-
name: 'args',
56-
type: 'string[]',
57-
isOptional: true,
58-
description: 'Command line arguments',
57+
name: 'close',
58+
type: '() => Promise<void>',
59+
description: 'A method that closes the transport',
5960
},
6061
{
61-
name: 'env',
62-
type: 'Record<string, string>',
63-
isOptional: true,
64-
description: 'Environment variables',
62+
name: 'onclose',
63+
type: '() => void',
64+
description:
65+
'A method that is called when the transport is closed',
6566
},
6667
{
67-
name: 'cwd',
68-
type: 'string',
69-
isOptional: true,
70-
description: 'Working directory for the server process',
68+
name: 'onerror',
69+
type: '(error: Error) => void',
70+
description:
71+
'A method that is called when the transport encounters an error',
7172
},
7273
{
73-
name: 'stderr',
74-
type: 'IOType | Stream | number',
75-
isOptional: true,
74+
name: 'onmessage',
75+
type: '(message: JSONRPCMessage) => void',
7676
description:
77-
'Handler for stderr output. Defaults to "inherit".',
77+
'A method that is called when the transport receives a message',
7878
},
7979
],
8080
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
title: Experimental_StdioMCPTransport
3+
description: Create a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams
4+
---
5+
6+
# `Experimental_StdioMCPTransport`
7+
8+
Creates a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams. This transport is only supported in Node.js environments.
9+
10+
This feature is experimental and may change or be removed in the future.
11+
12+
## Import
13+
14+
<Snippet
15+
text={`import { Experimental_StdioMCPTransport } from "ai/mcp-stdio"`}
16+
prompt={false}
17+
/>
18+
19+
## API Signature
20+
21+
### Parameters
22+
23+
<PropertiesTable
24+
content={[
25+
{
26+
name: 'config',
27+
type: 'StdioConfig',
28+
description: 'Configuration for the MCP client.',
29+
properties: [
30+
{
31+
type: 'StdioConfig',
32+
parameters: [
33+
{
34+
name: 'command',
35+
type: 'string',
36+
description: 'The command to run the MCP server.',
37+
},
38+
{
39+
name: 'args',
40+
type: 'string[]',
41+
isOptional: true,
42+
description: 'The arguments to pass to the MCP server.',
43+
},
44+
{
45+
name: 'env',
46+
type: 'Record<string, string>',
47+
isOptional: true,
48+
description:
49+
'The environment variables to set for the MCP server.',
50+
},
51+
{
52+
name: 'stderr',
53+
type: 'IOType | Stream | number',
54+
isOptional: true,
55+
description: "The stream to write the MCP server's stderr to.",
56+
},
57+
{
58+
name: 'cwd',
59+
type: 'string',
60+
isOptional: true,
61+
description: 'The current working directory for the MCP server.',
62+
},
63+
],
64+
},
65+
],
66+
},
67+
]}
68+
/>

‎examples/mcp/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"sse:client": "tsx src/sse/client.ts",
88
"stdio:build": "tsc src/stdio/server.ts --outDir src/stdio/dist --target es2023 --module nodenext",
99
"stdio:client": "tsx src/stdio/client.ts",
10+
"custom-transport:build": "tsc src/custom-transport/server.ts --outDir src/custom-transport/dist --target es2023 --module nodenext",
11+
"custom-transport:client": "tsx src/custom-transport/client.ts",
1012
"type-check": "tsc --noEmit"
1113
},
1214
"dependencies": {

‎examples/mcp/src/stdio/client.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import { openai } from '@ai-sdk/openai';
22
import { experimental_createMCPClient, generateText } from 'ai';
3+
// import { StdioClientTransport } from 'ai/mcp-stdio';
4+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
35
import 'dotenv/config';
46
import { z } from 'zod';
57

68
async function main() {
79
let mcpClient;
810

911
try {
12+
// Or use the AI SDK's stdio transport by importing:
13+
// import { StdioClientTransport } from 'ai/mcp-stdio';
14+
const stdioTransport = new StdioClientTransport({
15+
command: 'node',
16+
args: ['src/stdio/dist/server.js'],
17+
});
18+
1019
mcpClient = await experimental_createMCPClient({
11-
transport: {
12-
type: 'stdio',
13-
command: 'node',
14-
args: ['src/stdio/dist/server.js'],
15-
},
20+
transport: stdioTransport,
1621
});
1722

1823
const { text: answer } = await generateText({

‎examples/mcp/src/stdio/server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ server.tool(
4444
async function main() {
4545
const transport = new StdioServerTransport();
4646
await server.connect(transport);
47-
console.error('Pokemon MCP Server running on stdio');
47+
console.log('Pokemon MCP Server running on stdio');
4848
}
4949

5050
main().catch(error => {
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { openai } from '@ai-sdk/openai';
2+
import { experimental_createMCPClient, generateText } from 'ai';
3+
4+
export async function POST() {
5+
let client;
6+
7+
try {
8+
client = await experimental_createMCPClient({
9+
transport: {
10+
type: 'sse',
11+
url: 'http://localhost:8080/sse',
12+
},
13+
});
14+
15+
const tools = await client.tools();
16+
17+
const { text } = await generateText({
18+
model: openai('gpt-4o-mini', { structuredOutputs: true }),
19+
tools,
20+
maxSteps: 10,
21+
onStepFinish: async ({ toolResults }) => {
22+
console.log(`STEP RESULTS: ${JSON.stringify(toolResults, null, 2)}`);
23+
},
24+
system: 'You are a helpful chatbot',
25+
prompt: 'Can you find a product called The Product?',
26+
});
27+
28+
return Response.json({ text });
29+
} catch (error) {
30+
console.error(error);
31+
return Response.json({ error: 'Failed to generate text' });
32+
} finally {
33+
await client?.close();
34+
}
35+
}

‎examples/next-openai/app/mcp/page.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
export default function Page() {
6+
const [response, setResponse] = useState<string | null>(null);
7+
const [isLoading, setIsLoading] = useState(false);
8+
9+
const handleClick = async () => {
10+
setIsLoading(true);
11+
const response = await fetch('/api/mcp', {
12+
method: 'POST',
13+
body: JSON.stringify({
14+
prompt: 'Can you find a product called The Product?',
15+
}),
16+
});
17+
const data = await response.json();
18+
setResponse(data.text);
19+
setIsLoading(false);
20+
};
21+
22+
return (
23+
<div className="flex flex-col items-center justify-center h-screen gap-4">
24+
<button
25+
className="bg-blue-500 text-white p-2 rounded-md"
26+
onClick={handleClick}
27+
disabled={isLoading}
28+
>
29+
{isLoading ? 'Searching...' : 'Find Product'}
30+
</button>
31+
{response && <p>Result: {response}</p>}
32+
</div>
33+
);
34+
}

‎packages/ai/core/tool/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { createMCPClient as experimental_createMCPClient } from './mcp/mcp-client';
2+
export type { MCPTransport } from './mcp/mcp-transport';
23
export { tool } from './tool';
34
export type { CoreTool, Tool, ToolExecutionOptions } from './tool';

0 commit comments

Comments
 (0)
Please sign in to comment.