Skip to content
Open
Show file tree
Hide file tree
Changes from all 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`
);
}

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,7 +2,7 @@ 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 { actionRequired } from '../../../utils/diagnostics.js';
import { isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils.js';
import { NOTIFICATION_SCHEMA_TO_METHOD, SCHEMA_TO_METHOD } from '../mappings/schemaToMethodMap.js';

Expand Down Expand Up @@ -40,9 +40,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.`
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