mcp-trace
Transparent Go proxy for MCP servers that emits OpenTelemetry spans for every JSON-RPC tool call.
MCP Client → mcp-trace :8001 → MCP Server :8000
↓
OTEL Collector :4317
(Jaeger / Tempo / Honeycomb)
Quick start
# Run against a local MCP server, export spans to Jaeger
mcp-trace --target http://localhost:8000/sse --port 8001 --otel-endpoint localhost:4317
Point your MCP client at :8001 instead of :8000. Zero client changes required.
With Docker Compose (Jaeger all-in-one)
docker compose -f docker/compose.jaeger.yml up -d
mcp-trace --target http://localhost:8000/sse
# Open http://localhost:16686 — traces appear after the first tool call.
Installation
Pre-built binary
Download from GitHub Releases.
Build from source
go install github.com/anhermon/mcp-trace/cmd/mcp-trace@latest
Docker
No image is published. Build one locally:
task docker:build
docker run --rm mcp-trace:dev \
--target http://host.docker.internal:8000/sse \
--otel-endpoint host.docker.internal:4317
Configuration
All flags can be set via a .mcp-trace.yaml file (see .mcp-trace.yaml.example).
| Flag |
Default |
Description |
--target |
(required) |
Upstream MCP server SSE URL |
--port |
8001 |
Local port to listen on |
--otel-endpoint |
localhost:4317 |
OTLP gRPC endpoint |
--otel-http |
false |
Use HTTP OTLP exporter instead of gRPC |
--otel-http-endpoint |
http://localhost:4318 |
OTLP HTTP endpoint |
--otel-insecure |
true |
Disable TLS for OTLP |
--service-name |
mcp-trace |
OTel service.name resource attribute |
--trace-all |
false |
Trace all JSON-RPC methods (not just tools/call) |
--include-lifecycle |
false |
Include initialize/ping/notifications/* |
--log-level |
info |
debug | info | warn | error |
--config |
|
Path to config file |
Environment variables
Every CLI flag can also be set via an environment variable using the MCP_TRACE_ prefix. Nested OTel keys use _ as a separator (. → _).
| Environment variable |
Equivalent flag |
Example |
MCP_TRACE_TARGET |
--target |
http://localhost:8000/sse |
MCP_TRACE_PORT |
--port |
8001 |
MCP_TRACE_OTEL_ENDPOINT |
--otel-endpoint |
localhost:4317 |
MCP_TRACE_OTEL_HTTP |
--otel-http |
true |
MCP_TRACE_OTEL_HTTP_ENDPOINT |
--otel-http-endpoint |
http://localhost:4318 |
MCP_TRACE_OTEL_INSECURE |
--otel-insecure |
true |
MCP_TRACE_OTEL_SERVICE_NAME |
--service-name |
my-mcp-server |
MCP_TRACE_TRACE_ALL |
--trace-all |
true |
MCP_TRACE_INCLUDE_LIFECYCLE |
--include-lifecycle |
true |
MCP_TRACE_LOG_LEVEL |
--log-level |
debug |
The service-name variable is MCP_TRACE_OTEL_SERVICE_NAME, not
MCP_TRACE_SERVICE_NAME — the underlying config key is otel.service_name.
Environment variables override config-file values. CLI flags take the highest precedence.
Docker example — run mcp-trace entirely via environment, no flags needed:
docker run --rm \
-e MCP_TRACE_TARGET=http://host.docker.internal:8000/sse \
-e MCP_TRACE_OTEL_ENDPOINT=host.docker.internal:4317 \
-e MCP_TRACE_OTEL_SERVICE_NAME=my-service \
mcp-trace:dev
Span schema
Every traced call produces a span with these attributes:
| Attribute |
Present on |
Description |
mcp.method |
all spans |
JSON-RPC method |
mcp.request.id |
all spans |
JSON-RPC request id |
mcp.server.target |
all spans |
Upstream URL |
mcp.duration_ms |
all spans |
Wall-clock duration in ms |
mcp.status |
all spans |
ok or error |
mcp.tool.name |
tools/call only |
Tool name |
mcp.tool.duration_ms |
tools/call only |
Wall-clock duration in ms |
mcp.tool.status |
tools/call only |
ok or error |
error |
error spans |
true if errored |
error.message |
error spans |
Error message |
Spans are emitted with SpanKind = client. If the caller sends a traceparent
header, the span is created as a child of that trace; either way mcp-trace
injects traceparent into the request it forwards upstream, so the MCP server
can continue the same trace.
Span names follow the pattern:
mcp tools/call read_file (tool calls)
mcp tools/list (other methods, with --trace-all)
As a Claude Code plugin
{
"mcpServers": {
"my-server-traced": {
"command": "mcp-trace",
"args": [
"--target", "http://localhost:8000/sse",
"--port", "8001",
"--otel-endpoint", "localhost:4317"
]
}
}
}
Development
task build # build binary to bin/mcp-trace
task test # run all tests
task ci # full pipeline: vet + test + lint + build
task release # cross-compile all platforms to dist/
Pre-commit hooks
Install hooks that run task ci before every commit and task check before every push:
task hooks:install
Remove hooks:
task hooks:uninstall
Hooks are stored in scripts/hooks/ and copied into .git/hooks/. Bypass in emergencies with --no-verify (use sparingly).
Roadmap
- v1.0 — SSE proxy with OTLP spans (this release)
- v2.0 — stdio transport support (
mcp-trace --stdio -- <command>)
- v2.x — Metrics (counters, histograms), sampling
License
MIT