Skip to main content

AI

Run MCP Server in Docker

July 26, 2025 · 15 min read

Hero image for Run MCP Server in Docker

Docker is deployment, not transport. Containers package the same MCP server you run on the host; the client still speaks stdio over the container’s stdin and stdout. This guide takes the Echo stdio server from Building MCP Server Using .NET, adds a production-ready Dockerfile, and shows how to build, run, and test it with Claude Desktop.

For remote HTTP-based MCP (Streamable HTTP or legacy SSE), containerize a web API entrypoint and expose a port—see Build MCP Server with Streamable HTTP (recommended) or Build MCP Server with SSE Transport (legacy).

Key Takeaways

  • Claude Desktop can launch MCP servers via docker run with stdio attached.
  • Use -i, pass secrets with env vars, and keep stdout reserved for MCP protocol traffic.
  • Pin image versions and keep containers ephemeral with --rm where appropriate.
  • HTTP MCP runs as a long-lived container; stdio MCP runs as a spawned process.
  • Prebuilt images and patterns live on mcpsolutions.dev for common integrations.

Prerequisites

Project recap

You should already have a working stdio server from the .NET tutorial. The pieces that matter for Docker:

Console app project

Stdio MCP servers run as a console app, not a web API. Create the project with:

dotnet new console -n MyMcpServer
cd MyMcpServer

If you started from dotnet new webapi, switch to a console template or a new console project—stdio transport does not need Kestrel or HTTP listeners.

Required NuGet packages

dotnet add package Microsoft.Extensions.Hosting
dotnet add package ModelContextProtocol

Restore locally to confirm the project compiles: dotnet restore && dotnet build.

  • Transport: WithStdioTransport() — no HTTP port, no MapMcp() required for stdio-only
  • Tools: MyTools with an Echo tool the client can call
  • Logging: write diagnostics to stderr only. stdout carries the JSON-RPC protocol stream; anything else on stdout (including Console.WriteLine) breaks MCP inside and outside Docker

In Program.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateEmptyApplicationBuilder(settings: null);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

await app.RunAsync();

From the project root (MyMcpServer/), confirm the server runs locally before containerizing:

dotnet run

Stop it with Ctrl+C once verified. The MCP client (or Claude) will launch the process and attach to stdio—you do not browse to a URL.

Write the Dockerfile

Place a Dockerfile in the project root (same folder as MyMcpServer.csproj). This multi-stage build caches NuGet restores and produces a small runtime image. There is no EXPOSE — stdio servers do not listen on a port.

# Use the official .NET Core SDK image as the base image
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

# Copy project files
COPY . ./app

# Publish app to out folder
WORKDIR /app
RUN dotnet publish -c Release -o out

# Build the runtime image
FROM mcr.microsoft.com/dotnet/runtime:10.0-alpine AS runtime
WORKDIR /app
COPY --from=build /app/out .

# Entry point when the container starts
ENTRYPOINT ["dotnet", "stdio-mcp-dotnet-docker.dll"]

If your project file has a different name, replace MyMcpServer.csproj and MyMcpServer.dll accordingly.

Build the image

From the directory that contains the Dockerfile:

docker build -t my-mcp-server:latest .

Run and test (without Claude)

Stdio MCP requires an interactive container so the client can write JSON-RPC to stdin and read responses from stdout.

docker run -i --rm my-mcp-server:latest

Manual JSON-RPC test: with the container running, paste this line into the terminal and press Enter:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

You should see a JSON response on stdout listing available tools (for example Echo). If nothing appears, the server may require an initialize request first—see the MCP specification. Use stderr for any debug output so stdout stays valid JSON-RPC.

Flag Role
-i Keep stdin open — required for JSON-RPC
--rm Remove the container when the client exits

Smoke test: the container should start and wait (no immediate exit, no crash loop). That means the process is listening on stdio for MCP messages.

Common failures:

  • Missing -i — client cannot send requests; connection fails silently or immediately
  • Wrong image tagdocker images and align the tag with your config
  • Logs on stdout — use stderr for logging; polluted stdout corrupts the protocol
  • Image not rebuilt — old tool definitions after code changes

Test with Claude Desktop

Claude Desktop spawns the MCP server as a subprocess. For Docker, the subprocess is docker run with stdio attached to the container.

Prerequisites

  • Claude Desktop installed
  • Docker running
  • Image built: my-mcp-server:latest

Configure Claude Desktop

Edit claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "my-mcp-server:latest"]
    }
  }
}

The -i flag in args is mandatory — without it, JSON-RPC cannot reach the server.

Step-by-step

  1. Build the image: docker build -t my-mcp-server:latest .
  2. Add the config above to claude_desktop_config.json
  3. Fully quit Claude Desktop and relaunch (config is read at startup)
  4. Open Claude and check the MCP/tools indicator (hammer icon) — my-mcp-server should appear as connected
  5. Send a test prompt, for example: "Use the echo tool to repeat the phrase hello docker"
  6. Confirm Claude calls Echo and returns the echoed text

Verify and troubleshoot

  • Claude logs: Help → View Logs (or Developer settings) — look for Docker or MCP connection errors
  • Docker not running: start Docker Desktop before launching Claude
  • Image missing: run docker images and confirm my-mcp-server is listed
  • Stale config: quit Claude completely (not just close the window), then relaunch
  • Forgot -i: connection fails; ensure "args" includes -i before the image name
  • Old image: rebuild after code changes, then restart Claude
  • Protocol errors: check that the app logs only to stderr, not stdout

FAQ

How do I run an MCP server in Docker for Claude Desktop?

Publish your .NET MCP server as a self-contained or framework-dependent image, then reference docker run in Claude Desktop config with stdio attached (-i) and environment variables for secrets. The desktop client spawns the container as a subprocess the same way it would a local binary. Keep images small and pin versions for reproducibility.

What Docker flags does stdio MCP require?

Interactive stdio requires -i (keep stdin open) and often --rm for ephemeral containers. Pass env vars with -e or --env-file. Do not daemonize the server process inside the container—the client expects to own the process lifetime. Log to stderr; stdout is reserved for MCP protocol messages.

Can Docker MCP servers use HTTP instead of stdio?

Yes—run Streamable HTTP inside the container, publish ports, and point remote clients at the HTTPS URL. Claude Desktop stdio mode specifically expects a spawned process with piped stdin/stdout; team-wide HTTP deployment uses a long-running container behind a load balancer instead.

Where can I find prebuilt MCP server images?

The mcpsolutions.dev catalog lists packaged MCP servers and integration patterns for common business systems. You can also build custom images following the stdio or HTTP guides in this Learn series when off-the-shelf servers do not match your domain.

From AI licenses to measurable impact

Licenses are table stakes. Tell me where adoption stalls - we turn fragmented AI usage into measured gains and an operating model that scales.

Get Your AI Adoption Score

3 minutes · Instant result · No sales call required