Security-first Model Context Protocol observability for stdio and Streamable HTTP. Put one transparent proxy between an MCP client and a fixed upstream server to trace, measure, record, inspect, and safely replay JSON-RPC and SSE traffic.
Status: early preview (
0.x). The protocol boundary, recording schema, and CLI may evolve before1.0. Current limits are documented rather than hidden.
The public v0.1.1 container is the shortest released path on linux/amd64:
docker run --rm --network host \
ghcr.io/ryux1/mcp-trace:v0.1.1 proxy \
--upstream http://127.0.0.1:3001/mcpThe npm package is not published yet. To run the newer 0.2.0 code on main, use Node.js 20.19 or
newer and the deterministic source demo:
corepack enable
pnpm install --frozen-lockfile
pnpm demoThe deterministic demo starts a mock MCP server and MCP Trace on ephemeral localhost ports, sends a
real tools/call, verifies that the demo credential was redacted, prints a recording summary and a
Prometheus metric, and cleans up every process and temporary file.
Generic reverse proxies measure HTTP but do not understand MCP methods, protocol revisions, JSON-RPC identifiers, or MCP header/body mismatches. Application instrumentation has the opposite problem: it changes the server and can capture tool arguments or credentials too freely.
MCP Trace keeps the boundary narrow:
- Transparent: forwards JSON and streams SSE with backpressure and cancellation propagation.
- MCP-aware: identifies methods, protocol revisions, tool/resource names, and mismatches.
- Safe by default: records metadata only; body capture requires an explicit second switch.
- Vendor-neutral: exports bounded Prometheus metrics and OpenTelemetry spans over OTLP/HTTP.
- Replay-conscious: plans a dry run unless
--executeis supplied and skips unsafe entries. - Auditable: does not implement MCP methods, terminate authorization, or select upstreams.
Use MCP Trace when you operate a fixed Streamable HTTP endpoint or local stdio server and need evidence without modifying the client or server. Use the official MCP Inspector when you need an active testing client. Use a full MCP gateway when you need server discovery, routing, identity, or policy enforcement.
MCP client ──POST / GET / DELETE──▶ MCP Trace ──transparent HTTP──▶ MCP server
│
├── Prometheus metrics
├── OpenTelemetry spans
└── sanitized NDJSON recording
| MCP transport revision | Status | Behavior |
|---|---|---|
2026-07-28 Streamable HTTP |
Supported | POST, JSON or request-scoped SSE, Mcp-Method, Mcp-Name, Mcp-Param-* |
2025-03-26 through 2025-11-25 |
Supported | POST/GET/DELETE, Mcp-Session-Id, Last-Event-ID, standalone SSE |
2024-11-05 HTTP+SSE |
Not targeted | Separate endpoint discovery is outside this fixed-endpoint gateway |
| stdio | Supported | Exact-byte newline forwarding, child lifecycle, v2 message recordings |
The compatibility matrix distinguishes verified behavior from planned work.
Integration tests exercise initialization, tools/list, and tools/call using the official
TypeScript SDK 1.30.0 and Python SDK 2.1.1 as both clients and servers. The gateway targets the
2026 Streamable HTTP specification
and
2025 transport specification.
The registry packages shown in the release workflow are not published yet. Until the first release, run the CLI from a verified source checkout:
git clone https://github.com/ryux1/mcp-trace.git
cd mcp-trace
corepack enable
pnpm install --frozen-lockfile
pnpm build
pnpm start -- proxy \
--upstream http://127.0.0.1:3001/mcpPoint the MCP client at http://127.0.0.1:7331/mcp. MCP Trace binds only to 127.0.0.1 by default.
Use stdio when the MCP client normally launches a local server process. Everything after -- is
passed directly to the executable without a shell:
mcp-trace stdio --record ./traffic.ndjson -- node ./server.mjsAn MCP client configuration uses MCP Trace as the server command and places the real server after the separator:
{
"mcpServers": {
"local-server": {
"command": "mcp-trace",
"args": [
"stdio",
"--record",
"/absolute/path/to/traffic.ndjson",
"--",
"node",
"/absolute/path/to/server.mjs"
]
}
}
}The child inherits the proxy environment by default, which preserves environment-based MCP server
configuration. Use --clear-env for a minimal launch environment and repeat --pass-env NAME for
each additional variable the server needs. Values are never logged or added to recordings. The child
still has the same operating-system identity and filesystem access as MCP Trace; this is not a
sandbox.
Protocol messages are forwarded byte-for-byte and limited to 4 MiB each by default. Child stderr is
forwarded separately to MCP Trace's stderr and never enters protocol stdout. Metadata-only v2
message recording is the default; --record-bodies adds best-effort-redacted JSON bodies. The
HTTP-targeted replay command skips stdio message entries.
The public v0.1.1 image is available for linux/amd64. Pin the version so deployment behavior
does not change when a new release updates latest:
docker pull ghcr.io/ryux1/mcp-trace:v0.1.1
docker run --rm --network host \
ghcr.io/ryux1/mcp-trace:v0.1.1 proxy \
--host 127.0.0.1 \
--upstream http://127.0.0.1:3001/mcpThe image is also tagged latest. The next release workflow targets both linux/amd64 and
linux/arm64 with an SBOM and build provenance. CI builds and starts the container on native amd64
and arm64 runners before validating its health endpoint.
For a complete local Jaeger demonstration:
docker compose -f docker-compose.demo.yml up --build --detachThe Compose stack starts Jaeger, a mock MCP server, MCP Trace, and a one-shot client. Select the
mcp-trace service at http://127.0.0.1:16686. Stop the stack with:
docker compose -f docker-compose.demo.yml down --volumesMetadata-only recording is the default:
mcp-trace proxy \
--upstream http://127.0.0.1:3001/mcp \
--record ./traffic.ndjsonPayload capture requires a second, explicit switch:
mcp-trace proxy \
--upstream http://127.0.0.1:3001/mcp \
--record ./traffic.ndjson \
--record-bodies \
--redact-key tenant-secretRecording files are forced to owner-only mode (0600). Inspect them without starting a server:
mcp-trace inspect ./traffic.ndjsonThe output summarizes request counts, failures, bytes, and p50/p95/p99 latency by MCP method. Read the recording schema and security model before capturing production traffic.
Create a self-contained report for offline review:
mcp-trace report ./traffic.ndjson --output ./report.htmlThe report contains aggregate counts, latency percentiles, capture/redaction evidence, and malformed
line accounting. It includes no scripts, remote resources, recording path, headers, body values, or
error messages. The output is owner-readable (0600) and will not replace an existing file unless
--force is explicit. It also refuses any output path that resolves to the input recording.
Replay is a dry run by default. It reports how many requests are replayable and skips truncated, binary, body-less, and redacted entries.
mcp-trace replay ./traffic.ndjson \
--upstream http://127.0.0.1:3001/mcpExecute only against a system where repeating tool calls is safe:
mcp-trace replay ./traffic.ndjson \
--upstream http://127.0.0.1:3001/mcp \
--execute \
--concurrency 4 \
--rate 20Authorization can be supplied with --header-env. Legacy Mcp-Session-Id values are never recorded
or replayed, so replay is best suited to stateless 2026 traffic.
The gateway exposes two local administrative endpoints:
GET /__mcp_trace/healthzGET /__mcp_trace/metrics
Prometheus metrics include request totals, in-flight requests, recording failures, and latency histograms. Method-label cardinality is bounded; tool/resource names are not used as metric labels.
Export spans to any OTLP/HTTP collector:
export OTEL_AUTHORIZATION='Bearer collector-token'
mcp-trace proxy \
--upstream http://127.0.0.1:3001/mcp \
--otlp-endpoint http://127.0.0.1:4318 \
--otlp-header-env Authorization=OTEL_AUTHORIZATIONEach span includes HTTP method/status, MCP method, protocol revision, upstream address, and detected
header/body mismatch fields. Tool/resource names appear only on individual spans. MCP Trace
preserves JSON _meta.traceparent, _meta.tracestate, and _meta.baggage fields without rewriting
them, and separately propagates standard HTTP trace headers.
MCP Trace is not a data-loss-prevention system, authorization server, protocol validator, or dynamic forward proxy. The upstream is fixed at startup. Redaction is defense in depth, not proof that a recording contains no sensitive data.
Authenticated upstream headers are loaded from environment variables instead of command arguments:
export MCP_UPSTREAM_AUTHORIZATION='Bearer replace-me'
mcp-trace proxy \
--upstream https://mcp.example.com/mcp \
--upstream-header-env Authorization=MCP_UPSTREAM_AUTHORIZATIONWhen binding to 0.0.0.0 or ::, at least one --allow-host value is required. Browser origins
are rejected unless explicitly allowed. Review recordings before sharing them.
pnpm verify
pnpm smoke:package
pnpm demo
pnpm benchmarkThe verification gate runs formatting, ESLint, strict TypeScript checks, unit and integration tests with coverage thresholds, and a clean build. Package smoke testing installs the generated tarball in a temporary consumer project and proxies a real request through the installed executable. Hosted CI runs that consumer and demo path on Linux, macOS, and Windows.
Benchmark methodology and versioned raw results are documented in docs/benchmarks.md. Results are treated as regression evidence, not production capacity claims.