Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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/moody-humans-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@modelcontextprotocol/server": patch
---

Fix `registerPrompt` generic to allow the no-args callback overload. The `Args` type parameter now defaults to `undefined`, matching `registerTool`.
2 changes: 1 addition & 1 deletion packages/server/src/server/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ export class McpServer {
* );
* ```
*/
registerPrompt<Args extends StandardSchemaWithJSON>(
registerPrompt<Args extends StandardSchemaWithJSON | undefined = undefined>(
name: string,
config: {
title?: string;
Comment thread
claude[bot] marked this conversation as resolved.
Expand Down
34 changes: 34 additions & 0 deletions packages/server/test/server/mcp.types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Compile-time type checks for McpServer registration methods.
*
* These verify that generic type parameters resolve correctly for the
* no-argsSchema and with-argsSchema overloads of registerPrompt.
*/
import type { GetPromptResult, ServerContext } from '@modelcontextprotocol/core';
import * as z from 'zod/v4';

import { McpServer } from '../../src/server/mcp.js';

/* eslint-disable @typescript-eslint/no-unused-vars */

declare const server: McpServer;
declare const result: GetPromptResult;

// Without argsSchema, the callback must accept (ctx) only.
// Before the fix, Args had no default and could not be undefined, so the
// PromptCallback conditional never resolved to the no-args branch.
function registerPrompt_noArgs() {
server.registerPrompt('no-args', {}, (ctx: ServerContext) => result);

// @ts-expect-error -- callback cannot take an args parameter when argsSchema is omitted
server.registerPrompt('no-args', {}, (args: { code: string }, ctx: ServerContext) => result);
}

// With argsSchema, the callback must accept (args, ctx).
function registerPrompt_withArgs() {
server.registerPrompt(
'with-args',
{ argsSchema: z.object({ code: z.string() }) },
(args: { code: string }, ctx: ServerContext) => result
);
}
Comment on lines +29 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The registerPrompt_withArgs test only covers the positive case (callback correctly accepts (args, ctx)) but is missing a @ts-expect-error negative test verifying TypeScript rejects a no-args callback when argsSchema is provided. The registerPrompt_noArgs function correctly has both a positive and a negative test, but registerPrompt_withArgs only has the positive -- a regression in the Args extends StandardSchemaWithJSON branch of PromptCallback could go undetected.

Extended reasoning...

The new test file packages/server/test/server/mcp.types.test.ts was specifically added to validate the conditional type resolution of PromptCallback under both overloads of registerPrompt. The registerPrompt_noArgs function (lines 20-24) demonstrates the correct pattern: one positive test followed by one @ts-expect-error negative test. However, registerPrompt_withArgs (lines 27-34) only contains the positive case.

The missing negative test would verify that TypeScript rejects a no-args callback when argsSchema is present:
// @ts-expect-error -- callback must accept args when argsSchema is present
server.registerPrompt('with-args', { argsSchema: z.object({ code: z.string() }) }, (ctx: ServerContext) => result);

Without this test, a specific class of regression in PromptCallback could go undetected. The production code is correct today, and TypeScript structural typing means ServerContext (which has a required mcpReq property) is not directly assignable to { code: string }, providing incidental coverage. However, this structural incompatibility is a side-effect, not an intentional contract check.

The verifier refutation argues this is a test enhancement request rather than a blocking defect, which is fair. The primary fix (allowing undefined as the default Args type) is already validated by the existing tests, and the structural incompatibility between ServerContext and { code: string } means TypeScript would reject the mis-typed callback today even without an explicit negative test.

Nevertheless, the test file was designed to document and enforce the full type contract of both overloads symmetrically. The asymmetry between noArgs (2 tests: positive + negative) and withArgs (1 test: positive only) is a genuine gap. A deliberate regression that widens PromptCallback to accept either arity when Args extends StandardSchemaWithJSON would pass the existing positive test but would be caught by a negative test.

Step-by-step proof of the gap: (1) Suppose PromptCallback is changed to accept either arity as a union when Args extends StandardSchemaWithJSON. (2) The existing positive withArgs test still passes -- a callback with (args, ctx) is accepted. (3) There is no negative test to verify a no-arg-only callback (ctx: ServerContext) => result is rejected, so the regression goes undetected. (4) A @ts-expect-error negative test directly asserts this rejection, closing the gap.

Loading