Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
27e4ddf
feat(compat): registerTool/registerPrompt accept raw Zod shape (auto-…
felixweinberger Apr 15, 2026
5266131
fix: isZodRawShape treats empty object as raw shape (matches v1)
felixweinberger Apr 16, 2026
f2fdbe7
docs: changeset wording aligns with @deprecated overloads (not first-…
felixweinberger Apr 16, 2026
9576f20
docs: clarify isZodRawShape only supports Zod values for auto-wrap
felixweinberger Apr 16, 2026
0152b26
fix(compat): narrow ZodRawShape to Zod-only (detector + type); add ou…
felixweinberger Apr 17, 2026
1af9ed2
test(compat): add e2e raw-shape tools/call test; drop vestigial warn-…
felixweinberger Apr 17, 2026
3155be7
Merge branch 'main' into fweinberger/v2-bc-register-rawshape
KKonstantinov Apr 24, 2026
aba1d39
feat(compat): widen completable() constraint to StandardSchemaV1
felixweinberger Apr 24, 2026
0febd83
refactor(compat): move zod helpers to zodCompat.ts; throw on invalid …
felixweinberger Apr 27, 2026
7e80880
Merge branch 'main' into fweinberger/v2-bc-register-rawshape
felixweinberger Apr 27, 2026
c75bc88
test(compat): move zod-compat tests to zodCompat.test.ts; tighten nor…
felixweinberger Apr 27, 2026
a6b25ee
fix(compat): reject Zod v3 fields in raw-shape auto-wrap with actiona…
felixweinberger Apr 27, 2026
b5854c1
fix(compat): require plain-object prototype in isZodRawShape; null-gu…
felixweinberger Apr 27, 2026
9b7ee90
Merge branch 'main' into fweinberger/v2-bc-register-rawshape
felixweinberger Apr 29, 2026
27e2c4b
fix(compat): pass StandardSchema without ~standard.jsonSchema through…
felixweinberger Apr 29, 2026
617830b
Merge branch 'main' into fweinberger/v2-bc-register-rawshape
felixweinberger Apr 29, 2026
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
8 changes: 8 additions & 0 deletions .changeset/register-rawshape-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
---

`registerTool`/`registerPrompt` accept a raw Zod shape (`{ field: z.string() }`) for `inputSchema`/`outputSchema`/`argsSchema` in addition to a wrapped Standard Schema. Raw shapes are auto-wrapped with `z.object()`. The raw-shape overloads are `@deprecated`; prefer wrapping with `z.object()`.

Also widens the `completable()` constraint from `StandardSchemaWithJSON` to `StandardSchemaV1` so v1's `completable(z.string(), fn)` continues to work.
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './types/index.js';
export * from './util/inMemory.js';
export * from './util/schema.js';
export * from './util/standardSchema.js';
export * from './util/zodCompat.js';

// experimental exports
export * from './experimental/index.js';
Expand Down
72 changes: 72 additions & 0 deletions packages/core/src/util/zodCompat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Zod-specific helpers for the v1-compat raw-shape shorthand on
* `registerTool`/`registerPrompt`. Kept separate from `standardSchema.ts` so
* that file stays library-agnostic per the Standard Schema spec.
*/
Comment on lines +1 to +5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit: this file header says it's "Kept separate from standardSchema.ts so that file stays library-agnostic", and the reply on the earlier thread said "standardSchema.ts no longer imports zod" — both were true at 0febd83, but merge 9b7ee90 brought in #1895 (b256546), which re-added import * as z from 'zod/v4' to standardSchema.ts:9 for the z.toJSONSchema() fallback at line 178. Suggest softening to e.g. "Kept separate so standardSchema.ts stays focused on the Standard Schema spec interface", or moving the #1895 zod fallback into zodCompat.ts so the claim actually holds.

Extended reasoning...

What's wrong

The new file-level JSDoc at packages/core/src/util/zodCompat.ts:3-4 reads:

Kept separate from standardSchema.ts so that file stays library-agnostic per the Standard Schema spec.

And the author's reply on inline thread 3147227355 (resolving KKonstantinov's "move these out into a zodCompat one" ask) stated "standardSchema.ts no longer imports zod." Both statements were accurate at commit 0febd83, when the split was made. But the subsequent merge commit 9b7ee90 brought b256546 (#1895) onto this branch, and #1895 re-introduced a zod import into standardSchema.ts for its zod-4.0–4.1 z.toJSONSchema() fallback. So at HEAD (617830b), the rationale clause in this PR's newly-added comment is factually incorrect — standardSchema.ts is not library-agnostic.

Step-by-step proof

  1. packages/core/src/util/zodCompat.ts:1-5 (newly added in this PR's diff):
    /**
     * Zod-specific helpers for the v1-compat raw-shape shorthand on
     * `registerTool`/`registerPrompt`. Kept separate from `standardSchema.ts` so
     * that file stays library-agnostic per the Standard Schema spec.
     */
  2. git log shows 0febd83 (creates zodCompat.ts and removes the zod import from standardSchema.ts) was committed before merge 9b7ee90.
  3. Merge 9b7ee90 brought in b256546 (fix(core): fall back to z.toJSONSchema for zod schemas without ~standard.jsonSchema #1895), which adds the zod-4.0–4.1 fallback to standardSchemaToJsonSchema.
  4. At HEAD, packages/core/src/util/standardSchema.ts:9 reads import * as z from 'zod/v4'; and line 178 reads result = z.toJSONSchema(schema as unknown as z.ZodType, { target: 'draft-2020-12', io }) ....
  5. Therefore the claim "so that file stays library-agnostic" — written and shipped by this PR's diff — is contradicted by the same branch's standardSchema.ts.

Why nothing catches it

There's no lint or test that cross-references prose comments against imports. The comment was correct when written; it became stale via a textual-clean merge (no conflict markers, since #1895 touched standardSchema.ts and this PR added a new file). The author's earlier resolved reply ("standardSchema.ts no longer imports zod") is on a closed thread, so nothing prompts a re-check after the merge.

Impact

Zero behavioral impact — this is an internal file-header comment, not user-facing JSDoc, changelog, or .d.ts. The organizational split itself (raw-shape compat helpers vs. Standard Schema spec utilities) remains perfectly sound; only the stated rationale for the split is wrong. Hence nit, non-blocking. The reason it's worth a one-line fix is that it sits at the top of a brand-new file added by this PR and will mislead the next contributor who reads it ("so I shouldn't add zod to standardSchema.ts" — but it's already there).

Suggested fix

Either soften the comment so it doesn't make a falsifiable claim:

/**
 * Zod-specific helpers for the v1-compat raw-shape shorthand on
 * `registerTool`/`registerPrompt`. Kept separate from `standardSchema.ts` so
 * that file stays focused on the Standard Schema spec interface.
 */

…or, if you actually want to deliver on the "library-agnostic" claim, move #1895's z.toJSONSchema() fallback (standardSchema.ts:158-178) into zodCompat.ts and call it from standardSchemaToJsonSchema — that would make both files match their stated purpose. The first option is the one-liner.


import * as z from 'zod/v4';

import type { StandardSchemaWithJSON } from './standardSchema.js';
import { isStandardSchema, isStandardSchemaWithJSON } from './standardSchema.js';

function isZodV4Schema(v: unknown): v is z.ZodType {
// `_zod` is the v4 internal namespace property. Zod v3 schemas have `_def`
// and (since 3.24) `~standard.vendor === 'zod'`, but never `_zod`. We require
// v4 because the wrap path below uses v4's `z.object()`, which cannot consume
// v3 field schemas.
return typeof v === 'object' && v !== null && '_zod' in v;
}
Comment on lines +12 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit: same guard-over-acceptance class as the ArkType (0152b26) and Zod-v3 (068ff56) cases — isZodV4Schema keys on '_zod' in v, which is also present on zod/mini schemas. { a: zm.string() } passes isZodRawShape, gets wrapped as classic z.object({...}), and on tools/liststd.jsonSchema.input(...) throws [toJSONSchema]: Non-representable type encountered: string (the wrapper has ~standard.jsonSchema, so #1895's working z.toJSONSchema fallback is bypassed — meaning auto-wrap introduces a crash that zm.object({...}) passed directly would not hit). TS rejects it (mini's types ≠ z.ZodType) so this only bites JS/as any callers — exactly the cohort the line-70 guard was added for. Consider '_zod' in v && '_def' in v (classic v4 has both; mini has only _zod; v3 has only _def) — also subsumes the v3 rejection.

Extended reasoning...

What the bug is

isZodV4Schema (zodCompat.ts:17) is the per-field predicate that gates whether a raw shape is handed to z.object(). It accepts anything with a _zod property — but zod/mini schemas also have _zod (they share @zod/core with classic v4). So { a: zm.string() } passes isZodRawShape, gets wrapped as classic z4.object({ a: zm.string() }), and the resulting object's ~standard.jsonSchema.input() throws on tools/list. This is structurally the same deferred-crash guard-over-acceptance class as the ArkType (0152b26), Zod-v3 (068ff56), bare-V1 (c75bc88), null, and non-plain-object cases already tightened in this PR.

The code path that triggers it

import * as zm from 'zod/mini';
server.registerTool('x', { inputSchema: { a: zm.string() } } as any, async ({ a }) => );
  1. isZodRawShape({a: zm.string()}): not null/object ✓, not itself a StandardSchema ✓ (the record has no ~standard), proto === Object.prototype ✓, Object.values[zm.string()], isZodV4Schema(zm.string())'_zod' in vtrue.
  2. normalizeRawShapeSchema returns z.object({a: zm.string()}) — a classic v4 ZodObject. Constructs without throwing (lazy).
  3. Stored as tool.inputSchema.
  4. tools/liststandardSchemaToJsonSchema(wrapped, 'input')std.jsonSchema exists (classic v4 ZodObject has it) → calls std.jsonSchema.input({target:'draft-2020-12'})throws [toJSONSchema]: Non-representable type encountered: string.

Notably, if the user instead passes zm.object({a: zm.string()}) directly (no auto-wrap), it works: mini objects have no ~standard.jsonSchema, so #1895's z.toJSONSchema() fallback fires and succeeds. So the auto-wrap introduces a crash for an input that the non-wrapped path handles.

Why nothing else catches it

The guard chain ends at '_zod' in v (zodCompat.ts:17), which both zod/v4 and zod/mini schemas satisfy. z.object() constructs lazily so no error at wrap time. Because the wrapper is classic-v4, it has ~standard.jsonSchema, so standardSchemaToJsonSchema (standardSchema.ts:158) takes the direct path and bypasses the working z.toJSONSchema fallback. The line-70 guard (!isStandardSchema(schema)) is on the fall-through path; mini fields take the isZodRawShape → true branch and never reach it.

Step-by-step proof (verified against zod@4.3.6 in the repo)

Step Expression Result
1 '_zod' in zm.string() true (mini shares the v4 core)
2 '_def' in zm.string() false (classic-only)
3 zm.string() instanceof z.ZodType false
4 isZodV4Schema(zm.string()) true (via '_zod' in v)
5 isZodRawShape({a: zm.string()}) true
6 const w = z.object({a: zm.string()}) constructs OK, no throw
7 typeof w['~standard'].jsonSchema?.input 'function' — fallback bypassed
8 w['~standard'].jsonSchema.input({target:'draft-2020-12'}) throws [toJSONSchema]: Non-representable type encountered: string
9 z.toJSONSchema(w, {io:'input'}) succeeds — confirms the bypassed fallback would have worked
10 zm.object({a: zm.string()})['~standard'].jsonSchema undefined — would hit working fallback

Impact

Low — hence nit. TypeScript rejects it (zm.string() is not assignable to z.ZodType from zod/v4, so {a: zm.string()} doesn't satisfy Record<string, z.ZodType>), so this only bites JS / as any / @ts-ignore callers — but that is precisely the cohort the line-70 guard was added for per KKonstantinov's review (3146829297). zod/mini is also v4-only, so it has zero overlap with the v1-compat target audience this @deprecated shim exists for; mini users would naturally write zm.object({...}) (which works). The underlying ~standard.jsonSchemaz.toJSONSchema discrepancy for mini-in-classic-wrapper is arguably a zod bug. Mentioning because it's the same class this PR has tightened five times already and the fix bundles trivially.

Fix

Tighten isZodV4Schema to require the classic v4 marker:

return typeof v === 'object' && v !== null && '_zod' in v && '_def' in v;

Classic v4 has both _zod and _def; mini has only _zod; v3 has only _def — so this cleanly accepts classic-v4-only and also subsumes the existing v3 rejection. (Avoiding v instanceof z.ZodType since instanceof is fragile across dual zod installs.) With this, {a: zm.string()} falls through to the line-70 "must be a Standard Schema" TypeError at registration time instead of crashing on tools/list.


function looksLikeZodV3(v: unknown): boolean {
// v3 schemas have `_def.typeName` (e.g. 'ZodString') and no `_zod`.
return (
typeof v === 'object' &&
v !== null &&
!('_zod' in v) &&
'_def' in v &&
typeof (v as { _def?: { typeName?: unknown } })._def?.typeName === 'string'
);
}

/**
* Detects a "raw shape" — a plain object whose values are Zod field schemas,
* e.g. `{ name: z.string() }`. Powers the auto-wrap in
* {@linkcode normalizeRawShapeSchema}, which wraps with `z.object()`, so only
* Zod values are supported.
*
* @internal
*/
export function isZodRawShape(obj: unknown): obj is Record<string, z.ZodType> {
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 => isZodV4Schema(v));
}

Check warning on line 44 in packages/core/src/util/zodCompat.ts

View check run for this annotation

Claude / Claude Code Review

isZodRawShape vacuously accepts arrays / non-plain objects, silently bypassing the line-66 guard

nit: dropping the `values.length > 0` check (for the `{}` fix) means `isZodRawShape` now vacuously accepts any object with no own enumerable properties — `[]`, `[z.string()]`, `new Date()`, `new Map()`, `/regex/` all return `true`, get wrapped as `z.object({})` (or `z.object({'0': zString})` for the array), and silently bypass the line-66 `TypeError` that was added per review for JS/`as any` callers. Same posture as the still-open `null` case on line 61, but with a *silent* failure mode instead
Comment thread
claude[bot] marked this conversation as resolved.

/**
* Accepts either a {@linkcode StandardSchemaWithJSON} or a raw Zod shape
* `{ field: z.string() }` and returns a {@linkcode StandardSchemaWithJSON}.
* Raw shapes are wrapped with `z.object()` so the rest of the pipeline sees a
* uniform schema type; already-wrapped schemas pass through unchanged.
*
* @internal
*/
export function normalizeRawShapeSchema(
schema: StandardSchemaWithJSON | Record<string, z.ZodType> | undefined
): StandardSchemaWithJSON | undefined {
if (schema === undefined) return undefined;
if (isZodRawShape(schema)) {
return z.object(schema) as StandardSchemaWithJSON;
}
if (typeof schema === 'object' && !isStandardSchema(schema) && Object.values(schema).some(v => looksLikeZodV3(v))) {
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
throw new TypeError(
'Raw-shape inputSchema/outputSchema/argsSchema fields must be Zod v4 schemas. Got a Zod v3 field schema. Import from `zod/v4` (or upgrade your zod import), or wrap with `z.object({...})` yourself.'
);
}
if (!isStandardSchemaWithJSON(schema)) {
throw new TypeError(
'inputSchema/outputSchema/argsSchema must be a Standard Schema with JSON Schema export (`~standard.jsonSchema`, e.g. z.object({...}) from zod >=4.2.0) or a raw Zod shape ({ field: z.string() }).'
);
}
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
return schema;
Comment thread
felixweinberger marked this conversation as resolved.
}
58 changes: 58 additions & 0 deletions packages/core/test/util/zodCompat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as z from 'zod/v4';

import { standardSchemaToJsonSchema } from '../../src/util/standardSchema.js';
import { isZodRawShape, normalizeRawShapeSchema } from '../../src/util/zodCompat.js';

describe('isZodRawShape', () => {
test('treats empty object as a raw shape (matches v1)', () => {
expect(isZodRawShape({})).toBe(true);
});
test('detects raw shape with zod fields', () => {
expect(isZodRawShape({ a: z.string() })).toBe(true);
});
test('rejects a Standard Schema instance', () => {
expect(isZodRawShape(z.object({ a: z.string() }))).toBe(false);
});
test('rejects a shape with non-Zod Standard Schema fields', () => {
const nonZod = { '~standard': { version: 1, vendor: 'arktype', validate: () => ({ value: 'x' }) } };
expect(isZodRawShape({ a: nonZod })).toBe(false);
});
test('rejects a shape with Zod v3 fields (only v4 is wrappable)', () => {
expect(isZodRawShape({ a: mockZodV3String() })).toBe(false);
});
});

// Minimal structural mock of a Zod v3 schema: has `_def.typeName` and
// `~standard.vendor === 'zod'` (zod >=3.24), but no `_zod`.
function mockZodV3String(): unknown {
return {
_def: { typeName: 'ZodString', checks: [], coerce: false },
'~standard': { version: 1, vendor: 'zod', validate: (v: unknown) => ({ value: v }) },
parse: (v: unknown) => v
};
}

describe('normalizeRawShapeSchema', () => {
test('wraps empty raw shape into z.object({})', () => {
const wrapped = normalizeRawShapeSchema({});
expect(wrapped).toBeDefined();
expect(standardSchemaToJsonSchema(wrapped!, 'input').type).toBe('object');
});
test('passes through an already-wrapped Standard Schema unchanged', () => {
const schema = z.object({ a: z.string() });
expect(normalizeRawShapeSchema(schema)).toBe(schema);
});
test('returns undefined for undefined input', () => {
expect(normalizeRawShapeSchema(undefined)).toBeUndefined();
});
test('throws TypeError for an invalid object that is neither raw shape nor Standard Schema', () => {
expect(() => normalizeRawShapeSchema({ a: 'not a zod schema' } as never)).toThrow(TypeError);
});
test('throws TypeError for a Standard Schema without JSON Schema export', () => {
const noJson = { '~standard': { version: 1, vendor: 'x', validate: () => ({ value: {} }) } };
expect(() => normalizeRawShapeSchema(noJson as never)).toThrow(/~standard\.jsonSchema/);
});
test('throws actionable TypeError for a raw shape with Zod v3 fields', () => {
expect(() => normalizeRawShapeSchema({ a: mockZodV3String() } as never)).toThrow(/Zod v4 schemas.*Got a Zod v3 field schema/);
});
});
18 changes: 9 additions & 9 deletions packages/server/src/server/completable.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { StandardSchemaWithJSON } from '@modelcontextprotocol/core';
import type { StandardSchemaV1 } from '@modelcontextprotocol/core';

export const COMPLETABLE_SYMBOL: unique symbol = Symbol.for('mcp.completable');

export type CompleteCallback<T extends StandardSchemaWithJSON = StandardSchemaWithJSON> = (
value: StandardSchemaWithJSON.InferInput<T>,
export type CompleteCallback<T extends StandardSchemaV1 = StandardSchemaV1> = (
value: StandardSchemaV1.InferInput<T>,
context?: {
arguments?: Record<string, string>;
}
) => StandardSchemaWithJSON.InferInput<T>[] | Promise<StandardSchemaWithJSON.InferInput<T>[]>;
) => StandardSchemaV1.InferInput<T>[] | Promise<StandardSchemaV1.InferInput<T>[]>;

export type CompletableMeta<T extends StandardSchemaWithJSON = StandardSchemaWithJSON> = {
export type CompletableMeta<T extends StandardSchemaV1 = StandardSchemaV1> = {
complete: CompleteCallback<T>;
};

export type CompletableSchema<T extends StandardSchemaWithJSON> = T & {
export type CompletableSchema<T extends StandardSchemaV1> = T & {
Comment thread
felixweinberger marked this conversation as resolved.
[COMPLETABLE_SYMBOL]: CompletableMeta<T>;
};

Expand Down Expand Up @@ -48,7 +48,7 @@ export type CompletableSchema<T extends StandardSchemaWithJSON> = T & {
*
* @see {@linkcode server/mcp.McpServer.registerPrompt | McpServer.registerPrompt} for using completable schemas in prompt argument definitions
*/
export function completable<T extends StandardSchemaWithJSON>(schema: T, complete: CompleteCallback<T>): CompletableSchema<T> {
export function completable<T extends StandardSchemaV1>(schema: T, complete: CompleteCallback<T>): CompletableSchema<T> {
Object.defineProperty(schema as object, COMPLETABLE_SYMBOL, {
value: { complete } as CompletableMeta<T>,
enumerable: false,
Expand All @@ -61,14 +61,14 @@ export function completable<T extends StandardSchemaWithJSON>(schema: T, complet
/**
* Checks if a schema is completable (has completion metadata).
*/
export function isCompletable(schema: unknown): schema is CompletableSchema<StandardSchemaWithJSON> {
export function isCompletable(schema: unknown): schema is CompletableSchema<StandardSchemaV1> {
return !!schema && typeof schema === 'object' && COMPLETABLE_SYMBOL in (schema as object);
}

/**
* Gets the completer callback from a completable schema, if it exists.
*/
export function getCompleter<T extends StandardSchemaWithJSON>(schema: T): CompleteCallback<T> | undefined {
export function getCompleter<T extends StandardSchemaV1>(schema: T): CompleteCallback<T> | undefined {
const meta = (schema as unknown as { [COMPLETABLE_SYMBOL]?: CompletableMeta<T> })[COMPLETABLE_SYMBOL];
return meta?.complete as CompleteCallback<T> | undefined;
}
74 changes: 71 additions & 3 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,
normalizeRawShapeSchema,
promptArgumentsFromStandardSchema,
ProtocolError,
ProtocolErrorCode,
Expand All @@ -39,6 +40,7 @@ import {
validateStandardSchema
} from '@modelcontextprotocol/core';

import type * as z from 'zod/v4';
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
import { getCompleter, isCompletable } from './completable.js';
Expand Down Expand Up @@ -873,6 +875,31 @@ export class McpServer {
_meta?: Record<string, unknown>;
},
cb: ToolCallback<InputArgs>
): RegisteredTool;
Comment thread
claude[bot] marked this conversation as resolved.
/** @deprecated Wrap with `z.object({...})` instead. Raw-shape form: `inputSchema`/`outputSchema` may be a plain `{ field: z.string() }` record; it is auto-wrapped with `z.object()`. */
registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape | StandardSchemaWithJSON | undefined = undefined>(
name: string,
config: {
title?: string;
description?: string;
inputSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
_meta?: Record<string, unknown>;
},
cb: LegacyToolCallback<InputArgs>
): RegisteredTool;
Comment thread
felixweinberger marked this conversation as resolved.
registerTool(
name: string,
config: {
title?: string;
description?: string;
inputSchema?: StandardSchemaWithJSON | ZodRawShape;
outputSchema?: StandardSchemaWithJSON | ZodRawShape;
annotations?: ToolAnnotations;
_meta?: Record<string, unknown>;
},
cb: ToolCallback<StandardSchemaWithJSON | undefined> | LegacyToolCallback<ZodRawShape>
): RegisteredTool {
if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
Expand All @@ -884,8 +911,8 @@ export class McpServer {
name,
title,
description,
inputSchema,
outputSchema,
normalizeRawShapeSchema(inputSchema),
normalizeRawShapeSchema(outputSchema),
annotations,
{ taskSupport: 'forbidden' },
_meta,
Expand Down Expand Up @@ -928,6 +955,27 @@ export class McpServer {
_meta?: Record<string, unknown>;
},
cb: PromptCallback<Args>
): RegisteredPrompt;
/** @deprecated Wrap with `z.object({...})` instead. Raw-shape form: `argsSchema` may be a plain `{ field: z.string() }` record; it is auto-wrapped with `z.object()`. */
registerPrompt<Args extends ZodRawShape>(
name: string,
config: {
title?: string;
description?: string;
argsSchema?: Args;
_meta?: Record<string, unknown>;
},
cb: LegacyPromptCallback<Args>
): RegisteredPrompt;
registerPrompt(
name: string,
config: {
title?: string;
description?: string;
argsSchema?: StandardSchemaWithJSON | ZodRawShape;
_meta?: Record<string, unknown>;
},
cb: PromptCallback<StandardSchemaWithJSON> | LegacyPromptCallback<ZodRawShape>
): RegisteredPrompt {
if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
Expand All @@ -939,7 +987,7 @@ export class McpServer {
name,
title,
description,
argsSchema,
normalizeRawShapeSchema(argsSchema),
cb as PromptCallback<StandardSchemaWithJSON | undefined>,
_meta
);
Expand Down Expand Up @@ -1062,6 +1110,26 @@ export class ResourceTemplate {
}
}

/**
* A plain record of Zod field schemas, e.g. `{ name: z.string() }`. Accepted by
* `registerTool`/`registerPrompt` as a shorthand; auto-wrapped with `z.object()`.
* Zod schemas only — `z.object()` cannot wrap other Standard Schema libraries.
*/
export type ZodRawShape = Record<string, z.ZodType>;

/** Infers the parsed-output type of a {@linkcode ZodRawShape}. */
export type InferRawShape<S extends ZodRawShape> = z.infer<z.ZodObject<S>>;

/** {@linkcode ToolCallback} variant used when `inputSchema` is a {@linkcode ZodRawShape}. */
export type LegacyToolCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
? (args: InferRawShape<Args>, ctx: ServerContext) => CallToolResult | Promise<CallToolResult>
: (ctx: ServerContext) => CallToolResult | Promise<CallToolResult>;

/** {@linkcode PromptCallback} variant used when `argsSchema` is a {@linkcode ZodRawShape}. */
export type LegacyPromptCallback<Args extends ZodRawShape | undefined> = Args extends ZodRawShape
? (args: InferRawShape<Args>, ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>
: (ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>;
Comment thread
claude[bot] marked this conversation as resolved.

export type BaseToolCallback<
SendResultT extends Result,
Ctx extends ServerContext,
Expand Down
Loading
Loading