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).
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
Related reading
- Building MCP Server Using .NET — project setup, tools, and stdio transport
- What is the MCP Protocol? — transports, Claude config, and Docker vs stdio
- Build MCP Server with Streamable HTTP — recommended HTTP-based remote MCP
- Build MCP Server with SSE Transport — legacy HTTP+SSE