@ai-sdk/mcp 2.0.32 → 2.0.34
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 +15 -0
- package/README.md +15 -0
- package/dist/index.d.ts +54 -3
- package/dist/index.js +421 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp-stdio/index.d.ts +19 -1
- package/dist/mcp-stdio/index.js +15 -5
- package/dist/mcp-stdio/index.js.map +1 -1
- package/package.json +3 -3
- package/src/tool/json-rpc-message.ts +1 -1
- package/src/tool/mcp-client.ts +251 -22
- package/src/tool/mcp-http-headers.ts +161 -0
- package/src/tool/mcp-http-transport.ts +99 -10
- package/src/tool/mcp-sse-transport.ts +3 -2
- package/src/tool/mcp-stdio/mcp-stdio-transport.ts +1 -0
- package/src/tool/mcp-transport.ts +25 -1
- package/src/tool/mock-mcp-transport.ts +2 -2
- package/src/tool/oauth-types.ts +9 -0
- package/src/tool/oauth.ts +75 -1
- package/src/tool/types.ts +19 -8
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
validateJSONRPCMessage,
|
|
11
11
|
type JSONRPCMessage,
|
|
12
12
|
} from './json-rpc-message';
|
|
13
|
-
import type { MCPTransport } from './mcp-transport';
|
|
13
|
+
import type { MCPTransport, MCPTransportSendOptions } from './mcp-transport';
|
|
14
14
|
import { VERSION } from '../version';
|
|
15
15
|
import {
|
|
16
16
|
extractWWWAuthenticateParams,
|
|
@@ -19,7 +19,11 @@ import {
|
|
|
19
19
|
type AuthResult,
|
|
20
20
|
type OAuthClientProvider,
|
|
21
21
|
} from './oauth';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
LATEST_LEGACY_PROTOCOL_VERSION,
|
|
24
|
+
LATEST_PROTOCOL_VERSION,
|
|
25
|
+
} from './types';
|
|
26
|
+
import { encodeMCPHeaderValue } from './mcp-http-headers';
|
|
23
27
|
|
|
24
28
|
function isMessageEvent(event: string | undefined): boolean {
|
|
25
29
|
return event === undefined || event === 'message';
|
|
@@ -33,6 +37,8 @@ function isMessageEvent(event: string | undefined): boolean {
|
|
|
33
37
|
* for receiving messages.
|
|
34
38
|
*/
|
|
35
39
|
export class HttpMCPTransport implements MCPTransport {
|
|
40
|
+
readonly supportsProtocolVersionDiscovery = true;
|
|
41
|
+
readonly supportsMcpToolParameterHeaders = true;
|
|
36
42
|
private url: URL;
|
|
37
43
|
private abortController?: AbortController;
|
|
38
44
|
private headers?: Record<string, string>;
|
|
@@ -90,7 +96,8 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
90
96
|
this.authProvider = authProvider;
|
|
91
97
|
this.redirectMode = redirect;
|
|
92
98
|
this.sessionId = initialSessionId;
|
|
93
|
-
this.protocolVersion =
|
|
99
|
+
this.protocolVersion =
|
|
100
|
+
initialProtocolVersion ?? LATEST_LEGACY_PROTOCOL_VERSION;
|
|
94
101
|
this.onSessionIdChange = onSessionIdChange;
|
|
95
102
|
this.onSessionExpired = onSessionExpired;
|
|
96
103
|
this.terminateSessionOnClose = terminateSessionOnClose;
|
|
@@ -99,6 +106,27 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
99
106
|
|
|
100
107
|
setProtocolVersion(version: string): void {
|
|
101
108
|
this.protocolVersion = version;
|
|
109
|
+
|
|
110
|
+
if (!this.abortController) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.isModernProtocol()) {
|
|
115
|
+
this.inboundSseConnection?.close();
|
|
116
|
+
this.inboundSseConnection = undefined;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!this.inboundSseConnection) {
|
|
121
|
+
this.startInboundSse();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private isModernProtocol(): boolean {
|
|
126
|
+
return (
|
|
127
|
+
(this.protocolVersion ?? LATEST_PROTOCOL_VERSION) ===
|
|
128
|
+
LATEST_PROTOCOL_VERSION
|
|
129
|
+
);
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
private async commonHeaders({
|
|
@@ -111,10 +139,11 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
111
139
|
const headers: Record<string, string> = {
|
|
112
140
|
...this.headers,
|
|
113
141
|
...base,
|
|
114
|
-
'mcp-protocol-version':
|
|
142
|
+
'mcp-protocol-version':
|
|
143
|
+
this.protocolVersion ?? LATEST_LEGACY_PROTOCOL_VERSION,
|
|
115
144
|
};
|
|
116
145
|
|
|
117
|
-
if (includeSessionId && this.sessionId) {
|
|
146
|
+
if (!this.isModernProtocol() && includeSessionId && this.sessionId) {
|
|
118
147
|
headers['mcp-session-id'] = this.sessionId;
|
|
119
148
|
}
|
|
120
149
|
|
|
@@ -142,6 +171,10 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
142
171
|
}
|
|
143
172
|
|
|
144
173
|
private applySessionIdFromResponse(response: Response): void {
|
|
174
|
+
if (this.isModernProtocol()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
145
178
|
const sessionId = response.headers.get('mcp-session-id');
|
|
146
179
|
if (sessionId) {
|
|
147
180
|
this.setSessionId(sessionId);
|
|
@@ -190,7 +223,12 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
190
223
|
}
|
|
191
224
|
this.abortController = new AbortController();
|
|
192
225
|
|
|
193
|
-
|
|
226
|
+
if (
|
|
227
|
+
this.protocolVersion != null &&
|
|
228
|
+
this.protocolVersion !== LATEST_PROTOCOL_VERSION
|
|
229
|
+
) {
|
|
230
|
+
this.startInboundSse();
|
|
231
|
+
}
|
|
194
232
|
}
|
|
195
233
|
|
|
196
234
|
async close(options?: { signal?: AbortSignal }): Promise<void> {
|
|
@@ -199,6 +237,7 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
199
237
|
|
|
200
238
|
try {
|
|
201
239
|
if (
|
|
240
|
+
!this.isModernProtocol() &&
|
|
202
241
|
this.sessionId &&
|
|
203
242
|
this.terminateSessionOnClose &&
|
|
204
243
|
this.abortController
|
|
@@ -220,7 +259,7 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
220
259
|
|
|
221
260
|
async send(
|
|
222
261
|
message: JSONRPCMessage,
|
|
223
|
-
options?:
|
|
262
|
+
options?: MCPTransportSendOptions,
|
|
224
263
|
): Promise<void> {
|
|
225
264
|
options?.signal?.throwIfAborted();
|
|
226
265
|
|
|
@@ -243,6 +282,12 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
243
282
|
base: {
|
|
244
283
|
'Content-Type': 'application/json',
|
|
245
284
|
Accept: 'application/json, text/event-stream',
|
|
285
|
+
...(this.isModernProtocol() ? options?.headers : {}),
|
|
286
|
+
...(this.isModernProtocol() &&
|
|
287
|
+
'method' in message &&
|
|
288
|
+
'id' in message
|
|
289
|
+
? this.getStandardRequestHeaders(message)
|
|
290
|
+
: {}),
|
|
246
291
|
},
|
|
247
292
|
includeSessionId: !isInitializeRequest,
|
|
248
293
|
});
|
|
@@ -283,7 +328,7 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
283
328
|
if (response.status === 202) {
|
|
284
329
|
// If inbound SSE was not available earlier (e.g. 405 before init), try again now
|
|
285
330
|
// Do not await to avoid blocking send()
|
|
286
|
-
if (!this.inboundSseConnection) {
|
|
331
|
+
if (!this.isModernProtocol() && !this.inboundSseConnection) {
|
|
287
332
|
this.startInboundSse();
|
|
288
333
|
}
|
|
289
334
|
return;
|
|
@@ -291,15 +336,30 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
291
336
|
|
|
292
337
|
if (!response.ok) {
|
|
293
338
|
const text = await response.text().catch(() => null);
|
|
339
|
+
|
|
340
|
+
if ('id' in message && text != null) {
|
|
341
|
+
const jsonRpcMessage = await parseJSONRPCMessage(text).catch(
|
|
342
|
+
() => undefined,
|
|
343
|
+
);
|
|
344
|
+
if (jsonRpcMessage != null && 'error' in jsonRpcMessage) {
|
|
345
|
+
this.onmessage?.(
|
|
346
|
+
jsonRpcMessage.id == null
|
|
347
|
+
? { ...jsonRpcMessage, id: message.id }
|
|
348
|
+
: jsonRpcMessage,
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
294
354
|
let errorMessage = `MCP HTTP Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}`;
|
|
295
355
|
|
|
296
356
|
if (response.status === 404) {
|
|
297
|
-
if (sessionIdForRequest) {
|
|
357
|
+
if (!this.isModernProtocol() && sessionIdForRequest) {
|
|
298
358
|
this.expireSessionId(sessionIdForRequest);
|
|
299
359
|
|
|
300
360
|
errorMessage +=
|
|
301
361
|
'. The MCP session expired. Create a new client without `initialSessionId` to start a fresh session';
|
|
302
|
-
} else {
|
|
362
|
+
} else if (!this.isModernProtocol()) {
|
|
303
363
|
errorMessage +=
|
|
304
364
|
'. This server does not support HTTP transport. Try using `sse` transport instead';
|
|
305
365
|
}
|
|
@@ -413,6 +473,27 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
413
473
|
await attempt();
|
|
414
474
|
}
|
|
415
475
|
|
|
476
|
+
private getStandardRequestHeaders(
|
|
477
|
+
message: Extract<JSONRPCMessage, { method: string; id: unknown }>,
|
|
478
|
+
): Record<string, string> {
|
|
479
|
+
const headers: Record<string, string> = {
|
|
480
|
+
'Mcp-Method': message.method,
|
|
481
|
+
};
|
|
482
|
+
const params = message.params;
|
|
483
|
+
const name =
|
|
484
|
+
message.method === 'resources/read'
|
|
485
|
+
? params?.uri
|
|
486
|
+
: message.method === 'tools/call' || message.method === 'prompts/get'
|
|
487
|
+
? params?.name
|
|
488
|
+
: undefined;
|
|
489
|
+
|
|
490
|
+
if (typeof name === 'string') {
|
|
491
|
+
headers['Mcp-Name'] = encodeMCPHeaderValue(name);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return headers;
|
|
495
|
+
}
|
|
496
|
+
|
|
416
497
|
private getNextReconnectionDelay(attempt: number): number {
|
|
417
498
|
const {
|
|
418
499
|
initialReconnectionDelay,
|
|
@@ -448,6 +529,10 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
448
529
|
triedAuth: boolean = false,
|
|
449
530
|
resumeToken?: string,
|
|
450
531
|
): void {
|
|
532
|
+
if (this.isModernProtocol()) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
451
536
|
void this.openInboundSse(triedAuth, resumeToken).catch(error => {
|
|
452
537
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
453
538
|
return;
|
|
@@ -461,6 +546,10 @@ export class HttpMCPTransport implements MCPTransport {
|
|
|
461
546
|
triedAuth: boolean = false,
|
|
462
547
|
resumeToken?: string,
|
|
463
548
|
): Promise<void> {
|
|
549
|
+
if (this.isModernProtocol()) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
464
553
|
try {
|
|
465
554
|
const sessionIdForRequest = this.sessionId;
|
|
466
555
|
const headers = await this.commonHeaders({
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
auth,
|
|
15
15
|
type OAuthClientProvider,
|
|
16
16
|
} from './oauth';
|
|
17
|
-
import {
|
|
17
|
+
import { LATEST_LEGACY_PROTOCOL_VERSION } from './types';
|
|
18
18
|
|
|
19
19
|
function isMessageEvent(event: string | undefined): boolean {
|
|
20
20
|
return event === undefined || event === 'message';
|
|
@@ -69,7 +69,8 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
69
69
|
const headers: Record<string, string> = {
|
|
70
70
|
...this.headers,
|
|
71
71
|
...base,
|
|
72
|
-
'mcp-protocol-version':
|
|
72
|
+
'mcp-protocol-version':
|
|
73
|
+
this.protocolVersion ?? LATEST_LEGACY_PROTOCOL_VERSION,
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
if (this.authProvider) {
|
|
@@ -14,6 +14,7 @@ export interface StdioConfig {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class StdioMCPTransport implements MCPTransport {
|
|
17
|
+
readonly supportsProtocolVersionDiscovery = true;
|
|
17
18
|
private process?: ChildProcess;
|
|
18
19
|
private abortController: AbortController = new AbortController();
|
|
19
20
|
private readBuffer: ReadBuffer = new ReadBuffer();
|
|
@@ -4,6 +4,7 @@ import type { JSONRPCMessage } from './json-rpc-message';
|
|
|
4
4
|
import { SseMCPTransport } from './mcp-sse-transport';
|
|
5
5
|
import { HttpMCPTransport } from './mcp-http-transport';
|
|
6
6
|
import type { OAuthClientProvider } from './oauth';
|
|
7
|
+
import { LATEST_PROTOCOL_VERSION } from './types';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Transport interface for MCP (Model Context Protocol) communication.
|
|
@@ -15,6 +16,11 @@ export type MCPTransportSendOptions = {
|
|
|
15
16
|
*/
|
|
16
17
|
signal?: AbortSignal;
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Request-specific HTTP headers produced from MCP tool parameters.
|
|
21
|
+
*/
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* Associates an outgoing message with an incoming request.
|
|
20
26
|
*/
|
|
@@ -39,6 +45,20 @@ export type MCPTransportCloseOptions = {
|
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
export interface MCPTransport {
|
|
48
|
+
/**
|
|
49
|
+
* Whether this transport can probe for stateless MCP protocol versions.
|
|
50
|
+
*
|
|
51
|
+
* Custom transports default to the legacy initialization flow unless they
|
|
52
|
+
* explicitly opt in.
|
|
53
|
+
*/
|
|
54
|
+
supportsProtocolVersionDiscovery?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether this transport mirrors x-mcp-header tool parameters into request
|
|
58
|
+
* headers.
|
|
59
|
+
*/
|
|
60
|
+
supportsMcpToolParameterHeaders?: boolean;
|
|
61
|
+
|
|
42
62
|
/**
|
|
43
63
|
* Initialize and start the transport
|
|
44
64
|
*/
|
|
@@ -161,7 +181,11 @@ export function createMcpTransport(config: MCPTransportConfig): MCPTransport {
|
|
|
161
181
|
case 'sse':
|
|
162
182
|
return new SseMCPTransport(config);
|
|
163
183
|
case 'http':
|
|
164
|
-
return new HttpMCPTransport(
|
|
184
|
+
return new HttpMCPTransport({
|
|
185
|
+
...config,
|
|
186
|
+
initialProtocolVersion:
|
|
187
|
+
config.initialProtocolVersion ?? LATEST_PROTOCOL_VERSION,
|
|
188
|
+
});
|
|
165
189
|
default:
|
|
166
190
|
throw new MCPClientError({
|
|
167
191
|
message:
|
|
@@ -2,7 +2,7 @@ import { delay } from '@ai-sdk/provider-utils';
|
|
|
2
2
|
import type { JSONRPCMessage } from './json-rpc-message';
|
|
3
3
|
import type { MCPTransport } from './mcp-transport';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
LATEST_LEGACY_PROTOCOL_VERSION,
|
|
6
6
|
type MCPTool,
|
|
7
7
|
type MCPResource,
|
|
8
8
|
type MCPPrompt,
|
|
@@ -163,7 +163,7 @@ export class MockMCPTransport implements MCPTransport {
|
|
|
163
163
|
jsonrpc: '2.0',
|
|
164
164
|
id: message.id,
|
|
165
165
|
result: this.initializeResult || {
|
|
166
|
-
protocolVersion:
|
|
166
|
+
protocolVersion: LATEST_LEGACY_PROTOCOL_VERSION,
|
|
167
167
|
serverInfo: {
|
|
168
168
|
name: 'mock-mcp-server',
|
|
169
169
|
version: '1.0.0',
|
package/src/tool/oauth-types.ts
CHANGED
|
@@ -39,6 +39,7 @@ export const OAuthTokensSchema = z
|
|
|
39
39
|
expires_in: z.number().optional(),
|
|
40
40
|
scope: z.string().optional(),
|
|
41
41
|
refresh_token: z.string().optional(),
|
|
42
|
+
issuer: SafeUrlSchema.optional(),
|
|
42
43
|
authorization_server: SafeUrlSchema.optional(),
|
|
43
44
|
token_endpoint: SafeUrlSchema.optional(),
|
|
44
45
|
})
|
|
@@ -66,6 +67,8 @@ export const OAuthMetadataSchema = z.looseObject({
|
|
|
66
67
|
authorization_endpoint: SafeUrlSchema,
|
|
67
68
|
token_endpoint: SafeUrlSchema,
|
|
68
69
|
registration_endpoint: SafeUrlSchema.optional(),
|
|
70
|
+
authorization_response_iss_parameter_supported: z.boolean().optional(),
|
|
71
|
+
client_id_metadata_document_supported: z.boolean().optional(),
|
|
69
72
|
scopes_supported: z.array(z.string()).optional(),
|
|
70
73
|
response_types_supported: z.array(z.string()),
|
|
71
74
|
grant_types_supported: z.array(z.string()).optional(),
|
|
@@ -87,6 +90,8 @@ export const OpenIdProviderMetadataSchema = z.looseObject({
|
|
|
87
90
|
userinfo_endpoint: SafeUrlSchema.optional(),
|
|
88
91
|
jwks_uri: SafeUrlSchema,
|
|
89
92
|
registration_endpoint: SafeUrlSchema.optional(),
|
|
93
|
+
authorization_response_iss_parameter_supported: z.boolean().optional(),
|
|
94
|
+
client_id_metadata_document_supported: z.boolean().optional(),
|
|
90
95
|
scopes_supported: z.array(z.string()).optional(),
|
|
91
96
|
response_types_supported: z.array(z.string()),
|
|
92
97
|
grant_types_supported: z.array(z.string()).optional(),
|
|
@@ -114,6 +119,7 @@ export const OAuthClientInformationSchema = z
|
|
|
114
119
|
client_secret: z.string().optional(),
|
|
115
120
|
client_id_issued_at: z.number().optional(),
|
|
116
121
|
client_secret_expires_at: z.number().optional(),
|
|
122
|
+
issuer: SafeUrlSchema.optional(),
|
|
117
123
|
authorization_server: SafeUrlSchema.optional(),
|
|
118
124
|
token_endpoint: SafeUrlSchema.optional(),
|
|
119
125
|
})
|
|
@@ -122,6 +128,9 @@ export const OAuthClientInformationSchema = z
|
|
|
122
128
|
export const OAuthClientMetadataSchema = z
|
|
123
129
|
.object({
|
|
124
130
|
redirect_uris: z.array(SafeUrlSchema),
|
|
131
|
+
application_type: z
|
|
132
|
+
.union([z.literal('native'), z.literal('web')])
|
|
133
|
+
.optional(),
|
|
125
134
|
token_endpoint_auth_method: z.string().optional(),
|
|
126
135
|
grant_types: z.array(z.string()).optional(),
|
|
127
136
|
response_types: z.array(z.string()).optional(),
|
package/src/tool/oauth.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { parseJSON, type FetchFunction } from '@ai-sdk/provider-utils';
|
|
|
31
31
|
export type AuthResult = 'AUTHORIZED' | 'REDIRECT';
|
|
32
32
|
|
|
33
33
|
export interface OAuthAuthorizationServerInformation {
|
|
34
|
+
issuer?: string;
|
|
34
35
|
authorizationServerUrl: string;
|
|
35
36
|
tokenEndpoint: string;
|
|
36
37
|
}
|
|
@@ -122,11 +123,26 @@ function normalizeUrl(url: string | URL): string {
|
|
|
122
123
|
return new URL(url).href;
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
function validateAuthorizationResponseIssuer({
|
|
127
|
+
callbackIssuer,
|
|
128
|
+
expectedIssuer,
|
|
129
|
+
}: {
|
|
130
|
+
callbackIssuer: string | undefined;
|
|
131
|
+
expectedIssuer: string;
|
|
132
|
+
}): void {
|
|
133
|
+
if (callbackIssuer != null && callbackIssuer !== expectedIssuer) {
|
|
134
|
+
throw new MCPClientOAuthError({
|
|
135
|
+
message: `OAuth authorization response issuer ${callbackIssuer} does not match expected issuer ${expectedIssuer}`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
125
140
|
function createAuthorizationServerInformation(
|
|
126
141
|
authorizationServerUrl: string | URL,
|
|
127
142
|
metadata?: AuthorizationServerMetadata,
|
|
128
143
|
): OAuthAuthorizationServerInformation {
|
|
129
144
|
return {
|
|
145
|
+
issuer: metadata?.issuer ?? String(authorizationServerUrl),
|
|
130
146
|
authorizationServerUrl: normalizeUrl(authorizationServerUrl),
|
|
131
147
|
tokenEndpoint: normalizeUrl(
|
|
132
148
|
metadata?.token_endpoint
|
|
@@ -142,6 +158,7 @@ function addAuthorizationServerInformationToTokens(
|
|
|
142
158
|
): OAuthTokens {
|
|
143
159
|
return {
|
|
144
160
|
...tokens,
|
|
161
|
+
issuer: authorizationServerInformation.issuer,
|
|
145
162
|
authorization_server: authorizationServerInformation.authorizationServerUrl,
|
|
146
163
|
token_endpoint: authorizationServerInformation.tokenEndpoint,
|
|
147
164
|
};
|
|
@@ -155,12 +172,14 @@ function addAuthorizationServerInformationToClientInformation<
|
|
|
155
172
|
): CLIENT_INFORMATION {
|
|
156
173
|
return {
|
|
157
174
|
...clientInformation,
|
|
175
|
+
issuer: authorizationServerInformation.issuer,
|
|
158
176
|
authorization_server: authorizationServerInformation.authorizationServerUrl,
|
|
159
177
|
token_endpoint: authorizationServerInformation.tokenEndpoint,
|
|
160
178
|
};
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
function getAuthorizationServerInformationFromCredentials(credentials?: {
|
|
182
|
+
issuer?: string;
|
|
164
183
|
authorization_server?: string;
|
|
165
184
|
token_endpoint?: string;
|
|
166
185
|
}): OAuthAuthorizationServerInformation | undefined {
|
|
@@ -169,6 +188,7 @@ function getAuthorizationServerInformationFromCredentials(credentials?: {
|
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
return {
|
|
191
|
+
issuer: credentials.issuer,
|
|
172
192
|
authorizationServerUrl: normalizeUrl(credentials.authorization_server),
|
|
173
193
|
tokenEndpoint: normalizeUrl(credentials.token_endpoint),
|
|
174
194
|
};
|
|
@@ -193,6 +213,7 @@ async function getStoredAuthorizationServerInformation({
|
|
|
193
213
|
await provider.authorizationServerInformation?.();
|
|
194
214
|
if (providerAuthorizationServerInformation) {
|
|
195
215
|
return {
|
|
216
|
+
issuer: providerAuthorizationServerInformation.issuer,
|
|
196
217
|
authorizationServerUrl: normalizeUrl(
|
|
197
218
|
providerAuthorizationServerInformation.authorizationServerUrl,
|
|
198
219
|
),
|
|
@@ -258,6 +279,10 @@ function assertAuthorizationServerInformationMatches({
|
|
|
258
279
|
currentAuthorizationServerInformation: OAuthAuthorizationServerInformation;
|
|
259
280
|
}): void {
|
|
260
281
|
if (
|
|
282
|
+
(storedAuthorizationServerInformation.issuer != null &&
|
|
283
|
+
currentAuthorizationServerInformation.issuer != null &&
|
|
284
|
+
storedAuthorizationServerInformation.issuer !==
|
|
285
|
+
currentAuthorizationServerInformation.issuer) ||
|
|
261
286
|
storedAuthorizationServerInformation.authorizationServerUrl !==
|
|
262
287
|
currentAuthorizationServerInformation.authorizationServerUrl ||
|
|
263
288
|
storedAuthorizationServerInformation.tokenEndpoint !==
|
|
@@ -1050,12 +1075,18 @@ export async function registerClient(
|
|
|
1050
1075
|
registrationUrl = new URL('/register', authorizationServerUrl);
|
|
1051
1076
|
}
|
|
1052
1077
|
|
|
1078
|
+
const applicationType =
|
|
1079
|
+
clientMetadata.application_type ??
|
|
1080
|
+
inferOAuthApplicationType(clientMetadata.redirect_uris);
|
|
1053
1081
|
const response = await (fetchFn ?? fetch)(registrationUrl, {
|
|
1054
1082
|
method: 'POST',
|
|
1055
1083
|
headers: {
|
|
1056
1084
|
'Content-Type': 'application/json',
|
|
1057
1085
|
},
|
|
1058
|
-
body: JSON.stringify(
|
|
1086
|
+
body: JSON.stringify({
|
|
1087
|
+
...clientMetadata,
|
|
1088
|
+
application_type: applicationType,
|
|
1089
|
+
}),
|
|
1059
1090
|
});
|
|
1060
1091
|
|
|
1061
1092
|
if (!response.ok) {
|
|
@@ -1065,12 +1096,32 @@ export async function registerClient(
|
|
|
1065
1096
|
return OAuthClientInformationFullSchema.parse(await response.json());
|
|
1066
1097
|
}
|
|
1067
1098
|
|
|
1099
|
+
function inferOAuthApplicationType(redirectUris: string[]): 'native' | 'web' {
|
|
1100
|
+
const isNativeRedirectUri = (redirectUri: string): boolean => {
|
|
1101
|
+
const url = new URL(redirectUri);
|
|
1102
|
+
return (
|
|
1103
|
+
((url.protocol === 'http:' || url.protocol === 'https:') &&
|
|
1104
|
+
(url.hostname === 'localhost' ||
|
|
1105
|
+
url.hostname.endsWith('.localhost') ||
|
|
1106
|
+
url.hostname === '127.0.0.1' ||
|
|
1107
|
+
url.hostname === '[::1]')) ||
|
|
1108
|
+
(url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
1109
|
+
);
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
return redirectUris.every(isNativeRedirectUri) ? 'native' : 'web';
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1068
1115
|
export async function auth(
|
|
1069
1116
|
provider: OAuthClientProvider,
|
|
1070
1117
|
options: {
|
|
1071
1118
|
serverUrl: string | URL;
|
|
1072
1119
|
authorizationCode?: string;
|
|
1073
1120
|
callbackState?: string;
|
|
1121
|
+
/**
|
|
1122
|
+
* Value of the `iss` parameter from the authorization response.
|
|
1123
|
+
*/
|
|
1124
|
+
callbackIssuer?: string;
|
|
1074
1125
|
scope?: string;
|
|
1075
1126
|
resourceMetadataUrl?: URL;
|
|
1076
1127
|
fetchFn?: FetchFunction;
|
|
@@ -1131,6 +1182,7 @@ async function authInternal(
|
|
|
1131
1182
|
serverUrl,
|
|
1132
1183
|
authorizationCode,
|
|
1133
1184
|
callbackState,
|
|
1185
|
+
callbackIssuer,
|
|
1134
1186
|
scope,
|
|
1135
1187
|
resourceMetadataUrl,
|
|
1136
1188
|
fetchFn,
|
|
@@ -1138,6 +1190,7 @@ async function authInternal(
|
|
|
1138
1190
|
serverUrl: string | URL;
|
|
1139
1191
|
authorizationCode?: string;
|
|
1140
1192
|
callbackState?: string;
|
|
1193
|
+
callbackIssuer?: string;
|
|
1141
1194
|
scope?: string;
|
|
1142
1195
|
resourceMetadataUrl?: URL;
|
|
1143
1196
|
fetchFn?: FetchFunction;
|
|
@@ -1194,6 +1247,20 @@ async function authInternal(
|
|
|
1194
1247
|
|
|
1195
1248
|
/** Load or register client credentials with the AS pin attached. */
|
|
1196
1249
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
1250
|
+
if (clientInformation?.issuer != null) {
|
|
1251
|
+
const storedAuthorizationServerInformation =
|
|
1252
|
+
await getStoredAuthorizationServerInformation({
|
|
1253
|
+
provider,
|
|
1254
|
+
clientInformation,
|
|
1255
|
+
});
|
|
1256
|
+
if (storedAuthorizationServerInformation) {
|
|
1257
|
+
assertAuthorizationServerInformationMatches({
|
|
1258
|
+
storedAuthorizationServerInformation,
|
|
1259
|
+
currentAuthorizationServerInformation,
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1197
1264
|
if (!clientInformation) {
|
|
1198
1265
|
if (authorizationCode !== undefined) {
|
|
1199
1266
|
throw new Error(
|
|
@@ -1242,6 +1309,13 @@ async function authInternal(
|
|
|
1242
1309
|
'Stored OAuth authorization server metadata is required when exchanging an authorization code',
|
|
1243
1310
|
});
|
|
1244
1311
|
}
|
|
1312
|
+
validateAuthorizationResponseIssuer({
|
|
1313
|
+
callbackIssuer,
|
|
1314
|
+
expectedIssuer:
|
|
1315
|
+
storedAuthorizationServerInformation.issuer ??
|
|
1316
|
+
metadata?.issuer ??
|
|
1317
|
+
String(authorizationServerUrl),
|
|
1318
|
+
});
|
|
1245
1319
|
assertAuthorizationServerInformationMatches({
|
|
1246
1320
|
storedAuthorizationServerInformation,
|
|
1247
1321
|
currentAuthorizationServerInformation,
|
package/src/tool/types.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
import type { JSONObject } from '@ai-sdk/provider';
|
|
3
3
|
import type { FlexibleSchema, Tool } from '@ai-sdk/provider-utils';
|
|
4
4
|
|
|
5
|
-
export const LATEST_PROTOCOL_VERSION = '
|
|
5
|
+
export const LATEST_PROTOCOL_VERSION = '2026-07-28';
|
|
6
|
+
export const LATEST_LEGACY_PROTOCOL_VERSION = '2025-11-25';
|
|
6
7
|
export const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
7
8
|
LATEST_PROTOCOL_VERSION,
|
|
9
|
+
LATEST_LEGACY_PROTOCOL_VERSION,
|
|
8
10
|
'2025-06-18',
|
|
9
11
|
'2025-03-26',
|
|
10
12
|
'2024-11-05',
|
|
@@ -73,7 +75,9 @@ export const BaseParamsSchema = z.looseObject({
|
|
|
73
75
|
_meta: z.optional(z.object({}).loose()),
|
|
74
76
|
});
|
|
75
77
|
type BaseParams = z.infer<typeof BaseParamsSchema>;
|
|
76
|
-
export const ResultSchema = BaseParamsSchema
|
|
78
|
+
export const ResultSchema = BaseParamsSchema.extend({
|
|
79
|
+
resultType: z.optional(z.string()),
|
|
80
|
+
});
|
|
77
81
|
|
|
78
82
|
export const RequestSchema = z.object({
|
|
79
83
|
method: z.string(),
|
|
@@ -128,6 +132,15 @@ export const ClientCapabilitiesSchema = z
|
|
|
128
132
|
export type ClientCapabilities = z.infer<typeof ClientCapabilitiesSchema>;
|
|
129
133
|
export type ElicitationCapability = z.infer<typeof ElicitationCapabilitySchema>;
|
|
130
134
|
|
|
135
|
+
export const DiscoverResultSchema = ResultSchema.extend({
|
|
136
|
+
supportedVersions: z.array(z.string()),
|
|
137
|
+
capabilities: ServerCapabilitiesSchema,
|
|
138
|
+
instructions: z.optional(z.string()),
|
|
139
|
+
ttlMs: z.optional(z.number()),
|
|
140
|
+
cacheScope: z.optional(z.union([z.literal('public'), z.literal('private')])),
|
|
141
|
+
});
|
|
142
|
+
export type DiscoverResult = z.infer<typeof DiscoverResultSchema>;
|
|
143
|
+
|
|
131
144
|
export const InitializeResultSchema = ResultSchema.extend({
|
|
132
145
|
protocolVersion: z.string(),
|
|
133
146
|
capabilities: ServerCapabilitiesSchema,
|
|
@@ -154,12 +167,10 @@ const ToolSchema = z
|
|
|
154
167
|
*/
|
|
155
168
|
title: z.optional(z.string()),
|
|
156
169
|
description: z.optional(z.string()),
|
|
157
|
-
inputSchema: z
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
162
|
-
.loose(),
|
|
170
|
+
inputSchema: z.looseObject({
|
|
171
|
+
type: z.optional(z.unknown()),
|
|
172
|
+
properties: z.optional(z.object({}).loose()),
|
|
173
|
+
}),
|
|
163
174
|
/**
|
|
164
175
|
* @see https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
|
|
165
176
|
*/
|