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.

1 Create 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)

2 Write 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();
    }

3 Build 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`.

1 Build with HTTP enabled

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

2 HTTP 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;
    }

3 HTTP 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.

1 Define 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)

2 Register 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`.

1 Server 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();
    }

2 Client with bearer token

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

3 Upgrade 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.

1 Server 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();
    }

2 Client 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.

1 Register 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