modelcontextprotocol / modelcontextprotocol/typescript-sdk

Add hook or middleware for transforming tools/list responses

Open
#1,757 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement needs decision P3
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Is your feature request related to a problem? Please describe.

The MCP SDK provides no public mechanism to transform tools/list responses before they're sent to the client. This forces servers that need to augment tool definitions — for example, promoting fields from _meta to the tool root level — to access private internals and re-implement the SDK's serialization logic.

Concrete use case: ChatGPT mixed-auth security schemes

ChatGPT's Actions/Connectors platform expects securitySchemes as a root-level field on each tool definition in tools/list responses (OpenAI docs: Build with Auth).

However, the SDK's registerTool only supports securitySchemes inside _meta, and the
built-in tools/list handler serializes it there — not at the root level.

To bridge this gap, servers must:

  1. Access the private _registeredTools field on McpServer (via as any cast)
  2. Override the ListToolsRequestSchema handler via server.setRequestHandler
  3. Re-implement the entire tool serialization — including normalizeObjectSchema,
    toJsonSchemaCompat, outputSchema handling, annotations, execution, etc.

This is ~80 lines of duplicated SDK internals that will silently break on any refactor of
the tool listing logic. Multiple production MCP servers have independently converged on this
identical workaround.

Describe the solution you'd like

Any of the following would solve this cleanly (in rough order of preference):

Option A: Response transform hook
mcpServer.onToolsList((tools) => {
  return tools.map(tool => ({
    ...tool,
    securitySchemes: tool._meta?.securitySchemes,
  }));
});

A callback that receives the fully-serialized tool list and returns a (potentially modified) version. This is the most flexible option and avoids exposing internal data structures.

Option B: Middleware / chaining for setRequestHandler
// Get the current handler before replacing it
const original = server.getRequestHandler(ListToolsRequestSchema);

server.setRequestHandler(ListToolsRequestSchema, async (req, extra) => {
  const result = await original(req, extra);
  // transform result.tools
  return result;
});

Adding a getRequestHandler method to Protocol would let consumers wrap existing handlers without re-implementing them. This is more general-purpose and benefits all request types.

Option C: Public read-only accessor for registered tools
const tools: ReadonlyMap<string, RegisteredTool> = mcpServer.registeredTools;

This would at least eliminate the as any cast, though consumers would still need to re-implement serialization. (See also #1036.)

Describe alternatives you've considered

  • Accessing _registeredTools directly — works today but requires as any, duplicates serialization logic, and is fragile across SDK versions. We pin to patch versions (~1.26.0) to mitigate breakage.
  • Intercepting at the transport level — even more fragile; requires parsing/modifying JSON-RPC messages.
  • Saving RegisteredTool references at registration time — the RegisteredTool interface provides .update() but no control over response serialization shape.

Additional context

  • Related: #1036 (public access to registered tools) and #836 (dynamic tools based on auth context) — both touch adjacent problems but neither addresses the core issue of response transformation.
  • The setRequestHandler API is public and stable — the gap is specifically the inability to compose with the default handler rather than fully replacing it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the MCP server tools/list handler, the public setRequestHandler API, and the related issues #1036 and #836. Compare the proposed response-transform hook, handler composition, and registered-tools accessor, then define a public approach that preserves the SDK's existing serialization; done means callers can modify tools/list responses without private fields or duplicated serialization logic.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
40/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.