@ai-sdk/mcp 2.0.0-beta.5 → 2.0.0-beta.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +496 -8
- package/README.md +134 -0
- package/dist/index.d.ts +146 -1
- package/dist/index.js +764 -350
- package/dist/index.js.map +1 -1
- package/dist/mcp-stdio/index.d.ts +8 -0
- package/dist/mcp-stdio/index.js +170 -172
- package/dist/mcp-stdio/index.js.map +1 -1
- package/package.json +18 -19
- package/src/error/mcp-client-error.ts +40 -0
- package/src/index.ts +16 -1
- package/src/tool/index.ts +1 -0
- package/src/tool/json-rpc-message.ts +7 -0
- package/src/tool/mcp-apps.ts +254 -0
- package/src/tool/mcp-client.ts +128 -43
- package/src/tool/mcp-http-transport.ts +78 -23
- package/src/tool/mcp-sse-transport.ts +47 -15
- package/src/tool/mcp-stdio/create-child-process.ts +2 -2
- package/src/tool/mcp-stdio/mcp-stdio-transport.ts +17 -14
- package/src/tool/mcp-transport.ts +28 -2
- package/src/tool/mock-mcp-transport.ts +8 -9
- package/src/tool/oauth-types.ts +22 -18
- package/src/tool/oauth.ts +324 -37
- package/src/tool/types.ts +27 -3
- package/src/util/oauth-util.ts +13 -0
- package/dist/index.d.mts +0 -509
- package/dist/index.mjs +0 -2128
- package/dist/index.mjs.map +0 -1
- package/dist/mcp-stdio/index.d.mts +0 -89
- package/dist/mcp-stdio/index.mjs +0 -426
- package/dist/mcp-stdio/index.mjs.map +0 -1
|
@@ -2,19 +2,24 @@ import {
|
|
|
2
2
|
EventSourceParserStream,
|
|
3
3
|
withUserAgentSuffix,
|
|
4
4
|
getRuntimeEnvironmentUserAgent,
|
|
5
|
+
type FetchFunction,
|
|
5
6
|
} from '@ai-sdk/provider-utils';
|
|
6
7
|
import { MCPClientError } from '../error/mcp-client-error';
|
|
7
|
-
import {
|
|
8
|
-
import { MCPTransport } from './mcp-transport';
|
|
8
|
+
import { parseJSONRPCMessage, type JSONRPCMessage } from './json-rpc-message';
|
|
9
|
+
import type { MCPTransport } from './mcp-transport';
|
|
9
10
|
import { VERSION } from '../version';
|
|
10
11
|
import {
|
|
11
|
-
OAuthClientProvider,
|
|
12
12
|
extractResourceMetadataUrl,
|
|
13
13
|
UnauthorizedError,
|
|
14
14
|
auth,
|
|
15
|
+
type OAuthClientProvider,
|
|
15
16
|
} from './oauth';
|
|
16
17
|
import { LATEST_PROTOCOL_VERSION } from './types';
|
|
17
18
|
|
|
19
|
+
function isMessageEvent(event: string | undefined): boolean {
|
|
20
|
+
return event === undefined || event === 'message';
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
export class SseMCPTransport implements MCPTransport {
|
|
19
24
|
private endpoint?: URL;
|
|
20
25
|
private abortController?: AbortController;
|
|
@@ -26,23 +31,36 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
26
31
|
private headers?: Record<string, string>;
|
|
27
32
|
private authProvider?: OAuthClientProvider;
|
|
28
33
|
private resourceMetadataUrl?: URL;
|
|
34
|
+
private redirectMode: RequestRedirect;
|
|
35
|
+
private fetchFn: FetchFunction;
|
|
29
36
|
|
|
30
37
|
onclose?: () => void;
|
|
31
38
|
onerror?: (error: unknown) => void;
|
|
32
39
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
40
|
+
protocolVersion?: string;
|
|
33
41
|
|
|
34
42
|
constructor({
|
|
35
43
|
url,
|
|
36
44
|
headers,
|
|
37
45
|
authProvider,
|
|
46
|
+
redirect = 'error',
|
|
47
|
+
fetch: fetchFn,
|
|
38
48
|
}: {
|
|
39
49
|
url: string;
|
|
40
50
|
headers?: Record<string, string>;
|
|
41
51
|
authProvider?: OAuthClientProvider;
|
|
52
|
+
redirect?: 'follow' | 'error';
|
|
53
|
+
fetch?: FetchFunction;
|
|
42
54
|
}) {
|
|
43
55
|
this.url = new URL(url);
|
|
44
56
|
this.headers = headers;
|
|
45
57
|
this.authProvider = authProvider;
|
|
58
|
+
this.redirectMode = redirect;
|
|
59
|
+
this.fetchFn = fetchFn ?? globalThis.fetch;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setProtocolVersion(version: string): void {
|
|
63
|
+
this.protocolVersion = version;
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
private async commonHeaders(
|
|
@@ -51,7 +69,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
51
69
|
const headers: Record<string, string> = {
|
|
52
70
|
...this.headers,
|
|
53
71
|
...base,
|
|
54
|
-
'mcp-protocol-version': LATEST_PROTOCOL_VERSION,
|
|
72
|
+
'mcp-protocol-version': this.protocolVersion ?? LATEST_PROTOCOL_VERSION,
|
|
55
73
|
};
|
|
56
74
|
|
|
57
75
|
if (this.authProvider) {
|
|
@@ -81,9 +99,10 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
81
99
|
const headers = await this.commonHeaders({
|
|
82
100
|
Accept: 'text/event-stream',
|
|
83
101
|
});
|
|
84
|
-
const response = await
|
|
102
|
+
const response = await this.fetchFn(this.url.href, {
|
|
85
103
|
headers,
|
|
86
104
|
signal: this.abortController?.signal,
|
|
105
|
+
redirect: this.redirectMode,
|
|
87
106
|
});
|
|
88
107
|
|
|
89
108
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
@@ -92,6 +111,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
92
111
|
const result = await auth(this.authProvider, {
|
|
93
112
|
serverUrl: this.url,
|
|
94
113
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
114
|
+
fetchFn: this.fetchFn,
|
|
95
115
|
});
|
|
96
116
|
if (result !== 'AUTHORIZED') {
|
|
97
117
|
const error = new UnauthorizedError();
|
|
@@ -145,21 +165,28 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
145
165
|
const { event, data } = value;
|
|
146
166
|
|
|
147
167
|
if (event === 'endpoint') {
|
|
148
|
-
|
|
168
|
+
if (this.endpoint) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const endpoint = new URL(data, this.url);
|
|
149
173
|
|
|
150
|
-
if (
|
|
174
|
+
if (endpoint.origin !== this.url.origin) {
|
|
175
|
+
this.connected = false;
|
|
176
|
+
this.endpoint = undefined;
|
|
177
|
+
this.sseConnection?.close();
|
|
178
|
+
this.abortController?.abort();
|
|
151
179
|
throw new MCPClientError({
|
|
152
|
-
message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${
|
|
180
|
+
message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${endpoint.origin}`,
|
|
153
181
|
});
|
|
154
182
|
}
|
|
155
183
|
|
|
184
|
+
this.endpoint = endpoint;
|
|
156
185
|
this.connected = true;
|
|
157
186
|
resolve();
|
|
158
|
-
} else if (event
|
|
187
|
+
} else if (isMessageEvent(event)) {
|
|
159
188
|
try {
|
|
160
|
-
const message =
|
|
161
|
-
JSON.parse(data),
|
|
162
|
-
);
|
|
189
|
+
const message = await parseJSONRPCMessage(data);
|
|
163
190
|
this.onmessage?.(message);
|
|
164
191
|
} catch (error) {
|
|
165
192
|
const e = new MCPClientError({
|
|
@@ -203,6 +230,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
203
230
|
|
|
204
231
|
async close(): Promise<void> {
|
|
205
232
|
this.connected = false;
|
|
233
|
+
this.endpoint = undefined;
|
|
206
234
|
this.sseConnection?.close();
|
|
207
235
|
this.abortController?.abort();
|
|
208
236
|
this.onclose?.();
|
|
@@ -227,9 +255,10 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
227
255
|
headers,
|
|
228
256
|
body: JSON.stringify(message),
|
|
229
257
|
signal: this.abortController?.signal,
|
|
258
|
+
redirect: this.redirectMode,
|
|
230
259
|
};
|
|
231
260
|
|
|
232
|
-
const response = await
|
|
261
|
+
const response = await this.fetchFn(endpoint.href, init);
|
|
233
262
|
|
|
234
263
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
235
264
|
this.resourceMetadataUrl = extractResourceMetadataUrl(response);
|
|
@@ -237,6 +266,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
237
266
|
const result = await auth(this.authProvider, {
|
|
238
267
|
serverUrl: this.url,
|
|
239
268
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
269
|
+
fetchFn: this.fetchFn,
|
|
240
270
|
});
|
|
241
271
|
if (result !== 'AUTHORIZED') {
|
|
242
272
|
const error = new UnauthorizedError();
|
|
@@ -267,6 +297,8 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
267
297
|
}
|
|
268
298
|
}
|
|
269
299
|
|
|
270
|
-
export function deserializeMessage(
|
|
271
|
-
|
|
300
|
+
export async function deserializeMessage(
|
|
301
|
+
line: string,
|
|
302
|
+
): Promise<JSONRPCMessage> {
|
|
303
|
+
return parseJSONRPCMessage(line);
|
|
272
304
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import { getEnvironment } from './get-environment';
|
|
3
|
-
import { StdioConfig } from './mcp-stdio-transport';
|
|
3
|
+
import type { StdioConfig } from './mcp-stdio-transport';
|
|
4
4
|
|
|
5
5
|
export function createChildProcess(
|
|
6
6
|
config: StdioConfig,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChildProcess, IOType } from 'node:child_process';
|
|
2
|
-
import { Stream } from 'node:stream';
|
|
3
|
-
import {
|
|
4
|
-
import { MCPTransport } from '../mcp-transport';
|
|
2
|
+
import type { Stream } from 'node:stream';
|
|
3
|
+
import { parseJSONRPCMessage, type JSONRPCMessage } from '../json-rpc-message';
|
|
4
|
+
import type { MCPTransport } from '../mcp-transport';
|
|
5
5
|
import { MCPClientError } from '../../error/mcp-client-error';
|
|
6
6
|
import { createChildProcess } from './create-child-process';
|
|
7
7
|
|
|
@@ -68,7 +68,7 @@ export class StdioMCPTransport implements MCPTransport {
|
|
|
68
68
|
|
|
69
69
|
this.process.stdout?.on('data', chunk => {
|
|
70
70
|
this.readBuffer.append(chunk);
|
|
71
|
-
this.processReadBuffer();
|
|
71
|
+
void this.processReadBuffer();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
this.process.stdout?.on('error', error => {
|
|
@@ -81,14 +81,15 @@ export class StdioMCPTransport implements MCPTransport {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
private processReadBuffer() {
|
|
84
|
+
private async processReadBuffer() {
|
|
85
85
|
while (true) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
86
|
+
const line = this.readBuffer.readLine();
|
|
87
|
+
if (line === null) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
91
90
|
|
|
91
|
+
try {
|
|
92
|
+
const message = await deserializeMessage(line);
|
|
92
93
|
this.onmessage?.(message);
|
|
93
94
|
} catch (error) {
|
|
94
95
|
this.onerror?.(error as Error);
|
|
@@ -127,7 +128,7 @@ class ReadBuffer {
|
|
|
127
128
|
this.buffer = this.buffer ? Buffer.concat([this.buffer, chunk]) : chunk;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
readLine(): string | null {
|
|
131
132
|
if (!this.buffer) return null;
|
|
132
133
|
|
|
133
134
|
const index = this.buffer.indexOf('\n');
|
|
@@ -137,7 +138,7 @@ class ReadBuffer {
|
|
|
137
138
|
|
|
138
139
|
const line = this.buffer.toString('utf8', 0, index);
|
|
139
140
|
this.buffer = this.buffer.subarray(index + 1);
|
|
140
|
-
return
|
|
141
|
+
return line;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
clear(): void {
|
|
@@ -149,6 +150,8 @@ function serializeMessage(message: JSONRPCMessage): string {
|
|
|
149
150
|
return JSON.stringify(message) + '\n';
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
export function deserializeMessage(
|
|
153
|
-
|
|
153
|
+
export async function deserializeMessage(
|
|
154
|
+
line: string,
|
|
155
|
+
): Promise<JSONRPCMessage> {
|
|
156
|
+
return parseJSONRPCMessage(line);
|
|
154
157
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { FetchFunction } from '@ai-sdk/provider-utils';
|
|
1
2
|
import { MCPClientError } from '../error/mcp-client-error';
|
|
2
|
-
import { JSONRPCMessage } from './json-rpc-message';
|
|
3
|
+
import type { JSONRPCMessage } from './json-rpc-message';
|
|
3
4
|
import { SseMCPTransport } from './mcp-sse-transport';
|
|
4
5
|
import { HttpMCPTransport } from './mcp-http-transport';
|
|
5
|
-
import { OAuthClientProvider } from './oauth';
|
|
6
|
+
import type { OAuthClientProvider } from './oauth';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Transport interface for MCP (Model Context Protocol) communication.
|
|
@@ -39,6 +40,16 @@ export interface MCPTransport {
|
|
|
39
40
|
* Event handler for received messages
|
|
40
41
|
*/
|
|
41
42
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The protocol version negotiated during initialization.
|
|
46
|
+
*/
|
|
47
|
+
protocolVersion?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set the protocol version negotiated during initialization.
|
|
51
|
+
*/
|
|
52
|
+
setProtocolVersion?(version: string): void;
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
export type MCPTransportConfig = {
|
|
@@ -58,6 +69,21 @@ export type MCPTransportConfig = {
|
|
|
58
69
|
* An optional OAuth client provider to use for authentication for MCP servers.
|
|
59
70
|
*/
|
|
60
71
|
authProvider?: OAuthClientProvider;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Controls how HTTP redirects are handled for transport requests.
|
|
75
|
+
* - `'follow'`: Follow redirects automatically (standard fetch behavior).
|
|
76
|
+
* - `'error'`: Reject any redirect response with an error.
|
|
77
|
+
* @default 'error'
|
|
78
|
+
*/
|
|
79
|
+
redirect?: 'follow' | 'error';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional custom fetch implementation to use for HTTP requests.
|
|
83
|
+
* Useful for runtimes that need a request-local fetch.
|
|
84
|
+
* @default globalThis.fetch
|
|
85
|
+
*/
|
|
86
|
+
fetch?: FetchFunction;
|
|
61
87
|
};
|
|
62
88
|
|
|
63
89
|
export function createMcpTransport(config: MCPTransportConfig): MCPTransport {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { delay } from '@ai-sdk/provider-utils';
|
|
2
|
-
import { JSONRPCMessage } from './json-rpc-message';
|
|
3
|
-
import { MCPTransport } from './mcp-transport';
|
|
2
|
+
import type { JSONRPCMessage } from './json-rpc-message';
|
|
3
|
+
import type { MCPTransport } from './mcp-transport';
|
|
4
4
|
import {
|
|
5
|
-
MCPTool,
|
|
6
|
-
MCPResource,
|
|
7
|
-
MCPPrompt,
|
|
8
|
-
GetPromptResult,
|
|
9
|
-
CallToolResult,
|
|
10
5
|
LATEST_PROTOCOL_VERSION,
|
|
6
|
+
type MCPTool,
|
|
7
|
+
type MCPResource,
|
|
8
|
+
type MCPPrompt,
|
|
9
|
+
type CallToolResult,
|
|
11
10
|
} from './types';
|
|
12
|
-
|
|
13
11
|
const DEFAULT_TOOLS: MCPTool[] = [
|
|
14
12
|
{
|
|
15
13
|
name: 'mock-tool',
|
|
@@ -45,7 +43,7 @@ export class MockMCPTransport implements MCPTransport {
|
|
|
45
43
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
46
44
|
onclose?: () => void;
|
|
47
45
|
onerror?: (error: Error) => void;
|
|
48
|
-
|
|
46
|
+
protocolVersion?: string;
|
|
49
47
|
constructor({
|
|
50
48
|
overrideTools = DEFAULT_TOOLS,
|
|
51
49
|
resources = [
|
|
@@ -122,6 +120,7 @@ export class MockMCPTransport implements MCPTransport {
|
|
|
122
120
|
name?: string;
|
|
123
121
|
title?: string;
|
|
124
122
|
mimeType?: string;
|
|
123
|
+
_meta?: Record<string, unknown>;
|
|
125
124
|
} & ({ text: string } | { blob: string })
|
|
126
125
|
>;
|
|
127
126
|
failOnInvalidToolParams?: boolean;
|
package/src/tool/oauth-types.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
-
/**
|
|
3
|
-
* OAuth 2.1 token response
|
|
4
|
-
*/
|
|
5
|
-
export const OAuthTokensSchema = z
|
|
6
|
-
.object({
|
|
7
|
-
access_token: z.string(),
|
|
8
|
-
id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
|
9
|
-
token_type: z.string(),
|
|
10
|
-
expires_in: z.number().optional(),
|
|
11
|
-
scope: z.string().optional(),
|
|
12
|
-
refresh_token: z.string().optional(),
|
|
13
|
-
})
|
|
14
|
-
.strip();
|
|
15
|
-
|
|
16
2
|
/**
|
|
17
3
|
* Reusable URL validation that disallows javascript: scheme
|
|
18
4
|
*/
|
|
@@ -32,16 +18,32 @@ export const SafeUrlSchema = z
|
|
|
32
18
|
})
|
|
33
19
|
.refine(
|
|
34
20
|
url => {
|
|
35
|
-
const
|
|
21
|
+
const parsedUrl = new URL(url);
|
|
36
22
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
parsedUrl.protocol !== 'javascript:' &&
|
|
24
|
+
parsedUrl.protocol !== 'data:' &&
|
|
25
|
+
parsedUrl.protocol !== 'vbscript:'
|
|
40
26
|
);
|
|
41
27
|
},
|
|
42
28
|
{ message: 'URL cannot use javascript:, data:, or vbscript: scheme' },
|
|
43
29
|
);
|
|
44
30
|
|
|
31
|
+
/**
|
|
32
|
+
* OAuth 2.1 token response
|
|
33
|
+
*/
|
|
34
|
+
export const OAuthTokensSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
access_token: z.string(),
|
|
37
|
+
id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
|
38
|
+
token_type: z.string(),
|
|
39
|
+
expires_in: z.number().optional(),
|
|
40
|
+
scope: z.string().optional(),
|
|
41
|
+
refresh_token: z.string().optional(),
|
|
42
|
+
authorization_server: SafeUrlSchema.optional(),
|
|
43
|
+
token_endpoint: SafeUrlSchema.optional(),
|
|
44
|
+
})
|
|
45
|
+
.strip();
|
|
46
|
+
|
|
45
47
|
export const OAuthProtectedResourceMetadataSchema = z
|
|
46
48
|
.object({
|
|
47
49
|
resource: z.string().url(),
|
|
@@ -118,6 +120,8 @@ export const OAuthClientInformationSchema = z
|
|
|
118
120
|
client_secret: z.string().optional(),
|
|
119
121
|
client_id_issued_at: z.number().optional(),
|
|
120
122
|
client_secret_expires_at: z.number().optional(),
|
|
123
|
+
authorization_server: SafeUrlSchema.optional(),
|
|
124
|
+
token_endpoint: SafeUrlSchema.optional(),
|
|
121
125
|
})
|
|
122
126
|
.strip();
|
|
123
127
|
|