@ai-sdk/mcp 2.0.0-beta.2 → 2.0.0-beta.21
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 +149 -8
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +64 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -19
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-stdio/index.js +3 -3
- package/dist/mcp-stdio/index.mjs +1 -1
- package/package.json +6 -8
- package/src/tool/mcp-client.ts +6 -2
- 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/oauth.ts +24 -3
- package/src/util/oauth-util.ts +13 -0
package/dist/mcp-stdio/index.js
CHANGED
|
@@ -18,11 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/tool/mcp-stdio/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
23
|
Experimental_StdioMCPTransport: () => StdioMCPTransport
|
|
24
24
|
});
|
|
25
|
-
module.exports = __toCommonJS(
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
27
|
// src/tool/json-rpc-message.ts
|
|
28
28
|
var import_v42 = require("zod/v4");
|
package/dist/mcp-stdio/index.mjs
CHANGED
|
@@ -257,7 +257,7 @@ var MCPClientError = class extends (_b = AISDKError, _a = symbol, _b) {
|
|
|
257
257
|
};
|
|
258
258
|
|
|
259
259
|
// src/tool/mcp-stdio/create-child-process.ts
|
|
260
|
-
import { spawn } from "
|
|
260
|
+
import { spawn } from "child_process";
|
|
261
261
|
|
|
262
262
|
// src/tool/mcp-stdio/get-environment.ts
|
|
263
263
|
function getEnvironment(customEnv) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/mcp",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.21",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,17 +33,17 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"pkce-challenge": "^5.0.0",
|
|
36
|
-
"@ai-sdk/provider
|
|
37
|
-
"@ai-sdk/provider": "
|
|
36
|
+
"@ai-sdk/provider": "4.0.0-beta.9",
|
|
37
|
+
"@ai-sdk/provider-utils": "5.0.0-beta.15"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "20.17.24",
|
|
41
41
|
"tsup": "^8",
|
|
42
42
|
"typescript": "5.8.3",
|
|
43
|
-
"vitest": "
|
|
43
|
+
"vitest": "^4.1.0",
|
|
44
44
|
"zod": "3.25.76",
|
|
45
|
-
"@
|
|
46
|
-
"@ai-
|
|
45
|
+
"@ai-sdk/test-server": "2.0.0-beta.0",
|
|
46
|
+
"@vercel/ai-tsconfig": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -70,9 +70,7 @@
|
|
|
70
70
|
"build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
|
|
71
71
|
"build:watch": "pnpm clean && tsup --watch",
|
|
72
72
|
"clean": "rm -rf dist *.tsbuildinfo",
|
|
73
|
-
"lint": "eslint \"./**/*.ts*\"",
|
|
74
73
|
"type-check": "tsc --build",
|
|
75
|
-
"prettier-check": "prettier --check \"./**/*.ts*\"",
|
|
76
74
|
"test": "pnpm test:node && pnpm test:edge",
|
|
77
75
|
"test:update": "pnpm test:node -u",
|
|
78
76
|
"test:watch": "vitest --config vitest.node.config.js",
|
package/src/tool/mcp-client.ts
CHANGED
|
@@ -420,7 +420,7 @@ class DefaultMCPClient implements MCPClient {
|
|
|
420
420
|
}: {
|
|
421
421
|
name: string;
|
|
422
422
|
args: Record<string, unknown>;
|
|
423
|
-
options?: ToolExecutionOptions
|
|
423
|
+
options?: ToolExecutionOptions<{}>;
|
|
424
424
|
}): Promise<CallToolResult> {
|
|
425
425
|
try {
|
|
426
426
|
return this.request({
|
|
@@ -579,11 +579,15 @@ class DefaultMCPClient implements MCPClient {
|
|
|
579
579
|
|
|
580
580
|
const execute = async (
|
|
581
581
|
args: any,
|
|
582
|
-
options: ToolExecutionOptions
|
|
582
|
+
options: ToolExecutionOptions<{}>,
|
|
583
583
|
): Promise<unknown> => {
|
|
584
584
|
options?.abortSignal?.throwIfAborted();
|
|
585
585
|
const result = await self.callTool({ name, args, options });
|
|
586
586
|
|
|
587
|
+
if (result.isError) {
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
|
|
587
591
|
if (outputSchema != null) {
|
|
588
592
|
return self.extractStructuredContent(result, outputSchema, name);
|
|
589
593
|
}
|
|
@@ -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';
|
|
@@ -83,6 +84,8 @@ export interface OAuthClientProvider {
|
|
|
83
84
|
clientInformation: OAuthClientInformation,
|
|
84
85
|
): void | Promise<void>;
|
|
85
86
|
state?(): string | Promise<string>;
|
|
87
|
+
saveState?(state: string): void | Promise<void>;
|
|
88
|
+
storedState?(): string | undefined | Promise<string | undefined>;
|
|
86
89
|
validateResourceURL?(
|
|
87
90
|
serverUrl: string | URL,
|
|
88
91
|
resource?: string,
|
|
@@ -449,7 +452,10 @@ export async function startAuthorization(
|
|
|
449
452
|
}
|
|
450
453
|
|
|
451
454
|
if (resource) {
|
|
452
|
-
authorizationUrl.searchParams.set(
|
|
455
|
+
authorizationUrl.searchParams.set(
|
|
456
|
+
'resource',
|
|
457
|
+
resourceUrlStripSlash(resource),
|
|
458
|
+
);
|
|
453
459
|
}
|
|
454
460
|
|
|
455
461
|
return { authorizationUrl, codeVerifier };
|
|
@@ -673,7 +679,7 @@ export async function exchangeAuthorization(
|
|
|
673
679
|
}
|
|
674
680
|
|
|
675
681
|
if (resource) {
|
|
676
|
-
params.set('resource', resource
|
|
682
|
+
params.set('resource', resourceUrlStripSlash(resource));
|
|
677
683
|
}
|
|
678
684
|
|
|
679
685
|
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
@@ -760,7 +766,7 @@ export async function refreshAuthorization(
|
|
|
760
766
|
}
|
|
761
767
|
|
|
762
768
|
if (resource) {
|
|
763
|
-
params.set('resource', resource
|
|
769
|
+
params.set('resource', resourceUrlStripSlash(resource));
|
|
764
770
|
}
|
|
765
771
|
|
|
766
772
|
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
@@ -827,6 +833,7 @@ export async function auth(
|
|
|
827
833
|
options: {
|
|
828
834
|
serverUrl: string | URL;
|
|
829
835
|
authorizationCode?: string;
|
|
836
|
+
callbackState?: string;
|
|
830
837
|
scope?: string;
|
|
831
838
|
resourceMetadataUrl?: URL;
|
|
832
839
|
fetchFn?: FetchFunction;
|
|
@@ -886,12 +893,14 @@ async function authInternal(
|
|
|
886
893
|
{
|
|
887
894
|
serverUrl,
|
|
888
895
|
authorizationCode,
|
|
896
|
+
callbackState,
|
|
889
897
|
scope,
|
|
890
898
|
resourceMetadataUrl,
|
|
891
899
|
fetchFn,
|
|
892
900
|
}: {
|
|
893
901
|
serverUrl: string | URL;
|
|
894
902
|
authorizationCode?: string;
|
|
903
|
+
callbackState?: string;
|
|
895
904
|
scope?: string;
|
|
896
905
|
resourceMetadataUrl?: URL;
|
|
897
906
|
fetchFn?: FetchFunction;
|
|
@@ -960,6 +969,15 @@ async function authInternal(
|
|
|
960
969
|
|
|
961
970
|
// Exchange authorization code for tokens
|
|
962
971
|
if (authorizationCode !== undefined) {
|
|
972
|
+
if (provider.storedState) {
|
|
973
|
+
const expectedState = await provider.storedState();
|
|
974
|
+
if (expectedState !== undefined && expectedState !== callbackState) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
'OAuth state parameter mismatch - possible CSRF attack',
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
963
981
|
const codeVerifier = await provider.codeVerifier();
|
|
964
982
|
const tokens = await exchangeAuthorization(authorizationServerUrl, {
|
|
965
983
|
metadata,
|
|
@@ -1008,6 +1026,9 @@ async function authInternal(
|
|
|
1008
1026
|
}
|
|
1009
1027
|
|
|
1010
1028
|
const state = provider.state ? await provider.state() : undefined;
|
|
1029
|
+
if (state && provider.saveState) {
|
|
1030
|
+
await provider.saveState(state);
|
|
1031
|
+
}
|
|
1011
1032
|
|
|
1012
1033
|
// Start new authorization flow
|
|
1013
1034
|
const { authorizationUrl, codeVerifier } = await startAuthorization(
|
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,
|