Deployment Guide

Deploying an MCP server into a production environment requires careful consideration of transport selection, lifecycle management, and operational security. This guide covers the essential patterns for deploying MCP servers built with the mcp-cpp-sdk.

Overview

When moving from development to production, you must decide how your server will communicate with clients. The Model Context Protocol supports several transport mechanisms:

  • Stdio: Best for local integration with desktop applications (like Claude Desktop or IDEs).

  • HTTP/SSE: Recommended for remote deployments, multi-client support, or web-based integrations.

Key considerations for deployment include:

  • Choosing the right transport for your use case.

  • Implementing graceful shutdown to protect in-flight requests.

  • Integrating with system process managers (like systemd or Docker).

  • Monitoring and health checks.

HTTP Server Setup

The mcp-cpp-sdk provides two ways to set up an HTTP server: a high-level convenience API and a manual setup for fine-grained control.

Convenience API (run_http)

For most deployments, the mcp::Server::run_http() method is the simplest approach. It manages the io_context, sets up the mcp::HttpServerTransport, and handles signal mapping automatically.

std::thread server_thread([&server, http_port]() {
    try {
        // This convenience method handles everything:
        // - Creates io_context
        // - Creates HttpServerTransport
        // - Calls transport->listen()
        // - Calls server.run(transport, executor)
        // - Manages signal handling for graceful shutdown
        server.run_http("127.0.0.1", http_port);
    } catch (const std::exception& e) {
        std::cerr << "Server error: " << e.what() << '\n';
    }
});

constexpr auto startup_delay = std::chrono::milliseconds(200);

Manual HTTP Setup

If you need to integrate the MCP server into an existing Boost.Asio application or require custom middleware, you can set up the transport manually.

boost::asio::io_context io_ctx;
auto transport = std::make_shared<mcp::HttpServerTransport>(
    io_ctx.get_executor(), "0.0.0.0", 8080);

// Spawn the transport listener
boost::asio::co_spawn(io_ctx, transport->listen(), boost::asio::detached);

// Run the MCP server over the transport
boost::asio::co_spawn(io_ctx, server.run(transport, io_ctx.get_executor()),
                      boost::asio::detached);

io_ctx.run();

Graceful Shutdown

In production, servers are frequently restarted or moved. Graceful shutdown ensures that the server finishes processing active requests before exiting, preventing data loss or client-side errors.

Signal Handling

You should handle signals like SIGINT and SIGTERM to trigger the shutdown process. The SDK provides mcp::graceful_shutdown() in mcp/core/signal.hpp to close the transport and stop the io_context after in-flight work finishes or times out.

asio::co_spawn(
    io_ctx,
    [&, transport]() -> Task<void> {
        asio::steady_timer trigger(io_ctx.get_executor());
        trigger.expires_after(std::chrono::seconds(1));
        co_await trigger.async_wait(asio::use_awaitable);
        std::cerr << "[Main] Initiating graceful shutdown\n";
        input_buffer.close();
        graceful_shutdown(io_ctx, transport);
        std::cerr << "[Main] Graceful shutdown initiated (timeout: "
                  << constants::g_shutdown_timeout_ms << "ms)\n";
    },
    asio::detached);

The mcp::graceful_shutdown() function closes the transport (preventing new connections) and starts a watchdog timer. If requests don’t finish within the timeout (defined by mcp::constants::g_shutdown_timeout_ms), it stops the io_context forcefully.

Production Considerations

Port Binding and Security

  • Binding: Use 127.0.0.1 if the server only needs to be accessed by a local proxy (like Nginx) or 0.0.0.0 to listen on all interfaces.

  • TLS/SSL: The built-in HTTP transport does not handle TLS. In production, always place your MCP server behind a reverse proxy like Nginx, Apache, or a cloud load balancer that handles SSL termination.

  • Authentication: For sensitive tools, implement authentication middleware or use the OAuth flow patterns demonstrated in the oauth_flow.cpp example.

Unix Daemon / Service Setup

Running your server as a background process (daemon) ensures it stays alive and restarts on failure.

systemd Integration

On Linux, systemd is the standard way to manage services. Using a system service ensures that your MCP server starts automatically on boot and recovers from crashes.

Create a service file at /etc/systemd/system/mcp-server.service:

[Unit]
Description=MCP C++ Server
After=network.target

[Service]
Type=simple
User=mcpuser
Group=mcpuser
WorkingDirectory=/opt/mcp-server
ExecStart=/opt/mcp-server/bin/my_mcp_server
Restart=on-failure
RestartSec=5s
Environment=PORT=8080
# Recommended: limit privileges
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full

[Install]
WantedBy=multi-user.target

