@ai-sdk/mcp 2.0.0-beta.6 → 2.0.0-beta.67
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 +498 -8
- package/README.md +134 -0
- package/dist/index.d.ts +140 -2
- package/dist/index.js +750 -345
- 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 +72 -24
- package/src/tool/mcp-sse-transport.ts +42 -16
- 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 +21 -3
- 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 -516
- package/dist/index.mjs +0 -2137
- 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;
|
|
@@ -27,26 +32,35 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
27
32
|
private authProvider?: OAuthClientProvider;
|
|
28
33
|
private resourceMetadataUrl?: URL;
|
|
29
34
|
private redirectMode: RequestRedirect;
|
|
35
|
+
private fetchFn: FetchFunction;
|
|
30
36
|
|
|
31
37
|
onclose?: () => void;
|
|
32
38
|
onerror?: (error: unknown) => void;
|
|
33
39
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
40
|
+
protocolVersion?: string;
|
|
34
41
|
|
|
35
42
|
constructor({
|
|
36
43
|
url,
|
|
37
44
|
headers,
|
|
38
45
|
authProvider,
|
|
39
|
-
redirect = '
|
|
46
|
+
redirect = 'error',
|
|
47
|
+
fetch: fetchFn,
|
|
40
48
|
}: {
|
|
41
49
|
url: string;
|
|
42
50
|
headers?: Record<string, string>;
|
|
43
51
|
authProvider?: OAuthClientProvider;
|
|
44
52
|
redirect?: 'follow' | 'error';
|
|
53
|
+
fetch?: FetchFunction;
|
|
45
54
|
}) {
|
|
46
55
|
this.url = new URL(url);
|
|
47
56
|
this.headers = headers;
|
|
48
57
|
this.authProvider = authProvider;
|
|
49
58
|
this.redirectMode = redirect;
|
|
59
|
+
this.fetchFn = fetchFn ?? globalThis.fetch;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setProtocolVersion(version: string): void {
|
|
63
|
+
this.protocolVersion = version;
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
private async commonHeaders(
|
|
@@ -55,7 +69,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
55
69
|
const headers: Record<string, string> = {
|
|
56
70
|
...this.headers,
|
|
57
71
|
...base,
|
|
58
|
-
'mcp-protocol-version': LATEST_PROTOCOL_VERSION,
|
|
72
|
+
'mcp-protocol-version': this.protocolVersion ?? LATEST_PROTOCOL_VERSION,
|
|
59
73
|
};
|
|
60
74
|
|
|
61
75
|
if (this.authProvider) {
|
|
@@ -85,7 +99,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
85
99
|
const headers = await this.commonHeaders({
|
|
86
100
|
Accept: 'text/event-stream',
|
|
87
101
|
});
|
|
88
|
-
const response = await
|
|
102
|
+
const response = await this.fetchFn(this.url.href, {
|
|
89
103
|
headers,
|
|
90
104
|
signal: this.abortController?.signal,
|
|
91
105
|
redirect: this.redirectMode,
|
|
@@ -97,6 +111,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
97
111
|
const result = await auth(this.authProvider, {
|
|
98
112
|
serverUrl: this.url,
|
|
99
113
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
114
|
+
fetchFn: this.fetchFn,
|
|
100
115
|
});
|
|
101
116
|
if (result !== 'AUTHORIZED') {
|
|
102
117
|
const error = new UnauthorizedError();
|
|
@@ -150,21 +165,28 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
150
165
|
const { event, data } = value;
|
|
151
166
|
|
|
152
167
|
if (event === 'endpoint') {
|
|
153
|
-
|
|
168
|
+
if (this.endpoint) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const endpoint = new URL(data, this.url);
|
|
154
173
|
|
|
155
|
-
if (
|
|
174
|
+
if (endpoint.origin !== this.url.origin) {
|
|
175
|
+
this.connected = false;
|
|
176
|
+
this.endpoint = undefined;
|
|
177
|
+
this.sseConnection?.close();
|
|
178
|
+
this.abortController?.abort();
|
|
156
179
|
throw new MCPClientError({
|
|
157
|
-
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}`,
|
|
158
181
|
});
|
|
159
182
|
}
|
|
160
183
|
|
|
184
|
+
this.endpoint = endpoint;
|
|
161
185
|
this.connected = true;
|
|
162
186
|
resolve();
|
|
163
|
-
} else if (event
|
|
187
|
+
} else if (isMessageEvent(event)) {
|
|
164
188
|
try {
|
|
165
|
-
const message =
|
|
166
|
-
JSON.parse(data),
|
|
167
|
-
);
|
|
189
|
+
const message = await parseJSONRPCMessage(data);
|
|
168
190
|
this.onmessage?.(message);
|
|
169
191
|
} catch (error) {
|
|
170
192
|
const e = new MCPClientError({
|
|
@@ -208,6 +230,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
208
230
|
|
|
209
231
|
async close(): Promise<void> {
|
|
210
232
|
this.connected = false;
|
|
233
|
+
this.endpoint = undefined;
|
|
211
234
|
this.sseConnection?.close();
|
|
212
235
|
this.abortController?.abort();
|
|
213
236
|
this.onclose?.();
|
|
@@ -235,7 +258,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
235
258
|
redirect: this.redirectMode,
|
|
236
259
|
};
|
|
237
260
|
|
|
238
|
-
const response = await
|
|
261
|
+
const response = await this.fetchFn(endpoint.href, init);
|
|
239
262
|
|
|
240
263
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
241
264
|
this.resourceMetadataUrl = extractResourceMetadataUrl(response);
|
|
@@ -243,6 +266,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
243
266
|
const result = await auth(this.authProvider, {
|
|
244
267
|
serverUrl: this.url,
|
|
245
268
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
269
|
+
fetchFn: this.fetchFn,
|
|
246
270
|
});
|
|
247
271
|
if (result !== 'AUTHORIZED') {
|
|
248
272
|
const error = new UnauthorizedError();
|
|
@@ -273,6 +297,8 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
273
297
|
}
|
|
274
298
|
}
|
|
275
299
|
|
|
276
|
-
export function deserializeMessage(
|
|
277
|
-
|
|
300
|
+
export async function deserializeMessage(
|
|
301
|
+
line: string,
|
|
302
|
+
): Promise<JSONRPCMessage> {
|
|
303
|
+
return parseJSONRPCMessage(line);
|
|
278
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 = {
|
|
@@ -63,9 +74,16 @@ export type MCPTransportConfig = {
|
|
|
63
74
|
* Controls how HTTP redirects are handled for transport requests.
|
|
64
75
|
* - `'follow'`: Follow redirects automatically (standard fetch behavior).
|
|
65
76
|
* - `'error'`: Reject any redirect response with an error.
|
|
66
|
-
* @default '
|
|
77
|
+
* @default 'error'
|
|
67
78
|
*/
|
|
68
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;
|
|
69
87
|
};
|
|
70
88
|
|
|
71
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
|
|