[SEP-2663] refactor!: remove 2025-11 experimental tasks (#2128)
This commit is contained in:
parent
600ba75f34
commit
c8d7401b46
9 changed files with 423 additions and 18280 deletions
|
|
@ -8,8 +8,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'CallToolRequestParamsSchema',
|
||||
'CallToolRequestSchema',
|
||||
'CallToolResultSchema',
|
||||
'CancelTaskRequestSchema',
|
||||
'CancelTaskResultSchema',
|
||||
'CancelledNotificationParamsSchema',
|
||||
'CancelledNotificationSchema',
|
||||
'ClientCapabilitiesSchema',
|
||||
|
|
@ -25,7 +23,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'CreateMessageRequestSchema',
|
||||
'CreateMessageResultSchema',
|
||||
'CreateMessageResultWithToolsSchema',
|
||||
'CreateTaskResultSchema',
|
||||
'CursorSchema',
|
||||
'ElicitRequestFormParamsSchema',
|
||||
'ElicitRequestParamsSchema',
|
||||
|
|
@ -40,10 +37,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'GetPromptRequestParamsSchema',
|
||||
'GetPromptRequestSchema',
|
||||
'GetPromptResultSchema',
|
||||
'GetTaskPayloadRequestSchema',
|
||||
'GetTaskPayloadResultSchema',
|
||||
'GetTaskRequestSchema',
|
||||
'GetTaskResultSchema',
|
||||
'IconSchema',
|
||||
'IconsSchema',
|
||||
'ImageContentSchema',
|
||||
|
|
@ -70,8 +63,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'ListResourcesResultSchema',
|
||||
'ListRootsRequestSchema',
|
||||
'ListRootsResultSchema',
|
||||
'ListTasksRequestSchema',
|
||||
'ListTasksResultSchema',
|
||||
'ListToolsRequestSchema',
|
||||
'ListToolsResultSchema',
|
||||
'LoggingLevelSchema',
|
||||
|
|
@ -110,7 +101,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'ReadResourceRequestParamsSchema',
|
||||
'ReadResourceRequestSchema',
|
||||
'ReadResourceResultSchema',
|
||||
'RelatedTaskMetadataSchema',
|
||||
'RequestIdSchema',
|
||||
'RequestMetaSchema',
|
||||
'RequestSchema',
|
||||
|
|
@ -140,13 +130,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet<string> = new Set([
|
|||
'StringSchemaSchema',
|
||||
'SubscribeRequestParamsSchema',
|
||||
'SubscribeRequestSchema',
|
||||
'TaskAugmentedRequestParamsSchema',
|
||||
'TaskCreationParamsSchema',
|
||||
'TaskMetadataSchema',
|
||||
'TaskSchema',
|
||||
'TaskStatusNotificationParamsSchema',
|
||||
'TaskStatusNotificationSchema',
|
||||
'TaskStatusSchema',
|
||||
'TextContentSchema',
|
||||
'TextResourceContentsSchema',
|
||||
'TitledMultiSelectEnumSchemaSchema',
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ export const CONTEXT_PROPERTY_MAP: ContextMapping[] = [
|
|||
{ from: '.sessionId', to: '.sessionId' },
|
||||
{ from: '.requestInfo', to: '.http?.req' },
|
||||
{ from: '.closeSSEStream', to: '.http?.closeSSE' },
|
||||
{ from: '.closeStandaloneSSEStream', to: '.http?.closeStandaloneSSE' },
|
||||
{ from: '.taskStore', to: '.task?.store' },
|
||||
{ from: '.taskId', to: '.task?.id' },
|
||||
{ from: '.taskRequestedTtl', to: '.task?.requestedTtl' }
|
||||
{ from: '.closeStandaloneSSEStream', to: '.http?.closeStandaloneSSE' }
|
||||
];
|
||||
|
||||
export const EXTRA_PARAM_NAME = 'extra';
|
||||
|
|
|
|||
|
|
@ -152,12 +152,14 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
|
|||
},
|
||||
|
||||
'@modelcontextprotocol/sdk/experimental/tasks': {
|
||||
target: '@modelcontextprotocol/server',
|
||||
status: 'moved'
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage: 'Experimental tasks removed in v2 (SEP-2663 — tasks moved to the Extensions Track). No v2 equivalent.'
|
||||
},
|
||||
'@modelcontextprotocol/sdk/experimental/tasks.js': {
|
||||
target: '@modelcontextprotocol/server',
|
||||
status: 'moved'
|
||||
target: '',
|
||||
status: 'removed',
|
||||
removalMessage: 'Experimental tasks removed in v2 (SEP-2663 — tasks moved to the Extensions Track). No v2 equivalent.'
|
||||
},
|
||||
|
||||
'@modelcontextprotocol/sdk/inMemory.js': {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,7 @@ export const SCHEMA_TO_METHOD: Record<string, string> = {
|
|||
SetLevelRequestSchema: 'logging/setLevel',
|
||||
PingRequestSchema: 'ping',
|
||||
CompleteRequestSchema: 'completion/complete',
|
||||
ListRootsRequestSchema: 'roots/list',
|
||||
ListTasksRequestSchema: 'tasks/list',
|
||||
GetTaskRequestSchema: 'tasks/get',
|
||||
GetTaskPayloadRequestSchema: 'tasks/result',
|
||||
CancelTaskRequestSchema: 'tasks/cancel'
|
||||
ListRootsRequestSchema: 'roots/list'
|
||||
};
|
||||
|
||||
export const NOTIFICATION_SCHEMA_TO_METHOD: Record<string, string> = {
|
||||
|
|
@ -31,6 +27,5 @@ export const NOTIFICATION_SCHEMA_TO_METHOD: Record<string, string> = {
|
|||
CancelledNotificationSchema: 'notifications/cancelled',
|
||||
InitializedNotificationSchema: 'notifications/initialized',
|
||||
RootsListChangedNotificationSchema: 'notifications/roots/list_changed',
|
||||
TaskStatusNotificationSchema: 'notifications/tasks/status',
|
||||
ElicitationCompleteNotificationSchema: 'notifications/elicitation/complete'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { CONTEXT_PROPERTY_MAP, CTX_PARAM_NAME, EXTRA_PARAM_NAME } from '../mappi
|
|||
|
||||
const HANDLER_METHODS = new Set(['setRequestHandler', 'setNotificationHandler']);
|
||||
|
||||
const REGISTER_METHODS = new Set(['registerTool', 'registerPrompt', 'registerResource', 'registerToolTask', 'tool', 'prompt', 'resource']);
|
||||
const REGISTER_METHODS = new Set(['registerTool', 'registerPrompt', 'registerResource', 'tool', 'prompt', 'resource']);
|
||||
|
||||
/**
|
||||
* Attempt to rename the second parameter of a callback from 'extra' to 'ctx'
|
||||
|
|
@ -219,7 +219,7 @@ export const contextTypesTransform: Transform = {
|
|||
|
||||
if (!callbackArg) continue;
|
||||
|
||||
// Handle ObjectLiteralExpression for registerToolTask-style callbacks
|
||||
// Handle ObjectLiteralExpression callback containers (handler maps)
|
||||
if (Node.isObjectLiteralExpression(callbackArg)) {
|
||||
for (const prop of callbackArg.getProperties()) {
|
||||
let callbackNode: Node | undefined;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Node, SyntaxKind } from 'ts-morph';
|
|||
|
||||
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
|
||||
import { info, warning } from '../../../utils/diagnostics.js';
|
||||
import { isOriginalNameImportedFromMcp, resolveLocalImportName } from '../../../utils/importUtils.js';
|
||||
import { isOriginalNameImportedFromMcp } from '../../../utils/importUtils.js';
|
||||
|
||||
export const mcpServerApiTransform: Transform = {
|
||||
name: 'McpServer API migration',
|
||||
|
|
@ -121,8 +121,6 @@ export const mcpServerApiTransform: Transform = {
|
|||
}
|
||||
}
|
||||
|
||||
changesCount += migrateConstructorTaskOptions(sourceFile, diagnostics);
|
||||
|
||||
return { changesCount, diagnostics };
|
||||
}
|
||||
};
|
||||
|
|
@ -394,130 +392,3 @@ function migrateResourceCall(call: CallExpression, _sourceFile: SourceFile): boo
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
const TASK_OPTIONS = ['taskStore', 'taskMessageQueue'] as const;
|
||||
|
||||
function migrateConstructorTaskOptions(sourceFile: SourceFile, diagnostics: Diagnostic[]): number {
|
||||
const localName = resolveLocalImportName(sourceFile, 'McpServer');
|
||||
if (!localName) return 0;
|
||||
|
||||
let changes = 0;
|
||||
|
||||
for (const node of sourceFile.getDescendantsOfKind(SyntaxKind.NewExpression)) {
|
||||
if (node.wasForgotten()) continue;
|
||||
const expr = node.getExpression();
|
||||
if (!Node.isIdentifier(expr) || expr.getText() !== localName) continue;
|
||||
|
||||
const args = node.getArguments();
|
||||
if (args.length < 2) continue;
|
||||
|
||||
const optionsArg = args[1]!;
|
||||
if (!Node.isObjectLiteralExpression(optionsArg)) continue;
|
||||
|
||||
// Check if any task options are present at the top level
|
||||
const propsToMove: string[] = [];
|
||||
for (const propName of TASK_OPTIONS) {
|
||||
if (optionsArg.getProperty(propName)) {
|
||||
propsToMove.push(propName);
|
||||
}
|
||||
}
|
||||
if (propsToMove.length === 0) continue;
|
||||
|
||||
// Find the tasks object's position within the options text using AST,
|
||||
// then do all mutations via a single text replacement to avoid node invalidation.
|
||||
const capabilitiesProp = optionsArg.getProperty('capabilities');
|
||||
let tasksObjStart = -1;
|
||||
let tasksObjEnd = -1;
|
||||
const optionsStart = optionsArg.getStart();
|
||||
if (capabilitiesProp && Node.isPropertyAssignment(capabilitiesProp)) {
|
||||
const capInit = capabilitiesProp.getInitializer();
|
||||
if (capInit && Node.isObjectLiteralExpression(capInit)) {
|
||||
const tasksProp = capInit.getProperty('tasks');
|
||||
if (tasksProp && Node.isPropertyAssignment(tasksProp)) {
|
||||
const tasksInit = tasksProp.getInitializer();
|
||||
if (tasksInit && Node.isObjectLiteralExpression(tasksInit)) {
|
||||
tasksObjStart = tasksInit.getStart() - optionsStart;
|
||||
tasksObjEnd = tasksInit.getEnd() - optionsStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasksObjStart === -1) {
|
||||
for (const propName of propsToMove) {
|
||||
diagnostics.push(
|
||||
warning(
|
||||
sourceFile.getFilePath(),
|
||||
node.getStartLineNumber(),
|
||||
`Move '${propName}' from McpServer options into capabilities.tasks — v2 expects task runtime options inside the tasks capability.`
|
||||
)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Single text replacement: remove top-level props and insert into tasks object.
|
||||
// Use AST nodes (already located via getProperty) to get brace-balanced text and
|
||||
// exact positions, avoiding regex truncation on values containing commas/braces.
|
||||
// Collect all properties first, then process in reverse position order so each
|
||||
// removal doesn't invalidate the positions of subsequent removals.
|
||||
let optionsText = optionsArg.getText();
|
||||
const argStart = optionsArg.getStart();
|
||||
const propsWithPositions: { text: string; start: number; end: number }[] = [];
|
||||
for (const propName of propsToMove) {
|
||||
const prop = optionsArg.getProperty(propName);
|
||||
if (!prop) continue;
|
||||
propsWithPositions.push({
|
||||
text: prop.getText(),
|
||||
start: prop.getStart() - argStart,
|
||||
end: prop.getEnd() - argStart
|
||||
});
|
||||
}
|
||||
const propTexts = propsWithPositions.map(p => p.text);
|
||||
|
||||
// Remove in reverse position order so earlier positions remain valid
|
||||
const sortedProps = propsWithPositions.toSorted((a, b) => b.start - a.start);
|
||||
for (const { start, end } of sortedProps) {
|
||||
let remStart = start;
|
||||
let remEnd = end;
|
||||
// Consume trailing comma and whitespace
|
||||
const afterProp = optionsText.slice(remEnd);
|
||||
const trailingMatch = afterProp.match(/^\s*,?\s*/);
|
||||
if (trailingMatch) {
|
||||
remEnd += trailingMatch[0].length;
|
||||
}
|
||||
// Consume leading whitespace/newline
|
||||
const beforeProp = optionsText.slice(0, remStart);
|
||||
const leadingMatch = beforeProp.match(/[\n\r]?\s*$/);
|
||||
if (leadingMatch) {
|
||||
remStart -= leadingMatch[0].length;
|
||||
}
|
||||
optionsText = optionsText.slice(0, remStart) + optionsText.slice(remEnd);
|
||||
// Adjust tasks position if removal was before it
|
||||
if (remStart < tasksObjStart) {
|
||||
const shift = remEnd - remStart;
|
||||
tasksObjStart -= shift;
|
||||
tasksObjEnd -= shift;
|
||||
}
|
||||
}
|
||||
|
||||
if (propTexts.length === 0) continue;
|
||||
|
||||
// Insert into the tasks object (just before its closing brace)
|
||||
const tasksText = optionsText.slice(tasksObjStart, tasksObjEnd);
|
||||
const closingBrace = tasksText.lastIndexOf('}');
|
||||
const before = tasksText.slice(0, closingBrace).trimEnd();
|
||||
const sep = before.length > 1 ? ',\n' : '\n';
|
||||
const newTasksText = before + sep + propTexts.join(',\n') + '\n' + tasksText.slice(closingBrace);
|
||||
optionsText = optionsText.slice(0, tasksObjStart) + newTasksText + optionsText.slice(tasksObjEnd);
|
||||
|
||||
// Clean up double/trailing commas
|
||||
optionsText = optionsText.replaceAll(/,(\s*,)/g, ',');
|
||||
optionsText = optionsText.replaceAll(/,(\s*})/g, '$1');
|
||||
|
||||
optionsArg.replaceWithText(optionsText);
|
||||
changes += propTexts.length;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,18 +85,6 @@ describe('context-types transform', () => {
|
|||
expect(result).toContain('ctx.http?.authInfo');
|
||||
});
|
||||
|
||||
it('rewrites extra.taskStore to ctx.task?.store', () => {
|
||||
const input = [
|
||||
`server.setRequestHandler('tools/call', async (request, extra) => {`,
|
||||
` const store = extra.taskStore;`,
|
||||
` return { content: [] };`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain('ctx.task?.store');
|
||||
});
|
||||
|
||||
it('does not touch non-extra parameters', () => {
|
||||
const input = [
|
||||
`server.setRequestHandler('tools/call', async (request, context) => {`,
|
||||
|
|
@ -304,53 +292,6 @@ describe('context-types transform', () => {
|
|||
expect(sourceFile.getFullText()).toContain('extra');
|
||||
});
|
||||
|
||||
it('renames extra to ctx and remaps properties in registerToolTask handler object', () => {
|
||||
const input = [
|
||||
`server.experimental.tasks.registerToolTask('my-task', { schema: {} }, {`,
|
||||
` createTask: async (args, extra) => {`,
|
||||
` const s = extra.signal;`,
|
||||
` const store = extra.taskStore;`,
|
||||
` return { content: [] };`,
|
||||
` },`,
|
||||
` getTask: async (args, extra) => {`,
|
||||
` const auth = extra.authInfo;`,
|
||||
` return { content: [] };`,
|
||||
` },`,
|
||||
` getTaskResult: async (args, extra) => {`,
|
||||
` await extra.sendNotification({ method: 'test', params: {} });`,
|
||||
` return { content: [] };`,
|
||||
` },`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).not.toContain('extra');
|
||||
expect(result).toContain('(args, ctx)');
|
||||
expect(result).toContain('ctx.mcpReq.signal');
|
||||
expect(result).toContain('ctx.task?.store');
|
||||
expect(result).toContain('ctx.http?.authInfo');
|
||||
expect(result).toContain('ctx.mcpReq.notify(');
|
||||
});
|
||||
|
||||
it('handles registerToolTask with shorthand method syntax', () => {
|
||||
const input = [
|
||||
`server.registerToolTask('my-task', {}, {`,
|
||||
` async createTask(args, extra) {`,
|
||||
` const s = extra.signal;`,
|
||||
` return { content: [] };`,
|
||||
` },`,
|
||||
` async getTask(args, extra) {`,
|
||||
` return { content: [] };`,
|
||||
` },`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).not.toContain('extra');
|
||||
expect(result).toContain('(args, ctx)');
|
||||
expect(result).toContain('ctx.mcpReq.signal');
|
||||
});
|
||||
|
||||
it('does not rename "extra" inside string literals', () => {
|
||||
const input = [
|
||||
`server.setRequestHandler('tools/call', async (request, extra) => {`,
|
||||
|
|
|
|||
|
|
@ -192,69 +192,6 @@ describe('handler-registration transform', () => {
|
|||
expect(result.diagnostics[0]!.message).toContain('CustomNotification');
|
||||
});
|
||||
|
||||
it('replaces ListTasksRequestSchema with method string', () => {
|
||||
const input = [
|
||||
`import { ListTasksRequestSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
`client.setRequestHandler(ListTasksRequestSchema, async (request) => {`,
|
||||
` return { tasks: [] };`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain("setRequestHandler('tasks/list'");
|
||||
expect(result).not.toContain('ListTasksRequestSchema');
|
||||
});
|
||||
|
||||
it('replaces GetTaskRequestSchema with method string', () => {
|
||||
const input = [
|
||||
`import { GetTaskRequestSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
`client.setRequestHandler(GetTaskRequestSchema, async (request) => {`,
|
||||
` return { taskId: '1', status: 'completed' };`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain("setRequestHandler('tasks/get'");
|
||||
expect(result).not.toContain('GetTaskRequestSchema');
|
||||
});
|
||||
|
||||
it('replaces CancelTaskRequestSchema with method string', () => {
|
||||
const input = [
|
||||
`import { CancelTaskRequestSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
`client.setRequestHandler(CancelTaskRequestSchema, async (request) => {`,
|
||||
` return {};`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain("setRequestHandler('tasks/cancel'");
|
||||
expect(result).not.toContain('CancelTaskRequestSchema');
|
||||
});
|
||||
|
||||
it('replaces GetTaskPayloadRequestSchema with method string', () => {
|
||||
const input = [
|
||||
`import { GetTaskPayloadRequestSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
`client.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {`,
|
||||
` return { content: [] };`,
|
||||
`});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain("setRequestHandler('tasks/result'");
|
||||
expect(result).not.toContain('GetTaskPayloadRequestSchema');
|
||||
});
|
||||
|
||||
it('replaces TaskStatusNotificationSchema with method string', () => {
|
||||
const input = [
|
||||
`import { TaskStatusNotificationSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
`client.setNotificationHandler(TaskStatusNotificationSchema, async () => {});`,
|
||||
''
|
||||
].join('\n');
|
||||
const result = applyTransform(input);
|
||||
expect(result).toContain("setNotificationHandler('notifications/tasks/status'");
|
||||
expect(result).not.toContain('TaskStatusNotificationSchema');
|
||||
});
|
||||
|
||||
it('replaces ElicitationCompleteNotificationSchema with method string', () => {
|
||||
const input = [
|
||||
`import { ElicitationCompleteNotificationSchema } from '@modelcontextprotocol/sdk/types.js';`,
|
||||
|
|
|
|||
|
|
@ -307,77 +307,4 @@ describe('mcp-server-api transform', () => {
|
|||
expect(result).toContain('registerTool("ping", {}');
|
||||
expect(result).not.toContain('z.object');
|
||||
});
|
||||
|
||||
it('moves taskStore from top-level options into capabilities.tasks', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskStore, capabilities: { tasks: { list: {} } } });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
const result = mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const text = sourceFile.getFullText();
|
||||
expect(text).not.toMatch(/,\s*taskStore\s*,\s*capabilities/);
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskStore/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('moves taskMessageQueue from top-level options into capabilities.tasks', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskMessageQueue, capabilities: { tasks: { list: {} } } });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
const result = mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const text = sourceFile.getFullText();
|
||||
expect(text).not.toMatch(/,\s*taskMessageQueue\s*,\s*capabilities/);
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskMessageQueue/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('moves both taskStore and taskMessageQueue into capabilities.tasks', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskStore, taskMessageQueue, capabilities: { tasks: {} } });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const text = sourceFile.getFullText();
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskStore/);
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskMessageQueue/);
|
||||
});
|
||||
|
||||
it('moves both task props with complex values without corrupting output', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskStore: createStore(a, b), taskMessageQueue: new Queue({ size: 10 }), capabilities: { tasks: {} } });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const text = sourceFile.getFullText();
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskStore:\s*createStore\(a, b\)/);
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskMessageQueue:\s*new Queue\(\{\s*size:\s*10\s*\}\)/);
|
||||
expect(text).toContain('capabilities');
|
||||
});
|
||||
|
||||
it('emits warning when capabilities.tasks object is missing', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskStore, capabilities: {} });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
const result = mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const taskWarnings = result.diagnostics.filter(d => d.message.includes('taskStore'));
|
||||
expect(taskWarnings.length).toBe(1);
|
||||
expect(taskWarnings[0]!.message).toContain('capabilities.tasks');
|
||||
});
|
||||
|
||||
it('does not touch constructor without taskStore or taskMessageQueue', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { capabilities: {} });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
const result = mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const taskWarnings = result.diagnostics.filter(d => d.message.includes('taskStore') || d.message.includes('taskMessageQueue'));
|
||||
expect(taskWarnings.length).toBe(0);
|
||||
});
|
||||
|
||||
it('moves taskStore with complex value expression (nested braces/commas)', () => {
|
||||
const input = `const server = new McpServer({ name: "test", version: "1.0" }, { taskStore: new InMemoryTaskStore({ ttl: 5000 }), capabilities: { tasks: { list: {} } } });\n`;
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
const sourceFile = project.createSourceFile('test.ts', MCP_IMPORT + input);
|
||||
const result = mcpServerApiTransform.apply(sourceFile, ctx);
|
||||
const text = sourceFile.getFullText();
|
||||
expect(text).toMatch(/tasks:\s*\{[\s\S]*taskStore:\s*new InMemoryTaskStore\(\{\s*ttl:\s*5000\s*\}\)/);
|
||||
expect(text).not.toMatch(/,\s*taskStore.*capabilities/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue