Skip to content

feat(core): add getRequestHandler to Protocol#1759

Open
tizmagik wants to merge 1 commit intomodelcontextprotocol:mainfrom
tizmagik:feat/get-request-handler
Open

feat(core): add getRequestHandler to Protocol#1759
tizmagik wants to merge 1 commit intomodelcontextprotocol:mainfrom
tizmagik:feat/get-request-handler

Conversation

@tizmagik
Copy link

@tizmagik tizmagik commented Mar 25, 2026

Summary

Adds a public getRequestHandler() method to the Protocol class, enabling retrieval and composable wrapping of existing request handlers.

Closes #1757

Motivation

Currently, servers that need to transform request responses (e.g., tools/list) must either:

  • Access private _registeredTools via as any casts
  • Fully replace handlers via setRequestHandler, re-implementing all SDK serialization logic (~80 lines)

Both approaches are fragile and break on SDK refactors. This change provides a clean public API for the common "wrap and transform" pattern.

API

getRequestHandler<M extends RequestMethod>(
    method: M
): ((request: RequestTypeMap[M], ctx: ContextT) => Promise<ResultTypeMap[M]>) | undefined

Returns the currently registered handler for a method, or undefined if none exists. The returned function is properly typed for both input and output.

Design notes:

  • Returns a snapshot — if the handler is later replaced or removed, the returned function still delegates to the originally captured handler
  • The returned handler includes the SDK's internal schema validation layer, so requests passed to it will be re-validated (harmless but redundant in the wrapping pattern)

Usage Example

// Wrap tools/list to promote _meta.securitySchemes to root level (ChatGPT use case from #1757)
const original = server.getRequestHandler('tools/list');
if (original) {
    server.setRequestHandler('tools/list', async (request, ctx) => {
        const result = await original(request, ctx);
        result.tools = result.tools.map(tool => ({
            ...tool,
            securitySchemes: tool._meta?.securitySchemes,
        }));
        return result;
    });
}

Changes

  • packages/core/src/shared/protocol.ts — Added getRequestHandler() method with JSDoc documenting snapshot semantics and schema re-validation trade-off
  • packages/core/src/shared/protocol.examples.ts — Type-checked JSDoc example with proper undefined guard
  • packages/core/test/shared/protocol.test.ts — 7 tests: undefined for unregistered, returns handler, undefined after remove, reflects latest after replacement, snapshot works after replacement, wrapping with result verification, error propagation
  • .changeset/add-get-request-handler.md — Minor changeset for @modelcontextprotocol/core

Test Plan

  • pnpm --filter @modelcontextprotocol/core test — all 447 tests pass
  • pnpm check:all — typecheck + lint + docs all pass

🤖 Generated with Claude Code

@tizmagik tizmagik requested a review from a team as a code owner March 25, 2026 15:59
@changeset-bot
Copy link

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: b86068f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 25, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1759

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1759

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1759

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1759

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1759

commit: b86068f

Add a public `getRequestHandler()` method to Protocol that returns the
currently registered handler for a given request method. This enables
composable handler wrapping without re-implementing SDK internals.

Closes modelcontextprotocol#1757

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tizmagik tizmagik force-pushed the feat/get-request-handler branch from c3ea456 to b86068f Compare March 25, 2026 16:16
@tizmagik
Copy link
Author

Alternative approach: setResponseTransform

If the getRequestHandler approach isn't palatable, I wanted to flag an alternative middle-ground between Options A and B from the original issue.

Instead of retrieving and wrapping handlers, Protocol could support a response transform hook:

server.setResponseTransform('tools/list', (result) => {
    result.tools = result.tools.map(tool => ({
        ...tool,
        securitySchemes: tool._meta?.securitySchemes,
    }));
    return result;
});

This would plug into _onrequest between the handler returning and the JSON-RPC response being constructed (~line 851 in protocol.ts):

.then(() => handler(request, ctx))
.then(async result => {
    const transform = this._responseTransforms.get(request.method);
    const finalResult = transform ? await transform(result) : result;
    // build JSON-RPC response from finalResult...
})

Trade-offs vs the current PR (getRequestHandler):

getRequestHandler (this PR) setResponseTransform
Generality Any request method Any request method
Double schema parse Yes (documented, harmless) No
Type safety Has an internal as unknown cast Clean — transform receives/returns typed result
Caller complexity get/set/wrap dance Single call
Internal changes Pure addition, no existing code modified Small change to _onrequest (~3-4 lines + new map)
Snapshot semantics Yes (needs documenting) No

Trade-offs vs Option A from the issue (onToolsList):

setResponseTransform is general-purpose (works for any method) while Option A would need separate hooks per list type (onToolsList, onPromptsList, onResourcesList, etc.).

Happy to implement either direction — just wanted to surface this option in case it's a better fit for the SDK's design philosophy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add hook or middleware for transforming tools/list responses

1 participant