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
- Completed Building MCP Server Using .NET — project
MyMcpServer,WithStdioTransport(), and theEchotool inMyTools - Docker Desktop or Docker Engine installed and running
- Optional: What is the MCP Protocol? — stdio transport and how clients spawn servers
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, noMapMcp()required for stdio-only - Tools:
MyToolswith anEchotool 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 tag —
docker imagesand 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
- Build the image:
docker build -t my-mcp-server:latest . - Add the config above to
claude_desktop_config.json - Fully quit Claude Desktop and relaunch (config is read at startup)
- Open Claude and check the MCP/tools indicator (hammer icon) —
my-mcp-servershould appear as connected - Send a test prompt, for example: "Use the echo tool to repeat the phrase hello docker"
- Confirm Claude calls
Echoand 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 imagesand confirmmy-mcp-serveris listed - Stale config: quit Claude completely (not just close the window), then relaunch
- Forgot
-i: connection fails; ensure"args"includes-ibefore 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.
