Skip to main content
FrontMCP can expose process metrics (CPU, RSS, heap, event-loop lag) and every framework counter (frontmcp_skills_*_total, plus any counter emitted via createCounter()) as a Prometheus scrape endpoint on the same HTTP listener as /healthz. The endpoint is off by default — turn it on with metrics: { enabled: true }.

Quick Start

Then scrape it:
The Content-Type is the canonical Prometheus text/plain; version=0.0.4; charset=utf-8.

What the endpoint exposes

When enabled, every scrape returns: createCounter() from @frontmcp/observability writes into the same store the scrape reads from — no extra wiring needed.

Configuration

format: 'json'

Returns a { counters, gauges } envelope at Content-Type application/json — useful for tooling that prefers JSON over text parsing:

auth: 'token'

Sets Authorization: Bearer <token> as the gate. The token is read from process.env[tokenEnv] (default env var: FRONTMCP_METRICS_TOKEN) at startup. If the env var is unset, the service constructor throws MetricsTokenNotConfiguredError — failing fast so a token-gated endpoint never silently downgrades to public:
Behaviour:
  • Missing Authorization header → 401
  • Authorization: Bearer <wrong>403
  • Authorization: Bearer <correct>200
For local / non-secret testing, an inline { token: '...' } literal also works but is discouraged in production:

include[] filter

Each category maps to a counter-name prefix: Omit include to emit every category.

Path conflicts

metrics.path MUST NOT collide with MCP transport paths (/mcp, /sse, /messages). The service constructor throws MetricsPathConflictError at startup if it detects an overlap:

Off-by-default rationale

The endpoint is opt-in because process metrics, framework counter names, and tool vocabularies can hint at deployment scale and feature usage (e.g. frontmcp_skills_signature_failures_total reveals a signing infra exists, frontmcp_auth_checks_total{result="denied"} correlates to attack attempts). Recommendations:
  • Internet-exposed deployments: use auth: 'token' or terminate the endpoint at a sidecar/ingress with a network ACL.
  • Cluster-local deployments: auth: 'public' matches the Prometheus / Kubernetes convention; ensure the Prometheus pod can reach the port and external traffic cannot.

How it interacts with OpenTelemetry

createCounter() from @frontmcp/observability writes to two places: the in-memory snapshot store (which this endpoint reads) AND any globally configured OTel MeterProvider (which forwards to OTLP / Prometheus exporter / Grafana Cloud). If you’ve already wired an OTel MeterProvider via metrics.setGlobalMeterProvider(), the values in the scrape match the values pushed via OTel — both paths share the same counter handles.

Adding custom counters

Use createCounter() from @frontmcp/observability. The counter is automatically included in the scrape:
Label values should be bounded (status codes, enum members, tool names) — unbounded values (user IDs, URLs, JWTs) blow up the timeseries count.

Reference

  • @frontmcp/sdk exports: MetricsService, registerMetricsRoutes, MetricsPathConflictError, MetricsTokenNotConfiguredError
  • @frontmcp/observability exports: renderPrometheusExposition, renderJsonExposition, ProcessStatsCollector, PROMETHEUS_CONTENT_TYPE, createCounter, getMetricSnapshot
  • Type definitions: MetricsOptionsInterface, MetricsAuth, MetricsCategory, MetricsFormat, MetricsProcessOptionsInterface
  • Tracked under issue #397