Skip to content

feat: add middleware= hook to the server builders (closes #6) - #7

Merged
thorwhalen merged 2 commits into
mainfrom
middleware-hook
Jul 31, 2026
Merged

feat: add middleware= hook to the server builders (closes #6)#7
thorwhalen merged 2 commits into
mainfrom
middleware-hook

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

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 single FastMCP(...) construction point, mirroring how auth= 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

  • Zero regression on the no-middleware path: the middleware kwarg is only added to the FastMCP(...) call when a non-empty middleware is given, so existing callers get the exact old FastMCP(name, auth=auth) construction (no new fastmcp-version floor).
  • Normalization accepts None, a single Middleware, or any iterable (list/tuple/set/generator); a Middleware is matched first (so an iterable-ish one isn't treated as a collection) and generators are materialized.
  • Programmatic-only: middleware are Python objects, so the hook is not wired through the CLI / JSON-config path (documented in the README).

Tests

tests/test_middleware.py covers 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 opaque McpError — now fixed, plus doc and test-coverage gaps closed.

Closes #6

https://claude.ai/code/session_01Gw5RPgrQhC88Hc3DyACYWF

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
@thorwhalen
thorwhalen merged commit 0252537 into main Jul 31, 2026
10 checks passed
@thorwhalen
thorwhalen deleted the middleware-hook branch July 31, 2026 18:05
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 a first-class middleware= hook to the server builders (metering/logging/rate-limiting)

1 participant