@ai-sdk/mcp 1.0.80 → 1.0.82
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 +12 -0
- package/dist/index.js +59 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/tool/mcp-sse-transport.ts +50 -10
- package/src/tool/oauth.ts +30 -5
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
extractWWWAuthenticateParams,
|
|
13
13
|
UnauthorizedError,
|
|
14
14
|
auth,
|
|
15
|
+
type AuthResult,
|
|
15
16
|
type OAuthClientProvider,
|
|
16
17
|
} from './oauth';
|
|
17
18
|
import { LATEST_PROTOCOL_VERSION } from './types';
|
|
@@ -33,6 +34,7 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
33
34
|
private resourceMetadataUrl?: URL;
|
|
34
35
|
private redirectMode: RequestRedirect;
|
|
35
36
|
private fetchFn: FetchFunction;
|
|
37
|
+
private authPromise?: Promise<AuthResult>;
|
|
36
38
|
|
|
37
39
|
onclose?: () => void;
|
|
38
40
|
onerror?: (error: unknown) => void;
|
|
@@ -86,6 +88,40 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
86
88
|
);
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Runs a single OAuth recovery flow for concurrent 401 responses.
|
|
93
|
+
*/
|
|
94
|
+
private authorizeOnce(
|
|
95
|
+
resourceMetadataUrl?: URL,
|
|
96
|
+
scope?: string,
|
|
97
|
+
): Promise<AuthResult> {
|
|
98
|
+
if (!this.authProvider) {
|
|
99
|
+
return Promise.resolve('REDIRECT');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!this.authPromise) {
|
|
103
|
+
this.authPromise = auth(this.authProvider, {
|
|
104
|
+
serverUrl: this.url,
|
|
105
|
+
resourceMetadataUrl,
|
|
106
|
+
scope,
|
|
107
|
+
fetchFn: this.fetchFn,
|
|
108
|
+
}).finally(() => {
|
|
109
|
+
this.authPromise = undefined;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.authPromise;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async accessTokenChanged(
|
|
117
|
+
requestAuthorization: string | undefined,
|
|
118
|
+
): Promise<boolean> {
|
|
119
|
+
const accessToken = (await this.authProvider?.tokens())?.access_token;
|
|
120
|
+
return (
|
|
121
|
+
accessToken != null && requestAuthorization !== `Bearer ${accessToken}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
89
125
|
async start(): Promise<void> {
|
|
90
126
|
return new Promise<void>((resolve, reject) => {
|
|
91
127
|
if (this.connected) {
|
|
@@ -110,12 +146,10 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
110
146
|
extractWWWAuthenticateParams(response);
|
|
111
147
|
this.resourceMetadataUrl = resourceMetadataUrl;
|
|
112
148
|
try {
|
|
113
|
-
const result = await
|
|
114
|
-
|
|
115
|
-
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
149
|
+
const result = await this.authorizeOnce(
|
|
150
|
+
this.resourceMetadataUrl,
|
|
116
151
|
scope,
|
|
117
|
-
|
|
118
|
-
});
|
|
152
|
+
);
|
|
119
153
|
if (result !== 'AUTHORIZED') {
|
|
120
154
|
const error = new UnauthorizedError();
|
|
121
155
|
this.onerror?.(error);
|
|
@@ -276,15 +310,21 @@ export class SseMCPTransport implements MCPTransport {
|
|
|
276
310
|
const response = await this.fetchFn(endpoint.href, init);
|
|
277
311
|
|
|
278
312
|
if (response.status === 401 && this.authProvider && !triedAuth) {
|
|
313
|
+
if (
|
|
314
|
+
await this.accessTokenChanged(
|
|
315
|
+
new Headers(headers).get('authorization') ?? undefined,
|
|
316
|
+
)
|
|
317
|
+
) {
|
|
318
|
+
return attempt(true);
|
|
319
|
+
}
|
|
320
|
+
|
|
279
321
|
const { resourceMetadataUrl, scope } =
|
|
280
322
|
extractWWWAuthenticateParams(response);
|
|
281
323
|
this.resourceMetadataUrl = resourceMetadataUrl;
|
|
282
|
-
const result = await
|
|
283
|
-
|
|
284
|
-
resourceMetadataUrl: this.resourceMetadataUrl,
|
|
324
|
+
const result = await this.authorizeOnce(
|
|
325
|
+
this.resourceMetadataUrl,
|
|
285
326
|
scope,
|
|
286
|
-
|
|
287
|
-
});
|
|
327
|
+
);
|
|
288
328
|
if (result !== 'AUTHORIZED') {
|
|
289
329
|
throw new UnauthorizedError();
|
|
290
330
|
}
|
package/src/tool/oauth.ts
CHANGED
|
@@ -1199,6 +1199,10 @@ async function authInternal(
|
|
|
1199
1199
|
): Promise<AuthResult> {
|
|
1200
1200
|
let resourceMetadata: OAuthProtectedResourceMetadata | undefined;
|
|
1201
1201
|
let authorizationServerUrl: string | URL | undefined;
|
|
1202
|
+
let clientInformation: OAuthClientInformation | undefined;
|
|
1203
|
+
let callbackAuthorizationServerInformation:
|
|
1204
|
+
| OAuthAuthorizationServerInformation
|
|
1205
|
+
| undefined;
|
|
1202
1206
|
|
|
1203
1207
|
/** Reject Protected Resource Metadata URLs outside the configured MCP server origin. */
|
|
1204
1208
|
assertResourceMetadataUrlSameOrigin(serverUrl, resourceMetadataUrl);
|
|
@@ -1218,9 +1222,27 @@ async function authInternal(
|
|
|
1218
1222
|
}
|
|
1219
1223
|
} catch {}
|
|
1220
1224
|
|
|
1221
|
-
/**
|
|
1225
|
+
/**
|
|
1226
|
+
* A callback may not have the original PRM URL from the authentication
|
|
1227
|
+
* challenge. Use the authorization server pinned before redirecting when
|
|
1228
|
+
* rediscovery does not select one.
|
|
1229
|
+
*/
|
|
1230
|
+
if (authorizationCode !== undefined) {
|
|
1231
|
+
clientInformation = await Promise.resolve(provider.clientInformation());
|
|
1232
|
+
if (clientInformation) {
|
|
1233
|
+
callbackAuthorizationServerInformation =
|
|
1234
|
+
await getStoredAuthorizationServerInformation({
|
|
1235
|
+
provider,
|
|
1236
|
+
clientInformation,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** Reuse the callback pin, then fall back to the legacy MCP-as-AS behavior. */
|
|
1222
1242
|
if (!authorizationServerUrl) {
|
|
1223
|
-
authorizationServerUrl =
|
|
1243
|
+
authorizationServerUrl =
|
|
1244
|
+
callbackAuthorizationServerInformation?.authorizationServerUrl ??
|
|
1245
|
+
serverUrl;
|
|
1224
1246
|
}
|
|
1225
1247
|
|
|
1226
1248
|
/** Validate and select the resource value sent to the AS */
|
|
@@ -1253,7 +1275,9 @@ async function authInternal(
|
|
|
1253
1275
|
});
|
|
1254
1276
|
|
|
1255
1277
|
/** Load or register client credentials with the AS pin attached. */
|
|
1256
|
-
|
|
1278
|
+
if (authorizationCode === undefined) {
|
|
1279
|
+
clientInformation = await Promise.resolve(provider.clientInformation());
|
|
1280
|
+
}
|
|
1257
1281
|
if (!clientInformation) {
|
|
1258
1282
|
if (authorizationCode !== undefined) {
|
|
1259
1283
|
throw new Error(
|
|
@@ -1295,10 +1319,11 @@ async function authInternal(
|
|
|
1295
1319
|
}
|
|
1296
1320
|
|
|
1297
1321
|
const storedAuthorizationServerInformation =
|
|
1298
|
-
|
|
1322
|
+
callbackAuthorizationServerInformation ??
|
|
1323
|
+
(await getStoredAuthorizationServerInformation({
|
|
1299
1324
|
provider,
|
|
1300
1325
|
clientInformation,
|
|
1301
|
-
});
|
|
1326
|
+
}));
|
|
1302
1327
|
if (!storedAuthorizationServerInformation) {
|
|
1303
1328
|
throw new MCPClientOAuthError({
|
|
1304
1329
|
message:
|