@ai-sdk/mcp 1.0.73 → 1.0.75

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.73",
3
+ "version": "1.0.75",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "cross-spawn": "^7.0.6",
36
36
  "pkce-challenge": "^5.0.0",
37
37
  "@ai-sdk/provider": "3.0.15",
38
- "@ai-sdk/provider-utils": "4.0.47"
38
+ "@ai-sdk/provider-utils": "4.0.48"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/cross-spawn": "^6.0.6",
@@ -72,7 +72,7 @@
72
72
  "scripts": {
73
73
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
74
74
  "build:watch": "pnpm clean && tsup --watch",
75
- "clean": "rm -rf dist *.tsbuildinfo",
75
+ "clean": "del-cli dist *.tsbuildinfo",
76
76
  "type-check": "tsc --build",
77
77
  "test": "pnpm test:node && pnpm test:edge",
78
78
  "test:update": "pnpm test:node -u",
@@ -903,8 +903,17 @@ class DefaultMCPClient implements MCPClient {
903
903
  }: {
904
904
  schemas?: TOOL_SCHEMAS;
905
905
  } = {}): Promise<McpToolSet<TOOL_SCHEMAS>> {
906
- const definitions = await this.listTools();
907
- return this.toolsFromDefinitions(definitions, {
906
+ let definitions = await this.listTools();
907
+ const tools = [...definitions.tools];
908
+
909
+ while (definitions.nextCursor != null) {
910
+ definitions = await this.listTools({
911
+ params: { cursor: definitions.nextCursor },
912
+ });
913
+ tools.push(...definitions.tools);
914
+ }
915
+
916
+ return this.toolsFromDefinitions({ ...definitions, tools }, {
908
917
  schemas,
909
918
  } as { schemas?: TOOL_SCHEMAS });
910
919
  }
package/src/tool/oauth.ts CHANGED
@@ -27,8 +27,11 @@ import {
27
27
  resourceUrlStripSlash,
28
28
  } from '../util/oauth-util';
29
29
  import { LATEST_PROTOCOL_VERSION } from './types';
30
- import { parseJSON, type FetchFunction } from '@ai-sdk/provider-utils';
31
-
30
+ import {
31
+ parseJSON,
32
+ validateDownloadUrl,
33
+ type FetchFunction,
34
+ } from '@ai-sdk/provider-utils';
32
35
  export type AuthResult = 'AUTHORIZED' | 'REDIRECT';
33
36
 
34
37
  export interface OAuthAuthorizationServerInformation {
@@ -123,6 +126,45 @@ function normalizeUrl(url: string | URL): string {
123
126
  return new URL(url).href;
124
127
  }
125
128
 
129
+ /** Allow loopback HTTP(S) for local MCP OAuth (RFC 8252 §7.3, RFC 6761 §6.3). */
130
+ function isOAuthLoopbackHost(hostname: string): boolean {
131
+ const normalized = hostname.toLowerCase().replace(/\.+$/, '');
132
+ return (
133
+ normalized === 'localhost' ||
134
+ normalized.endsWith('.localhost') ||
135
+ normalized === '127.0.0.1' ||
136
+ normalized === '[::1]' ||
137
+ normalized === '::1'
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Guards metadata-derived token/registration URLs before credentials are sent.
143
+ * Loopback is allowed for local OAuth; every other target uses the shared
144
+ * download URL guard (http(s) only, no private/link-local IPs).
145
+ *
146
+ * Credential POSTs use `redirect: 'error'` instead of
147
+ * `fetchWithValidatedRedirects`, which is GET-only and would follow hops with
148
+ * the authorization code, PKCE verifier, and client secret still attached.
149
+ */
150
+ function assertSafeOAuthEndpoint(endpointUrl: URL): void {
151
+ if (
152
+ (endpointUrl.protocol === 'http:' || endpointUrl.protocol === 'https:') &&
153
+ isOAuthLoopbackHost(endpointUrl.hostname)
154
+ ) {
155
+ return;
156
+ }
157
+
158
+ try {
159
+ validateDownloadUrl(endpointUrl.href);
160
+ } catch (error) {
161
+ throw new MCPClientOAuthError({
162
+ message: `OAuth endpoint URL is not allowed: ${endpointUrl.href}`,
163
+ cause: error,
164
+ });
165
+ }
166
+ }
167
+
126
168
  function createAuthorizationServerInformation(
127
169
  authorizationServerUrl: string | URL,
128
170
  metadata?: AuthorizationServerMetadata,
@@ -872,6 +914,7 @@ export async function exchangeAuthorization(
872
914
  const tokenUrl = metadata?.token_endpoint
873
915
  ? new URL(metadata.token_endpoint)
874
916
  : new URL('/token', authorizationServerUrl);
917
+ assertSafeOAuthEndpoint(tokenUrl);
875
918
 
876
919
  if (
877
920
  metadata?.grant_types_supported &&
@@ -919,6 +962,7 @@ export async function exchangeAuthorization(
919
962
  method: 'POST',
920
963
  headers,
921
964
  body: params,
965
+ redirect: 'error',
922
966
  });
923
967
 
924
968
  if (!response.ok) {
@@ -975,6 +1019,7 @@ export async function refreshAuthorization(
975
1019
  } else {
976
1020
  tokenUrl = new URL('/token', authorizationServerUrl);
977
1021
  }
1022
+ assertSafeOAuthEndpoint(tokenUrl);
978
1023
 
979
1024
  const headers = new Headers({
980
1025
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -1011,6 +1056,7 @@ export async function refreshAuthorization(
1011
1056
  method: 'POST',
1012
1057
  headers,
1013
1058
  body: params,
1059
+ redirect: 'error',
1014
1060
  });
1015
1061
  if (!response.ok) {
1016
1062
  throw await parseErrorResponse(response);
@@ -1050,6 +1096,7 @@ export async function registerClient(
1050
1096
  } else {
1051
1097
  registrationUrl = new URL('/register', authorizationServerUrl);
1052
1098
  }
1099
+ assertSafeOAuthEndpoint(registrationUrl);
1053
1100
 
1054
1101
  const response = await (fetchFn ?? fetch)(registrationUrl, {
1055
1102
  method: 'POST',
@@ -1057,6 +1104,7 @@ export async function registerClient(
1057
1104
  'Content-Type': 'application/json',
1058
1105
  },
1059
1106
  body: JSON.stringify(clientMetadata),
1107
+ redirect: 'error',
1060
1108
  });
1061
1109
 
1062
1110
  if (!response.ok) {
@@ -1192,6 +1240,12 @@ async function authInternal(
1192
1240
  );
1193
1241
  const currentAuthorizationServerInformation =
1194
1242
  createAuthorizationServerInformation(authorizationServerUrl, metadata);
1243
+ const clientMetadata = provider.clientMetadata;
1244
+ const selectedScope = selectScope({
1245
+ scope,
1246
+ resourceMetadata,
1247
+ clientMetadata,
1248
+ });
1195
1249
 
1196
1250
  /** Load or register client credentials with the AS pin attached. */
1197
1251
  let clientInformation = await Promise.resolve(provider.clientInformation());
@@ -1210,7 +1264,10 @@ async function authInternal(
1210
1264
 
1211
1265
  const fullInformation = await registerClient(authorizationServerUrl, {
1212
1266
  metadata,
1213
- clientMetadata: provider.clientMetadata,
1267
+ clientMetadata: {
1268
+ ...clientMetadata,
1269
+ scope: selectedScope,
1270
+ },
1214
1271
  fetchFn,
1215
1272
  });
1216
1273
 
@@ -1337,11 +1394,7 @@ async function authInternal(
1337
1394
  clientInformation,
1338
1395
  state,
1339
1396
  redirectUrl: provider.redirectUrl,
1340
- scope: selectScope({
1341
- scope,
1342
- resourceMetadata,
1343
- clientMetadata: provider.clientMetadata,
1344
- }),
1397
+ scope: selectedScope,
1345
1398
  resource,
1346
1399
  },
1347
1400
  );