-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Modify registerPrompt to accept undefined Args type #1516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -915,7 +915,7 @@ export class McpServer { | |
| * ); | ||
| * ``` | ||
| */ | ||
| registerPrompt<Args extends StandardSchemaWithJSON>( | ||
| registerPrompt<Args extends StandardSchemaWithJSON | undefined = undefined>( | ||
| name: string, | ||
| config: { | ||
| title?: string; | ||
|
|
@@ -1239,7 +1239,7 @@ export type RegisteredPrompt = { | |
| enabled: boolean; | ||
| enable(): void; | ||
| disable(): void; | ||
| update<Args extends StandardSchemaWithJSON>(updates: { | ||
| update<Args extends StandardSchemaWithJSON | undefined = undefined>(updates: { | ||
| name?: string | null; | ||
| title?: string; | ||
| description?: string; | ||
|
Comment on lines
1239
to
1245
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 The update() type change introduces a runtime mismatch: TypeScript now accepts a no-args callback on a prompt originally registered with an argsSchema, but at runtime the no-args callback silently receives the parsed schema data as its ctx parameter instead of the real ServerContext. To fix this properly, RegisteredPrompt must be made generic (carrying the original Args type) so that update() can enforce callback arity matches the stored schema. Extended reasoning...What the bug isThe PR changes The specific code path that triggers itInside Why existing code does not prevent itInside ImpactAny server that registers a with-args prompt and later calls How to fix itThe root cause is that Step-by-step proof
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * 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 { describe, it } from 'vitest'; | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: 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. |
||
|
|
||
| describe('registerPrompt types', () => { | ||
| it('compiles', () => { | ||
| // The functions above are compile-time type assertions; this suite | ||
| // exists so vitest detects the file as containing tests. | ||
| }); | ||
| }); | ||
|
Comment on lines
+37
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The new mcp.types.test.ts file covers only the registerPrompt half of the type fix, leaving RegisteredPrompt.update() without compile-time assertions. If the update fix were reverted, the test suite would not catch the regression. Extended reasoning...The PR makes two parallel type fixes: adding undefined = undefined to Args in both registerPrompt (line 918) and RegisteredPrompt.update (line 1242). Both fixes are present in the diff and production code is correct. However, mcp.types.test.ts only exercises registerPrompt via registerPrompt_noArgs and registerPrompt_withArgs, with zero compile-time assertions for the update() method. The specific uncovered path is RegisteredPrompt.update typed as update<Args extends StandardSchemaWithJSON | undefined = undefined>. The test file verifies registerPrompt overloads but never calls .update() on a returned RegisteredPrompt handle. If the update fix were reverted to update, no existing test would fail. The registerPrompt_noArgs and registerPrompt_withArgs functions call server.registerPrompt() directly and never exercise .update() on the returned handle. TypeScript would only surface the regression when a user called .update({ callback: (ctx) => result }) on a no-args prompt in real code. This is a test coverage gap, not a production bug — the actual fix in mcp.ts is correct. The risk is a future refactor accidentally removing the undefined from update() without any test catching it, since the test file was specifically introduced to guard against this class of type regression. To fix, add to mcp.types.test.ts: const prompt = server.registerPrompt('no-args', {}, (ctx: ServerContext) => result); prompt.update({ callback: (ctx: ServerContext) => result }); // should compile. And a @ts-expect-error line rejecting an args callback when no argsSchema. Step-by-step proof: (1) Revert line 1242 of mcp.ts from update<Args extends StandardSchemaWithJSON | undefined = undefined> back to update. (2) Run the type check — all tests in mcp.types.test.ts still pass because none call .update(). (3) A user calling prompt.update({ callback: (ctx) => result }) in their own project would get a TS error, but the test suite stays green. (4) Adding prompt.update() assertions to the test file would immediately catch the regression in step 2. |
||
Uh oh!
There was an error while loading. Please reload this page.