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
.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();
}
inputSchemais generated fromSearchArgsfields- Input JSON is deserialized into
SearchArgswith type checking SearchResponseis 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