OpenTelemetry Integration¶
This guide covers enabling OpenTelemetry (OTel) on the MCP Gateway for distributed tracing and log export. When enabled, the MCP Router (ext_proc) emits trace spans for every request and can export structured logs via OTLP. When no endpoint is configured, OTel is completely disabled with zero overhead.
Prerequisites¶
- MCP Gateway installed and configured
- An OTLP-compatible collector endpoint (e.g., OpenTelemetry Collector, Grafana Alloy, Datadog Agent)
Note: For a pre-configured local stack with OTEL Collector, Tempo, Loki, and Grafana, see the observability example.
Step 1: Enable OpenTelemetry¶
Set the following environment variables on the MCP Gateway deployment:
| Variable | Required | Description |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Yes | Your OTLP collector endpoint (e.g., http://your-collector:4318) |
OTEL_EXPORTER_OTLP_INSECURE |
No | Set to true for non-TLS endpoints |
Helm Install¶
After installing the MCP Gateway with Helm, set the environment variables on the deployment. Use helm list -A to find your release name and namespace:
kubectl set env deployment/<release-name> -n <namespace> \
OTEL_EXPORTER_OTLP_ENDPOINT="http://your-collector:4318" \
OTEL_EXPORTER_OTLP_INSECURE="true"
Kubernetes (kubectl)¶
If you deployed the gateway manifests directly:
kubectl set env deployment/mcp-gateway -n mcp-system \
OTEL_EXPORTER_OTLP_ENDPOINT="http://your-collector:4318" \
OTEL_EXPORTER_OTLP_INSECURE="true"
Step 2: Verify Traces Are Being Exported¶
After enabling OTel, generate some traffic against the gateway (e.g., an initialize or tools/list request) and confirm traces appear in your collector backend. The gateway emits spans under the service name mcp-gateway by default.
Environment Variables¶
| Variable | Description | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Base OTLP endpoint for all signals | (none -- disabled) |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Override endpoint for traces only | Falls back to base |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
Override endpoint for logs only | Falls back to base |
OTEL_EXPORTER_OTLP_INSECURE |
Disable TLS verification | false |
OTEL_SERVICE_NAME |
Service name reported in traces and logs | mcp-gateway |
OTEL_SERVICE_VERSION |
Service version reported in traces and logs | Build version |
Endpoint Schemes¶
The endpoint URL scheme determines the transport protocol:
| Scheme | Protocol | Typical Port | Example |
|---|---|---|---|
http:// |
OTLP/HTTP (insecure) | 4318 | http://collector:4318 |
https:// |
OTLP/HTTP (TLS) | 4318 | https://collector:4318 |
rpc:// |
OTLP/gRPC | 4317 | rpc://collector:4317 |
When using http://, TLS is automatically disabled regardless of the OTEL_EXPORTER_OTLP_INSECURE setting. For https:// and rpc://, set OTEL_EXPORTER_OTLP_INSECURE=true to skip TLS verification.
Sending Traces and Logs to Different Backends¶
Use signal-specific endpoint overrides to route traces and logs to different collectors or backends:
kubectl set env deployment/mcp-gateway -n mcp-system \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://traces-collector:4318" \
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://logs-collector:4318" \
OTEL_EXPORTER_OTLP_INSECURE="true"
To enable only traces (without log export), set only OTEL_EXPORTER_OTLP_TRACES_ENDPOINT. To enable only log export, set only OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.
What Gets Exported¶
Traces¶
The MCP Router emits spans for each ext_proc request lifecycle:
mcp-router.process
├── mcp-router.route-decision
│ ├── mcp-router.broker-passthrough (initialize, tools/list, etc.)
│ └── mcp-router.tool-call (tools/call)
│ ├── mcp-router.broker.get-server-info
│ ├── mcp-router.session-cache.get
│ ├── mcp-router.session-init (on cache miss)
│ └── mcp-router.session-cache.store (on cache miss)
Span attributes follow OpenTelemetry MCP Semantic Conventions and include:
mcp.method.name-- MCP method (initialize, tools/call, tools/list)gen_ai.operation.name-- same asmcp.method.namegen_ai.tool.name-- tool name (for tools/call requests)mcp.session.id-- gateway session IDmcp.server-- resolved backend server namemcp.route-- routing decision (tool-call,broker, orelicitation-response)http.method,http.path,http.request_id,http.status_codejsonrpc.request.id,jsonrpc.protocol.versionclient.address-- from x-forwarded-for header
On error, spans include error.type, error_source, and http.status_code.
Logs¶
When log export is enabled, all slog log lines are sent to the collector via OTLP in addition to stdout. Log lines emitted within a traced request automatically include trace_id and span_id fields, enabling log-to-trace correlation in backends like Grafana (Loki to Tempo).
Resource Attributes¶
Every span and log record includes:
service.name-- fromOTEL_SERVICE_NAME(default:mcp-gateway)service.version-- fromOTEL_SERVICE_VERSIONor build versionvcs.revision-- git SHA (set at build time)build.go.version-- Go runtime version
Trace Context Propagation¶
The router extracts W3C Trace Context (traceparent header) from incoming requests. This means:
- If Envoy/Istio is configured with tracing, the router spans automatically join the Istio trace as child spans.
- Clients can pass a
traceparentheader to create end-to-end traces from outside the mesh. - If no
traceparentis present, the router creates a new root trace.
Example with explicit trace propagation (replace the URL with your gateway endpoint):
TRACE_ID=$(openssl rand -hex 16)
curl -s -X POST http://your-gateway-host/mcp \
-H "Content-Type: application/json" \
-H "traceparent: 00-${TRACE_ID}-$(openssl rand -hex 8)-01" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
echo "Search for trace: $TRACE_ID"
Next Steps¶
- For a pre-configured local observability stack (OTEL Collector, Tempo, Loki, Grafana), see the observability example.
- To scale the gateway with shared session state, see the scaling guide.