@ai-sdk/mcp 2.0.0-beta.3 → 2.0.0-beta.31
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 +223 -9
- package/dist/index.d.ts +25 -1
- package/dist/index.js +288 -279
- package/dist/index.js.map +1 -1
- package/dist/mcp-stdio/index.js +134 -160
- package/dist/mcp-stdio/index.js.map +1 -1
- package/package.json +10 -13
- package/src/index.ts +1 -0
- package/src/tool/mcp-client.ts +20 -3
- package/src/tool/mcp-http-transport.ts +17 -3
- package/src/tool/mcp-sse-transport.ts +15 -2
- package/src/tool/mcp-transport.ts +16 -0
- package/src/tool/mock-mcp-transport.ts +0 -1
- package/src/tool/oauth.ts +7 -3
- package/src/tool/types.ts +2 -0
- 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
package/src/tool/mcp-client.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
CallToolResult,
|
|
30
30
|
CallToolResultSchema,
|
|
31
31
|
ClientCapabilities,
|
|
32
|
+
Configuration,
|
|
32
33
|
Configuration as ClientConfiguration,
|
|
33
34
|
ElicitationRequest,
|
|
34
35
|
ElicitationRequestSchema,
|
|
@@ -81,7 +82,7 @@ function mcpToModelOutput({
|
|
|
81
82
|
}
|
|
82
83
|
if (part.type === 'image' && 'data' in part && 'mimeType' in part) {
|
|
83
84
|
return {
|
|
84
|
-
type: '
|
|
85
|
+
type: 'file-data' as const,
|
|
85
86
|
data: part.data as string,
|
|
86
87
|
mediaType: part.mimeType as string,
|
|
87
88
|
};
|
|
@@ -119,6 +120,12 @@ export async function createMCPClient(
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
export interface MCPClient {
|
|
123
|
+
/**
|
|
124
|
+
* Information about the connected MCP server, as reported during initialization.
|
|
125
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation
|
|
126
|
+
*/
|
|
127
|
+
readonly serverInfo: Configuration;
|
|
128
|
+
|
|
122
129
|
tools<TOOL_SCHEMAS extends ToolSchemas = 'automatic'>(options?: {
|
|
123
130
|
schemas?: TOOL_SCHEMAS;
|
|
124
131
|
}): Promise<McpToolSet<TOOL_SCHEMAS>>;
|
|
@@ -201,6 +208,7 @@ class DefaultMCPClient implements MCPClient {
|
|
|
201
208
|
(response: JSONRPCResponse | Error) => void
|
|
202
209
|
> = new Map();
|
|
203
210
|
private serverCapabilities: ServerCapabilities = {};
|
|
211
|
+
private _serverInfo: Configuration = { name: '', version: '' };
|
|
204
212
|
private isClosed = true;
|
|
205
213
|
private elicitationRequestHandler?: (
|
|
206
214
|
request: ElicitationRequest,
|
|
@@ -247,6 +255,10 @@ class DefaultMCPClient implements MCPClient {
|
|
|
247
255
|
};
|
|
248
256
|
}
|
|
249
257
|
|
|
258
|
+
get serverInfo(): Configuration {
|
|
259
|
+
return this._serverInfo;
|
|
260
|
+
}
|
|
261
|
+
|
|
250
262
|
async init(): Promise<this> {
|
|
251
263
|
try {
|
|
252
264
|
await this.transport.start();
|
|
@@ -277,6 +289,7 @@ class DefaultMCPClient implements MCPClient {
|
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
this.serverCapabilities = result.capabilities;
|
|
292
|
+
this._serverInfo = result.serverInfo;
|
|
280
293
|
|
|
281
294
|
// Complete initialization handshake:
|
|
282
295
|
await this.notification({
|
|
@@ -420,7 +433,7 @@ class DefaultMCPClient implements MCPClient {
|
|
|
420
433
|
}: {
|
|
421
434
|
name: string;
|
|
422
435
|
args: Record<string, unknown>;
|
|
423
|
-
options?: ToolExecutionOptions
|
|
436
|
+
options?: ToolExecutionOptions<{}>;
|
|
424
437
|
}): Promise<CallToolResult> {
|
|
425
438
|
try {
|
|
426
439
|
return this.request({
|
|
@@ -579,11 +592,15 @@ class DefaultMCPClient implements MCPClient {
|
|
|
579
592
|
|
|
580
593
|
const execute = async (
|
|
581
594
|
args: any,
|
|
582
|
-
options: ToolExecutionOptions
|
|
595
|
+
options: ToolExecutionOptions<{}>,
|
|
583
596
|
): Promise<unknown> => {
|
|
584
597
|
options?.abortSignal?.throwIfAborted();
|
|
585
598
|
const result = await self.callTool({ name, args, options });
|
|
586
599
|
|
|
600
|
+
if (result.isError) {
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
|
|
587
604
|
if (outputSchema != null) {
|
|
588
605
|
return self.extractStructuredContent(result, outputSchema, name);
|
|
589
606
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EventSourceParserStream,
|
|
3
|
+
FetchFunction,
|
|
3
4
|
withUserAgentSuffix,
|
|
4
5
|
getRuntimeEnvironmentUserAgent,
|
|
5
6
|
} from '@ai-sdk/provider-utils';
|
|
@@ -30,6 +31,8 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
30
31
|
private resourceMetadataUrl?: URL;
|
|
31
32
|
private sessionId?: string;
|
|
32
33
|
private inboundSseConnection?: { close: () => void };
|
|
34
|
+
private redirectMode: RequestRedirect;
|
|
35
|
+
private fetchFn: FetchFunction;
|
|
33
36
|
|
|
34
37
|
// Inbound SSE resumption and reconnection state
|
|
35
38
|
private lastInboundEventId?: string;
|
|
@@ -49,14 +52,20 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
49
52
|
url,
|
|
50
53
|
headers,
|
|
51
54
|
authProvider,
|
|
55
|
+
redirect = 'error',
|
|
56
|
+
fetch: fetchFn,
|
|
52
57
|
}: {
|
|
53
58
|
url: string;
|
|
54
59
|
headers?: Record<string, string>;
|
|
55
60
|
authProvider?: OAuthClientProvider;
|
|
61
|
+
redirect?: 'follow' | 'error';
|
|
62
|
+
fetch?: FetchFunction;
|
|
56
63
|
}) {
|
|
57
64
|
this.url = new URL(url);
|
|
58
65
|
this.headers = headers;
|
|
59
66
|
this.authProvider = authProvider;
|
|
67
|
+
this.redirectMode = redirect;
|
|
68
|
+
this.fetchFn = fetchFn ?? globalThis.fetch;
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
private async commonHeaders(
|
|
@@ -107,10 +116,11 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
107
116
|
!this.abortController.signal.aborted
|
|
108
117
|
) {
|
|
109
118
|
const headers = await this.commonHeaders({});
|
|
110
|
-
await
|
|
119
|
+
await this.fetchFn(this.url.href, {
|
|
111
120
|
method: 'DELETE',
|
|
112
121
|
headers,
|
|
113
122
|
signal: this.abortController.signal,
|
|
123
|
+
redirect: this.redirectMode,
|
|
114
124
|
}).catch(() => undefined);
|
|
115
125
|
}
|
|
116
126
|
} catch {}
|
|
@@ -132,9 +142,10 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
132
142
|
headers,
|
|
133
143
|
body: JSON.stringify(message),
|
|
134
144
|
signal: this.abortController?.signal,
|
|
145
|
+
redirect: this.redirectMode,
|
|
135
146
|
} satisfies RequestInit;
|
|
136
147
|
|
|
137
|
-
const response = await
|
|
148
|
+
const response = await this.fetchFn(this.url.href, init);
|
|
138
149
|
|
|
139
150
|
const sessionId = response.headers.get('mcp-session-id');
|
|
140
151
|
if (sessionId) {
|
|
@@ -147,6 +158,7 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
147
158
|
const result = await auth(this.authProvider, {
|
|
148
159
|
serverUrl: this.url,
|
|
149
160
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
161
|
+
fetchFn: this.fetchFn,
|
|
150
162
|
});
|
|
151
163
|
if (result !== 'AUTHORIZED') {
|
|
152
164
|
const error = new UnauthorizedError();
|
|
@@ -308,10 +320,11 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
308
320
|
headers['last-event-id'] = resumeToken;
|
|
309
321
|
}
|
|
310
322
|
|
|
311
|
-
const response = await
|
|
323
|
+
const response = await this.fetchFn(this.url.href, {
|
|
312
324
|
method: 'GET',
|
|
313
325
|
headers,
|
|
314
326
|
signal: this.abortController?.signal,
|
|
327
|
+
redirect: this.redirectMode,
|
|
315
328
|
});
|
|
316
329
|
|
|
317
330
|
const sessionId = response.headers.get('mcp-session-id');
|
|
@@ -325,6 +338,7 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
325
338
|
const result = await auth(this.authProvider, {
|
|
326
339
|
serverUrl: this.url,
|
|
327
340
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
341
|
+
fetchFn: this.fetchFn,
|
|
328
342
|
});
|
|
329
343
|
if (result !== 'AUTHORIZED') {
|
|
330
344
|
const error = new UnauthorizedError();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EventSourceParserStream,
|
|
3
|
+
FetchFunction,
|
|
3
4
|
withUserAgentSuffix,
|
|
4
5
|
getRuntimeEnvironmentUserAgent,
|
|
5
6
|
} from '@ai-sdk/provider-utils';
|
|
@@ -26,6 +27,8 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
26
27
|
private headers?: Record<string, string>;
|
|
27
28
|
private authProvider?: OAuthClientProvider;
|
|
28
29
|
private resourceMetadataUrl?: URL;
|
|
30
|
+
private redirectMode: RequestRedirect;
|
|
31
|
+
private fetchFn: FetchFunction;
|
|
29
32
|
|
|
30
33
|
onclose?: () => void;
|
|
31
34
|
onerror?: (error: unknown) => void;
|
|
@@ -35,14 +38,20 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
35
38
|
url,
|
|
36
39
|
headers,
|
|
37
40
|
authProvider,
|
|
41
|
+
redirect = 'error',
|
|
42
|
+
fetch: fetchFn,
|
|
38
43
|
}: {
|
|
39
44
|
url: string;
|
|
40
45
|
headers?: Record<string, string>;
|
|
41
46
|
authProvider?: OAuthClientProvider;
|
|
47
|
+
redirect?: 'follow' | 'error';
|
|
48
|
+
fetch?: FetchFunction;
|
|
42
49
|
}) {
|
|
43
50
|
this.url = new URL(url);
|
|
44
51
|
this.headers = headers;
|
|
45
52
|
this.authProvider = authProvider;
|
|
53
|
+
this.redirectMode = redirect;
|
|
54
|
+
this.fetchFn = fetchFn ?? globalThis.fetch;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
private async commonHeaders(
|
|
@@ -81,9 +90,10 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
81
90
|
const headers = await this.commonHeaders({
|
|
82
91
|
Accept: 'text/event-stream',
|
|
83
92
|
});
|
|
84
|
-
const response = await
|
|
93
|
+
const response = await this.fetchFn(this.url.href, {
|
|
85
94
|
headers,
|
|
86
95
|
signal: this.abortController?.signal,
|
|
96
|
+
redirect: this.redirectMode,
|
|
87
97
|
});
|
|
88
98
|
|
|
89
99
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
@@ -92,6 +102,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
92
102
|
const result = await auth(this.authProvider, {
|
|
93
103
|
serverUrl: this.url,
|
|
94
104
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
105
|
+
fetchFn: this.fetchFn,
|
|
95
106
|
});
|
|
96
107
|
if (result !== 'AUTHORIZED') {
|
|
97
108
|
const error = new UnauthorizedError();
|
|
@@ -227,9 +238,10 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
227
238
|
headers,
|
|
228
239
|
body: JSON.stringify(message),
|
|
229
240
|
signal: this.abortController?.signal,
|
|
241
|
+
redirect: this.redirectMode,
|
|
230
242
|
};
|
|
231
243
|
|
|
232
|
-
const response = await
|
|
244
|
+
const response = await this.fetchFn(endpoint.href, init);
|
|
233
245
|
|
|
234
246
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
235
247
|
this.resourceMetadataUrl = extractResourceMetadataUrl(response);
|
|
@@ -237,6 +249,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
237
249
|
const result = await auth(this.authProvider, {
|
|
238
250
|
serverUrl: this.url,
|
|
239
251
|
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
252
|
+
fetchFn: this.fetchFn,
|
|
240
253
|
});
|
|
241
254
|
if (result !== 'AUTHORIZED') {
|
|
242
255
|
const error = new UnauthorizedError();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FetchFunction } from '@ai-sdk/provider-utils';
|
|
1
2
|
import { MCPClientError } from '../error/mcp-client-error';
|
|
2
3
|
import { JSONRPCMessage } from './json-rpc-message';
|
|
3
4
|
import { SseMCPTransport } from './mcp-sse-transport';
|
|
@@ -58,6 +59,21 @@ export type MCPTransportConfig = {
|
|
|
58
59
|
* An optional OAuth client provider to use for authentication for MCP servers.
|
|
59
60
|
*/
|
|
60
61
|
authProvider?: OAuthClientProvider;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Controls how HTTP redirects are handled for transport requests.
|
|
65
|
+
* - `'follow'`: Follow redirects automatically (standard fetch behavior).
|
|
66
|
+
* - `'error'`: Reject any redirect response with an error.
|
|
67
|
+
* @default 'error'
|
|
68
|
+
*/
|
|
69
|
+
redirect?: 'follow' | 'error';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Optional custom fetch implementation to use for HTTP requests.
|
|
73
|
+
* Useful for runtimes that need a request-local fetch.
|
|
74
|
+
* @default globalThis.fetch
|
|
75
|
+
*/
|
|
76
|
+
fetch?: FetchFunction;
|
|
61
77
|
};
|
|
62
78
|
|
|
63
79
|
export function createMcpTransport(config: MCPTransportConfig): MCPTransport {
|
package/src/tool/oauth.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
resourceUrlFromServerUrl,
|
|
26
26
|
checkResourceAllowed,
|
|
27
|
+
resourceUrlStripSlash,
|
|
27
28
|
} from '../util/oauth-util';
|
|
28
29
|
import { LATEST_PROTOCOL_VERSION } from './types';
|
|
29
30
|
import { FetchFunction } from '@ai-sdk/provider-utils';
|
|
@@ -451,7 +452,10 @@ export async function startAuthorization(
|
|
|
451
452
|
}
|
|
452
453
|
|
|
453
454
|
if (resource) {
|
|
454
|
-
authorizationUrl.searchParams.set(
|
|
455
|
+
authorizationUrl.searchParams.set(
|
|
456
|
+
'resource',
|
|
457
|
+
resourceUrlStripSlash(resource),
|
|
458
|
+
);
|
|
455
459
|
}
|
|
456
460
|
|
|
457
461
|
return { authorizationUrl, codeVerifier };
|
|
@@ -675,7 +679,7 @@ export async function exchangeAuthorization(
|
|
|
675
679
|
}
|
|
676
680
|
|
|
677
681
|
if (resource) {
|
|
678
|
-
params.set('resource', resource
|
|
682
|
+
params.set('resource', resourceUrlStripSlash(resource));
|
|
679
683
|
}
|
|
680
684
|
|
|
681
685
|
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
@@ -762,7 +766,7 @@ export async function refreshAuthorization(
|
|
|
762
766
|
}
|
|
763
767
|
|
|
764
768
|
if (resource) {
|
|
765
|
-
params.set('resource', resource
|
|
769
|
+
params.set('resource', resourceUrlStripSlash(resource));
|
|
766
770
|
}
|
|
767
771
|
|
|
768
772
|
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
package/src/tool/types.ts
CHANGED
|
@@ -56,8 +56,10 @@ export type McpToolSet<TOOL_SCHEMAS extends ToolSchemas = 'automatic'> =
|
|
|
56
56
|
const ClientOrServerImplementationSchema = z.looseObject({
|
|
57
57
|
name: z.string(),
|
|
58
58
|
version: z.string(),
|
|
59
|
+
title: z.optional(z.string()),
|
|
59
60
|
});
|
|
60
61
|
|
|
62
|
+
// Maps to `Implementation` in the MCP specification
|
|
61
63
|
export type Configuration = z.infer<typeof ClientOrServerImplementationSchema>;
|
|
62
64
|
|
|
63
65
|
export const BaseParamsSchema = z.looseObject({
|
package/src/util/oauth-util.ts
CHANGED
|
@@ -14,6 +14,19 @@ export function resourceUrlFromServerUrl(url: URL | string): URL {
|
|
|
14
14
|
return resourceURL;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Serializes a resource URL to a string, removing the trailing slash that
|
|
19
|
+
* URL.href adds to pathless URLs. Per the MCP spec, implementations SHOULD
|
|
20
|
+
* use the form without the trailing slash for better interoperability.
|
|
21
|
+
*/
|
|
22
|
+
export function resourceUrlStripSlash(resource: URL): string {
|
|
23
|
+
const href = resource.href;
|
|
24
|
+
if (resource.pathname === '/' && href.endsWith('/')) {
|
|
25
|
+
return href.slice(0, -1);
|
|
26
|
+
}
|
|
27
|
+
return href;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* Checks if a requested resource URL matches a configured resource URL.
|
|
19
32
|
* A requested resource matches if it has the same scheme, domain, port,
|