C++ SDK
Source: github.com/Eigenwise/tesseron-cpp
The C++ SDK lives in tesseron-cpp and builds one static library, tesseron::tesseron. Your application binds a loopback WebSocket, writes an instance manifest, and the MCP gateway dials in. There is no port to configure and no gateway address to point at.
Consume it from source through CMake's FetchContent, linking tesseron::tesseron. See Install & build for the declaration. The SDK also fetches its own dependencies.
What it covers
Section titled “What it covers”The whole host half of protocol 1.2.0:
- The handshake, claiming, and session resume with token rotation.
- Action invocation with input validation, cancellation, and a per-action timeout.
- Streaming progress, clamped and monotonic.
- Resource reads and subscriptions, with a teardown that runs on unsubscribe and on a closing transport.
ActionContextround trips back into the agent:sample,confirm,elicit, andlog.- The v2 instance manifest, written after the URL is known and removed on shutdown,
0700on its directory and0600on the file where the platform has them.
All four Capabilities flags are declared. Host-minted claim codes are the one thing left out: the gateway mints the code, and a restarted process is a new session.
A host, end to end
Section titled “A host, end to end”The shipped todo example registers the same camelCase action names used by the other SDK examples:
void register_actions(tesseron::HostBuilder& builder, const std::shared_ptr<TodoState>& state) { builder.action("addTodo") .description("Add one todo") .input(tesseron::schema::object({ tesseron::schema::required("text", tesseron::schema::string().min_length(1)), tesseron::schema::optional("tag", tesseron::schema::string()), })) .output_schema(todo_output_schema()) .handler([state](Json input, ActionContext) -> boost::asio::awaitable<Result<Json>> { const auto tag = optional_string(input, "tag"); if (!tag.ok()) co_return tag.error(); Todo todo = state->create(input.at("text").get<std::string>(), tag.value()); state->publish(); co_return todo_payload(todo); });}A complete app creates a HostBuilder, registers the actions and resources, then calls listen():
auto state = std::make_shared<TodoState>();auto builder = tesseron::Host::builder();builder.application("cpp_todo", "C++ Todo");builder.on_event([](const tesseron::HostEvent& event) { if (event.kind == tesseron::HostEvent::Kind::Welcome && event.welcome.has_value() && event.welcome->claim_code.has_value()) { std::cout << "Claim code: " << *event.welcome->claim_code << std::endl; }});register_actions(builder, state);register_resource(builder, state);
auto listening = builder.listen();if (!listening.ok()) { std::cerr << "tesseron-example-todo: " << listening.error().message() << "\n"; return 1;}auto host = std::move(listening).value();Register the event listener before listen(). The gateway can dial and finish the handshake before listen() returns, and a listener installed afterwards misses the welcome that carries the claim code.
Run the examples
Section titled “Run the examples”From the tesseron-cpp repository root, configure and build the two shipped apps:
cmake -S . -B build -G Ninja -DTESSERON_BUILD_EXAMPLES=ONcmake --build build --target tesseron-example-todo tesseron-example-promptsRun the built examples with the gateway installed, then claim the printed code from your MCP client. The example guide covers both apps.
Three things that will surprise you
Section titled “Three things that will surprise you”Boost.Asio is a public dependency. A handler is a C++20 coroutine returning boost::asio::awaitable<tesseron::Result<tesseron::Json>>, so anything that links this library sees Asio's headers. That is deliberate: a hand-rolled coroutine type would have to be re-taught every executor, timer, and cancellation trick Asio already knows, and would not compose with the Asio code a real application already has. Boost.Beast, which implements the WebSocket listener, stays private.
Nothing throws across a handler boundary. Every fallible call answers Result<T> or Result<T, HostError>, so the error type is part of the signature instead of something a caller has to guess at. A handler that throws anyway is caught and answered as -32603 with the cause logged locally, never sent.
The host binds loopback only. HostOptions::bind_address set to anything outside 127.0.0.0/8 or ::1 is a HostError before a socket opens. The gateway runs on the same machine by design; there is no configuration that turns this into a network service.
Threading
Section titled “Threading”One host owns one boost::asio::io_context and one thread to run it. Every handler, reader, and subscriber runs on that thread, so a co_await yields rather than blocking the read loop. See Threading for the application dispatcher and UI handoff.
ResourceEmitter::emit is safe from any thread and hops onto the host's thread before touching the subscription. ActionContext::on_application_thread hands work to the application dispatcher and resumes the handler on the host's thread.