Documentation
¶
Overview ¶
Command mcpfixture is a deterministic MCP upstream fixture for the release QA gate (Spec 081, FR-006/FR-007). One binary serves the same two tools over three transports so gate matrix cells never depend on third-party services:
mcpfixture --transport stdio # newline-delimited JSON-RPC on stdin/stdout mcpfixture --transport http --port 18080 # streamable-http at http://127.0.0.1:18080/mcp mcpfixture --transport sse --port 18081 # legacy SSE at http://127.0.0.1:18081/sse (+ POST /message)
Transports are served by mark3labs/mcp-go's server package — the same library whose CLIENT side mcpproxy pins (internal/upstream/core uses transport.CreateSSEClient / NewStreamableHTTP / NewStdio), so the wire contract is protocol-correct by construction instead of hand-rolled:
- sse: GET /sse opens the event stream and announces the message endpoint ("endpoint" event, relative URL resolved against the client's base URL); requests are POSTed to /message?sessionId=… and responses stream back as SSE "message" events.
- http: POST /mcp single-shot JSON responses (StreamableHTTPServer).
- stdio: newline-delimited JSON-RPC frames (StdioServer).
Tools (deterministic, FR-007c):
- echo — returns the call arguments back as JSON text: {"echo": {...arguments...}}
- ping — returns {"message":"pong","counter":N,"instance_id":"…"}; the counter increases monotonically per process and the instance_id is random per process start, so kill/restart tests (FR-007d) can prove the reconnected upstream is a NEW instance (counter reset, new id).
SIGTERM/SIGINT exits cleanly with code 0 on every transport (docker stop sends SIGTERM to PID 1, and the gate kills fixtures between matrix steps).
Docker cell (FR-009) argv composition, verified against internal/upstream/core/isolation.go + connection_docker.go:
server config: command="/mcpfixture", args=["--transport","stdio"],
isolation.image="mcpfixture:gate"
DetectRuntimeType(filepath.Base("/mcpfixture")) → "binary" (default case)
TransformCommandForContainer(binary) → command+args passed through unchanged
GetDockerImage → buildFullImageName("mcpfixture:gate") → "docker.io/library/mcpfixture:gate"
(no slash ⇒ registry+library prefix; Docker normalizes local short tags
the same way, so a locally built `mcpfixture:gate` resolves without a pull)
final argv: docker run --rm -i --name mcpproxy-<server>-<rand> <labels>
[--log-opt …] [--network <mode>] [limits] [-e K=V …]
docker.io/library/mcpfixture:gate /mcpfixture --transport stdio
Because the container command is always appended explicitly after the image, the image must NOT set an ENTRYPOINT (it would prefix the argv and break `/mcpfixture --transport stdio`); it ships CMD ["/mcpfixture","--transport","stdio"] as a default for bare `docker run` instead. See cmd/mcpfixture/Dockerfile and scripts/gate/build-fixture-image.sh.