mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
refactor(core): Migrate MCP OAuth server to the v2 SDK auth toolkit
Wave 4 of the v2 SDK migration (ADO-5681). Moves the oauth-server module
off @modelcontextprotocol/sdk 1.x and onto
@modelcontextprotocol/server-legacy/auth 2.0.0 for the authorization-server
toolkit (handlers, provider, store, errors), sourcing the shared OAuth data
types from @modelcontextprotocol/server. cli no longer depends on the 1.x
package.
With the toolkit on v2, authorizationHandler now accepts { issuerUrl }, so the
handler itself stamps the RFC 9207 iss parameter onto its request-validation
error redirects. This replaces the rfc9207IssuerParam middleware that patched
res.location, which is deleted along with the now-unused setIssuerParam helper.
The SDK derives iss from issuerUrl.href, which canonicalizes an origin-only
base URL with a trailing slash. RFC 9207 (mandatory for clients under SEP-2468)
requires iss to match the advertised issuer exactly, so the authorization-server
metadata issuer, the protected-resource authorization_servers, and the
consent-flow iss now use the same href form via OAuthHelpers.canonicalIssuer.
This commit is contained in:
parent
b4cc709db8
commit
7a40b4eafe
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<string, unknown> = {
|
||||
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<string, unknown> = {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user