@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/mcp",
3
- "version": "1.0.97",
3
+ "version": "1.0.98",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -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(provider, options);
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 =