Documentation
¶
Overview ¶
Package harness provides a unified TransportHarness interface that lets one test body exercise stdio, legacy HTTP, streamable HTTP, and gRPC in parallel. Each factory binds an OS-assigned port (or bufconn for gRPC) so tests can run with t.Parallel() without colliding on fixed ports.
The harness is test-only; it is NOT imported by production code.
Index ¶
- Variables
- func BlockingTool(name string, started chan<- struct{}) mcp.ToolDescriptor
- func ContainsTool(tools []map[string]any, name string) bool
- func IsNotFound(err *RPCError) bool
- func MockTool(name string, handler mcp.ToolHandler) mcp.ToolDescriptor
- func ProtocolVersion(resp Response) (string, error)
- func ToolName(m map[string]any) string
- func ToolsFromListResult(resp Response) ([]map[string]any, error)
- func WaitForHTTP200(ctx context.Context, url string) error
- type Factory
- type Options
- type RPCError
- type Response
- type ServerSharer
- type Transport
Constants ¶
This section is empty.
Variables ¶
ErrGRPCUnavailable signals that the harness was compiled without the grpc build tag; callers should skip gRPC-specific tests rather than fail.
Functions ¶
func BlockingTool ¶
func BlockingTool(name string, started chan<- struct{}) mcp.ToolDescriptor
BlockingTool returns a ToolDescriptor whose handler signals start on the given channel, then blocks until its context is cancelled. The handler returns the context's error, so a successful cancellation surfaces as an RPC error with code=-32603 (internal error carrying context.Canceled) or an isError=true result. Used by cancellation parity tests to prove that Cancel() / ctx cancellation actually aborts in-flight work rather than sitting there until the per-tool 45s timeout.
func ContainsTool ¶
ContainsTool returns true iff the decoded tools list includes one whose name equals the given value.
func IsNotFound ¶
IsNotFound returns true when the error message matches the common JSON-RPC "method not found" / "tool not found" shapes the transports emit. Used by parity tests that check invalid-params behaviour without hardcoding error-code numbers.
func MockTool ¶
func MockTool(name string, handler mcp.ToolHandler) mcp.ToolDescriptor
MockTool is a convenience constructor for tests that need a minimal ToolDescriptor with a fixed handler.
func ProtocolVersion ¶
ProtocolVersion unmarshals an initialize Response.Result and extracts the protocolVersion string.
func ToolName ¶
ToolName extracts the "name" field from a tools/list result.tools[] entry decoded into a generic map. Returns "" if the shape is unexpected.
func ToolsFromListResult ¶
ToolsFromListResult decodes a tools/list Response.Result into a flat list of tool descriptors (each a generic map) for assertions.
Types ¶
type Factory ¶
Factory constructs a Transport for a given Options; used by the parity matrix so one test can iterate factories without knowing their types.
type Options ¶
type Options struct {
// MaxMessageSize, if >0, configures the transport's size cap and the
// server's MaxMessageSize. Zero leaves both at their default (4 MiB).
MaxMessageSize int64
// BearerToken, if non-empty, enables static-bearer auth on transports
// that require it (legacy HTTP, streamable HTTP, gRPC). stdio ignores
// this field.
BearerToken string
// Tools lists the ToolDescriptors the mock server should expose. Nil
// means the harness registers only the default mock_tool.
Tools []mcp.ToolDescriptor
// Enforcement, if non-nil, is installed on the mock server. Most harness
// tests leave this nil; parity tests that exercise protocol-level
// validation can opt in without rebuilding each transport fixture.
Enforcement mcp.Enforcement
}
Options controls per-harness construction. Defaults are chosen to match the production defaults (4 MiB size cap, stdio-style single-user tools).
type RPCError ¶
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
RPCError is the JSON-RPC 2.0 error envelope.
type Response ¶
type Response struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
}
Response is the decoded JSON-RPC envelope returned by harness operations. Result is kept as raw JSON so each test can unmarshal into whatever struct it cares about; tests that only want a sanity check can compare against Error == nil.
type ServerSharer ¶
type ServerSharer interface {
}
ServerSharer is an optional interface implemented by transports whose underlying mcp.Server is reachable from the test body — used for firing server-initiated notifications in parity tests. stdio and streamable HTTP implement it directly; legacy HTTP and gRPC return (nil, false) so tests can skip.
type Transport ¶
type Transport interface {
Name() string
Initialize(ctx context.Context) (Response, error)
ListTools(ctx context.Context) (Response, error)
CallTool(ctx context.Context, name string, args map[string]any) (Response, error)
CallToolAsync(ctx context.Context, name string, args map[string]any) (requestID int, done <-chan Response, err error)
Cancel(ctx context.Context, requestID int) error
// Notifications returns a channel of server→client frames (for
// transports that support them: stdio, streamable HTTP, gRPC).
// Legacy HTTP returns nil — callers must handle the nil case
// explicitly rather than blocking forever on <-nil.
Notifications() <-chan Response
// SendRaw delivers arbitrary bytes through the transport's framing
// layer without wrapping them in a JSON-RPC envelope and returns
// the decoded server reply. Intended for malformed-JSON / parse-
// error boundary tests; not for normal call flow.
//
// Line-delimited transports (stdio, gRPC) send the bytes verbatim
// and read the next anonymous error frame. HTTP transports POST
// the bytes as the request body and decode the HTTP response. Only
// one SendRaw at a time per harness instance — serialisation is
// enforced internally.
SendRaw(ctx context.Context, frame []byte) (Response, error)
MaxSupportedSize() int64
Close() error
}
Transport is the contract every transport adapter satisfies. Callers issue Initialize → ListTools → CallTool and optionally Cancel; tests that need to assert server-initiated notifications read from Notifications(). MaxSupportedSize is what the transport was configured to enforce — tests use it to compute at-limit / over-limit boundaries without hard-coding numbers.
func NewGRPC ¶
NewGRPC is a stub for non-grpc builds. The real implementation lives in grpc.go behind -tags=grpc. Parity tests skip gRPC when this factory returns ErrGRPCUnavailable.
func NewLegacyHTTP ¶
NewLegacyHTTP builds a harness over the legacy POST-only /mcp transport (mcp.Server.ServeHTTPListener). Each call is a single HTTP POST; there is no server→client notification channel, so Notifications() returns nil.
func NewStdio ¶
NewStdio constructs a TransportHarness wrapping an mcp.Server.Run loop communicating over an in-memory io.Pipe pair. The harness owns both the server goroutine and the response reader; Close terminates both.
Request IDs are monotonic atomic counters; tests that need to pair a response with its request use the ID from CallToolAsync.
func NewStreamable ¶
NewStreamable builds a harness over the MCP Streamable HTTP 2025-03-26 transport. POST /mcp delivers requests; GET /mcp opens the long-lived SSE stream on which the server emits responses and notifications.
This adapter:
- opens an ephemeral TCP listener (127.0.0.1:0)
- creates a memory control-plane store
- establishes an initialize round-trip to capture the session ID
- opens the SSE subscription
- parses SSE frames into Response values and routes them by request ID (or onto the notifications channel for server-initiated frames)