github.com/skosovsky/toolsy/mcp is a strict MCP client and protocol bridge. It supports exactly 2026-07-28; there is no legacy negotiation, fallback, compatibility mode or MCP server implementation.
transport := mcp.NewStdioTransport("my-mcp-server", nil)
// or: transport := mcp.NewStreamableHTTPTransport("https://example.com/mcp")
client, err := mcp.Connect(ctx, transport)
if err != nil {
return err
}
defer client.Close()Connect sends server/discover first and accepts the server only when supportedVersions contains exact 2026-07-28. The discovery result includes capabilities, extensions, cache metadata and optional server identity from result _meta. initialize and notifications/initialized do not exist in this implementation, and a failed discovery never falls back to them.
Every request carries the protocol version, actual client capabilities and client identity in reserved _meta.io.modelcontextprotocol/* fields. Caller Meta.Extra cannot create a MetaObject key whose second DNS label is mcp or modelcontextprotocol; the protocol builder exclusively owns those namespaces. Duplicate JSON keys and malformed tagged unions fail closed.
Set RequestMeta.LogLevel to opt a single request into bounded notifications/message diagnostics; no session-wide logging state exists. ExtensionRegistry and ExtensionCodec are the BYO-types boundary for typed extension payloads. Vendor IDs and official capability extension IDs such as io.modelcontextprotocol/* may be registered. Unknown declarations remain lossless and inert; registration does not advertise or enable a capability by itself. logging, completions and experimental advertisements are likewise inert data and expose no legacy runtime API.
Every wire result requires resultType. complete is returned as a method-specific typed result. input_required is accepted only from tools/call, resources/read and prompts/get and is surfaced distinctly; the library does not automatically answer or retry MRTR rounds. A host-driven retry uses a fresh request ID, current-round inputResponses and the server's byte-exact opaque requestState. Use CallTool, ReadResourceRound and GetPromptRound for explicit rounds; convenience APIs return InputRequiredError rather than hiding interim results.
Discovery and cacheable list/read results expose typed ttlMs and cacheScope (public or private). Missing, null, fractional, negative or unknown values are protocol errors. Cache hints do not enable hidden caching or refresh.
Use ComputeSnapshotDigest when a host needs a stable identity for a discovery, tools, resources, resource-templates, resource-read or prompts snapshot. It first runs the strict wire encoder, rejects encodings above 8 MiB before canonical decoding, then hashes a bounded canonical JSON representation with explicit version and snapshot-type domain separation. The digest includes ttlMs, cacheScope, ordered list entries and all lossless wire metadata; pointer and value forms are identical. SnapshotDigest.String returns the 64-character lowercase SHA-256 hex value.
Tool schemas default to JSON Schema 2020-12 when $schema is absent and respect explicitly supported dialects. Local $ref is supported with bounded evaluation; arbitrary network fetch is not. structuredContent may be any JSON value. Content and resource unions, binary Base64 data and output schemas are validated symmetrically.
Streamable HTTP uses one endpoint and POST only. Each request includes:
MCP-Protocol-Version: 2026-07-28;Mcp-Method, equal to the JSON-RPC method;Mcp-Nameexactly fortools/call,prompts/getandresources/read, derived fromparams.nameorparams.uriso its decoded value matches the body value;Mcp-Param-*for a present non-null tool argument whose static top-level property has a validx-mcp-headerannotation.
x-mcp-header supports string, integer and boolean; integers must fit the JSON safe-integer range. Header suffixes are non-empty RFC 9110 tchar strings and case-insensitively unique. A malformed annotation removes that tool from a tools/list snapshot without discarding valid siblings. Unsafe values and values resembling the sentinel are UTF-8/Base64 encoded as =?base64?{value}?=. Base64 provides no confidentiality.
Responses may be terminal JSON or request-scoped SSE. Cancelling an HTTP request closes that request's response stream; it does not send notifications/cancelled. A broken stream is not resumed or automatically retried. Sessions, Mcp-Session-Id, GET polling, DELETE-on-close, Last-Event-ID and SSE redelivery are absent. A correlated HTTP 400 JSON-RPC HeaderMismatch (-32020) is returned as a typed protocol error.
The request decorator is for authentication and trace headers. It cannot replace protocol-derived Mcp-*/Mcp-Param-*, method, URL, Host or body fields. The transport retains SSRF-safe dialing and redirects, bounded responses and secret-safe diagnostics.
Stdio uses one JSON-RPC message per line and sends server/discover first. Writes are serialized; stdout is protocol-only and stderr is bounded logging. After a request reaches the wire, context cancellation sends notifications/cancelled with the exact raw request ID. Cancellation before delivery sends no notification. Process failure unblocks all waiters, and Close terminates the complete child process tree.
This is intentionally different from HTTP cancellation: stdio has no per-request response stream, while HTTP does.
Use subscriptions/listen for list changes and resource updates. The first SSE message must acknowledge the subscription with the effective filter and _meta.io.modelcontextprotocol/subscriptionId; notifications before acknowledgment are rejected. Later notifications must match both the active subscription ID and effective filter. The library does not silently reconnect a failed listen stream. Discovery generation counters remain authoritative if a bounded consumer channel overflows.
builder := toolsy.NewRegistryBuilder()
for proxy, err := range client.GetTools(ctx) {
if err != nil {
return err
}
builder.Add(proxy)
}
registry, err := builder.Build()inputSchema maps to ToolManifest.Parameters; outputSchema maps to ToolManifest.OutputSchema. isError: true becomes a remote execution error, distinct from JSON-RPC, schema and transport errors. Annotations remain hints, not authorization policy.
The clear break removes initialize DTOs/lifecycle, WithClientRoots, WithRoots, roots handlers, logging/setLevel, base ping, ErrSessionExpired, server-request dispatch, resource subscribe/unsubscribe, session/GET/resume internals and every older protocol revision. No deprecated aliases are provided.
See migration-task34.md for the full migration checklist.
I/O, pagination, schema composition and diagnostics remain bounded. Context cancellation takes precedence over transport/read-limit mapping. Errors do not expose authorization/cookie headers, full binary blocks or unbounded bodies.
The Contract-First test anchor is the byte-exact official 2026-07-28 schema at testdata/task34/schema/mcp-2026-07-28.schema.json, pinned to upstream commit 271ecc9accafdd9b83a3c869fa67c22953b2af80 with SHA-256 ef70b61f99b6d2e5e3b46863822eab08dff6a45bedc7a08914e0e5b133f40203. Its provenance manifest cites all 19 wire fixtures, and make task34-preflight verifies the trust anchor without network access.
go test -race ./...
make test
make lint
make task34-preflight
make release-break is the actual destructive release workflow: after lint, tests and preflight it may create a release commit, create tags and push tags. Run it only when intentionally publishing the clear break.