@ai-sdk/mcp 1.0.97 → 1.0.98
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 +6 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +48 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/tool/oauth-types.ts +1 -0
- package/src/tool/oauth.ts +78 -3
package/package.json
CHANGED
package/src/tool/oauth-types.ts
CHANGED
|
@@ -120,6 +120,7 @@ export const OAuthClientInformationSchema = z
|
|
|
120
120
|
client_secret: z.string().optional(),
|
|
121
121
|
client_id_issued_at: z.number().optional(),
|
|
122
122
|
client_secret_expires_at: z.number().optional(),
|
|
123
|
+
issuer: SafeUrlSchema.optional(),
|
|
123
124
|
authorization_server: SafeUrlSchema.optional(),
|
|
124
125
|
token_endpoint: SafeUrlSchema.optional(),
|
|
125
126
|
})
|
package/src/tool/oauth.ts
CHANGED
|
@@ -92,6 +92,11 @@ export interface OAuthClientProvider {
|
|
|
92
92
|
): void | Promise<void>;
|
|
93
93
|
get redirectUrl(): string | URL;
|
|
94
94
|
get clientMetadata(): OAuthClientMetadata;
|
|
95
|
+
/**
|
|
96
|
+
* Returns client credentials. Pre-registered confidential clients must specify
|
|
97
|
+
* their authorization server's `issuer` or provide stored authorization server
|
|
98
|
+
* information. `validateAuthorizationServerURL` does not replace this binding.
|
|
99
|
+
*/
|
|
95
100
|
clientInformation():
|
|
96
101
|
| OAuthClientInformation
|
|
97
102
|
| undefined
|
|
@@ -119,6 +124,7 @@ export interface OAuthClientProvider {
|
|
|
119
124
|
/**
|
|
120
125
|
* Validates an authorization server URL discovered from MCP protected resource
|
|
121
126
|
* metadata before the client fetches its OAuth metadata.
|
|
127
|
+
* This additional restriction does not bind client credentials to that server.
|
|
122
128
|
*/
|
|
123
129
|
validateAuthorizationServerURL?(
|
|
124
130
|
serverUrl: string | URL,
|
|
@@ -1251,11 +1257,20 @@ export async function auth(
|
|
|
1251
1257
|
await provider.invalidateCredentials?.('all');
|
|
1252
1258
|
return await authInternal(provider, options);
|
|
1253
1259
|
} else if (error instanceof InvalidGrantError && refreshAttempt.tokens) {
|
|
1260
|
+
// Keep the binding validated before the refresh, even when deleting the
|
|
1261
|
+
// rejected tokens removes the provider's only copy of that binding.
|
|
1262
|
+
const retryAuthorizationServerInformation =
|
|
1263
|
+
getAuthorizationServerInformationFromCredentials(refreshAttempt.tokens);
|
|
1254
1264
|
// Only invalidate the tokens used by a failed refresh.
|
|
1255
1265
|
await provider.invalidateCredentials?.('tokens', {
|
|
1256
1266
|
tokens: refreshAttempt.tokens,
|
|
1257
1267
|
});
|
|
1258
|
-
return await authInternal(
|
|
1268
|
+
return await authInternal(
|
|
1269
|
+
provider,
|
|
1270
|
+
options,
|
|
1271
|
+
undefined,
|
|
1272
|
+
retryAuthorizationServerInformation,
|
|
1273
|
+
);
|
|
1259
1274
|
}
|
|
1260
1275
|
|
|
1261
1276
|
throw error;
|
|
@@ -1311,6 +1326,7 @@ async function authInternal(
|
|
|
1311
1326
|
fetchFn?: FetchFunction;
|
|
1312
1327
|
},
|
|
1313
1328
|
refreshAttempt?: { tokens?: OAuthTokens },
|
|
1329
|
+
retryAuthorizationServerInformation?: OAuthAuthorizationServerInformation,
|
|
1314
1330
|
): Promise<AuthResult> {
|
|
1315
1331
|
let resourceMetadata: OAuthProtectedResourceMetadata | undefined;
|
|
1316
1332
|
let authorizationServerUrl: string | URL | undefined;
|
|
@@ -1401,6 +1417,12 @@ async function authInternal(
|
|
|
1401
1417
|
);
|
|
1402
1418
|
const currentAuthorizationServerInformation =
|
|
1403
1419
|
createAuthorizationServerInformation(authorizationServerUrl, metadata);
|
|
1420
|
+
if (retryAuthorizationServerInformation) {
|
|
1421
|
+
assertAuthorizationServerInformationMatches({
|
|
1422
|
+
storedAuthorizationServerInformation: retryAuthorizationServerInformation,
|
|
1423
|
+
currentAuthorizationServerInformation,
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1404
1426
|
const clientMetadata = provider.clientMetadata;
|
|
1405
1427
|
const selectedScope = selectScope({
|
|
1406
1428
|
scope,
|
|
@@ -1412,6 +1434,61 @@ async function authInternal(
|
|
|
1412
1434
|
if (authorizationCode === undefined) {
|
|
1413
1435
|
clientInformation = await Promise.resolve(provider.clientInformation());
|
|
1414
1436
|
}
|
|
1437
|
+
const tokens =
|
|
1438
|
+
authorizationCode === undefined ? await provider.tokens() : undefined;
|
|
1439
|
+
if (clientInformation) {
|
|
1440
|
+
// A flow pin must never override the issuer attached to the credentials.
|
|
1441
|
+
if (
|
|
1442
|
+
clientInformation.issuer != null &&
|
|
1443
|
+
clientInformation.issuer !==
|
|
1444
|
+
(metadata?.issuer ?? authorizationServerUrl.toString())
|
|
1445
|
+
) {
|
|
1446
|
+
throw new MCPClientOAuthError({
|
|
1447
|
+
message:
|
|
1448
|
+
'OAuth authorization server does not match the issuer of the client credentials',
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const clientAuthorizationServerInformation =
|
|
1453
|
+
getAuthorizationServerInformationFromCredentials(clientInformation);
|
|
1454
|
+
if (clientAuthorizationServerInformation) {
|
|
1455
|
+
assertAuthorizationServerInformationMatches({
|
|
1456
|
+
storedAuthorizationServerInformation:
|
|
1457
|
+
clientAuthorizationServerInformation,
|
|
1458
|
+
currentAuthorizationServerInformation,
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const storedAuthorizationServerInformation =
|
|
1463
|
+
callbackAuthorizationServerInformation ??
|
|
1464
|
+
(await getStoredAuthorizationServerInformation({
|
|
1465
|
+
provider,
|
|
1466
|
+
clientInformation,
|
|
1467
|
+
tokens,
|
|
1468
|
+
})) ??
|
|
1469
|
+
retryAuthorizationServerInformation;
|
|
1470
|
+
if (storedAuthorizationServerInformation) {
|
|
1471
|
+
assertAuthorizationServerInformationMatches({
|
|
1472
|
+
storedAuthorizationServerInformation,
|
|
1473
|
+
currentAuthorizationServerInformation,
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// Do not turn an MCP server's choice of AS into a trust binding for an
|
|
1478
|
+
// existing secret. Dynamic registration below binds newly issued credentials.
|
|
1479
|
+
if (
|
|
1480
|
+
(clientInformation.client_secret != null ||
|
|
1481
|
+
provider.addClientAuthentication) &&
|
|
1482
|
+
clientInformation.issuer == null &&
|
|
1483
|
+
!storedAuthorizationServerInformation
|
|
1484
|
+
) {
|
|
1485
|
+
throw new MCPClientOAuthError({
|
|
1486
|
+
message:
|
|
1487
|
+
'OAuth client credentials must be bound to an authorization server: configure their issuer or store authorization server information',
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1415
1492
|
if (!clientInformation) {
|
|
1416
1493
|
if (authorizationCode !== undefined) {
|
|
1417
1494
|
throw new Error(
|
|
@@ -1490,8 +1567,6 @@ async function authInternal(
|
|
|
1490
1567
|
return 'AUTHORIZED';
|
|
1491
1568
|
}
|
|
1492
1569
|
|
|
1493
|
-
const tokens = await provider.tokens();
|
|
1494
|
-
|
|
1495
1570
|
/** Refresh only when stored credentials match the current AS pin */
|
|
1496
1571
|
if (tokens?.refresh_token) {
|
|
1497
1572
|
const storedAuthorizationServerInformation =
|