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.

Secure the server

Out of the box the server runs wide open. Anyone who can reach /mcp can call every tool, including the one that deletes reviews. That’s lovely on a laptop and less lovely in production, so here’s how to close the door.

  1. Tell the server who issues tokens and who they’re for. Either bake it in when you create the solution:

    Terminal window
    dotnet new cleanmodelcontextprotocol --name Acme.Movies \
    --authority https://login.example.com --audience https://movies.example.com/mcp

    Or set it on a solution you already have, in appsettings.json or as environment variables:

    Terminal window
    Authentication__Authority=https://login.example.com
    Authentication__Audience=https://movies.example.com/mcp
  2. List the scopes a client should ask for, if your authorization server uses them.

    {
    "Authentication": {
    "Authority": "https://login.example.com",
    "Audience": "https://movies.example.com/mcp",
    "Scopes": ["movies.read", "movies.write"]
    }
    }
  3. Get a token from the authority for that audience, and send it.

    Terminal window
    curl -X POST "http://localhost:5032/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
    -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

    Without a token, or with one from the wrong issuer or for the wrong audience, the server answers 401 Unauthorized with a WWW-Authenticate header that points at the metadata below.

With authentication on, the server publishes protected resource metadata at /.well-known/oauth-protected-resource/mcp:

{
"resource": "https://movies.example.com/mcp",
"authorization_servers": ["https://login.example.com"],
"scopes_supported": ["movies.read", "movies.write"],
"resource_name": "Clean Model Context Protocol"
}

A client that gets a 401 reads it, discovers the authorization server, and runs the OAuth flow itself. Claude, VS Code and the Inspector all do this, so for the user it’s a sign-in screen and then everything works. Your server never sees a password and never stores a secret. Tokens are validated against the authority’s published keys.

/health/live and /health/ready answer without a token, so load balancers and curious humans can still check the lights are on. Everything under /mcp needs one: tools, resources, prompts and completions alike.

AuthenticationSettings reads the Authentication section. When the authority is set, the server registers the JWT bearer handler for authentication, the MCP handler for the challenge and the metadata, and puts RequireAuthorization() on the /mcp endpoint. When it isn’t, none of that is registered and /mcp is mapped as it is. The tools don’t know either way, which is why turning authentication on doesn’t touch a single one.