Skip to content

Telemetry (OpenTelemetry)

discord-mcp ships with OpenTelemetry traces and metrics built on top of the official OTel JS SDK. Mutation audit events can additionally use the OTel Logs signal when MCP_AUDIT_SINK=otlp. This page covers how to enable telemetry, where to send the data, and what signals you get out of the box.

The SDK is disabled by default: if you do not set OTEL_ENABLED=true the server runs with no exporter wired and zero overhead. All telemetry configuration is environment-variable driven (no code changes needed).

Pick the exporter that matches your environment.

Set the OTLP endpoint and your team API key as headers, then start the server:

Terminal window
export OTEL_ENABLED=true
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
export OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=YOUR_API_KEY,x-honeycomb-dataset=discord-mcp
export OTEL_SERVICE_NAME=discord-mcp
discord-mcp serve

Traces appear in the Honeycomb UI under the discord-mcp dataset within ~10 seconds (the OTLP exporter batches).

Set MCP_AUDIT_SINK=otlp alongside OTEL_ENABLED=true to send the redacted mutation audit events described in Audit logging to the collector’s /v1/logs endpoint. The logs processor uses a bounded 1,024-record queue, batches up to 128 records, and never makes collector availability part of the Discord tool success path. Shutdown performs a bounded flush; delivery is best-effort rather than a durable spool.

Metric Type Description
mcp.tool.duration_ms Histogram Wall-clock duration of each tool call (milliseconds).
mcp.tool.calls Counter Total tool calls, labelled by status{ok, tool_error, error}.
mcp.tool.errors Counter Subset of calls that ended in error (tool_error or thrown).

Common labels on every metric: mcp.tool.name, mcp.tool.category, mcp.tool.idempotent, mcp.transport, status.

Metric Type Description
mcp.discovery.searches Counter Successful progressive-catalog searches.
mcp.discovery.response_bytes Histogram UTF-8 byte size of the structured search response sent to the MCP transport.
mcp.channel_guild_cache.lookups Counter Channel-to-guild resolver attempts, split by cache hit or miss and status. A miss is one REST lookup; a hit reused an existing lookup or result.
mcp.channel_guild_cache.duration_ms Histogram Resolver wait time for the same bounded cache labels.

Discovery metrics use only mcp.discovery.detail (compact or full), mcp.discovery.contract_mode (none, selected, or all), and the bounded mcp.discovery.match_bucket. Cache metrics use only mcp.cache.outcome (hit or miss) plus status (ok or error). They never export search queries, tool names, channel or guild IDs, message content, tokens, or complete MCP payloads. The response is serialized locally only to measure its byte size.

Metric Type Description
mcp.circuit.transitions Counter Circuit breaker state transitions. One label: to_state{open, half-open, closed}. There is no from label - the destination state alone identifies the transition.
mcp.bulkhead.rejected.count Counter Calls fast-rejected because the bulkhead semaphore was full. No labels.
mcp.deadletter.count Counter Calls that exhausted retries / were circuit-rejected and surfaced to the client as a structured error. One label: error.type.

Every tool invocation produces an mcp.tool.<tool_name> SERVER span with:

  • Standard attributes: mcp.tool.name, mcp.tool.category, mcp.tool.idempotent, mcp.transport, mcp.request_id (if known).
  • Span event mcp.tool.args with attribute mcp.args.redacted containing the JSON-stringified, redacted arg payload (see Audit → Privacy redaction for the policy).
  • Status: OK on success, ERROR with tool returned isError for structured tool errors, or the thrown exception message for crashes.

If OTEL_ENABLED=true AND the underlying Discord REST call fires under the active span context, you also get a child CLIENT span from @opentelemetry/instrumentation-undici with the standard http.request.method, url.full, http.response.status_code attributes plus the discord.route (route-redacted, e.g. POST /channels/:id/messages).

For Grafana + Prometheus, useful starter queries:

  • Tool error rate, last 5m: sum by (mcp.tool.name) (rate(mcp_tool_errors_total[5m])) / sum by (mcp.tool.name) (rate(mcp_tool_calls_total[5m]))
  • p95 tool latency: histogram_quantile(0.95, sum by (mcp.tool.name, le) (rate(mcp_tool_duration_ms_bucket[5m])))
  • Circuit open events: sum(rate(mcp_circuit_transitions_total{to_state="open"}[1h]))
  • Bulkhead saturation: sum(rate(mcp_bulkhead_rejected_count_total[5m]))
  • Dead letters by cause: sum by (error_type) (rate(mcp_deadletter_count_total[5m]))

The label is to_state, not to - a query written as {to="open"} matches nothing and silently reports a permanently healthy circuit. Neither the bulkhead nor the circuit metric has a route label to group by.

For Honeycomb, useful starting BubbleUp / triggers:

  • Slow mcp.tool.<name> spans (P95 > 1s).
  • mcp.tool.errors > 10 per minute.
  • mcp.deadletter.count > 0 (every dead-letter is a real failure the client saw).

The shipped repository does NOT include exported board JSON - every deployment has different SLOs. Use the queries above as a starting point and tune.

The default trace sampler is parentbased_always_on: if an incoming trace context is set (parent), defer to it; otherwise sample 100%. This is fine for low-volume bot deployments. For high-volume servers, switch to:

Terminal window
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.05 # 5% sampling

Metrics are NOT affected by trace sampling - counters and histograms are always reported.

Var Default Description
OTEL_ENABLED false Master switch. When false, SDK is not booted.
OTEL_SERVICE_NAME discord-mcp service.name resource attribute.
OTEL_SERVICE_VERSION (package version) service.version resource attribute.
OTEL_EXPORTER_OTLP_ENDPOINT (unset) OTLP collector endpoint (e.g. http://localhost:4318).
OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf Parsed for compatibility but currently ignored; runtime export is always OTLP HTTP/protobuf.
OTEL_EXPORTER_OTLP_HEADERS (unset) Comma-separated key=value pairs (e.g. for vendor auth).
OTEL_TRACES_SAMPLER parentbased_always_on One of the standard OTel samplers.
OTEL_TRACES_SAMPLER_ARG 1 Ratio for ratio-based samplers.
OTEL_CONSOLE_EXPORTER false Unsupported with stdio: replaces OTLP trace export and writes spans to stdout.
  • Resilience - circuit/bulkhead/retry knobs that emit the resilience metrics above.
  • Audit - mutating-call trail; uses the same redaction policy as span events.
  • Architecture → Middleware chain - where the telemetry middleware sits in the call path.