Reference

Telemetry & semconv

Kams implements the published OpenTelemetry MCP model rather than inventing one. Custom behaviour is namespaced kams.*.

Spans

One CLIENT span per request/response pair, named by the published convention:

text
tools/list
tools/call save_note
initialize

Attributes

AttributeExampleNotes
mcp.method.nametools/call
mcp.session.id9f2c1a4b7e0d5386From _meta when present
mcp.protocol.version2025-06-18Learned from the initialize exchange
jsonrpc.request.id7Stringified
rpc.response.status_code-32602JSON-RPC error code
network.transportpipe / tcpstdio / HTTP
error.type-32602Unset on success
gen_ai.operation.nameexecute_toolOn tools/call only
gen_ai.tool.namesave_note
mcp.request.size / mcp.response.size412Bytes on the wire
mcp.tool.count2On tools/list
mcp.context.cost.tokens1180
mcp.context.cost.estimatedtrueMandatory discriminator
kams.baseline.statepinnedpinned / provisional / first_sighting
kams.trace.propagatedtrueWhether inbound context existed

Successful spans stay UNSET

OpenTelemetry represents a successful client span with an unset status, not OK. Kams follows that, so OK never appears and error queries stay meaningful.

Enforcement spans

A block emits kams.enforce <action> — a child of the active MCP span when one exists, or a standalone control-plane span when the webhook fires later.

AttributeExample
kams.enforce.actionquarantine_server
kams.enforce.originreflex / signoz
kams.enforce.ttl3600
kams.rule.namequarantine-poisoned-pinned-definition
kams.server.namenotes-mcp

Finding events

Findings attach to the active span as events, not attributes — a single tools/list can produce several findings and attribute keys cannot repeat.

text
kams.finding.integrity_drift
  kams.finding.kind       = INTEGRITY_DRIFT
  kams.finding.severity   = CRITICAL
  kams.finding.detector   = integrity.definition_drift
  kams.finding.confidence = 0.87
  kams.finding.summary    = Tool 'save_note' description changed on a pinned…

Metrics

MetricUnitLabels
mcp.client.operation.durationsserver, method, tool
mcp.tool.call.countserver, method, tool
mcp.tool.error.countserver, method, tool
mcp.integrity.drift.countserver, kind, severity, detector, baseline_state, tool
kams.finding.countas above
mcp.egress.classified.countserver, class, tool
mcp.context.cost.tokens{token}server, method, tool, estimated
kams.enforcement.countserver, action, rule, origin

Two deliberate choices:

  • Explicit buckets on duration, so p95 is meaningful at MCP latencies rather than landing in a default bucket sized for HTTP services.
  • Delta temporality on counters, because shims are short-lived and cumulative temporality would produce reset artifacts on every agent disconnect.

One metric, one meaning

mcp.integrity.drift.count is incremented only by integrity.definition_drift. Every other finding family increments kams.finding.count. This is what allows the critical alert to be trusted — it cannot fire on the wrong condition.

Trace propagation (SEP-414)

MCP carries W3C trace context in params._meta. Kams extracts inbound context, starts its CLIENT span as a child, and reinjects the new context upstream:

jsonc
{
  "method": "tools/call",
  "params": {
    "name": "save_note",
    "arguments": { "note": "buy milk" },
    "_meta": {
      "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
      "tracestate": "vendor=value",
      "baggage": "deployment=prod",
      "progressToken": 42
    }
  }
}

traceparent, tracestate, and baggage are all preserved, and unrelated _meta keys such as progressToken are never dropped — the injection copies rather than mutates, so an accidental in-place edit cannot silently break byte-fidelity.

When no inbound context exists, Kams starts a valid root span and records kams.trace.propagated=false rather than fabricating a parent.

Logs

Python logging is exported over OTLP. Records emitted inside an active span arrive with non-empty trace_id and span_id, which is what makes the dashboard's log panel clickable through to the trace.

text
kams.finding.kind      = INTEGRITY_DRIFT
kams.finding.severity  = CRITICAL
kams.finding.detector  = integrity.definition_drift
kams.server.name       = notes-mcp
gen_ai.tool.name       = save_note

Sensitive values are excluded at finding construction time, not filtered at export.

The semconv proposal

src/kams/semconv/mcp.yaml deliberately proposes only the remaining gap — context-cost attribution — rather than redefining the existing MCP convention:

AttributeRequirement
mcp.context.cost.tokensRecommended
mcp.context.cost.estimatedRequired when tokens is present

The discriminator is mandatory by design. A cost metric that mixed estimates with provider-reported measurements without saying which is which would not be one anyone should trust.