Skip to content

Record HTTP request and tool call metrics for FastMCP in Prometheus - #155

Open
personage-hub wants to merge 2 commits into
community-of-python:mainfrom
personage-hub:fastmcp-prometheus
Open

personage-hub wants to merge 2 commits into
community-of-python:mainfrom
personage-hub:fastmcp-prometheus

Conversation

@personage-hub

Copy link
Copy Markdown
Contributor

Summary

FastMcpPrometheusInstrument only exposed the registry on the metrics route, so a FastMCP service had nothing in /metrics but process_* and python_*, unlike the Litestar, FastAPI and FastStream bootstrappers, which also record request metrics. Two commits, the second one can be dropped if you prefer to keep the library to HTTP metrics:

  1. HTTP request metrics through prometheus-fastapi-instrumentator, the same library the FastAPI bootstrapper uses; it officially supports plain Starlette apps. Every ASGI application returned by http_app() is instrumented via the add_http_application_postprocessor hook from Add OpenTelemetry instrument for FastMCP #152, so requests are counted by route template (handler="/mcp", handler="/health/"), unknown paths are grouped into handler="none", the metrics route itself is excluded. FastMcpPrometheusConfig gets the FastAPI-style prometheus_instrumentator_params, prometheus_instrument_params and prometheus_custom_labels; prometheus_registry is reused for the request metrics. The fastmcp extra now depends on prometheus-fastapi-instrumentator>=7.1 (it only requires starlette and prometheus-client).
  2. Tool call metrics: FastMcpPrometheusMiddleware (a FastMCP middleware next to FastMcpLoggingMiddleware) records fastmcp_tool_calls_total{tool, status} and fastmcp_tool_call_duration_seconds{tool}. Enabled by default, prometheus_tool_metrics=False turns it off.

No library for MCP-level metrics exists, so the tool call part is hand-written; the HTTP part reuses an existing dependency instead of adding a custom ASGI middleware.

Tests

  • test_fastmcp_prometheus_counts_http_requests_by_route_template: health route counted twice as 2xx, unknown path as none, POST /mcp by the mount path, metrics route not counted, custom label present.
  • test_fastmcp_prometheus_instrumentator_params_are_passed: excluded_handlers and should_group_status_codes overrides.
  • test_fastmcp_prometheus_counts_tool_calls / ..._can_be_disabled with the in-memory fastmcp.Client.
  • just test: 346 passed.

Note: uv run mypy . reports two pre-existing errors in microbootstrap/helpers.py:29 with the locked mypy on Python 3.14; untouched here.

🤖 Generated with Claude Code

Alexander Niyazov and others added 2 commits October 7, 2026 11:13
The FastMCP Prometheus instrument only exposed the registry on the metrics route, so the endpoint
had nothing but process_* and python_* series. Every ASGI application returned by http_app() is now
instrumented with prometheus-fastapi-instrumentator, the same library the FastAPI bootstrapper uses:
requests are counted by route template (handler="/mcp", handler="/health/"), unknown paths are
grouped into handler="none" and the metrics route itself is excluded.

FastMcpPrometheusConfig gets the FastAPI-style prometheus_instrumentator_params,
prometheus_instrument_params and prometheus_custom_labels; prometheus_registry is used for the request
metrics as well.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tool calls are the unit of work of an MCP server, so FastMcpPrometheusMiddleware records
fastmcp_tool_calls_total{tool, status} and fastmcp_tool_call_duration_seconds{tool}, sharing the
registry and custom labels of the request metrics. Enabled by default, prometheus_tool_metrics=False
turns it off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant