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
5 changes: 5 additions & 0 deletions .changeset/mcpserver-variadic-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---

Restore `McpServer.tool()`, `.prompt()`, `.resource()` variadic overloads as `@deprecated` v1-compat shims forwarding to `registerTool`/`registerPrompt`/`registerResource`.
6 changes: 5 additions & 1 deletion packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export type {
AnyToolHandler,
BaseToolCallback,
CompleteResourceTemplateCallback,
InferRawShape,
LegacyPromptCallback,
LegacyToolCallback,
Comment thread
claude[bot] marked this conversation as resolved.
ListResourcesCallback,
PromptCallback,
ReadResourceCallback,
Expand All @@ -21,7 +24,8 @@ export type {
RegisteredResourceTemplate,
RegisteredTool,
ResourceMetadata,
ToolCallback
ToolCallback,
ZodRawShape
} from './server/mcp.js';
export { McpServer, ResourceTemplate } from './server/mcp.js';
export type { HostHeaderValidationResult } from './server/middleware/hostHeaderValidation.js';
Expand Down
173 changes: 173 additions & 0 deletions packages/server/src/server/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {
import {
assertCompleteRequestPrompt,
assertCompleteRequestResourceTemplate,
isStandardSchema,
promptArgumentsFromStandardSchema,
ProtocolError,
ProtocolErrorCode,
Expand All @@ -38,6 +39,7 @@ import {
validateAndWarnToolName,
validateStandardSchema
} from '@modelcontextprotocol/core';
import * as z from 'zod/v4';

import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
Expand Down Expand Up @@ -950,6 +952,132 @@ export class McpServer {
return registeredPrompt;
}

// ---------------------------------------------------------------------
// v1-compat variadic registration methods. Frozen at 2025-03-26 surface.
// ---------------------------------------------------------------------
Comment thread
felixweinberger marked this conversation as resolved.

/** @deprecated Use {@linkcode registerTool}. */
tool(name: string, cb: LegacyToolCallback<undefined>): RegisteredTool;
/** @deprecated Use {@linkcode registerTool}. */
tool(name: string, description: string, cb: LegacyToolCallback<undefined>): RegisteredTool;
/** @deprecated Use {@linkcode registerTool}. */
tool<Args extends ZodRawShape>(
name: string,
paramsSchemaOrAnnotations: Args | ToolAnnotations,
cb: LegacyToolCallback<Args>
): RegisteredTool;
/** @deprecated Use {@linkcode registerTool}. */
tool<Args extends ZodRawShape>(
name: string,
description: string,
paramsSchemaOrAnnotations: Args | ToolAnnotations,
cb: LegacyToolCallback<Args>
): RegisteredTool;
/** @deprecated Use {@linkcode registerTool}. */
tool<Args extends ZodRawShape>(
name: string,
paramsSchema: Args,
annotations: ToolAnnotations,
cb: LegacyToolCallback<Args>
): RegisteredTool;
/** @deprecated Use {@linkcode registerTool}. */
tool<Args extends ZodRawShape>(
name: string,
description: string,
paramsSchema: Args,
annotations: ToolAnnotations,
cb: LegacyToolCallback<Args>
): RegisteredTool;
tool(name: string, ...rest: unknown[]): RegisteredTool {
let description: string | undefined;
let inputSchema: StandardSchemaWithJSON | undefined;
let annotations: ToolAnnotations | undefined;

if (typeof rest[0] === 'string') description = rest.shift() as string;

if (rest.length > 1) {
const first = rest[0];
if (isZodRawShape(first) || isStandardSchema(first)) {
inputSchema = wrapRawShape(rest.shift());
if (
rest.length > 1 &&
typeof rest[0] === 'object' &&
rest[0] !== null &&
!isZodRawShape(rest[0]) &&
!isStandardSchema(rest[0])
) {
annotations = rest.shift() as ToolAnnotations;
}
} else if (typeof first === 'object' && first !== null) {
annotations = rest.shift() as ToolAnnotations;
}
}

if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
}
const cb = rest[0] as ToolCallback<StandardSchemaWithJSON | undefined>;
return this._createRegisteredTool(
name,
undefined,
description,
inputSchema,
undefined,
annotations,
{ taskSupport: 'forbidden' },
undefined,
cb
);
}

/** @deprecated Use {@linkcode registerPrompt}. */
prompt(name: string, cb: PromptCallback): RegisteredPrompt;
/** @deprecated Use {@linkcode registerPrompt}. */
prompt(name: string, description: string, cb: PromptCallback): RegisteredPrompt;
/** @deprecated Use {@linkcode registerPrompt}. */
prompt<Args extends ZodRawShape>(name: string, argsSchema: Args, cb: LegacyPromptCallback<Args>): RegisteredPrompt;
/** @deprecated Use {@linkcode registerPrompt}. */
prompt<Args extends ZodRawShape>(name: string, description: string, argsSchema: Args, cb: LegacyPromptCallback<Args>): RegisteredPrompt;
prompt(name: string, ...rest: unknown[]): RegisteredPrompt {
let description: string | undefined;
if (typeof rest[0] === 'string') description = rest.shift() as string;

let argsSchema: StandardSchemaWithJSON | undefined;
if (rest.length > 1) argsSchema = wrapRawShape(rest.shift());

if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
}
const cb = rest[0] as PromptCallback<StandardSchemaWithJSON | undefined>;
const registered = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb, undefined);
this.setPromptRequestHandlers();
this.sendPromptListChanged();
return registered;
}

