Skip to content

Commit 4e0c457

Browse files
feat(compat): restore McpServer.tool/.prompt/.resource variadic overloads as deprecated shims
1 parent a33c305 commit 4e0c457

File tree

5 files changed

+279
-2
lines changed

5 files changed

+279
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/server': patch
3+
---
4+
5+
Restore `McpServer.tool()`, `.prompt()`, `.resource()` variadic overloads as `@deprecated` v1-compat shims forwarding to `registerTool`/`registerPrompt`/`registerResource`. Emits a one-time deprecation warning; removed in v3.

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ export * from './validators/fromJsonSchema.js';
4848
*/
4949

5050
// Core types only - implementations are exported via separate entry points
51+
export { _resetDeprecationWarnings, deprecate } from './util/deprecate.js';
5152
export type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, JsonSchemaValidatorResult } from './validators/types.js';
52-
export { deprecate } from './util/deprecate.js';

packages/server/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export type {
1212
AnyToolHandler,
1313
BaseToolCallback,
1414
CompleteResourceTemplateCallback,
15+
// eslint-disable-next-line @typescript-eslint/no-deprecated
16+
LegacyPromptCallback,
17+
// eslint-disable-next-line @typescript-eslint/no-deprecated
18+
LegacyToolCallback,
1519
ListResourcesCallback,
1620
PromptCallback,
1721
ReadResourceCallback,
@@ -21,7 +25,9 @@ export type {
2125
RegisteredResourceTemplate,
2226
RegisteredTool,
2327
ResourceMetadata,
24-
ToolCallback
28+
ToolCallback,
29+
// eslint-disable-next-line @typescript-eslint/no-deprecated
30+
ZodRawShape
2531
} from './server/mcp.js';
2632
export { McpServer, ResourceTemplate } from './server/mcp.js';
2733
export type { HostHeaderValidationResult } from './server/middleware/hostHeaderValidation.js';

packages/server/src/server/mcp.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import type {
3030
import {
3131
assertCompleteRequestPrompt,
3232
assertCompleteRequestResourceTemplate,
33+
deprecate,
34+
isStandardSchema,
3335
promptArgumentsFromStandardSchema,
3436
ProtocolError,
3537
ProtocolErrorCode,
@@ -38,6 +40,7 @@ import {
3840
validateAndWarnToolName,
3941
validateStandardSchema
4042
} from '@modelcontextprotocol/core';
43+
import { z } from 'zod';
4144

4245
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
4346
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
@@ -950,6 +953,135 @@ export class McpServer {
950953
return registeredPrompt;
951954
}
952955

956+
// ---------------------------------------------------------------------
957+
// v1-compat variadic registration methods. Frozen at 2025-03-26 surface.
958+
// ---------------------------------------------------------------------
959+
960+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
961+
tool(name: string, cb: LegacyToolCallback<undefined>): RegisteredTool;
962+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
963+
tool(name: string, description: string, cb: LegacyToolCallback<undefined>): RegisteredTool;
964+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
965+
tool<Args extends ZodRawShape>(
966+
name: string,
967+
paramsSchemaOrAnnotations: Args | ToolAnnotations,
968+
cb: LegacyToolCallback<Args>
969+
): RegisteredTool;
970+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
971+
tool<Args extends ZodRawShape>(
972+
name: string,
973+
description: string,
974+
paramsSchemaOrAnnotations: Args | ToolAnnotations,
975+
cb: LegacyToolCallback<Args>
976+
): RegisteredTool;
977+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
978+
tool<Args extends ZodRawShape>(
979+
name: string,
980+
paramsSchema: Args,
981+
annotations: ToolAnnotations,
982+
cb: LegacyToolCallback<Args>
983+
): RegisteredTool;
984+
/** @deprecated Use {@linkcode registerTool}. Removed in v3. */
985+
tool<Args extends ZodRawShape>(
986+
name: string,
987+
description: string,
988+
paramsSchema: Args,
989+
annotations: ToolAnnotations,
990+
cb: LegacyToolCallback<Args>
991+
): RegisteredTool;
992+
tool(name: string, ...rest: unknown[]): RegisteredTool {
993+
deprecate('McpServer.tool', 'McpServer.tool() is deprecated. Use registerTool(name, config, cb). Removed in v3.');
994+
995+
let description: string | undefined;
996+
let inputSchema: StandardSchemaWithJSON | undefined;
997+
let annotations: ToolAnnotations | undefined;
998+
999+
if (typeof rest[0] === 'string') description = rest.shift() as string;
1000+
1001+
if (rest.length > 1) {
1002+
const first = rest[0];
1003+
if (isZodRawShape(first) || isStandardSchema(first)) {
1004+
inputSchema = wrapRawShape(rest.shift());
1005+
if (
1006+
rest.length > 1 &&
1007+
typeof rest[0] === 'object' &&
1008+
rest[0] !== null &&
1009+
!isZodRawShape(rest[0]) &&
1010+
!isStandardSchema(rest[0])
1011+
) {
1012+
annotations = rest.shift() as ToolAnnotations;
1013+
}
1014+
} else if (typeof first === 'object' && first !== null) {
1015+
annotations = rest.shift() as ToolAnnotations;
1016+
}
1017+
}
1018+
1019+
if (this._registeredTools[name]) {
1020+
throw new Error(`Tool ${name} is already registered`);
1021+
}
1022+
const cb = rest[0] as ToolCallback<StandardSchemaWithJSON | undefined>;
1023+
return this._createRegisteredTool(
1024+
name,
1025+
undefined,
1026+
description,
1027+
inputSchema,
1028+
undefined,
1029+
annotations,
1030+
{ taskSupport: 'forbidden' },
1031+
undefined,
1032+
cb
1033+
);
1034+
}
1035+
1036+
/** @deprecated Use {@linkcode registerPrompt}. Removed in v3. */
1037+
prompt(name: string, cb: PromptCallback): RegisteredPrompt;
1038+
/** @deprecated Use {@linkcode registerPrompt}. Removed in v3. */
1039+
prompt(name: string, description: string, cb: PromptCallback): RegisteredPrompt;
1040+
/** @deprecated Use {@linkcode registerPrompt}. Removed in v3. */
1041+
prompt<Args extends ZodRawShape>(name: string, argsSchema: Args, cb: LegacyPromptCallback<Args>): RegisteredPrompt;
1042+
/** @deprecated Use {@linkcode registerPrompt}. Removed in v3. */
1043+
prompt<Args extends ZodRawShape>(name: string, description: string, argsSchema: Args, cb: LegacyPromptCallback<Args>): RegisteredPrompt;
1044+
prompt(name: string, ...rest: unknown[]): RegisteredPrompt {
1045+
deprecate('McpServer.prompt', 'McpServer.prompt() is deprecated. Use registerPrompt(name, config, cb). Removed in v3.');
1046+
1047+
let description: string | undefined;
1048+
if (typeof rest[0] === 'string') description = rest.shift() as string;
1049+
1050+
let argsSchema: StandardSchemaWithJSON | undefined;
1051+
if (rest.length > 1) argsSchema = wrapRawShape(rest.shift());
1052+
1053+
if (this._registeredPrompts[name]) {
1054+
throw new Error(`Prompt ${name} is already registered`);
1055+
}
1056+
const cb = rest[0] as PromptCallback<StandardSchemaWithJSON | undefined>;
1057+
const registered = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb, undefined);
1058+
this.setPromptRequestHandlers();
1059+
this.sendPromptListChanged();
1060+
return registered;
1061+
}
1062+
1063+
/** @deprecated Use {@linkcode registerResource}. Removed in v3. */
1064+
resource(name: string, uri: string, readCallback: ReadResourceCallback): RegisteredResource;
1065+
/** @deprecated Use {@linkcode registerResource}. Removed in v3. */
1066+
resource(name: string, uri: string, metadata: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;
1067+
/** @deprecated Use {@linkcode registerResource}. Removed in v3. */
1068+
resource(name: string, template: ResourceTemplate, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
1069+
/** @deprecated Use {@linkcode registerResource}. Removed in v3. */
1070+
resource(
1071+
name: string,
1072+
template: ResourceTemplate,
1073+
metadata: ResourceMetadata,
1074+
readCallback: ReadResourceTemplateCallback
1075+
): RegisteredResourceTemplate;
1076+
resource(name: string, uriOrTemplate: string | ResourceTemplate, ...rest: unknown[]): RegisteredResource | RegisteredResourceTemplate {
1077+
deprecate('McpServer.resource', 'McpServer.resource() is deprecated. Use registerResource(name, uri, config, cb). Removed in v3.');
1078+
1079+
let metadata: ResourceMetadata = {};
1080+
if (typeof rest[0] === 'object') metadata = rest.shift() as ResourceMetadata;
1081+
const readCallback = rest[0] as ReadResourceCallback & ReadResourceTemplateCallback;
1082+
return this.registerResource(name, uriOrTemplate as string, metadata, readCallback);
1083+
}
1084+
9531085
/**
9541086
* Checks if the server is connected to a transport.
9551087
* @returns `true` if the server is connected
@@ -1062,6 +1194,50 @@ export class ResourceTemplate {
10621194
}
10631195
}
10641196

1197+
/**
1198+
* v1 raw-shape: a plain record of field schemas, e.g. `{ name: z.string() }`.
1199+
* @deprecated Wrap in `z.object({...})` and pass to `registerTool`. Removed in v3.
1200+
*/
1201+
export type ZodRawShape = Record<string, StandardSchemaWithJSON>;
1202+
1203+
type InferRawShape<S extends ZodRawShape> = { [K in keyof S]: StandardSchemaWithJSON.InferOutput<S[K]> };
1204+
1205+
/** @deprecated Use {@linkcode ToolCallback} with `registerTool`. Removed in v3. */
1206+
export type LegacyToolCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
1207+
? (args: InferRawShape<Args>, ctx: ServerContext) => CallToolResult | Promise<CallToolResult>
1208+
: (ctx: ServerContext) => CallToolResult | Promise<CallToolResult>;
1209+
1210+
/** @deprecated Use {@linkcode PromptCallback} with `registerPrompt`. Removed in v3. */
1211+
export type LegacyPromptCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
1212+
? (args: InferRawShape<Args>, ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>
1213+
: (ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>;
1214+
1215+
/**
1216+
* Detects a v1 "raw shape" — a plain object whose values are Standard Schema
1217+
* field schemas, e.g. `{ name: z.string() }`. Used by the deprecated variadic
1218+
* `.tool()`/`.prompt()` shims to disambiguate the schema arg from annotations.
1219+
*
1220+
* @internal
1221+
*/
1222+
function isZodRawShape(obj: unknown): obj is ZodRawShape {
1223+
if (typeof obj !== 'object' || obj === null) return false;
1224+
if (isStandardSchema(obj)) return false;
1225+
const values = Object.values(obj);
1226+
return values.length > 0 && values.every(v => isStandardSchema(v) || (typeof v === 'object' && v !== null && '_def' in v));
1227+
}
1228+
1229+
/**
1230+
* Wraps a v1 raw shape in `z.object()` for the variadic shims; passes Standard
1231+
* Schemas through unchanged.
1232+
*
1233+
* @internal
1234+
*/
1235+
function wrapRawShape(schema: unknown): StandardSchemaWithJSON | undefined {
1236+
if (schema === undefined) return undefined;
1237+
if (isZodRawShape(schema)) return z.object(schema as z.core.$ZodLooseShape) as StandardSchemaWithJSON;
1238+
return schema as StandardSchemaWithJSON;
1239+
}
1240+
10651241
export type BaseToolCallback<
10661242
SendResultT extends Result,
10671243
Ctx extends ServerContext,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* eslint-disable @typescript-eslint/no-deprecated */
2+
import { _resetDeprecationWarnings } from '@modelcontextprotocol/core';
3+
import { z } from 'zod';
4+
import { McpServer, ResourceTemplate } from '../../src/server/mcp.js';
5+
6+
describe('McpServer v1-compat variadic shims', () => {
7+
let warnSpy: ReturnType<typeof vi.spyOn>;
8+
9+
beforeEach(() => {
10+
_resetDeprecationWarnings();
11+
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
12+
});
13+
14+
afterEach(() => {
15+
warnSpy.mockRestore();
16+
});
17+
18+
describe('.tool()', () => {
19+
it('registers with raw-shape schema and warns once', () => {
20+
const server = new McpServer({ name: 't', version: '1' });
21+
22+
server.tool('x', { a: z.string() }, ({ a }) => ({ content: [{ type: 'text', text: a }] }));
23+
server.tool('y', { b: z.number() }, ({ b }) => ({ content: [{ type: 'text', text: String(b) }] }));
24+
25+
// both registered
26+
// @ts-expect-error private access for test
27+
expect(server._registeredTools['x']).toBeDefined();
28+
// @ts-expect-error private access for test
29+
expect(server._registeredTools['y']).toBeDefined();
30+
31+
// deprecation warned exactly once for the .tool() shim
32+
const toolWarnings = warnSpy.mock.calls.filter((c: unknown[]) => String(c[0]).includes('McpServer.tool()'));
33+
expect(toolWarnings).toHaveLength(1);
34+
});
35+
36+
it('supports (name, description, paramsSchema, annotations, cb) overload', () => {
37+
const server = new McpServer({ name: 't', version: '1' });
38+
39+
const reg = server.tool('x', 'desc', { a: z.string() }, { readOnlyHint: true }, ({ a }) => ({
40+
content: [{ type: 'text', text: a }]
41+
}));
42+
43+
expect(reg.description).toBe('desc');
44+
expect(reg.annotations).toEqual({ readOnlyHint: true });
45+
expect(reg.inputSchema).toBeDefined();
46+
});
47+
48+
it('supports (name, cb) zero-arg overload', () => {
49+
const server = new McpServer({ name: 't', version: '1' });
50+
const reg = server.tool('x', () => ({ content: [{ type: 'text', text: 'ok' }] }));
51+
expect(reg.inputSchema).toBeUndefined();
52+
});
53+
});
54+
55+
describe('.prompt()', () => {
56+
it('registers with raw-shape argsSchema and warns once', () => {
57+
const server = new McpServer({ name: 't', version: '1' });
58+
59+
server.prompt('p1', { topic: z.string() }, ({ topic }) => ({
60+
messages: [{ role: 'user', content: { type: 'text', text: topic } }]
61+
}));
62+
server.prompt('p2', () => ({ messages: [] }));
63+
64+
// @ts-expect-error private access for test
65+
expect(server._registeredPrompts['p1']).toBeDefined();
66+
// @ts-expect-error private access for test
67+
expect(server._registeredPrompts['p2']).toBeDefined();
68+
69+
const promptWarnings = warnSpy.mock.calls.filter((c: unknown[]) => String(c[0]).includes('McpServer.prompt()'));
70+
expect(promptWarnings).toHaveLength(1);
71+
});
72+
});
73+
74+
describe('.resource()', () => {
75+
it('forwards to registerResource and warns once', () => {
76+
const server = new McpServer({ name: 't', version: '1' });
77+
78+
server.resource('r1', 'file:///a', () => ({ contents: [] }));
79+
server.resource('r2', new ResourceTemplate('file:///{id}', { list: undefined }), () => ({ contents: [] }));
80+
81+
// @ts-expect-error private access for test
82+
expect(server._registeredResources['file:///a']).toBeDefined();
83+
// @ts-expect-error private access for test
84+
expect(server._registeredResourceTemplates['r2']).toBeDefined();
85+
86+
const resourceWarnings = warnSpy.mock.calls.filter((c: unknown[]) => String(c[0]).includes('McpServer.resource()'));
87+
expect(resourceWarnings).toHaveLength(1);
88+
});
89+
});
90+
});

0 commit comments

Comments
 (0)