Skip to content

Commit b8e397a

Browse files
committed
fixes
1 parent cf990e3 commit b8e397a

13 files changed

Lines changed: 123 additions & 15 deletions

packages/codemod/src/migrations/v1-to-v2/mappings/schemaToMethodMap.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export const SCHEMA_TO_METHOD: Record<string, string> = {
1313
ElicitRequestSchema: 'elicitation/create',
1414
SetLevelRequestSchema: 'logging/setLevel',
1515
PingRequestSchema: 'ping',
16-
CompleteRequestSchema: 'completion/complete'
16+
CompleteRequestSchema: 'completion/complete',
17+
ListRootsRequestSchema: 'roots/list'
1718
};
1819

1920
export const NOTIFICATION_SCHEMA_TO_METHOD: Record<string, string> = {
@@ -24,5 +25,6 @@ export const NOTIFICATION_SCHEMA_TO_METHOD: Record<string, string> = {
2425
ResourceUpdatedNotificationSchema: 'notifications/resources/updated',
2526
ProgressNotificationSchema: 'notifications/progress',
2627
CancelledNotificationSchema: 'notifications/cancelled',
27-
InitializedNotificationSchema: 'notifications/initialized'
28+
InitializedNotificationSchema: 'notifications/initialized',
29+
RootsListChangedNotificationSchema: 'notifications/roots/list_changed'
2830
};

packages/codemod/src/migrations/v1-to-v2/transforms/contextTypes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ export const contextTypesTransform: Transform = {
6565

6666
const body = callbackArg.getBody();
6767

68+
const otherParams = callbackArg.getParameters().filter(p => p !== extraParam);
69+
if (otherParams.some(p => p.getName() === CTX_PARAM_NAME)) {
70+
diagnostics.push(
71+
warning(
72+
sourceFile.getFilePath(),
73+
extraParam.getStartLineNumber(),
74+
`Cannot rename '${EXTRA_PARAM_NAME}' to '${CTX_PARAM_NAME}': another parameter is already named '${CTX_PARAM_NAME}'. Manual migration required.`
75+
)
76+
);
77+
continue;
78+
}
79+
6880
if (body) {
6981
let ctxAlreadyInScope = false;
7082
body.forEachDescendant(node => {

packages/codemod/src/migrations/v1-to-v2/transforms/expressMiddleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { Node, SyntaxKind } from 'ts-morph';
33

44
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
55
import { info } from '../../../utils/diagnostics.js';
6-
import { hasMcpImports } from '../../../utils/importUtils.js';
6+
import { isImportedFromMcp } from '../../../utils/importUtils.js';
77

88
export const expressMiddlewareTransform: Transform = {
99
name: 'Express middleware signature migration',
1010
id: 'express-middleware',
1111
apply(sourceFile: SourceFile, _context: TransformContext): TransformResult {
12-
if (!hasMcpImports(sourceFile)) {
12+
if (!isImportedFromMcp(sourceFile, 'hostHeaderValidation')) {
1313
return { changesCount: 0, diagnostics: [] };
1414
}
1515

packages/codemod/src/migrations/v1-to-v2/transforms/mcpServerApi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,9 @@ function migrateResourceCall(call: CallExpression, _sourceFile: SourceFile): boo
261261
const uriArg = args[1]!;
262262
const uriText = uriArg.getText();
263263

264-
expr.getNameNode().replaceWithText('registerResource');
265-
266264
if (args.length === 3) {
267265
// server.resource(name, uri, callback) → server.registerResource(name, uri, {}, callback)
266+
expr.getNameNode().replaceWithText('registerResource');
268267
const callbackText = args[2]!.getText();
269268
for (let i = args.length - 1; i >= 0; i--) {
270269
call.removeArgument(i);
@@ -273,6 +272,7 @@ function migrateResourceCall(call: CallExpression, _sourceFile: SourceFile): boo
273272
} else if (args.length === 4) {
274273
// server.resource(name, uri, metadata, callback) → server.registerResource(name, uri, metadata, callback)
275274
// Already has metadata, just rename the method
275+
expr.getNameNode().replaceWithText('registerResource');
276276
} else {
277277
return false;
278278
}

packages/codemod/src/migrations/v1-to-v2/transforms/removedApis.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ function handleIsomorphicHeaders(sourceFile: SourceFile, diagnostics: Diagnostic
9393

9494
if (!foundImport || !foundImportDecl) return 0;
9595

96+
const localName = foundImport.getAliasNode()?.getText() ?? 'IsomorphicHeaders';
9697
const line = foundImportDecl.getStartLineNumber();
9798

98-
renameAllReferences(sourceFile, 'IsomorphicHeaders', 'Headers');
99+
renameAllReferences(sourceFile, localName, 'Headers');
99100
changesCount++;
100101

101102
foundImport.remove();
@@ -134,12 +135,13 @@ function handleStreamableHTTPError(sourceFile: SourceFile, diagnostics: Diagnost
134135

135136
if (!foundImport || !foundImportDecl) return 0;
136137

138+
const localName = foundImport.getAliasNode()?.getText() ?? 'StreamableHTTPError';
137139
const line = foundImportDecl.getStartLineNumber();
138140
const moduleSpec = foundImportDecl.getModuleSpecifierValue();
139141

140142
for (const node of sourceFile.getDescendantsOfKind(SyntaxKind.NewExpression)) {
141143
const expr = node.getExpression();
142-
if (!Node.isIdentifier(expr) || expr.getText() !== 'StreamableHTTPError') continue;
144+
if (!Node.isIdentifier(expr) || expr.getText() !== localName) continue;
143145
diagnostics.push(
144146
warning(
145147
sourceFile.getFilePath(),
@@ -150,7 +152,7 @@ function handleStreamableHTTPError(sourceFile: SourceFile, diagnostics: Diagnost
150152
);
151153
}
152154

153-
renameAllReferences(sourceFile, 'StreamableHTTPError', 'SdkError');
155+
renameAllReferences(sourceFile, localName, 'SdkError');
154156
changesCount++;
155157

156158
foundImport.remove();

packages/codemod/src/migrations/v1-to-v2/transforms/schemaParamRemoval.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Node, SyntaxKind } from 'ts-morph';
44
import type { Transform, TransformContext, TransformResult } from '../../../types.js';
55
import { isImportedFromMcp, removeUnusedImport } from '../../../utils/importUtils.js';
66

7-
const TARGET_METHODS = new Set(['request', 'callTool', 'send']);
7+
const TARGET_METHODS = new Set(['request', 'callTool', 'send', 'sendRequest']);
88

99
export const schemaParamRemovalTransform: Transform = {
1010
name: 'Schema parameter removal',
@@ -33,7 +33,7 @@ export const schemaParamRemovalTransform: Transform = {
3333
call.removeArgument(1);
3434
changesCount++;
3535

36-
removeUnusedImport(sourceFile, schemaName);
36+
removeUnusedImport(sourceFile, schemaName, true);
3737
}
3838

3939
return { changesCount, diagnostics: [] };

packages/codemod/src/migrations/v1-to-v2/transforms/symbolRenames.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ function handleSchemaInput(sourceFile: SourceFile, context: TransformContext, di
312312
});
313313
const isServerFile = sourceFile.getImportDeclarations().some(i => {
314314
const spec = i.getModuleSpecifierValue();
315-
return spec.includes('/server') || spec === '@modelcontextprotocol/server';
315+
return spec.includes('/server/') || spec === '@modelcontextprotocol/server';
316316
});
317317
const targetModule = resolveTypesPackage(context, isClientFile, isServerFile);
318318

packages/codemod/test/v1-to-v2/transforms/contextTypes.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,22 @@ describe('context-types transform', () => {
183183
expect(sourceFile.getFullText()).toContain('extra.signal');
184184
});
185185

186+
it('emits warning when another parameter is already named ctx', () => {
187+
const input = [
188+
`server.setRequestHandler('tools/call', async (ctx, extra) => {`,
189+
` const s = extra.signal;`,
190+
` return { content: [] };`,
191+
`});`,
192+
''
193+
].join('\n');
194+
const project = new Project({ useInMemoryFileSystem: true });
195+
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
196+
const result = contextTypesTransform.apply(sourceFile, ctx);
197+
expect(result.diagnostics.length).toBeGreaterThan(0);
198+
expect(result.diagnostics[0]!.message).toContain('another parameter');
199+
expect(sourceFile.getFullText()).toContain('extra.signal');
200+
});
201+
186202
it('emits warning when ctx variable already exists in scope', () => {
187203
const input = [
188204
`const ctx = getApplicationContext();`,

packages/codemod/test/v1-to-v2/transforms/expressMiddleware.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,16 @@ describe('express-middleware transform', () => {
9393
expect(result.changesCount).toBe(0);
9494
expect(text).toContain("{ allowedHosts: ['localhost'] }");
9595
});
96+
97+
it('does not modify non-MCP hostHeaderValidation even when other MCP imports exist', () => {
98+
const input = [
99+
`import { McpServer } from '@modelcontextprotocol/server';`,
100+
`import { hostHeaderValidation } from './my-middleware.js';`,
101+
`app.use(hostHeaderValidation({ allowedHosts: ['localhost'] }));`,
102+
''
103+
].join('\n');
104+
const { text, result } = applyTransform(input);
105+
expect(result.changesCount).toBe(0);
106+
expect(text).toContain("{ allowedHosts: ['localhost'] }");
107+
});
96108
});

packages/codemod/test/v1-to-v2/transforms/handlerRegistration.test.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,39 @@ describe('handler-registration transform', () => {
104104
expect(result).not.toContain("'tools/call'");
105105
});
106106

107-
it('only removes MCP import when same symbol exists in non-MCP package', () => {
107+
it('does not remove non-MCP import when MCP import of same name is consumed', () => {
108108
const input = [
109109
`import { CallToolRequestSchema } from './local-schemas.js';`,
110110
`import { CallToolRequestSchema as McpSchema } from '@modelcontextprotocol/sdk/types.js';`,
111-
`server.setRequestHandler(McpSchema, async () => ({ content: [] }));`,
112-
`validateSchema(CallToolRequestSchema);`,
111+
`server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [] }));`,
112+
`validateSchema(McpSchema);`,
113113
''
114114
].join('\n');
115115
const result = applyTransform(input);
116116
expect(result).toContain("from './local-schemas.js'");
117+
expect(result).toContain("'tools/call'");
118+
expect(result).not.toMatch(/setRequestHandler\(CallToolRequestSchema/);
119+
});
120+
121+
it('replaces ListRootsRequestSchema with method string', () => {
122+
const input = [
123+
`import { ListRootsRequestSchema } from '@modelcontextprotocol/sdk/types.js';`,
124+
`client.setRequestHandler(ListRootsRequestSchema, async () => ({ roots: [] }));`,
125+
''
126+
].join('\n');
127+
const result = applyTransform(input);
128+
expect(result).toContain("'roots/list'");
129+
expect(result).not.toContain('ListRootsRequestSchema');
130+
});
131+
132+
it('replaces RootsListChangedNotificationSchema with method string', () => {
133+
const input = [
134+
`import { RootsListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';`,
135+
`server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {});`,
136+
''
137+
].join('\n');
138+
const result = applyTransform(input);
139+
expect(result).toContain("'notifications/roots/list_changed'");
140+
expect(result).not.toContain('RootsListChangedNotificationSchema');
117141
});
118142
});

0 commit comments

Comments
 (0)