diff --git a/packages/cli/package.json b/packages/cli/package.json index ceeb3d4144b..d6d535f4853 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -140,8 +140,8 @@ "@joplin/turndown-plugin-gfm": "catalog:", "@langchain/core": "catalog:", "@modelcontextprotocol/node": "catalog:", - "@modelcontextprotocol/sdk": "catalog:", "@modelcontextprotocol/server": "catalog:", + "@modelcontextprotocol/server-legacy": "catalog:", "@mozilla/readability": "catalog:", "@n8n/agents": "workspace:*", "@n8n/ai-node-sdk": "workspace:*", diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-authorization-code.service.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-authorization-code.service.test.ts index db15dbaee2d..6280c22b169 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-authorization-code.service.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-authorization-code.service.test.ts @@ -1,5 +1,5 @@ import type { Mocked } from 'vitest'; -import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { InvalidGrantError } from '@modelcontextprotocol/server-legacy/auth'; import { mockInstance } from '@n8n/backend-test-utils'; import { mock } from 'vitest-mock-extended'; diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts index b49e0706fde..5fef61081a5 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts @@ -288,7 +288,7 @@ describe('POST /rest/consent/approve', () => { expect(redirectUrl.searchParams.get('code')?.length).toBeGreaterThan(32); expect(redirectUrl.searchParams.get('state')).toBe('test-state'); expect(redirectUrl.searchParams.get('iss')).toBe( - Container.get(UrlService).getInstanceBaseUrl(), + new URL(Container.get(UrlService).getInstanceBaseUrl()).href, ); }); @@ -310,7 +310,7 @@ describe('POST /rest/consent/approve', () => { expect(redirectUrl.searchParams.get('error_description')).toBeTruthy(); expect(redirectUrl.searchParams.get('state')).toBe('test-state'); expect(redirectUrl.searchParams.get('iss')).toBe( - Container.get(UrlService).getInstanceBaseUrl(), + new URL(Container.get(UrlService).getInstanceBaseUrl()).href, ); }); diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.service.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.service.test.ts index e044d02b37f..e8f96a69ddc 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.service.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.service.test.ts @@ -345,7 +345,7 @@ describe('OAuthConsentService', () => { 'error_description=User+denied+the+authorization+request', ); expect(result.redirectUrl).toContain('state=state-xyz'); - expect(new URL(result.redirectUrl).searchParams.get('iss')).toBe(issuer); + expect(new URL(result.redirectUrl).searchParams.get('iss')).toBe(new URL(issuer).href); expect(logger.info).toHaveBeenCalledWith('Consent denied', { clientId: 'client-123', userId: 'user-123', @@ -379,7 +379,7 @@ describe('OAuthConsentService', () => { expect(result.redirectUrl).toContain('code=generated-auth-code'); expect(result.redirectUrl).toContain('state=state-xyz'); - expect(new URL(result.redirectUrl).searchParams.get('iss')).toBe(issuer); + expect(new URL(result.redirectUrl).searchParams.get('iss')).toBe(new URL(issuer).href); expect(userConsentRepository.upsert).toHaveBeenCalledWith( { userId: 'user-123', diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts index 519f95d36f8..b1315a48777 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts @@ -1,7 +1,4 @@ -import { - InvalidGrantError, - InvalidTargetError, -} from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { InvalidGrantError, InvalidTargetError } from '@modelcontextprotocol/server-legacy/auth'; import { Logger, type ModuleRegistry } from '@n8n/backend-common'; import { mockInstance } from '@n8n/backend-test-utils'; import { GlobalConfig } from '@n8n/config'; diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts index 0ac5a2a4ed9..7bec873753d 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts @@ -63,10 +63,13 @@ describe('GET /.well-known/oauth-authorization-server', () => { } = response.body; expect(issuer).toMatch(/^https?:\/\//); - expect(authorization_endpoint).toBe(`${issuer}/mcp-oauth/authorize`); - expect(token_endpoint).toBe(`${issuer}/mcp-oauth/token`); - expect(registration_endpoint).toBe(`${issuer}/mcp-oauth/register`); - expect(revocation_endpoint).toBe(`${issuer}/mcp-oauth/revoke`); + // `issuer` is the canonical RFC 9207 form (trailing slash for an origin-only + // base URL); the endpoints are built from the bare base URL, so trim it. + const origin = issuer.replace(/\/$/, ''); + expect(authorization_endpoint).toBe(`${origin}/mcp-oauth/authorize`); + expect(token_endpoint).toBe(`${origin}/mcp-oauth/token`); + expect(registration_endpoint).toBe(`${origin}/mcp-oauth/register`); + expect(revocation_endpoint).toBe(`${origin}/mcp-oauth/revoke`); }); test('should include all required OAuth 2.1 fields', async () => { @@ -461,7 +464,7 @@ describe('GET /mcp-oauth/authorize', () => { expect(redirectUrl.origin + redirectUrl.pathname).toBe('https://example.com/callback'); expect(redirectUrl.searchParams.get('error')).toBe('invalid_request'); expect(redirectUrl.searchParams.get('iss')).toBe( - Container.get(UrlService).getInstanceBaseUrl(), + new URL(Container.get(UrlService).getInstanceBaseUrl()).href, ); }); }); @@ -672,7 +675,7 @@ describe('Full authorization-code flow (PKCE)', () => { expect(code).toBeTruthy(); expect(redirectUrl.searchParams.get('state')).toBe('flow-state'); expect(redirectUrl.searchParams.get('iss')).toBe( - Container.get(UrlService).getInstanceBaseUrl(), + new URL(Container.get(UrlService).getInstanceBaseUrl()).href, ); // 4. Token exchange diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts index ffef6f50fe3..b29b21cf41c 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts @@ -1,4 +1,4 @@ -import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; +import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/server-legacy/auth'; import { mockInstance } from '@n8n/backend-test-utils'; import type { Request, Response } from 'express'; import { mock } from 'vitest-mock-extended'; @@ -108,7 +108,8 @@ describe('OAuthController', () => { expect(res.json).toHaveBeenCalledWith({ resource: 'https://n8n.test/mcp-server/http', bearer_methods_supported: ['header'], - authorization_servers: ['https://n8n.test'], + // Canonical issuer form (trailing slash for an origin-only base URL). + authorization_servers: ['https://n8n.test/'], scopes_supported: scopes, }); }); diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.helpers.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.helpers.test.ts index aa8d5bc68f6..d03641c093a 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.helpers.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.helpers.test.ts @@ -185,37 +185,22 @@ describe('OAuthHelpers', () => { }); }); - describe('setIssuerParam', () => { - it('should set iss on an absolute URL without one', () => { - const result = OAuthHelpers.setIssuerParam('https://example.com/callback', issuer); - - expect(new URL(result).searchParams.get('iss')).toBe(issuer); - }); - - it('should preserve existing query parameters', () => { - const result = OAuthHelpers.setIssuerParam( - 'https://example.com/callback?error=invalid_request&state=state-xyz', - issuer, + describe('canonicalIssuer', () => { + it('appends a trailing slash to an origin-only base URL', () => { + expect(OAuthHelpers.canonicalIssuer('https://n8n.example.com')).toBe( + 'https://n8n.example.com/', ); - - const url = new URL(result); - expect(url.searchParams.get('error')).toBe('invalid_request'); - expect(url.searchParams.get('state')).toBe('state-xyz'); - expect(url.searchParams.get('iss')).toBe(issuer); }); - it('should replace a pre-existing iss parameter with the server issuer', () => { - const result = OAuthHelpers.setIssuerParam( - 'https://example.com/callback?iss=https%3A%2F%2Fother.example.com', - issuer, + it('leaves a base URL that already has a path untouched', () => { + expect(OAuthHelpers.canonicalIssuer('https://example.com/n8n')).toBe( + 'https://example.com/n8n', ); - - expect(new URL(result).searchParams.get('iss')).toBe(issuer); - expect(new URL(result).searchParams.getAll('iss')).toHaveLength(1); }); - it('should return relative URLs unchanged', () => { - expect(OAuthHelpers.setIssuerParam('/oauth/consent', issuer)).toBe('/oauth/consent'); + it('matches the href form the authorize handler stamps as iss', () => { + const baseUrl = 'https://n8n.example.com'; + expect(OAuthHelpers.canonicalIssuer(baseUrl)).toBe(new URL(baseUrl).href); }); }); }); diff --git a/packages/cli/src/modules/oauth-server/oauth-authorization-code.service.ts b/packages/cli/src/modules/oauth-server/oauth-authorization-code.service.ts index c3381897126..8bf89889d5a 100644 --- a/packages/cli/src/modules/oauth-server/oauth-authorization-code.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-authorization-code.service.ts @@ -1,4 +1,4 @@ -import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { InvalidGrantError } from '@modelcontextprotocol/server-legacy/auth'; import { Time } from '@n8n/constants'; import { Service } from '@n8n/di'; import { randomBytes } from 'node:crypto'; diff --git a/packages/cli/src/modules/oauth-server/oauth-consent.service.ts b/packages/cli/src/modules/oauth-server/oauth-consent.service.ts index 3874c1debc8..a88fc0cf3d0 100644 --- a/packages/cli/src/modules/oauth-server/oauth-consent.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-consent.service.ts @@ -160,7 +160,9 @@ export class OAuthConsentService { throw new UserError('Invalid or expired session'); } - const issuer = this.urlService.getInstanceBaseUrl(); + // Same canonical `href` form the authorize handler and metadata use, so + // the RFC 9207 `iss` on the consent redirect matches the advertised issuer. + const issuer = OAuthHelpers.canonicalIssuer(this.urlService.getInstanceBaseUrl()); if (!approved) { const redirectUrl = OAuthHelpers.buildErrorRedirectUrl( diff --git a/packages/cli/src/modules/oauth-server/oauth-flow.service.ts b/packages/cli/src/modules/oauth-server/oauth-flow.service.ts index 229959f518d..81e8586ed84 100644 --- a/packages/cli/src/modules/oauth-server/oauth-flow.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-flow.service.ts @@ -1,4 +1,4 @@ -import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { InvalidGrantError } from '@modelcontextprotocol/server-legacy/auth'; import { Time } from '@n8n/constants'; import { Service } from '@n8n/di'; import { UserError, type N8nOAuth2FlowResult } from 'n8n-workflow'; diff --git a/packages/cli/src/modules/oauth-server/oauth-server.service.ts b/packages/cli/src/modules/oauth-server/oauth-server.service.ts index 4941f1ab31b..b434f10402a 100644 --- a/packages/cli/src/modules/oauth-server/oauth-server.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-server.service.ts @@ -1,18 +1,15 @@ -import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; -import { - InvalidGrantError, - InvalidTargetError, -} from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import type { - AuthorizationParams, - OAuthServerProvider, -} from '@modelcontextprotocol/sdk/server/auth/provider.js'; -import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; import type { + AuthInfo, OAuthClientInformationFull, OAuthTokens, OAuthTokenRevocationRequest, -} from '@modelcontextprotocol/sdk/shared/auth.js'; +} from '@modelcontextprotocol/server'; +import { InvalidGrantError, InvalidTargetError } from '@modelcontextprotocol/server-legacy/auth'; +import type { + AuthorizationParams, + OAuthRegisteredClientsStore, + OAuthServerProvider, +} from '@modelcontextprotocol/server-legacy/auth'; import type { McpClientConnectedPeriod, McpClientTypeFilter } from '@n8n/api-types'; import { getMcpClientType, MCP_CLIENT_TYPE_FILTER_BUCKETS } from '@n8n/api-types'; import { Logger } from '@n8n/backend-common'; diff --git a/packages/cli/src/modules/oauth-server/oauth-token.service.ts b/packages/cli/src/modules/oauth-server/oauth-token.service.ts index 087398ea9db..a0ff6be4ee2 100644 --- a/packages/cli/src/modules/oauth-server/oauth-token.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-token.service.ts @@ -1,6 +1,5 @@ -import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; -import { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js'; +import type { AuthInfo, OAuthTokens } from '@modelcontextprotocol/server'; +import { InvalidGrantError } from '@modelcontextprotocol/server-legacy/auth'; import { Logger } from '@n8n/backend-common'; import { Time } from '@n8n/constants'; import { TransactionRunner, UserRepository } from '@n8n/db'; diff --git a/packages/cli/src/modules/oauth-server/oauth.controller.ts b/packages/cli/src/modules/oauth-server/oauth.controller.ts index e7011ef4814..1d6b6412536 100644 --- a/packages/cli/src/modules/oauth-server/oauth.controller.ts +++ b/packages/cli/src/modules/oauth-server/oauth.controller.ts @@ -1,7 +1,9 @@ -import { authorizationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/authorize.js'; -import { clientRegistrationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/register.js'; -import { revocationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/revoke.js'; -import { tokenHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/token.js'; +import { + authorizationHandler, + clientRegistrationHandler, + revocationHandler, + tokenHandler, +} from '@modelcontextprotocol/server-legacy/auth'; import { Logger } from '@n8n/backend-common'; import { GlobalConfig } from '@n8n/config'; import { Time } from '@n8n/constants'; @@ -57,32 +59,22 @@ const oauthClientLimitGuard: RequestHandler = async (_req, res, next) => { next(); }; -/** - * The SDK's authorization handler redirects request-validation errors (e.g. - * missing `code_challenge`) back to the client without the RFC 9207 `iss` - * parameter, while our metadata advertises - * `authorization_response_iss_parameter_supported`. Wrap `res.location` - * (which `res.redirect` sets its target through) so every absolute-URL - * redirect from the authorize route carries `iss` matching the advertised - * issuer. Internal relative redirects (consent screen) pass through - * untouched. Remove once `@modelcontextprotocol/sdk` ships - * `authorizationHandler({ issuerUrl })` (on upstream main, unreleased as of - * 1.29.0) and the catalog version is bumped past it. - */ -const rfc9207IssuerParam: RequestHandler = (_req, res, next) => { - const originalLocation = res.location.bind(res); - res.location = (url: string) => - originalLocation(OAuthHelpers.setIssuerParam(url, urlService.getInstanceBaseUrl())); - next(); -}; - // Built once and mounted under both the legacy `/mcp-oauth/*` paths (existing // DCR clients hold them in their stored discovery metadata) and the neutral // `/oauth/*` paths that future, non-MCP protected resources will advertise. const registerRouter = clientRegistrationHandler({ clientsStore: oauthServerService.clientsStore, }) as Router; -const authorizeRouter = authorizationHandler({ provider: oauthServerService }) as Router; +// `issuerUrl` makes the handler stamp the RFC 9207 `iss` parameter onto every +// redirect it drives back to the client's validated `redirect_uri` — including +// its own request-validation error redirects (e.g. missing `code_challenge`), +// which is what our metadata's `authorization_response_iss_parameter_supported` +// promises. Success redirects run through the consent flow, which sets `iss` +// itself. +const authorizeRouter = authorizationHandler({ + provider: oauthServerService, + issuerUrl: new URL(urlService.getInstanceBaseUrl()), +}) as Router; const tokenRouter = tokenHandler({ provider: oauthServerService }) as Router; const revokeRouter = revocationHandler({ provider: oauthServerService }) as Router; @@ -101,7 +93,6 @@ const sharedEndpointRouters = (basePath: '/mcp-oauth' | '/oauth'): StaticRouterM path: `${basePath}/authorize`, router: authorizeRouter, skipAuth: true, - middlewares: [rfc9207IssuerParam], ipRateLimit: createIpRateLimit( oauthServerConfig.rateLimitAuthorize, 5 * Time.minutes.toMilliseconds, @@ -182,7 +173,9 @@ export class OAuthController { const baseUrl = this.urlService.getInstanceBaseUrl(); const allScopes = this.resourceRegistry.getAllScopes(); const metadata: Record = { - issuer: baseUrl, + // Canonical `href` form so it matches the RFC 9207 `iss` the authorize + // handler stamps onto redirects (see OAuthHelpers.canonicalIssuer). + issuer: OAuthHelpers.canonicalIssuer(baseUrl), authorization_endpoint: `${baseUrl}/mcp-oauth/authorize`, token_endpoint: `${baseUrl}/mcp-oauth/token`, registration_endpoint: `${baseUrl}/mcp-oauth/register`, @@ -294,7 +287,9 @@ export class OAuthController { const metadata: Record = { resource: resource.getResourceUrl(), bearer_methods_supported: ['header'], - authorization_servers: [baseUrl], + // Canonical issuer form so it matches the `issuer` in the + // authorization-server metadata a client cross-checks it against. + authorization_servers: [OAuthHelpers.canonicalIssuer(baseUrl)], }; if (resource.scopes.length > 0) { diff --git a/packages/cli/src/modules/oauth-server/oauth.errors.ts b/packages/cli/src/modules/oauth-server/oauth.errors.ts index 1a2a8464e85..0880bc51060 100644 --- a/packages/cli/src/modules/oauth-server/oauth.errors.ts +++ b/packages/cli/src/modules/oauth-server/oauth.errors.ts @@ -1,4 +1,4 @@ -import { ServerError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { ServerError } from '@modelcontextprotocol/server-legacy/auth'; import { AuthError } from '@/errors/response-errors/auth.error'; diff --git a/packages/cli/src/modules/oauth-server/oauth.helpers.ts b/packages/cli/src/modules/oauth-server/oauth.helpers.ts index fd2f531e0da..812593955b5 100644 --- a/packages/cli/src/modules/oauth-server/oauth.helpers.ts +++ b/packages/cli/src/modules/oauth-server/oauth.helpers.ts @@ -2,6 +2,20 @@ * Static utility functions for OAuth URL building */ export class OAuthHelpers { + /** + * Canonical RFC 8414/9207 issuer identifier for a given instance base URL. + * + * The `server-legacy/auth` authorize handler derives the RFC 9207 `iss` + * parameter from `issuerUrl.href`, so an origin-only base URL comes back with + * a trailing slash (`https://host` → `https://host/`). RFC 9207 requires the + * `iss` on every authorization response to be byte-identical to the `issuer` + * advertised in metadata, so the metadata document and the consent redirects + * must use this same `href` normalization rather than the bare base URL. + */ + static canonicalIssuer(baseUrl: string): string { + return new URL(baseUrl).href; + } + /** * Build success redirect URL with authorization code * Used when user approves consent @@ -47,19 +61,4 @@ export class OAuthHelpers { targetUrl.searchParams.set('iss', issuer); return targetUrl.toString(); } - - /** - * Set the RFC 9207 `iss` parameter on an absolute redirect URL, replacing - * any pre-existing value (e.g. one baked into the client's registered - * redirect URI) so the response always asserts this server's identity. - * Relative URLs (internal redirects, e.g. to the consent screen) are - * returned unchanged. - */ - static setIssuerParam(url: string, issuer: string): string { - if (!URL.canParse(url)) return url; - - const targetUrl = new URL(url); - targetUrl.searchParams.set('iss', issuer); - return targetUrl.toString(); - } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13365d9a9f6..e2e778c8eea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ catalogs: '@modelcontextprotocol/server': specifier: 2.0.0 version: 2.0.0 + '@modelcontextprotocol/server-legacy': + specifier: 2.0.0 + version: 2.0.0 '@mozilla/readability': specifier: ^0.6.0 version: 0.6.0 @@ -4084,12 +4087,12 @@ importers: '@modelcontextprotocol/node': specifier: 'catalog:' version: 2.0.0(@modelcontextprotocol/server@2.0.0)(hono@4.12.27) - '@modelcontextprotocol/sdk': - specifier: 'catalog:' - version: 1.26.0(zod@3.25.76) '@modelcontextprotocol/server': specifier: 'catalog:' version: 2.0.0 + '@modelcontextprotocol/server-legacy': + specifier: 'catalog:' + version: 2.0.0(express@5.1.0) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -9999,6 +10002,16 @@ packages: '@cfworker/json-schema': optional: true + '@modelcontextprotocol/server-legacy@2.0.0': + resolution: {integrity: sha512-LnffC1BSqFMHtMQxEz92lqDpHWma+ErV3ghdHDgdkCyYzVcCYKcUT5loq4kflty+Bf9C9qjJqbnphyBWyCqo8Q==} + engines: {node: '>=20'} + deprecated: This package is a frozen copy of v1's SSE transport and OAuth Authorization Server helpers for migration purposes only. Use StreamableHTTP from @modelcontextprotocol/server and a dedicated OAuth server in production. Will not receive new features. + peerDependencies: + express: ^4.18.0 || ^5.0.0 + peerDependenciesMeta: + express: + optional: true + '@modelcontextprotocol/server@2.0.0': resolution: {integrity: sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==} engines: {node: '>=20'} @@ -27343,6 +27356,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/server-legacy@2.0.0(express@5.1.0)': + dependencies: + '@modelcontextprotocol/core': 2.0.0 + content-type: 1.0.5 + cors: 2.8.5 + express-rate-limit: 8.2.2(express@5.1.0) + pkce-challenge: 5.0.0(patch_hash=651e785d0b7bbf5be9210e1e895c39a16dc3ce8a5a3843b4819565fb6e175b90) + raw-body: 3.0.0 + zod: 3.25.76 + optionalDependencies: + express: 5.1.0 + '@modelcontextprotocol/server@2.0.0': dependencies: '@modelcontextprotocol/core': 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 40644113698..f4f679fc94f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -58,6 +58,7 @@ catalog: '@modelcontextprotocol/node': 2.0.0 '@modelcontextprotocol/sdk': 1.26.0 '@modelcontextprotocol/server': 2.0.0 + '@modelcontextprotocol/server-legacy': 2.0.0 '@mozilla/readability': ^0.6.0 '@n8n_io/ai-assistant-sdk': 1.25.0 '@openrouter/ai-sdk-provider': ^3.0.0