Skip to content
Clean Model Context Protocol
Lean Mean Clean Apps

Five templates, one search box. Everything in the docs is in here, including the bits we hoped you'd skim.

Try one of these

Results say which template they came from, so you won't end up reading about Lambda when you wanted GraphQL. Again.

Run it locally

There are two ready-made environments. local is for dotnet run: sample data on, logs to the console. docker is for Docker Compose: sample data on, logs to Seq, database on a volume, and the MCP Inspector next door. Both are for your machine. Neither is for production, which is why neither is the default.

Terminal window
dotnet run --project ./src/Presentation

launchSettings.json sets the environment to local and listens on http://localhost:5032 and https://localhost:7032. The server is at /mcp.

Where What
/mcp The server. Streamable HTTP, stateless, JSON-RPC in and server-sent events out
/health/live Answers 200 while the process is up
/health/ready Answers 200 when the database can be reached
/.well-known/oauth-protected-resource/mcp Protected resource metadata, only when authentication is on
http://localhost:6274 The MCP Inspector, when running with Docker Compose
http://localhost:5341 Seq, when running with Docker Compose

The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.

The MCP Inspector is the official test client, and Docker Compose runs it already pointed at http://api:8080/mcp. Open it, press Connect, and:

  • Tools lists every tool with its description, annotations and input schema, and gives you a form to call it.
  • Resources lets you read server://version or any movies://, authors:// or reviews:// URI.
  • Prompts renders each prompt with its arguments, and the title and name arguments autocomplete from the database.

Running with dotnet run instead? Start the Inspector yourself with npx @modelcontextprotocol/inspector and point it at http://localhost:5032/mcp.

The server is stateless, so a single tools/call works without an initialize first. The sample data uses a fixed seed, so these ids are the same on every machine. See the tool reference for every argument and result.

Terminal window
# One movie, with every review
curl -X POST "http://localhost:5032/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}}}'
# Write a review
curl -X POST "http://localhost:5032/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}}}'
# Read a resource
curl -X POST "http://localhost:5032/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
Terminal window
dotnet test

Each test class builds its own in-memory SQLite database from the migrations and the same fixed seed, and the Presentation tests talk to the server through a real McpClient from the C# SDK, so they exercise the same protocol path as any other client. No test can change what another one sees, and nothing touches movie-reviews.db.

The Dockerfile has a stage for each job, so you can build only what you need.

Stage What it does
build Restores and builds the solution
test Runs every test with coverage
coverage Exports the coverage report to your machine
publish Publishes the Presentation project
run The image you deploy
Terminal window
docker build . --target coverage --output type=local,dest=out
Terminal window
docker build . --target run --tag acme-movies:latest \
--build-arg VERSION_PREFIX=1.2.3 --build-arg VERSION_SUFFIX=beta

The server://version resource and the serverInfo in the initialize response then report 1.2.3-beta and the commit it was built from.