feat: add middleware= hook to the server builders (closes #6) - #7
Merged
Conversation
Thread an optional middleware= (a single FastMCP middleware or a list) through mk_mcp_server / mk_mcp_from_refs / mk_mcp_from_store / mk_http_app / serve_http / serve_stdio, attached at FastMCP construction — mirroring how auth= is threaded. This is the clean seam for cross-cutting concerns that must wrap every tool call (usage metering, cost logging, audit, rate limiting), replacing error-prone per-tool decorators. The no-middleware path is unchanged (the kwarg is only added when given). Adds tests (incl. an in-memory functional test asserting the hook fires around a tool call) and README docs. Claude-Session: https://claude.ai/code/session_01Gw5RPgrQhC88Hc3DyACYWF
… doc/test gaps Adversarial-review follow-ups (issue #6): - _normalize_middleware now accepts any iterable of middlewares (set, generator, ...), not just list/tuple. A Middleware instance is matched first so an iterable-ish one isn't mistaken for a collection; generators are materialized. Previously a set/generator was silently wrapped as one bogus element -> the server built fine but every tool call failed with an opaque McpError. - An empty middleware iterable now takes the plain FastMCP(name, auth=auth) path. - Document middleware= on mk_mcp_from_store and mk_mcp_from_refs; README enumerates every builder, says 'iterable', and notes middleware is a programmatic-only hook. - Tests: generator/set/empty coverage, mk_mcp_from_store + serve_stdio forwarding, and mk_http_app forwarding now asserted precisely (not just a smoke build). Claude-Session: https://claude.ai/code/session_01Gw5RPgrQhC88Hc3DyACYWF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements #6: a first-class
middleware=hook on the py2mcp server builders.middleware=(a single FastMCP middleware or any iterable of them) is threaded through every builder —mk_mcp_server,mk_mcp_from_refs,mk_mcp_from_store,mk_http_app,serve_http,serve_stdio— and attached at the singleFastMCP(...)construction point, mirroring howauth=is threaded. It's the clean seam for cross-cutting concerns that must wrap every tool call (usage metering, cost logging, audit, rate limiting), replacing error-prone per-tool decorators where a forgotten wrap on a paid tool means untracked cost.Design notes
middlewarekwarg is only added to theFastMCP(...)call when a non-empty middleware is given, so existing callers get the exact oldFastMCP(name, auth=auth)construction (no new fastmcp-version floor).None, a singleMiddleware, or any iterable (list/tuple/set/generator); aMiddlewareis matched first (so an iterable-ish one isn't treated as a collection) and generators are materialized.Tests
tests/test_middleware.pycovers all six builders' forwarding, single/list/tuple/set/generator/empty normalization, and the acceptance test that the hook actually fires around a tool call (via an in-memory client). Full suite: 68 passed (pytest + doctests); ruff clean.Review
Ran an adversarial review (correctness / API-consistency / tests-docs). It caught a real bug — a
set/generator of middlewares was silently wrapped as a single bogus element, so the server built fine but every tool call then failed with an opaqueMcpError— now fixed, plus doc and test-coverage gaps closed.Closes #6
https://claude.ai/code/session_01Gw5RPgrQhC88Hc3DyACYWF