Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/codemod/src/bin/batchTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ function main(): void {
codemodResult = run(migration, { targetDir: fullSourceDir, verbose: true });
} catch (error) {
console.log(` ERROR: codemod threw: ${error}`);
codemodResult = { filesChanged: 0, totalChanges: 0, diagnostics: [], fileResults: [] };
codemodResult = { filesChanged: 0, totalChanges: 0, diagnostics: [], fileResults: [], commentCount: 0 };
}
console.log(
` Codemod: files=${codemodResult.filesChanged} changes=${codemodResult.totalChanges} diags=${codemodResult.diagnostics.length}`
Expand Down
9 changes: 8 additions & 1 deletion packages/codemod/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Command } from 'commander';
import { listMigrations } from './migrations/index.js';
import { run } from './runner.js';
import { DiagnosticLevel } from './types.js';
import { formatDiagnostic } from './utils/diagnostics.js';
import { CODEMOD_ERROR_PREFIX, formatDiagnostic } from './utils/diagnostics.js';

const require = createRequire(import.meta.url);
const { version } = require('../package.json') as { version: string };
Expand Down Expand Up @@ -140,6 +140,13 @@ for (const [name, migration] of listMigrations()) {
console.log('');
}

if (result.commentCount > 0) {
console.log(
`${result.commentCount} location(s) marked with ${CODEMOD_ERROR_PREFIX} comments — search your code to find them:\n` +
` grep -r '${CODEMOD_ERROR_PREFIX}' "${resolvedDir}"\n`
);
}
Comment thread
claude[bot] marked this conversation as resolved.

if (opts['dryRun']) {
console.log('Run without --dry-run to apply changes.\n');
} else {
Expand Down
10 changes: 2 additions & 8 deletions packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,8 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
status: 'moved'
},
'@modelcontextprotocol/sdk/client/stdio.js': {
target: '@modelcontextprotocol/client',
status: 'moved',
symbolTargetOverrides: {
StdioClientTransport: '@modelcontextprotocol/client/stdio',
DEFAULT_INHERITED_ENV_VARS: '@modelcontextprotocol/client/stdio',
getDefaultEnvironment: '@modelcontextprotocol/client/stdio',
StdioServerParameters: '@modelcontextprotocol/client/stdio'
}
target: '@modelcontextprotocol/client/stdio',
status: 'moved'
},
'@modelcontextprotocol/sdk/client/websocket.js': {
target: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { SourceFile } from 'ts-morph';
import { Node, SyntaxKind } from 'ts-morph';

import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
import { info, warning } from '../../../utils/diagnostics.js';
import { isKeyPositionIdentifier } from '../../../utils/astUtils.js';
import { actionRequired, info } from '../../../utils/diagnostics.js';
import { hasMcpImports } from '../../../utils/importUtils.js';
import { CONTEXT_PROPERTY_MAP, CTX_PARAM_NAME, EXTRA_PARAM_NAME } from '../mappings/contextPropertyMap.js';

Expand Down Expand Up @@ -32,9 +33,9 @@ function processCallback(
const paramNameNode = extraParam.getNameNode();
if (Node.isObjectBindingPattern(paramNameNode)) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
extraParam.getStartLineNumber(),
extraParam,
`Destructuring of context parameter in signature: "${paramNameNode.getText()}". ` +
'Properties have been reorganized in v2 (e.g., signal is now ctx.mcpReq.signal). Manual refactoring required.'
)
Expand All @@ -49,9 +50,9 @@ function processCallback(
const otherParams = callbackNode.getParameters().filter(p => p !== extraParam);
if (otherParams.some(p => p.getName() === CTX_PARAM_NAME)) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
extraParam.getStartLineNumber(),
extraParam,
`Cannot rename '${EXTRA_PARAM_NAME}' to '${CTX_PARAM_NAME}': another parameter is already named '${CTX_PARAM_NAME}'. Manual migration required.`
)
);
Expand All @@ -74,9 +75,9 @@ function processCallback(
});
if (ctxAlreadyInScope) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
extraParam.getStartLineNumber(),
extraParam,
`Cannot rename '${EXTRA_PARAM_NAME}' to '${CTX_PARAM_NAME}': '${CTX_PARAM_NAME}' is already referenced in this scope. Manual migration required.`
)
);
Expand All @@ -98,11 +99,7 @@ function processCallback(
body.forEachDescendant(node => {
if (!Node.isIdentifier(node) || node.getText() !== EXTRA_PARAM_NAME) return;
const parent = node.getParent();
// Skip property-name positions (e.g., meta.extra, { extra: value }, { extra }, { extra: x } = obj)
if (parent && Node.isPropertyAccessExpression(parent) && parent.getNameNode() === node) return;
if (parent && Node.isPropertyAssignment(parent) && parent.getNameNode() === node) return;
if (parent && Node.isShorthandPropertyAssignment(parent)) return;
if (parent && Node.isBindingElement(parent) && parent.getPropertyNameNode() === node) return;
if (parent && isKeyPositionIdentifier(node)) return;
identifiers.push(node);
});

Expand Down Expand Up @@ -131,6 +128,11 @@ function processCallback(
}
}
}
// Shorthand property assignment: { extra } → { extra: ctx }
if (parent && Node.isShorthandPropertyAssignment(parent)) {
replacements.push({ node: parent, newText: `${EXTRA_PARAM_NAME}: ${CTX_PARAM_NAME}` });
continue;
}
replacements.push({ node: id, newText: CTX_PARAM_NAME });
}