/** @deprecated Use {@linkcode registerResource}. */
resource(name: string, uri: string, readCallback: ReadResourceCallback): RegisteredResource;
/** @deprecated Use {@linkcode registerResource}. */
resource(name: string, uri: string, metadata: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;
/** @deprecated Use {@linkcode registerResource}. */
resource(name: string, template: ResourceTemplate, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
/** @deprecated Use {@linkcode registerResource}. */
resource(
name: string,
template: ResourceTemplate,
metadata: ResourceMetadata,
readCallback: ReadResourceTemplateCallback
): RegisteredResourceTemplate;
resource(name: string, uriOrTemplate: string | ResourceTemplate, ...rest: unknown[]): RegisteredResource | RegisteredResourceTemplate {
let metadata: ResourceMetadata = {};
if (typeof rest[0] === 'object') metadata = rest.shift() as ResourceMetadata;
const readCallback = rest[0] as ReadResourceCallback & ReadResourceTemplateCallback;
if (typeof uriOrTemplate === 'string') {
return this.registerResource(name, uriOrTemplate, metadata, readCallback);
}
return this.registerResource(name, uriOrTemplate, metadata, readCallback);
}

/**
* Checks if the server is connected to a transport.
* @returns `true` if the server is connected
Expand Down Expand Up @@ -1062,6 +1190,51 @@ export class ResourceTemplate {
}
}

/**
* A plain record of Zod field schemas, e.g. `{ name: z.string() }`. Used by the v1 variadic
* `.tool()`/`.prompt()` overloads. For `registerTool`/`registerPrompt`, wrap in `z.object({...})`.
*/
export type ZodRawShape = z.ZodRawShape;

/** Infers `{ [K]: T }` from a {@linkcode ZodRawShape} `{ [K]: z.ZodType<T> }`. */
export type InferRawShape<S extends ZodRawShape> = { [K in keyof S]: z.infer<S[K]> };

/** Callback shape for the v1 variadic `.tool()` overloads. See also {@linkcode ToolCallback}. */
export type LegacyToolCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
? (args: InferRawShape<Args>, ctx: ServerContext) => CallToolResult | Promise<CallToolResult>
: (ctx: ServerContext) => CallToolResult | Promise<CallToolResult>;

/** Callback shape for the v1 variadic `.prompt()` overloads. See also {@linkcode PromptCallback}. */
export type LegacyPromptCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
? (args: InferRawShape<Args>, ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>
: (ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>;

/**
* Detects a v1 "raw shape" — a plain object whose values are Standard Schema
* field schemas, e.g. `{ name: z.string() }`. Used by the deprecated variadic
* `.tool()`/`.prompt()` shims to disambiguate the schema arg from annotations.
*
* @internal
*/
function isZodRawShape(obj: unknown): obj is ZodRawShape {
if (typeof obj !== 'object' || obj === null) return false;
if (isStandardSchema(obj)) return false;
// [].every() is true, so an empty object is a valid raw shape (matches v1).
return Object.values(obj).every(v => isStandardSchema(v));
}
Comment thread
claude[bot] marked this conversation as resolved.

/**
* Wraps a v1 raw shape in `z.object()` for the variadic shims; passes Standard
* Schemas through unchanged.
*
* @internal
*/
function wrapRawShape(schema: unknown): StandardSchemaWithJSON | undefined {
if (schema === undefined) return undefined;
if (isZodRawShape(schema)) return z.object(schema);
return schema as StandardSchemaWithJSON;
}

export type BaseToolCallback<
SendResultT extends Result,
Ctx extends ServerContext,
Expand Down
74 changes: 74 additions & 0 deletions packages/server/test/server/mcp.compat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-deprecated */
import * as z from 'zod/v4';
import { McpServer, ResourceTemplate } from '../../src/server/mcp.js';

describe('McpServer v1-compat variadic shims', () => {
describe('.tool()', () => {
it('registers with raw-shape schema', () => {
const server = new McpServer({ name: 't', version: '1' });

server.tool('x', { a: z.string() }, ({ a }) => ({ content: [{ type: 'text', text: a }] }));
server.tool('y', { b: z.number() }, ({ b }) => ({ content: [{ type: 'text', text: String(b) }] }));

// @ts-expect-error private access for test
expect(server._registeredTools['x']).toBeDefined();
// @ts-expect-error private access for test
expect(server._registeredTools['y']).toBeDefined();
});

it('supports (name, description, paramsSchema, annotations, cb) overload', () => {
const server = new McpServer({ name: 't', version: '1' });

const reg = server.tool('x', 'desc', { a: z.string() }, { readOnlyHint: true }, ({ a }) => ({
content: [{ type: 'text', text: a }]
}));

expect(reg.description).toBe('desc');
expect(reg.annotations).toEqual({ readOnlyHint: true });
expect(reg.inputSchema).toBeDefined();
});

it('supports (name, cb) zero-arg overload', () => {
const server = new McpServer({ name: 't', version: '1' });
const reg = server.tool('x', () => ({ content: [{ type: 'text', text: 'ok' }] }));
expect(reg.inputSchema).toBeUndefined();
});

it('treats empty object as raw shape, not annotations (matches v1)', () => {
const server = new McpServer({ name: 't', version: '1' });
const reg = server.tool('x', {}, () => ({ content: [{ type: 'text', text: 'ok' }] }));
expect(reg.inputSchema).toBeDefined();
expect(reg.annotations).toBeUndefined();
});
});

describe('.prompt()', () => {
it('registers with raw-shape argsSchema', () => {
const server = new McpServer({ name: 't', version: '1' });

server.prompt('p1', { topic: z.string() }, ({ topic }) => ({
messages: [{ role: 'user', content: { type: 'text', text: topic } }]
}));
server.prompt('p2', () => ({ messages: [] }));

// @ts-expect-error private access for test
expect(server._registeredPrompts['p1']).toBeDefined();
// @ts-expect-error private access for test
expect(server._registeredPrompts['p2']).toBeDefined();
});
});

describe('.resource()', () => {
it('forwards to registerResource for both string URIs and ResourceTemplates', () => {
const server = new McpServer({ name: 't', version: '1' });

server.resource('r1', 'file:///a', () => ({ contents: [] }));
server.resource('r2', new ResourceTemplate('file:///{id}', { list: undefined }), () => ({ contents: [] }));

// @ts-expect-error private access for test
expect(server._registeredResources['file:///a']).toBeDefined();
// @ts-expect-error private access for test
expect(server._registeredResourceTemplates['r2']).toBeDefined();
});
});
});
Loading