diff --git a/packages/core/package.json b/packages/core/package.json index 7b42a50d0ed..4ff63aad5a1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -111,8 +111,8 @@ "@google/genai": "2.6.0", "@iarna/toml": "^2.2.5", "@jitl/quickjs-singlefile-mjs-release-sync": "0.32.0", - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/core": "2.0.0", + "@modelcontextprotocol/client": "2.4.0", + "@modelcontextprotocol/core": "2.4.0", "@modelcontextprotocol/sdk": "^1.30.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.221.0", diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts index 5a7e5f45de0..e536acbc7a6 100644 --- a/packages/core/src/tools/mcp-client.test.ts +++ b/packages/core/src/tools/mcp-client.test.ts @@ -2377,6 +2377,31 @@ lOTTGqPpwFUbw2EMOOpFYuIyzGMIpUNMBjE2gvJiqFQ= }); }); + // The assertion above only proves the inner fetch was told to stop. + // The SDK resolves redirects outside it, against `requestInit`, so the + // transport has to carry the same answer or a same-origin 30x is + // replayed with the credential attached. + it('carries the Agent Plugin redirect refusal into the transport', async () => { + const plugin = await transportFor( + { + httpUrl: 'https://example.com/mcp', + headers: { 'X-Api-Key': 'synthetic' }, + agentPluginV1: true, + }, + 'plugin-redirect', + ); + const plain = await transportFor( + { + httpUrl: 'https://example.com/mcp', + headers: { 'X-Api-Key': 'synthetic' }, + }, + 'plain-redirect', + ); + + expect(plugin._requestInit?.redirect).toBe('manual'); + expect(plain._requestInit?.redirect).toBeUndefined(); + }); + /** * Sends `init` (the optional GET/SSE probe by default) to /mcp through * the compatibility fetch over a stub answering `body` with `status`. diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 457ad4df4d4..6f4afd053db 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -272,6 +272,27 @@ function isStreamableHttpGetSseRequest(init?: RequestInit): boolean { .some((type) => type === 'text/event-stream'); } +/** + * Whether the Agent Plugin transport must refuse redirects: it may only reach + * the address it was configured with, and a configured or bearer credential + * makes a forwarded 30x a credential leak. + * + * The SDK resolves redirects outside the compatibility fetch, against + * `requestInit.redirect`, so both layers have to be told. Asking the same + * question in one place keeps them from disagreeing. + */ +function agentPluginForbidsRedirect( + mcpServerConfig: MCPServerConfig | undefined, + headers: Headers | undefined, +): boolean { + return ( + mcpServerConfig?.agentPluginV1 === true && + ((mcpServerConfig.headers !== undefined && + Object.keys(mcpServerConfig.headers).length > 0) || + headers?.has('authorization') === true) + ); +} + /** * Wraps fetch to preserve OAuth challenges before the SDK discards response * metadata and to normalize conventional "GET not supported" rejections of @@ -297,11 +318,10 @@ export function createStreamableHttpCompatibilityFetch( const requestHeaders = new Headers( init?.headers ?? (input instanceof Request ? input.headers : undefined), ); - const stopAgentPluginRedirect = - mcpServerConfig?.agentPluginV1 === true && - ((mcpServerConfig.headers !== undefined && - Object.keys(mcpServerConfig.headers).length > 0) || - requestHeaders.has('authorization')); + const stopAgentPluginRedirect = agentPluginForbidsRedirect( + mcpServerConfig, + requestHeaders, + ); const effectiveInit = stopAgentPluginRedirect ? { ...init, redirect: 'manual' as const } : init; @@ -1352,13 +1372,20 @@ async function createTransportWithOAuth( try { if (mcpServerConfig.httpUrl) { // Create HTTP transport with OAuth token + const oauthHeaders = { + ...mcpServerConfig.headers, + Authorization: `Bearer ${accessToken}`, + }; const oauthTransportOptions: StreamableHTTPClientTransportOptions = { fetch: createMcpStreamableHttpFetch(mcpServerName, mcpServerConfig), requestInit: { - headers: { - ...mcpServerConfig.headers, - Authorization: `Bearer ${accessToken}`, - }, + headers: oauthHeaders, + ...(agentPluginForbidsRedirect( + mcpServerConfig, + new Headers(oauthHeaders), + ) + ? { redirect: 'manual' as const } + : {}), }, }; @@ -2644,6 +2671,18 @@ export async function createTransport( }; } + // The compatibility fetch answers the request it issues, but the SDK + // resolves a redirect before that, against this field. + if ( + transportOptions.requestInit && + agentPluginForbidsRedirect( + mcpServerConfig, + new Headers(transportOptions.requestInit.headers as HeadersInit), + ) + ) { + transportOptions.requestInit.redirect = 'manual'; + } + return new StreamableHTTPClientTransport( new URL(mcpServerConfig.httpUrl), transportOptions, diff --git a/packages/vscode-ide-companion/NOTICES.txt b/packages/vscode-ide-companion/NOTICES.txt index 08a22bdd0fa..e60fab5fedc 100644 --- a/packages/vscode-ide-companion/NOTICES.txt +++ b/packages/vscode-ide-companion/NOTICES.txt @@ -5751,7 +5751,7 @@ SOFTWARE. ============================================================ -@modelcontextprotocol/client@2.0.0 +@modelcontextprotocol/client@2.4.0 (git+https://github.com/modelcontextprotocol/typescript-sdk.git) The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. @@ -5972,6 +5972,60 @@ CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for the full license text. +============================================================ +eventsource-parser@3.1.1 +(git+ssh://git@github.com/rexxars/eventsource-parser.git) + +MIT License + +Copyright (c) 2026 Espen Hovlandsdal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +============================================================ +jose@6.2.12 +(https://github.com/panva/jose) + +The MIT License (MIT) + +Copyright (c) 2018 Filip Skokan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + ============================================================ zod@4.4.3 (git+https://github.com/colinhacks/zod.git) @@ -6000,7 +6054,7 @@ SOFTWARE. ============================================================ -@modelcontextprotocol/core@2.0.0 +@modelcontextprotocol/core@2.4.0 (git+https://github.com/modelcontextprotocol/typescript-sdk.git) The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74032b934e9..3a8ad214980 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -990,11 +990,11 @@ importers: specifier: 0.32.0 version: 0.32.0 '@modelcontextprotocol/client': - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.4.0 + version: 2.4.0 '@modelcontextprotocol/core': - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.4.0 + version: 2.4.0 '@modelcontextprotocol/sdk': specifier: ^1.30.0 version: 1.30.0(supports-color@7.2.0)(zod@3.25.76) @@ -3130,12 +3130,12 @@ packages: resolution: {integrity: sha512-AAFtGyjsI4LcNhuXavc3ALIeDGqV/1CMclg285rV6h0Wpf5DObZmn9D2fTKTgRHKyGcclVEWc1pt3eJ9KEiPIg==} engines: {node: '>=18'} - '@modelcontextprotocol/client@2.0.0': - resolution: {integrity: sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==} + '@modelcontextprotocol/client@2.4.0': + resolution: {integrity: sha512-LgrD2OphHuG12vfKFKz6SOnq7bKHQmhGJIAYdD+MZBn2npHEyry6uD8zvktodtl5CQM85MO0anUyD9JwmUmxww==} engines: {node: '>=20'} - '@modelcontextprotocol/core@2.0.0': - resolution: {integrity: sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==} + '@modelcontextprotocol/core@2.4.0': + resolution: {integrity: sha512-z8QBKqdfXLPzkLQuG4xp8hrVEMm1PZ1IgxMN2sMbJiFiAKOWXnAzogXn2W4aqIBlP1ea6U5yiqVquvUNM1Cj6Q==} engines: {node: '>=20'} '@modelcontextprotocol/ext-apps@1.7.5': @@ -3511,6 +3511,7 @@ packages: '@opentelemetry/propagator-jaeger@2.10.0': resolution: {integrity: sha512-yw/IX8DL470dSMZJoE82ScfYGp7JWZ/G8kFJo35ZILUVTB2jFPTOaioN+8s09pH0RHsWNhweVZb+ZnjJJpCChg==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7077 peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3541,12 +3542,14 @@ packages: '@opentelemetry/sdk-trace-base@2.10.0': resolution: {integrity: sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' '@opentelemetry/sdk-trace-node@2.10.0': resolution: {integrity: sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -6967,6 +6970,10 @@ packages: resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} engines: {node: '>=20.0.0'} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + eventsource@3.0.7: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} @@ -12351,17 +12358,17 @@ snapshots: '@mobilewright/protocol@0.0.53': {} - '@modelcontextprotocol/client@2.0.0': + '@modelcontextprotocol/client@2.4.0': dependencies: - '@modelcontextprotocol/core': 2.0.0 + '@modelcontextprotocol/core': 2.4.0 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.0.3 - jose: 6.1.3 + eventsource-parser: 3.1.1 + jose: 6.2.12 pkce-challenge: 5.0.0 zod: 4.4.3 - '@modelcontextprotocol/core@2.0.0': + '@modelcontextprotocol/core@2.4.0': dependencies: zod: 4.4.3 @@ -16861,6 +16868,8 @@ snapshots: eventsource-parser@3.0.3: {} + eventsource-parser@3.1.1: {} + eventsource@3.0.7: dependencies: eventsource-parser: 3.0.3