v2 backcompat: server legacy package (#2206)
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
parent
83a54a454d
commit
e03bca90c1
9 changed files with 5797 additions and 71 deletions
|
|
@ -9,7 +9,8 @@ const PACKAGE_DIRS: Record<string, string> = {
|
|||
'@modelcontextprotocol/client': 'client',
|
||||
'@modelcontextprotocol/server': 'server',
|
||||
'@modelcontextprotocol/node': 'middleware/node',
|
||||
'@modelcontextprotocol/express': 'middleware/express'
|
||||
'@modelcontextprotocol/express': 'middleware/express',
|
||||
'@modelcontextprotocol/server-legacy': 'server-legacy'
|
||||
};
|
||||
|
||||
const versions: Record<string, string> = {};
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ export const V2_PACKAGE_VERSIONS: Record<string, string> = {
|
|||
'@modelcontextprotocol/client': '^2.0.0-alpha.2',
|
||||
'@modelcontextprotocol/server': '^2.0.0-alpha.2',
|
||||
'@modelcontextprotocol/node': '^2.0.0-alpha.2',
|
||||
'@modelcontextprotocol/express': '^2.0.0-alpha.2'
|
||||
'@modelcontextprotocol/express': '^2.0.0-alpha.2',
|
||||
'@modelcontextprotocol/server-legacy': '^2.0.0-alpha.2'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ export interface ImportMapping {
|
|||
removalMessage?: string;
|
||||
/** No entries currently set this; scaffolding for when a v1 symbol has no v2 equivalent yet. */
|
||||
isV2Gap?: boolean;
|
||||
/** Emitted as an info diagnostic after a successful move, suggesting eventual migration to v2 equivalents. */
|
||||
migrationHint?: string;
|
||||
/**
|
||||
* Subpath suffix appended after `RESOLVE_BY_CONTEXT` resolves the base package (e.g. `/validators/ajv`).
|
||||
* The final target becomes `@modelcontextprotocol/{client,server}<subpathSuffix>`.
|
||||
|
|
@ -74,9 +76,10 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
|
|||
status: 'moved'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/sse.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage: 'SSE server transport removed in v2. Migrate to NodeStreamableHTTPServerTransport from @modelcontextprotocol/node.'
|
||||
target: '@modelcontextprotocol/server-legacy/sse',
|
||||
status: 'moved',
|
||||
migrationHint:
|
||||
'SSEServerTransport is deprecated. Migrate to NodeStreamableHTTPServerTransport from @modelcontextprotocol/node or WebStandardStreamableHTTPServerTransport from @modelcontextprotocol/server.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/middleware.js': {
|
||||
target: '@modelcontextprotocol/express',
|
||||
|
|
@ -90,34 +93,29 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
|
|||
},
|
||||
|
||||
'@modelcontextprotocol/sdk/server/auth/types.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth removed in v2. AuthInfo is now re-exported by @modelcontextprotocol/client and @modelcontextprotocol/server.'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy auth types. AuthInfo is also re-exported by @modelcontextprotocol/server.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/auth/provider.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth provider removed in v2. For Resource-Server auth (token verification), see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy OAuth AS provider. For RS-only auth, see requireBearerAuth from @modelcontextprotocol/express.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/auth/router.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth router removed in v2. For metadata endpoints, see mcpAuthMetadataRouter from @modelcontextprotocol/express. For full OAuth AS router, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy OAuth AS router. For metadata-only endpoints, see mcpAuthMetadataRouter from @modelcontextprotocol/express.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/auth/middleware.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth middleware removed in v2. For bearer token validation, see requireBearerAuth from @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy OAuth AS middleware. For bearer-only auth, see requireBearerAuth from @modelcontextprotocol/express.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/auth/errors.js': {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Auth error subclasses consolidated in v2. Use OAuthError + OAuthErrorCode from @modelcontextprotocol/server. See also @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy error subclasses. v2 consolidates to OAuthError + OAuthErrorCode in @modelcontextprotocol/server.'
|
||||
},
|
||||
|
||||
'@modelcontextprotocol/sdk/types.js': {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { SourceFile } from 'ts-morph';
|
|||
|
||||
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
|
||||
import { renameAllReferences } from '../../../utils/astUtils.js';
|
||||
import { v2Gap, warning } from '../../../utils/diagnostics.js';
|
||||
import { info, v2Gap, warning } from '../../../utils/diagnostics.js';
|
||||
import { addOrMergeImport, getSdkExports, getSdkImports, isTypeOnlyImport } from '../../../utils/importUtils.js';
|
||||
import { resolveTypesPackage } from '../../../utils/projectAnalyzer.js';
|
||||
import { IMPORT_MAP, isAuthImport } from '../mappings/importMap.js';
|
||||
|
|
@ -75,10 +75,9 @@ export const importPathsTransform: Transform = {
|
|||
|
||||
if (!mapping && isAuthImport(specifier)) {
|
||||
mapping = {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth removed in v2. For RS auth, see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy auth module. For RS-only auth, see @modelcontextprotocol/express.'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -156,6 +155,9 @@ export const importPathsTransform: Transform = {
|
|||
}
|
||||
}
|
||||
changesCount++;
|
||||
if (mapping.migrationHint) {
|
||||
diagnostics.push(info(filePath, line, mapping.migrationHint));
|
||||
}
|
||||
for (const [oldName, newName] of symbolsToRenameInFile) {
|
||||
renameAllReferences(sourceFile, oldName, newName);
|
||||
}
|
||||
|
|
@ -172,6 +174,9 @@ export const importPathsTransform: Transform = {
|
|||
}
|
||||
imp.remove();
|
||||
changesCount++;
|
||||
if (mapping.migrationHint) {
|
||||
diagnostics.push(info(filePath, line, mapping.migrationHint));
|
||||
}
|
||||
for (const [oldName, newName] of symbolsToRenameInFile) {
|
||||
renameAllReferences(sourceFile, oldName, newName);
|
||||
}
|
||||
|
|
@ -222,10 +227,9 @@ function rewriteExportDeclarations(
|
|||
|
||||
if (!mapping && isAuthImport(specifier)) {
|
||||
mapping = {
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage:
|
||||
'Server auth removed in v2. For RS auth, see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
|
||||
target: '@modelcontextprotocol/server-legacy/auth',
|
||||
status: 'moved',
|
||||
migrationHint: 'Legacy auth module. For RS-only auth, see @modelcontextprotocol/express.'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -288,6 +292,9 @@ function rewriteExportDeclarations(
|
|||
}
|
||||
}
|
||||
changesCount++;
|
||||
if (mapping.migrationHint) {
|
||||
diagnostics.push(info(filePath, line, mapping.migrationHint));
|
||||
}
|
||||
}
|
||||
|
||||
return changesCount;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ function resolveTarget(
|
|||
| { removed: true; isV2Gap?: boolean; removalMessage?: string }
|
||||
| null {
|
||||
const mapping = IMPORT_MAP[specifier];
|
||||
if (!mapping && isAuthImport(specifier)) return { removed: true };
|
||||
if (!mapping && isAuthImport(specifier)) return { target: '@modelcontextprotocol/server-legacy/auth' };
|
||||
if (!mapping) return null;
|
||||
if (mapping.status === 'removed') return { removed: true, isV2Gap: mapping.isV2Gap, removalMessage: mapping.removalMessage };
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe('CLI diagnostic behavior', () => {
|
||||
it('warnings do not produce errors-level diagnostics', () => {
|
||||
it('info diagnostics do not produce error-level diagnostics', () => {
|
||||
const dir = createTempDir();
|
||||
writeFileSync(
|
||||
path.join(dir, 'server.ts'),
|
||||
|
|
@ -36,9 +36,9 @@ describe('CLI diagnostic behavior', () => {
|
|||
|
||||
const result = run(migration, { targetDir: dir });
|
||||
|
||||
const warnings = result.diagnostics.filter(d => d.level === DiagnosticLevel.Warning);
|
||||
const infos = result.diagnostics.filter(d => d.level === DiagnosticLevel.Info);
|
||||
const errors = result.diagnostics.filter(d => d.level === DiagnosticLevel.Error);
|
||||
expect(warnings.length).toBeGreaterThan(0);
|
||||
expect(infos.length).toBeGreaterThan(0);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ describe('integration', () => {
|
|||
expect(result.packageJsonChanges!.removed).toContain('@modelcontextprotocol/sdk');
|
||||
});
|
||||
|
||||
it('emits diagnostics for removed imports', () => {
|
||||
it('emits info diagnostics for legacy-moved imports', () => {
|
||||
const dir = createTempDir();
|
||||
const input = [
|
||||
`import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';`,
|
||||
|
|
@ -586,7 +586,7 @@ describe('integration', () => {
|
|||
const result = run(migration, { targetDir: dir });
|
||||
|
||||
expect(result.diagnostics.length).toBeGreaterThan(0);
|
||||
expect(result.diagnostics.some(d => d.level === DiagnosticLevel.Warning)).toBe(true);
|
||||
expect(result.diagnostics.some(d => d.level === DiagnosticLevel.Info)).toBe(true);
|
||||
});
|
||||
|
||||
it('transform ordering: critical dependencies are maintained', () => {
|
||||
|
|
|
|||
|
|
@ -59,14 +59,19 @@ describe('import-paths transform', () => {
|
|||
expect(result.diagnostics[0]!.message).toContain('WebSocketClientTransport');
|
||||
});
|
||||
|
||||
it('removes SSE server import with warning', () => {
|
||||
it('moves SSE server import to server-legacy/sse with info diagnostic', () => {
|
||||
const input = `import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`;
|
||||
const ctx: TransformContext = { projectType: 'server' };
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = importPathsTransform.apply(sourceFile, ctx);
|
||||
const output = sourceFile.getFullText();
|
||||
expect(output).toContain('@modelcontextprotocol/server-legacy/sse');
|
||||
expect(output).toContain('SSEServerTransport');
|
||||
expect(output).not.toContain('@modelcontextprotocol/sdk');
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
expect(result.diagnostics.length).toBeGreaterThan(0);
|
||||
expect(result.diagnostics[0]!.message).toContain('SSE server transport');
|
||||
expect(result.diagnostics[0]!.message).toContain('SSEServerTransport is deprecated');
|
||||
});
|
||||
|
||||
it('resolves sdk/types.js based on sibling client imports', () => {
|
||||
|
|
@ -270,13 +275,79 @@ describe('import-paths transform', () => {
|
|||
expect(result).toContain('NodeStreamableHTTPServerTransport as StreamableHTTPServerTransport');
|
||||
});
|
||||
|
||||
it('removes auth imports with warning', () => {
|
||||
it('moves auth router import to server-legacy/auth with info diagnostic', () => {
|
||||
const input = `import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
|
||||
const output = sourceFile.getFullText();
|
||||
expect(output).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
expect(output).toContain('mcpAuthRouter');
|
||||
expect(output).not.toContain('@modelcontextprotocol/sdk');
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
expect(result.diagnostics.length).toBeGreaterThan(0);
|
||||
expect(result.diagnostics[0]!.message).toContain('auth router removed');
|
||||
expect(result.diagnostics[0]!.message).toContain('Legacy OAuth AS router');
|
||||
});
|
||||
|
||||
it('moves auth provider import to server-legacy/auth', () => {
|
||||
const input = `import type { OAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/provider.js';\n`;
|
||||
const result = applyTransform(input, { projectType: 'server' });
|
||||
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
expect(result).toContain('OAuthServerProvider');
|
||||
expect(result).not.toContain('@modelcontextprotocol/sdk');
|
||||
});
|
||||
|
||||
it('moves auth middleware import to server-legacy/auth', () => {
|
||||
const input = `import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware.js';\n`;
|
||||
const result = applyTransform(input, { projectType: 'server' });
|
||||
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
expect(result).toContain('requireBearerAuth');
|
||||
expect(result).not.toContain('@modelcontextprotocol/sdk');
|
||||
});
|
||||
|
||||
it('moves auth errors import to server-legacy/auth', () => {
|
||||
const input = `import { InvalidTokenError, OAuthError } from '@modelcontextprotocol/sdk/server/auth/errors.js';\n`;
|
||||
const result = applyTransform(input, { projectType: 'server' });
|
||||
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
expect(result).toContain('InvalidTokenError');
|
||||
expect(result).toContain('OAuthError');
|
||||
expect(result).not.toContain('@modelcontextprotocol/sdk');
|
||||
});
|
||||
|
||||
it('consolidates multiple auth subpath imports into single server-legacy/auth import', () => {
|
||||
const input = [
|
||||
`import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';`,
|
||||
`import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware.js';`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input, { projectType: 'server' });
|
||||
expect(result).toContain('mcpAuthRouter');
|
||||
expect(result).toContain('requireBearerAuth');
|
||||
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
const importLines = result.split('\n').filter((l: string) => l.includes('@modelcontextprotocol/server-legacy/auth'));
|
||||
expect(importLines.length).toBe(1);
|
||||
});
|
||||
|
||||
it('moves SSE server re-export to server-legacy/sse', () => {
|
||||
const input = `export { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
|
||||
const output = sourceFile.getFullText();
|
||||
expect(output).toContain('@modelcontextprotocol/server-legacy/sse');
|
||||
expect(output).toContain('SSEServerTransport');
|
||||
expect(result.diagnostics.some(d => d.message.includes('SSEServerTransport is deprecated'))).toBe(true);
|
||||
});
|
||||
|
||||
it('includes server-legacy in usedPackages for SSE import', () => {
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile(
|
||||
'test.ts',
|
||||
`import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`
|
||||
);
|
||||
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
|
||||
expect(result.usedPackages).toBeDefined();
|
||||
expect(result.usedPackages!.has('@modelcontextprotocol/server-legacy/sse')).toBe(true);
|
||||
});
|
||||
|
||||
it('handles per-specifier type modifiers', () => {
|
||||
|
|
@ -373,14 +444,17 @@ describe('import-paths transform', () => {
|
|||
expect(result.diagnostics[0]!.message).toContain('removed');
|
||||
});
|
||||
|
||||
it('handles unknown auth subpath via catch-all', () => {
|
||||
it('moves unknown auth subpath to server-legacy/auth via catch-all', () => {
|
||||
const input = `import { SomeType } from '@modelcontextprotocol/sdk/server/auth/handler.js';\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
|
||||
expect(result.changesCount).toBe(1);
|
||||
expect(sourceFile.getFullText()).not.toContain('@modelcontextprotocol/sdk');
|
||||
expect(result.diagnostics.some(d => d.message.includes('Server auth removed'))).toBe(true);
|
||||
const output = sourceFile.getFullText();
|
||||
expect(output).toContain('@modelcontextprotocol/server-legacy/auth');
|
||||
expect(output).toContain('SomeType');
|
||||
expect(output).not.toContain('@modelcontextprotocol/sdk');
|
||||
expect(result.diagnostics.some(d => d.message.includes('Legacy auth module'))).toBe(true);
|
||||
});
|
||||
|
||||
it('rewrites InMemoryTransport to @modelcontextprotocol/server for server projects', () => {
|
||||
|
|
|
|||
|
|
@ -193,13 +193,22 @@ describe('mock-paths transform', () => {
|
|||
expect(result.diagnostics[0]!.message).toContain('Unknown SDK dynamic import path');
|
||||
});
|
||||
|
||||
it('emits warning for removed SDK dynamic import path', () => {
|
||||
it('rewrites SSE dynamic import to server-legacy/sse', () => {
|
||||
const input = [`const m = await import('@modelcontextprotocol/sdk/server/sse.js');`, ''].join('\n');
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = mockPathsTransform.apply(sourceFile, ctx);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/server-legacy/sse');
|
||||
});
|
||||
|
||||
it('emits warning for removed SDK dynamic import path', () => {
|
||||
const input = [`const m = await import('@modelcontextprotocol/sdk/client/websocket.js');`, ''].join('\n');
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', input);
|
||||
const result = mockPathsTransform.apply(sourceFile, ctx);
|
||||
expect(result.diagnostics.length).toBeGreaterThan(0);
|
||||
expect(result.diagnostics[0]!.message).toContain('SSE server transport removed in v2');
|
||||
expect(result.diagnostics[0]!.message).toContain('WebSocketClientTransport removed in v2');
|
||||
});
|
||||
|
||||
it('emits warning for unknown SDK mock path', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue