Skip to content

Tool-call middleware seam #374

Description

@gbrlcustodio

Part of the hosted on-behalf-of MCP server proposal (#297).

Scope

Add a documented middleware (interceptor) chain around tool invocation, so cross-cutting concerns can be layered without swapping the server's internal request handlers.

Today the only way to observe or govern a tool call is to replace the low-level CallToolRequest handler on the server. That is a single private slot: it is undocumented, and the next feature that needs the same seam overwrites the previous one. Several accepted and proposed items all need this exact interception point:

Each of these is "run something around a tool invocation with the caller's validated identity in hand." A single ordered middleware chain turns all of them into registered middleware rather than internal extension.

Proposed shape (illustrative, not prescriptive):

async def middleware(ctx: ToolCallContext, call_next):
    # ctx exposes: tool name, arguments, the validated auth context
    # (subject, client id, scopes) from request-scoped identity, and a request id
    result = await call_next(ctx)   # runs inner middleware, then the tool
    # after: result error flag, duration
    return result

Middleware runs in registration order (outer to inner) around the handler, may short-circuit (for example, a quota middleware returning an error result without calling call_next), and has read access to the validated auth context so it never re-decodes the bearer.

Acceptance criteria

  • A public, documented way to register tool-call middleware; the internal request-handler dict is no longer the extension surface.
  • Middleware receives the tool name, arguments, validated caller identity (subject, client id, scopes), and a request id correlating to the HTTP request.
  • Middleware runs in a defined order and can short-circuit a call (return an error result without invoking the tool).
  • The structured tool logging in Structured request and tool logging #373 is reimplemented as a middleware on this chain, demonstrating the seam end to end.
  • Registering middleware does not require importing or mutating server internals.

Dependencies

Builds on request-scoped identity (#302, closed) for the auth context passed to middleware. Unblocks the tool-call half of per-user observability and quotas (#309), rate limiting and abuse protection (#307), and is the seam #373 should be built on.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesthosted-mcpHosted, multi-user, on-behalf-of MCP server profile

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions