Tutorials

Build your first MCP server and client in C++.

Each tutorial walks through a complete working example. Copy-paste the code, build it, and run it.

Tutorial 1: Minimal Stdio Server

Build an MCP server that communicates over stdin/stdout. This is the simplest transport — ideal for CLI tools and local integrations.

1Create the CMake project

cmake_minimum_required(VERSION 3.16)
project(my-mcp-server LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)

find_package(cxxmcp CONFIG REQUIRED)
add_executable(server server.cpp)
target_link_libraries(server PRIVATE cxxmcp::server)

2Write the server

// server.cpp
#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

using Json = mcp::protocol::Json;

int main() {
    return mcp::ServerPeer::builder()
        .name("my-first-server")
        .version("1.0.0")
        .stdio()
        .tool<Json, Json>("greet",
            [](const Json& input) {
                auto name = input.value("name", "world");
                return Json{{"message", "Hello, " + name + "!"}};
            })
        .run();
}

3Build and test

cmake -S . -B build
cmake --build build
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | ./build/server
Tip: The .run() call builds, serves, and blocks until shutdown — all in one line. For more control, use .build() + mcp::serve() separately.

Tutorial 2: HTTP Server and Client

Serve MCP over Streamable HTTP. Requires CXXMCP_ENABLE_HTTP=ON.

1Build with HTTP enabled

cmake -S . -B build \
  -DCXXMCP_ENABLE_HTTP=ON \
  -DCXXMCP_BUILD_SERVER=ON \
  -DCXXMCP_BUILD_CLIENT=ON
cmake --build build

2HTTP server

// http_server.cpp
#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

using Json = mcp::protocol::Json;

int main() {
    auto server = mcp::ServerPeer::builder()
        .name("http-demo")
        .version("1.0.0")
        .streamable_http("127.0.0.1", 3000, "/mcp")
        .tool<Json, Json>("echo",
            [](const Json& in) { return in; })
        .build();

    auto running = mcp::serve(std::move(*server));
    running->wait_until_ready();
    return running->wait().has_value() ? 0 : 1;
}

3HTTP client

// http_client.cpp
#include <iostream>
#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

using Json = mcp::protocol::Json;

int main() {
    return mcp::ClientPeer::builder()
        .streamable_http("http://127.0.0.1:3000/mcp")
        .run([](auto& svc) {
            svc.peer().initialize();
            auto result = svc.peer().call_tool(
                "echo", Json{{"value", "hello"}});
            std::cout << result->dump(2) << std::endl;
        });
}

Tutorial 3: Type-Safe Tools with Reflection

Use CXXMCP_REFLECT to get automatic JSON serialization and schema generation for your tool arguments and results.

1Define your types

#include <string>
#include <vector>
#include <cxxmcp/protocol/reflect.hpp>

struct SearchArgs {
    std::string query;
    int limit = 10;
};

struct SearchResult {
    std::string title;
    std::string url;
    double score;
};

struct SearchResponse {
    std::vector<SearchResult> results;
    int total;
};

CXXMCP_REFLECT(SearchArgs, query, limit)
CXXMCP_REFLECT(SearchResult, title, url, score)
CXXMCP_REFLECT(SearchResponse, results, total)

2Register the typed tool

#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

int main() {
    return mcp::ServerPeer::builder()
        .name("search-server")
        .version("1.0.0")
        .stdio()
        .tool(mcp::server::tool<SearchArgs, SearchResponse>("search")
                  .description("Search documents by query.")
                  .handler([](SearchArgs args,
                              const mcp::server::ToolContext& ctx) {
                      SearchResponse resp;
                      resp.total = 1;
                      resp.results.push_back(SearchResult{
                          .title = "Result for: " + args.query,
                          .url = "https://example.com/1",
                          .score = 0.95,
                      });
                      return resp;
                  }))
        .run();
}
What happens automatically:
  • inputSchema is generated from SearchArgs fields
  • Input JSON is deserialized into SearchArgs with type checking
  • SearchResponse is serialized back to JSON content blocks
  • Compile-time validation via CXXMCP_REFLECT_CHECK(Type, N)

Tutorial 4: Adding Authentication

Protect your HTTP server with bearer token auth. Requires CXXMCP_ENABLE_AUTH=ON.

1Server with bearer auth

#include <memory>
#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

using Json = mcp::protocol::Json;

int main() {
    // Create an auth provider with static tokens
    auto auth = std::make_unique<mcp::server::StaticBearerAuthProvider>();
    auth->add_token("secret-token-123",
                    mcp::server::AuthIdentity{
                        "alice",
                        {{"role", "admin"}},
                    });

    return mcp::ServerPeer::builder()
        .name("auth-demo")
        .version("1.0.0")
        .auth_provider(std::move(auth))
        .streamable_http("127.0.0.1", 3001, "/mcp")
        .tool(mcp::server::tool<Json, Json>("whoami")
                  .description("Return the authenticated user.")
                  .handler([](const Json&,
                              const mcp::server::ToolContext& ctx) {
                      return Json{
                          {"subject",
                           ctx.auth_identity
                               ? ctx.auth_identity->subject
                               : "anonymous"},
                      };
                  }))
        .run();
}

2Client with bearer token

auto client = mcp::ClientPeer::builder()
    .streamable_http("http://127.0.0.1:3001/mcp")
    .bearer_token("secret-token-123")
    .build();

3Upgrade to DPoP with OpenSSL

cmake -S . -B build \
  -DCXXMCP_ENABLE_AUTH=ON \
  -DCXXMCP_AUTH_CRYPTO=OpenSSL

#include <cxxmcp/auth/openssl/server_auth_provider.hpp>

mcp::auth::DpopAuthProviderOptions opts;
opts.require_dpop = true;
opts.issuer = "https://auth.example.com";
opts.audience = "https://resource.example/mcp";

auto auth =
    std::make_unique<mcp::auth::openssl::StaticJwksDpopBearerAuthProvider>(
        std::move(jwks), &replay_cache, opts);

Tutorial 5: Async Tasks with Progress

Run long-running operations as background tasks with progress reporting and cancellation support.

1Server with task support

#include <cxxmcp/peer.hpp>
#include <cxxmcp/run.hpp>

using Json = mcp::protocol::Json;

int main() {
    return mcp::ServerPeer::builder()
        .name("task-demo")
        .version("1.0.0")
        .stdio()
        .task_manager(mcp::server::TaskOperationProcessorOptions{
            .worker_count = 2,
            .queue_size = 16,
        })
        .tool(mcp::server::tool<Json, Json>("process")
                  .description("Process data asynchronously.")
                  .task_support(mcp::protocol::TaskSupport::Optional)
                  .handler([](const Json& args,
                              const mcp::server::ToolContext& ctx) {
                      // The tool runs in a background task
                      // Progress is reported via ctx
                      return Json{{"status", "done"}};
                  }))
        .run();
}

2Client calling a task

svc.peer().initialize();

// Start an async task
auto task = svc.peer().call_tool(
    "process", Json{{"data", "large-dataset"}});

// Poll for completion
auto status = svc.peer().get_task(task->task_id);
while (status->status != mcp::protocol::TaskStatus::Completed) {
    // Check progress, handle cancellation
    status = svc.peer().get_task(task->task_id);
}

// Cancel if needed
svc.peer().cancel_task(task->task_id);

Tutorial 6: Resources and Prompts

Expose data as resources and reusable prompt templates alongside your tools.

1Register resources and prompts

return mcp::ServerPeer::builder()
    .name("full-demo")
    .version("1.0.0")
    .stdio()

    // Static resource
    .resource("file:///data/config.json",
              [] {
                  return mcp::protocol::ResourceContents{
                      .uri = "file:///data/config.json",
                      .mime_type = "application/json",
                      .text = R"({"debug": false})",
                  };
              })

    // Dynamic resource with context
    .resource("file:///data/session.txt",
              [](std::string uri,
                 const mcp::server::ResourceContext& ctx) {
                  return ctx.session_id + " @ " + uri;
              })

    // Resource template
    .resource_template("file:///data/{path}",
                       [] {
                           return mcp::protocol::ResourceTemplate{
                               .uri_template = "file:///data/{path}",
                               .name = "Data files",
                           };
                       })

    // Prompt with arguments
    .prompt(mcp::protocol::Prompt{
                .name = "summarize",
                .description = "Summarize text",
                .arguments = {mcp::protocol::PromptArgument{
                    .name = "text",
                    .description = "Text to summarize",
                    .required = true,
                }},
            },
            [](const mcp::server::PromptContext& ctx) {
                auto text = ctx.arguments.at("text").get<std::string>();
                mcp::protocol::PromptsGetResult result;
                result.messages.push_back(mcp::protocol::PromptMessage{
                    .role = "user",
                    .content = mcp::protocol::ContentBlock{
                        .type = "text",
                        .text = "Summarize: " + text,
                    },
                });
                return result;
            })

    .run();

Next Steps

  • Concepts — understand Peer/Service, Transports, and Capabilities
  • Cookbook — quick copy-paste code for common tasks
  • Auth — full OAuth 2.1 / DPoP documentation
  • API Reference — generated symbol-level docs