Expand Down Expand Up @@ -163,9 +165,9 @@ function processCallback(
const nameNode = node.getNameNode();
if (!Node.isObjectBindingPattern(nameNode)) return;
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
node.getStartLineNumber(),
node,
`Destructuring of context parameter detected: "const ${nameNode.getText()} = ${CTX_PARAM_NAME}". ` +
'Properties have been reorganized in v2 (e.g., signal is now ctx.mcpReq.signal). Manual refactoring required.'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { SourceFile } from 'ts-morph';
import { Node, SyntaxKind } from 'ts-morph';

import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
import { warning } from '../../../utils/diagnostics.js';
import { isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils.js';
import { actionRequired } from '../../../utils/diagnostics.js';
import { hasMcpImports, isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils.js';
import { NOTIFICATION_SCHEMA_TO_METHOD, SCHEMA_TO_METHOD } from '../mappings/schemaToMethodMap.js';

const ALL_SCHEMA_TO_METHOD: Record<string, string> = {
Expand All @@ -15,6 +15,10 @@ export const handlerRegistrationTransform: Transform = {
name: 'Handler registration migration',
id: 'handlers',
apply(sourceFile: SourceFile, _context: TransformContext): TransformResult {
if (!hasMcpImports(sourceFile)) {
return { changesCount: 0, diagnostics: [] };
}

let changesCount = 0;
const diagnostics: Diagnostic[] = [];

Expand All @@ -40,9 +44,9 @@ export const handlerRegistrationTransform: Transform = {
const methodString = ALL_SCHEMA_TO_METHOD[originalName];
if (!methodString) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
call.getStartLineNumber(),
call,
`Custom method handler: ${methodName}(${schemaName}, ...). ` +
`In v2, use the 3-arg form: ${methodName}('method/name', { params, result? }, handler). ` +
`See migration.md for details.`
Comment thread
claude[bot] marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { actionRequired, 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';
Expand Down Expand Up @@ -83,7 +83,7 @@ export const importPathsTransform: Transform = {
}

if (!mapping) {
diagnostics.push(warning(filePath, line, `Unknown SDK import path: ${specifier}. Manual migration required.`));
diagnostics.push(actionRequired(filePath, imp, `Unknown SDK import path: ${specifier}. Manual migration required.`));
continue;
}

Expand Down Expand Up @@ -123,9 +123,9 @@ export const importPathsTransform: Transform = {
effectiveTarget = mapping.symbolTargetOverrides[namedImports[0]!.getName()]!;
} else if (namedImports.some(n => n.getName() in mapping.symbolTargetOverrides!)) {
diagnostics.push(
warning(
actionRequired(
filePath,
line,
imp,
`Aliased import from ${specifier} mixes symbols that belong to different v2 packages. ` +
`Split the import manually so each symbol targets the correct package.`
)
Expand All @@ -143,9 +143,9 @@ export const importPathsTransform: Transform = {
}
if (namespaceImport) {
diagnostics.push(
warning(
actionRequired(
filePath,
line,
imp,
`Namespace import of ${specifier}: exported symbol(s) ${Object.keys(mapping.renamedSymbols).join(', ')} ` +
`were renamed in ${effectiveTarget}. Update qualified accesses manually.`
)
Expand Down Expand Up @@ -227,7 +227,7 @@ function rewriteExportDeclarations(
}

if (!mapping) {
diagnostics.push(warning(filePath, line, `Unknown SDK export path: ${specifier}. Manual migration required.`));
diagnostics.push(actionRequired(filePath, exp, `Unknown SDK export path: ${specifier}. Manual migration required.`));
continue;
}

Expand Down Expand Up @@ -259,9 +259,9 @@ function rewriteExportDeclarations(
targetPackage = mapping.symbolTargetOverrides[namedExports[0]!.getName()]!;
} else if (namedExports.some(s => s.getName() in mapping.symbolTargetOverrides!)) {
diagnostics.push(
warning(
actionRequired(
filePath,
line,
exp,
`Re-export from ${specifier} mixes symbols that belong to different v2 packages. ` +
`Split the export manually so each symbol targets the correct package.`
)
Expand All @@ -278,7 +278,7 @@ function rewriteExportDeclarations(
spec.setName(newName);
}
if (REEXPORT_WARNINGS[name]) {
diagnostics.push(warning(filePath, line, REEXPORT_WARNINGS[name]!));
diagnostics.push(actionRequired(filePath, exp, REEXPORT_WARNINGS[name]!));
}
}
changesCount++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CallExpression, SourceFile } from 'ts-morph';
import { Node, SyntaxKind } from 'ts-morph';

import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
import { info, warning } from '../../../utils/diagnostics.js';
import { actionRequired, info } from '../../../utils/diagnostics.js';
import { isOriginalNameImportedFromMcp, resolveLocalImportName } from '../../../utils/importUtils.js';

export const mcpServerApiTransform: Transform = {
Expand All @@ -23,7 +23,6 @@ export const mcpServerApiTransform: Transform = {
const resourceCalls: CallExpression[] = [];
const registerToolCalls: CallExpression[] = [];
const registerPromptCalls: CallExpression[] = [];
const registerResourceCalls: CallExpression[] = [];

for (const call of calls) {
const expr = call.getExpression();
Expand Down Expand Up @@ -51,10 +50,6 @@ export const mcpServerApiTransform: Transform = {
registerPromptCalls.push(call);
break;
}
case 'registerResource': {
registerResourceCalls.push(call);
break;
}
}
}

Expand All @@ -64,9 +59,9 @@ export const mcpServerApiTransform: Transform = {
changesCount++;
} else {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
call.getStartLineNumber(),
call,
'Could not automatically migrate .tool() call. Manual migration required.'
)
);
Expand All @@ -79,9 +74,9 @@ export const mcpServerApiTransform: Transform = {
changesCount++;
} else {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
call.getStartLineNumber(),
call,
'Could not automatically migrate .prompt() call. Manual migration required.'
)
);
Expand All @@ -94,9 +89,9 @@ export const mcpServerApiTransform: Transform = {
changesCount++;
} else {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
call.getStartLineNumber(),
call,
'Could not automatically migrate .resource() call. Manual migration required.'
)
);
Expand All @@ -115,12 +110,6 @@ export const mcpServerApiTransform: Transform = {
}
}

for (const call of registerResourceCalls) {
if (wrapSchemaInConfig(call, 'uriSchema', sourceFile, diagnostics)) {
changesCount++;
}
}

changesCount += migrateConstructorTaskOptions(sourceFile, diagnostics);

return { changesCount, diagnostics };
Expand All @@ -131,6 +120,13 @@ function isStringArg(node: Node): boolean {
return Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node) || Node.isTemplateExpression(node);
}

function isZodObjectCall(node: Node): boolean {
if (!Node.isCallExpression(node)) return false;
const expr = node.getExpression();
if (!Node.isPropertyAccessExpression(expr)) return false;
return expr.getName() === 'object' && expr.getExpression().getText() === 'z';
}

function wrapWithZObject(schemaText: string): string {
return `z.object(${schemaText})`;
}
Expand All @@ -152,6 +148,14 @@ function emitWrapDiagnostic(node: Node, sourceFile: SourceFile, call: CallExpres
'Raw object literal wrapped with z.object(). Verify that zod (z) is imported in this file.'
)
);
} else if (!isZodObjectCall(node)) {
diagnostics.push(
actionRequired(
sourceFile.getFilePath(),
call,
'Schema argument is not an object literal — verify it is a z.object() schema. V2 requires a Zod schema, not a raw object.'
)
);
}
}

Expand Down Expand Up @@ -179,9 +183,9 @@ function wrapSchemaInConfig(call: CallExpression, schemaPropertyName: string, so

if (Node.isShorthandPropertyAssignment(schemaProp)) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
call.getStartLineNumber(),
call,
`Shorthand \`{ ${schemaPropertyName} }\` in config: verify the value is a z.object() schema, not a raw object. V2 requires a Zod schema.`
)
);
Expand All @@ -206,13 +210,15 @@ function wrapSchemaInConfig(call: CallExpression, schemaPropertyName: string, so
return true;
}

diagnostics.push(
warning(
sourceFile.getFilePath(),
call.getStartLineNumber(),
`\`${schemaPropertyName}\` value is not an object literal — verify it is a z.object() schema. V2 requires a Zod schema, not a raw object.`
)
);
if (!isZodObjectCall(initializer)) {
diagnostics.push(
actionRequired(
sourceFile.getFilePath(),
call,
`\`${schemaPropertyName}\` value is not an object literal — verify it is a z.object() schema. V2 requires a Zod schema, not a raw object.`
)
);
}
return false;
}

Expand Down Expand Up @@ -446,9 +452,9 @@ function migrateConstructorTaskOptions(sourceFile: SourceFile, diagnostics: Diag
if (tasksObjStart === -1) {
for (const propName of propsToMove) {
diagnostics.push(
warning(
actionRequired(
sourceFile.getFilePath(),
node.getStartLineNumber(),
node,
`Move '${propName}' from McpServer options into capabilities.tasks — v2 expects task runtime options inside the tasks capability.`
)
);
Expand Down
Loading
Loading