C++ is where the world's data infrastructure lives — databases, game engines, embedded devices, trading systems, HPC. Agents increasingly need to reach into those systems, and MCP is the protocol everyone is standardizing on. The official SDK roster lists ten languages: TypeScript, Python, C#, Go, Rust, Ruby, Java, Swift, PHP, Kotlin. C++ — the language those systems are written in — is not one of them. So we wrote mcp-cpp: a C++17 SDK targeting the 2026-07-28 specification. No async runtime. No OpenSSL either — the crypto (SHA-256/HMAC, CSPRNG) is in-tree. One bundled JSON header, one optional HTTP library. find_package(mcp CONFIG) and it's in your build. This post is about what it is, what the official conformance suite taught us — and one bug we found in the harness itself. What implementing the 2026-07-28 spec actually means The current revision is a big departure from the 2025-era model: no initialize handshake, no session ids. Every request carries its protocol version and client capabilities in params._meta; servers advertise themselves via server/discover; the server never initiates a request — when it needs more input it answers with an input_required result and the client retries (MRTR); live updates flow through subscriptions/listen. mcp-cpp implements all of it: Server: Builder with tools/resources/prompts (including RFC 6570-style URI templates), capability gating, MRTR with a signed requestState codec (HMAC-SHA256, context-bound, TTL — in-tree SHA-256, no crypto dependency), progress notifications, cancellation, CacheableResult. Client: blocking and value-semantic, MRTR auto-fulfil (callToolWithInput), and a full OAuth 2.1 client — protected-resource metadata discovery (RFC 9728), AS metadata (RFC 8414 + OIDC), DCR (RFC 7591), Client ID Metadata Documents (SEP-991), PKCE (S256) from a CSPRNG, RFC 8707 resource, RFC 9207 iss validation, scope step-up, SEP-2352 migration. Transports: stdio and Streamable HTTP on both sides — mirrored headers (MCP-Protocol-Version, Mcp-Method, Mcp-Name, Mcp-Param-*) validated with -32020 enforcement, SSE responses, a GET listening stream for legacy servers, Last-Event-ID reconnect honoring the retry: field. Validation: a JSON Schema 2020-12 validator (local $ref, resource bounds) enforcing inputSchema/outputSchema on every call. A complete server in 15 lines: #include #include int main() { mcp::server::Builder b; b.name("my-server").version("1.0"); b.tool(mcp::Json(R"({"name":"add","description":"adds two integers", "inputSchema":{"type":"object","properties":{"a":{"type":"integer"}, "b":{"type":"integer"}},"required":["a","b"]}})"), [](mcp::server::Ctx& c) -> mcp::McpResult { const mcp::Json args = mcp::member(c.params, "arguments"); const int64_t sum = mcp::member(args, "a").toInt64() + mcp::member(args, "b").toInt64(); mcp::Json out(R"({})"); out.add("sum", mcp::Json(sum)); return mcp::Ok(mcp::server::structuredResult(out, "sum = " + std::to_string(sum))); }); mcp::StdioTransport t; return b.build()->run(t).has_value() ? 0 : 1; } Enter fullscreen mode Exit fullscreen mode The proof: the official conformance suite We didn't self-certify parity — we ran it against modelcontextprotocol/conformance (v0.2.0-alpha.12), the same suite the official SDKs use, for both spec revisions and both roles: Role 2026-07-28 2025-11-25 Server 42/42 checks · 20/20 scenarios (100%) 26/30 (see below) Client 32/32 scenarios · 399/399 checks (100%), including all 25 auth/* OAuth 2.1 + CIMD scenarios 17/18 (see below) Every check that doesn't pass is baselined with an explanation: The four 2025 server failures (tools-call-sampling, tools-call-elicitation, elicitation-sep1034-defaults, elicitation-sep1330-enums) all require the server to send a request to the client mid-call — the mechanism 2026-07-28 replaced with MRTR. Implementing a removed mechanism for a 2025-only scenario is a deliberate non-goal. The one 2025 client failure is a harness bug — see next section. What the suite caught (the honest part) Running the official harness against a C++ SDK found five real bugs in days. Each is in the commit history with a named spec clause: Notifications emitted during a request were silently dropped over HTTP — the transport kept only the last line of the response. Progress and logging on Streamable HTTP didn't exist. Fix: stream multi-message responses as SSE. resultType was injected on legacy responses — pre-2026 schemas reject unknown fields. A 2026-correct response was a 2025 violation. JSON-RPC 2.0 allows "id": null on error responses — 2025-era peers send it; our strict parser rejected it and the client hung until timeout instead of surfacing the error. The Mcp-Session-Id from initialize raced the next request — stateful 2025 servers answered 400 Invalid or missing session ID. The response is now held until the id is captured. An empty string is a valid mirrored header value — Mcp-Param-X: must be sent, not deleted. Our setHeader(name, "") semantics were wrong, and the suite's http-custom-headers scenario (18 checks) proved it. Plus one we found in their mock: the elicitation-sep1034-client-defaults scenario's mock never delivers the server→client elicitation — its GET stream only answers keepalives, and the tool call never completes. We didn't shrug: we built the official rust-sdk reference client in the same environment and ran the same scenario — it fails identically (0/5, and crashes with 0xC0000005). A mock defect, not an SDK defect. We filed it upstream (conformance#557) and baseline it with that evidence until it's fixed. Design notes (C++ flavor) C++17, not 20/23. The audience is existing C++ infrastructure, and GCC 9 and MSVC v142 are everywhere. Conditional std::expected aliasing gives modern compilers the standard types without splitting the build matrix. Value-semantic, exception-free protocol layer. McpResult, [[nodiscard]], nothing throws between transport and handler. Zero crypto dependency. HMAC-SHA256 and the CSPRNG (PKCE verifiers, OAuth state) are in-tree — RtlGenRandom on Windows, /dev/urandom on POSIX. Single-threaded, stateless dispatch — one connection, one server, no locks except the subscription fan-out. For an ORM/database MCP server this is the right shape: handlers are the slow part, not the loop. Docs bilingual by default — English primary, full zh-CN siblings. Who's using it This SDK is the protocol layer behind zorm-mcp — a production MCP server exposing 13 structured database tools over sqlite3/jsonfile/mysql/postgres/dm8 through one dialect-free surface — and its ORM, zorm. Real users, five backends, one bug tracker. Roadmap and the ask Next up: OAuth server-side enforcement, the logging helper, a zero-copy parse pass (raw parse is currently slower than serde_json and we say so in the README), vcpkg/Conan ports, Doxygen. Try it: git clone + cmake --preset clang-rlz — or find_package(mcp) after cmake --install. The CI matrix covers Linux/macOS/Windows × gcc/clang/MSVC plus ASan/UBSan and conformance. File issues. Even "the docs are wrong about X". If you agree C++ belongs on the official MCP SDK roster, say so where it counts — the MCP SDK Working Group decides the roster, and our proposal to it is the next step in this plan. Conformance scores, a CI-green repo, real users and a dependency policy are ready. Repo: https://github.com/zhoutk/mcp-cpp · MIT · conformance reports in-tree (2026-07-28 server).
There is no official C++ SDK for MCP. So we wrote one — and ran it through the official conformance suite.
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.