Skip to content

Observability

Telemetry is gated on RA_MCP_OTEL_ENABLED=true. When enabled, traces, metrics, and logs are exported via OTLP.


Environment Variables

Variable Default Description
RA_MCP_OTEL_ENABLED false Master switch
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317 Collector endpoint
OTEL_EXPORTER_OTLP_PROTOCOL grpc grpc or http/protobuf
OTEL_SERVICE_NAME ra-mcp Service name (service.name)
ENVIRONMENT (unset) deployment.environment.name resource attribute (falls back to DEPLOYMENT_ENVIRONMENT)
RA_MCP_OTEL_LOG_BRIDGE true Bridge Python logging to OTel

The SDK sets service.name, service.version, a per-process service.instance.id, and — when ENVIRONMENT is set — deployment.environment.name as resource attributes; OTEL_RESOURCE_ATTRIBUTES is merged on top.

service.instance.id defaults to a fresh UUID per process so replicas are distinguishable (the chart autoscales to several pods). To pin a stable per-pod id, set it from the Kubernetes downward API:

env:
  - name: POD_NAME
    valueFrom: { fieldRef: { fieldPath: metadata.name } }
  - name: OTEL_RESOURCE_ATTRIBUTES
    value: "service.instance.id=$(POD_NAME)"

Trace Tree

Every MCP tool call produces a trace spanning from the protocol layer down to HTTP:

graph TD
  A["tools/call search_transcribed\n<i>FastMCP — automatic</i>"]
  B["delegate search_transcribed\n<i>FastMCP — composed server</i>"]
  C["tools/call search_transcribed\n<i>FastMCP — provider</i>"]
  D["SearchOperations.search\n<i>domain layer</i>"]
  E["SearchClient.search\n<i>API client</i>"]
  F["HTTP GET\n<i>HTTP client</i>"]

  A --> B --> C --> D --> E --> F

Instrumented Components

Component Tracer name Spans Metrics
HTTP client ra_mcp.http_client HTTP GET request count, error count, retry count, duration, response size
Search client ra_mcp.search.client SearchClient.search
Search ops ra_mcp.search_operations SearchOperations.search ra_mcp.search.requests, ra_mcp.search.results
Browse ops ra_mcp.browse_operations BrowseOperations.browse_document, BrowseOperations._fetch_page_contexts ra_mcp.browse.requests, ra_mcp.browse.pages, ra_mcp.browse.empty_pages
ALTO client ra_mcp.alto_client ALTOClient.fetch_content ra_mcp.alto.fetches
IIIF client ra_mcp.iiif_client IIIFClient.get_collection, IIIFClient.fetch_manifest
OAI-PMH client ra_mcp.oai_pmh_client OAIPMHClient.get_metadata, OAIPMHClient.extract_manifest_id ra_mcp.oai_pmh.fetches
Viewer fetchers fastmcp tracer (fetchers.py) fetch_text_layer
LanceDB spine ra_mcp.lancedb search <table> (CLIENT) — all 13 datasets + PDF-guide search ra_mcp.lancedb.queries, ra_mcp.lancedb.errors, ra_mcp.lancedb.query.duration, ra_mcp.lancedb.results
TORA geocoding ra_mcp.tora.client tora sparql (CLIENT)
PDF cache ra_mcp.pdf.cache fetch pdf range (CLIENT), prefetch pdf (INTERNAL)
CLI commands ra_mcp.cli.* cli.search, cli.browse

FastMCP adds automatic spans for all tools/call and resources/read operations. Handlers enrich the current tool-call span with behavioural intent (search terms via db.query.text, view.reference_code, pdf.url, geocode.place) and mark it ERROR via mark_span_error when they return an error string instead of raising — see Behavioural analytics for the product-question map.

Custom metrics all declare a UCUM unit (s, By, or an annotation such as {request} / {fetch} / {page}); metric dimensions are kept low-cardinality (method, status, result enums), while per-request identifiers (URLs, PIDs, reference codes, keywords, session ids) live on spans only.

Error recording

Failures set span.set_status(ERROR, "{ErrorClass}: …") and record error.type + a structured exception.* log record via record_span_exception() — the OTel Span Event API (record_exception) is deprecated, and the log record carries the active trace_id/span_id through the log bridge. The stacktrace is logged once per exception even as it unwinds through several layers (the counter isn't inflated). Retried-then-succeeded requests stay UNSET.

A tool handler that catches an exception (or fails input validation) and returns an error string instead of raising calls mark_span_error(message, error_type=…) first, so the FastMCP tools/call span goes ERROR — otherwise a normal return leaves it green and the tool failure rate reads zero.

Verify Telemetry

# Start Jaeger, exercise tools, check trace tree
dagger call verify-telemetry --source=.