|
1 | 1 | # .NET MCP Server Template |
2 | 2 |
|
3 | | -A `dotnet new` template for **Model Context Protocol servers** on ASP.NET Core (.NET 10) with the official |
4 | | -MCP C# SDK 2.0. |
| 3 | +[](https://www.nuget.org/packages/Code4mk.McpServer.Template) |
| 4 | +[](https://www.nuget.org/packages/Code4mk.McpServer.Template) |
| 5 | +[](https://github.com/code4mk/dotnet-mcp-server-template/actions/workflows/template-ci.yml) |
| 6 | +[](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/LICENSE) |
| 7 | + |
| 8 | +**A production-ready [Model Context Protocol](https://modelcontextprotocol.io) server for ASP.NET Core in one |
| 9 | +command.** Sign-in with any identity provider, interactive UIs inside the chat, typed API clients, tests, Docker and |
| 10 | +docs: the parts that take weeks are already done, so you write tools. |
5 | 11 |
|
6 | 12 | ```bash |
7 | 13 | dotnet new install Code4mk.McpServer.Template |
8 | | -dotnet new code4mk-mcp -n NexusRE.Mcp -o nexusre-mcp |
| 14 | +dotnet new code4mk-mcp -n Acme.Mcp -o acme-mcp |
9 | 15 | ``` |
10 | 16 |
|
11 | | -## What you get |
| 17 | +Built on the official [MCP C# SDK](https://github.com/modelcontextprotocol/csharp-sdk) 2.0 and .NET 10. |
12 | 18 |
|
13 | | -| Area | Included | |
| 19 | +## Why this template |
| 20 | + |
| 21 | +**Remote MCP servers need real OAuth, and that's the hard part.** MCP clients such as Claude expect OAuth 2.1 with |
| 22 | +dynamic client registration, PKCE, and protected resource metadata. Most identity providers (Entra ID, Google, |
| 23 | +Okta, ...) don't offer dynamic registration. This template ships an **OIDC proxy** that makes any OIDC provider work |
| 24 | +with three settings, including the consent screen, encrypted token storage, rotating refresh tokens and Redis for |
| 25 | +multiple instances. |
| 26 | + |
| 27 | +**Tools can show real UI, not just text.** [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) are wired |
| 28 | +end to end: React + Tailwind views that follow the host's theme, call your tools themselves (paging, drill-down), |
| 29 | +validate every response, and preview with sample data without a server. |
| 30 | + |
| 31 | +**The structure stays clean as it grows.** Tools → services → integrations, one direction, enforced by |
| 32 | +architecture tests: the build fails if a tool skips auth, calls an API directly, or leaks external models. |
| 33 | + |
| 34 | +**Errors the model can fix.** Every tool and prompt argument is validated before your code runs; the model gets a |
| 35 | +list like `request.durationDays: High-priority projects must be 90 days or less.` and retries correctly. |
| 36 | + |
| 37 | +| | | |
14 | 38 | | --- | --- | |
15 | | -| MCP | Tools, resources, prompts (all three), stateless Streamable HTTP, MCP Apps (`ui://` bundles) | |
16 | | -| Structure | `Capabilities/` (tools, resources, prompts), `Services/`, `Models/`, `Integrations/`; plumbing in `Core/` | |
17 | | -| Validation | Every tool/prompt call validated from data annotations; clear errors the model can fix | |
18 | | -| Auth | OIDC proxy for **any** OIDC identity provider (discovery URL + client id + optional secret + scopes), ID token + userinfo merged into one identity; `jwt` provider; custom providers via `IAuthProvider`; per-item `[Authorize]` / `[AllowAnonymous]`, scope and role policies | |
19 | | -| API clients | `ApiClient` base + one line per data source; auth, retries, circuit breaker, timeouts from `{PREFIX}_*` env vars | |
20 | | -| Settings | `.env` + validated typed settings (same pattern as dotnet-api-template), `APP_PORT`, `APP_URL` | |
21 | | -| Server info | `GET /` → `{ name, version, status, mcp }` | |
22 | | -| Tests | Unit, integration (real MCP client; full OAuth flow against a fake IdP), architecture rules | |
23 | | -| DevOps | Dockerfile (+ optional UI build), Compose, GitHub Actions, ADRs and guides | |
24 | | - |
25 | | -## 1. Install |
| 39 | +|  |  | |
| 40 | +| An MCP App: the tool's result as a dashboard; selecting an owner calls another tool from the view | Consent before a new MCP client connects, then sign-in at your identity provider | |
| 41 | + |
| 42 | +## Quick start |
26 | 43 |
|
27 | 44 | ```bash |
28 | | -dotnet new install Code4mk.McpServer.Template # from nuget.org |
29 | | -# or from source: |
30 | | -git clone https://github.com/code4mk/dotnet-mcp-server-template && dotnet new install ./dotnet-mcp-server-template/templates/DotnetMcpTemplate |
| 45 | +dotnet new install Code4mk.McpServer.Template |
| 46 | +dotnet new code4mk-mcp -n Acme.Mcp -o acme-mcp |
| 47 | +cd acme-mcp |
| 48 | +cp .env.example .env # set MCP_AUTH_MODE=none for a first run without an identity provider |
| 49 | +dotnet watch --project src/Acme.Mcp # → Server running at http://localhost:5080 |
| 50 | +npx @modelcontextprotocol/inspector # Streamable HTTP → http://localhost:5080/mcp |
31 | 51 | ``` |
32 | 52 |
|
33 | | -## 2. Create a project |
| 53 | +`-n` is the .NET name (PascalCase), `-o` the folder. Namespaces, the solution, Docker names and GUIDs follow it. |
34 | 54 |
|
35 | | -Run it outside this repository: |
| 55 | +**Connect a real identity provider:** register one web app with redirect URI `http://localhost:5080/oauth/callback`, |
| 56 | +then set `OIDC_DISCOVERY_URL`, `OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET` in `.env` and `MCP_AUTH_MODE=required`. |
| 57 | +Add the server in Claude (Settings → Connectors → Add custom connector) and it opens your login page. |
36 | 58 |
|
37 | | -```bash |
38 | | -dotnet new code4mk-mcp -n NexusRE.Mcp -o nexusre-mcp |
39 | | -``` |
| 59 | +## What you get |
40 | 60 |
|
41 | | -`-n` is the .NET name (PascalCase, no dashes), `-o` the folder / repo name. Every `DotnetMcpTemplate` becomes |
42 | | -`NexusRE.Mcp`; Docker names become `nexusre-mcp`; GUIDs are regenerated. |
| 61 | +| Area | Included | |
| 62 | +| --- | --- | |
| 63 | +| **MCP** | Tools, resources and prompts discovered from attributes; stateless Streamable HTTP (scales horizontally); annotations; structured content with output schemas | |
| 64 | +| **Auth** | OIDC proxy for any OIDC provider (Entra ID, Google, Auth0, Okta, Keycloak, Zitadel, ...): DCR, PKCE, consent, ID token + userinfo merged into one `AppUser`. Or the `jwt` provider, or your own `IAuthProvider`. `[Authorize]` per item, scope and role policies | |
| 65 | +| **MCP Apps** | `ui/` workspace (React 19, Vite, Tailwind 4, ext-apps 2): one bundle per view, host theme, tool calls from the view, zod-validated outputs, sandbox preview, per-entry builds | |
| 66 | +| **Validation** | Data annotations and `IValidatableObject` on every tool and prompt call, nested objects included | |
| 67 | +| **Integrations** | Typed API clients from one line of registration; retries, circuit breaker, timeouts; `bearer`, `api_key`, `basic`, `client_credentials`, or the signed-in user's token | |
| 68 | +| **Settings** | `.env` plus typed settings classes validated at startup, every problem listed by variable name | |
| 69 | +| **Dev loop** | `dotnet watch` with hot reload, `pnpm run watch` for views, dual-stack `localhost`, startup URL in the log | |
| 70 | +| **Tests** | Unit, integration (a real MCP client against the in-memory server, including the full OAuth flow against a fake IdP) and architecture rules: 64 tests out of the box | |
| 71 | +| **Ship** | Multi-stage Dockerfile, Compose, GitHub Actions (build, test, UI bundle check, Docker), pinned SDK, central package versions | |
| 72 | +| **Docs** | 10 guides organized by task, 4 architecture decision records | |
| 73 | + |
| 74 | +## Project layout |
43 | 75 |
|
44 | | -## 3. Run |
| 76 | +```text |
| 77 | +acme-mcp/ |
| 78 | +├── src/Acme.Mcp/ |
| 79 | +│ ├── Capabilities/ Tools/, Resources/, Prompts/: what the server exposes (thin) |
| 80 | +│ ├── Services/ Business logic |
| 81 | +│ ├── Models/ Request / response DTOs |
| 82 | +│ ├── Integrations/ One folder per external API |
| 83 | +│ ├── Core/ Auth, MCP setup and filters, validation, settings, errors: plumbing you rarely touch |
| 84 | +│ └── ui_dist/ Built MCP App bundles |
| 85 | +├── ui/ MCP App views (React + Vite + Tailwind) |
| 86 | +├── tests/ Unit, Integration, Architecture (mirror src/) |
| 87 | +├── docs/ Guides and ADRs |
| 88 | +└── docker/ Dockerfile, Compose |
| 89 | +``` |
45 | 90 |
|
46 | | -```bash |
47 | | -cd nexusre-mcp |
48 | | -cp .env.example .env # set MCP_AUTH_MODE=none for a first run without an IdP |
49 | | -dotnet test |
50 | | -dotnet run --project src/NexusRE.Mcp |
51 | | -npx @modelcontextprotocol/inspector # Streamable HTTP → http://localhost:5080/mcp |
| 91 | +Adding a tool is one class; there is no registration line: |
| 92 | + |
| 93 | +```csharp |
| 94 | +[McpServerToolType] |
| 95 | +[Authorize] |
| 96 | +public sealed class InvoiceTools(IInvoiceService invoices) |
| 97 | +{ |
| 98 | + [McpServerTool(Name = "get_invoice", ReadOnly = true, UseStructuredContent = true)] |
| 99 | + [Description("Gets an invoice by number.")] |
| 100 | + public Task<InvoiceDto> GetInvoice( |
| 101 | + [Required, RegularExpression("^INV-[0-9]{6}$")] string number, |
| 102 | + AppUser user, |
| 103 | + CancellationToken cancellationToken) => |
| 104 | + invoices.GetAsync(number, user, cancellationToken); |
| 105 | +} |
52 | 106 | ``` |
53 | 107 |
|
54 | | -Then connect your identity provider: register one app with redirect URI `${APP_URL}/oauth/callback` and set |
55 | | -`OIDC_DISCOVERY_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_SCOPES`. Full guides are in each project's `docs/`. |
| 108 | +## Works with |
56 | 109 |
|
57 | | -Hidden files (`.env.example`, `.gitignore`, `.github/`, `.template.config/`) start with a dot: show hidden files |
58 | | -in Finder (`Cmd+Shift+.`) or Explorer. |
| 110 | +- **Clients:** Claude (claude.ai and Desktop), VS Code, Cursor, MCP Inspector, and any client that speaks Streamable HTTP with OAuth. |
| 111 | +- **Identity providers:** anything with an OpenID Connect discovery URL. |
| 112 | +- **Hosting:** anywhere a container runs; set `APP_URL` behind a reverse proxy. |
59 | 113 |
|
60 | | -## Repository layout |
| 114 | +## Documentation |
61 | 115 |
|
62 | | -```text |
63 | | -dotnet-mcp-server-template/ |
64 | | -├── README.md, CHANGELOG.md, LICENSE |
65 | | -├── TemplatePack.csproj packs the template (dotnet pack) |
66 | | -├── .github/workflows/ template-ci (create + build + test a project), publish (nuget.org on tags) |
67 | | -└── templates/DotnetMcpTemplate/ the template |
| 116 | +Each generated project includes its docs. Browse them on GitHub: |
| 117 | + |
| 118 | +- [Development guides](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/templates/DotnetMcpTemplate/docs/development/README.md): getting started, configuration, tools, validation, MCP Apps, API clients, auth, testing, connecting clients |
| 119 | +- [MCP Apps UI workspace](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/templates/DotnetMcpTemplate/ui/README.md) |
| 120 | +- [Architecture decisions](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/templates/DotnetMcpTemplate/docs/README.md) |
| 121 | +- [Changelog](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/CHANGELOG.md) |
| 122 | + |
| 123 | +**Requirements:** .NET 10 SDK. Optional: Docker; Node 22+ and pnpm for MCP App views. |
| 124 | + |
| 125 | +## Contributing |
| 126 | + |
| 127 | +Issues and pull requests are welcome. To work on the template itself: |
| 128 | + |
| 129 | +```bash |
| 130 | +git clone https://github.com/code4mk/dotnet-mcp-server-template |
| 131 | +dotnet new install ./dotnet-mcp-server-template/templates/DotnetMcpTemplate --force |
| 132 | +dotnet new code4mk-mcp -n Sample.Mcp -o ../sample-mcp # always create projects outside the repo |
68 | 133 | ``` |
69 | 134 |
|
70 | | -## Maintaining |
| 135 | +- `DotnetMcpTemplate` (and `dotnetmcptemplate-slug`) are placeholders; keep them. Package versions live in |
| 136 | + `templates/DotnetMcpTemplate/Directory.Packages.props`. |
| 137 | +- CI creates a project from the template, builds and tests it, checks the UI bundles, and packs the template. |
| 138 | +- Release: update `CHANGELOG.md`, then `git tag v1.1.0 && git push origin v1.1.0`; the publish workflow pushes to |
| 139 | + nuget.org (secret `NUGET_API_KEY`). |
| 140 | + |
| 141 | +## License |
71 | 142 |
|
72 | | -- Keep `DotnetMcpTemplate` (and `dotnetmcptemplate-slug`) as placeholders. Never create projects inside this repo. |
73 | | -- Versions live in `templates/DotnetMcpTemplate/Directory.Packages.props`. |
74 | | -- Test: `dotnet new install ./templates/DotnetMcpTemplate --force`, create a project elsewhere, `dotnet build && dotnet test`. |
75 | | -- Publish: update CHANGELOG, `git tag v1.1.0 && git push origin v1.1.0` (needs secret `NUGET_API_KEY`), or manually: |
76 | | - `dotnet pack TemplatePack.csproj -c Release -o artifacts -p:PackageVersion=1.1.0` then `dotnet nuget push`. |
| 143 | +[MIT](https://github.com/code4mk/dotnet-mcp-server-template/blob/main/LICENSE) © Code4mk |
0 commit comments