After creating the file, reload the systemd manager and enable the service:

sudo systemctl daemon-reload
sudo systemctl enable mcp-server
sudo systemctl start mcp-server

Docker Deployment

Containerization provides a consistent environment for your MCP server, making it easier to deploy across different cloud providers. Use a multi-stage Dockerfile to keep the production image small and secure.

# Build stage
FROM gcc:12 AS builder
RUN apt-get update && apt-get install -y cmake libboost-all-dev
COPY . /src
WORKDIR /src
RUN mkdir build && cd build && \
    cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF && \
    make -j$(nproc)

# Production stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y libboost-dev && \
    rm -rf /var/lib/apt/lists/*

# Create a non-root user
RUN useradd -m mcpuser
USER mcpuser

COPY --from=builder /src/build/bin/my_mcp_server /usr/local/bin/
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/my_mcp_server"]

When running in Docker, ensure you map the ports correctly and provide any necessary configuration via environment variables.

Load Balancing and Scaling

As your MCP application grows, you might need to scale horizontally by running multiple instances.

Sticky Sessions

Since MCP is stateful over the course of a session (initialization, followed by multiple requests), ensure your load balancer uses sticky sessions (session affinity). If a client’s request is routed to a different instance that hasn’t seen the initialize request, the server will reject the request.

Health Checks

For HTTP deployments, you can implement a health check endpoint. While the MCP protocol doesn’t define a standard health check, you can add a simple tool or a parallel HTTP route to verify the server’s status.

  • Liveness: Verify the process is running and the io_context event loop is responsive.

  • Readiness: Verify that any external resources (databases, APIs, or model endpoints) that the tools depend on are currently available.

Monitoring and Logging

Production servers should emit structured logs for troubleshooting.

  • Standard Streams: In Stdio mode, avoid writing anything to stdout except MCP messages. Use stderr for all logging.

  • Structured Logging: When using HTTP, consider using a logging library that outputs JSON for better integration with log aggregators like ELK or Datadog.

Example Walkthrough

The http_server_convenience.cpp example provides a complete, runnable demonstration of an HTTP-based deployment using the high-level API.

const auto http_port = probe_acceptor.local_endpoint().port();
probe_acceptor.close();

std::cout << "Starting HTTP server on port " << http_port << " (convenience method)\n";
std::cout << "Server will run in background thread\n";

// Spawn server in background thread
std::thread server_thread([&server, http_port]() {
    try {
        // This convenience method handles everything:
        // - Creates io_context
        // - Creates HttpServerTransport
        // - Calls transport->listen()
        // - Calls server.run(transport, executor)
        // - Manages signal handling for graceful shutdown
        server.run_http("127.0.0.1", http_port);
    } catch (const std::exception& e) {
        std::cerr << "Server error: " << e.what() << '\n';
    }

For advanced users, graceful_shutdown.cpp demonstrates how to build a robust server that respects system signals and manages the connection lifecycle manually. This is particularly useful when you need to coordinate the shutdown of multiple services or databases alongside the MCP server.

                               {"required", nlohmann::json::array({"value"})}};

server.add_tool("quick_task", "A quick task that completes immediately", quick_schema,
                [](const nlohmann::json& args) -> nlohmann::json {
                    std::cerr << "[Server] quick_task called\n";
                    int value = args.at("value").get<int>();
                    CallToolResult result;
                    TextContent content;
                    content.text = "Quick task result: " + std::to_string(value * 2);
                    result.content.emplace_back(std::move(content));
                    return nlohmann::json(result);
                });

// Tool: slow_task — simulates work with delay
nlohmann::json slow_schema = {{"type", "object"},
                              {"properties", {{"duration_ms", {{"type", "integer"}}}}},
                              {"required", nlohmann::json::array({"duration_ms"})}};

server.add_tool<nlohmann::json, nlohmann::json>(
    "slow_task", "A task that takes time to complete", slow_schema,
    [](nlohmann::json args) -> Task<nlohmann::json> {
        std::cerr << "[Server] slow_task called\n";
        int duration_ms = args.at("duration_ms").get<int>();

        // Simulate work with async delay
        asio::steady_timer work_timer(
            asio::get_associated_executor(co_await asio::this_coro::executor));
        work_timer.expires_after(std::chrono::milliseconds(duration_ms));
        co_await work_timer.async_wait(asio::use_awaitable);

        std::cerr << "[Server] slow_task completed after " << duration_ms << "ms\n";
        CallToolResult result;
        TextContent content;

Cross-References

  • See Transports for more details on HTTP and Stdio transport specifics.

  • See Error Handling for patterns on managing production errors and logging exceptions.