Skip to content

Commit 9a362c2

Browse files
committed
Rebuild MCP Apps UI, expand docs, prepare 1.0.0 for NuGet
- MCP Apps UI: ext-apps 2, React 19, Vite 8, Tailwind 4; one folder per view, shared host connection, zod-validated tool outputs, failed/cancelled results shown in the view, dev sandbox left out of production bundles, per-entry or full builds; projects-dashboard sample with drill-down via list_projects (now returns structured content) - CI builds the UI and fails on stale ui_dist bundles - Docs: development guide index, testing guide, ADR-0004, structured content - Package: exclude .env and other local-only files from the nupkg, guard in CI and publish; NuGet description and tags - README rewritten for NuGet with screenshots; CHANGELOG as 1.0.0
1 parent 06edaf9 commit 9a362c2

43 files changed

Lines changed: 2814 additions & 243 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/publish.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ jobs:
2020
run: |
2121
VERSION="${GITHUB_REF_NAME#v}"
2222
dotnet pack TemplatePack.csproj -c Release -o artifacts -p:PackageVersion=$VERSION
23+
- name: Package contains no local-only files
24+
run: |
25+
if unzip -l artifacts/*.nupkg | awk '{print $4}' | grep -E '(^|/)\.env(\..+)?$' | grep -v '\.env\.example$'; then
26+
echo "::error::The package contains a .env file"; exit 1
27+
fi
2328
- name: Push to nuget.org
2429
run: >
2530
dotnet nuget push "artifacts/*.nupkg"

‎.github/workflows/template-ci.yml‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,31 @@ jobs:
3636
3737
- name: Pack the template
3838
run: dotnet pack TemplatePack.csproj -c Release -o artifacts
39+
40+
- name: Package contains no local-only files
41+
run: |
42+
if unzip -l artifacts/*.nupkg | awk '{print $4}' | grep -E '(^|/)\.env(\..+)?$' | grep -v '\.env\.example$'; then
43+
echo "::error::The package contains a .env file"; exit 1
44+
fi
45+
46+
# The template's MCP App views: typecheck, build, and check the committed bundles are current.
47+
ui:
48+
runs-on: ubuntu-latest
49+
defaults:
50+
run:
51+
working-directory: templates/DotnetMcpTemplate/ui
52+
steps:
53+
- uses: actions/checkout@v4
54+
- uses: actions/setup-node@v4
55+
with:
56+
node-version: 22
57+
- run: corepack enable
58+
- run: pnpm install --frozen-lockfile
59+
- run: pnpm run build
60+
- name: Committed bundles match the source
61+
run: |
62+
if ! git diff --quiet -- ../src/DotnetMcpTemplate/ui_dist; then
63+
git diff --stat -- ../src/DotnetMcpTemplate/ui_dist
64+
echo "::error::ui_dist/ is out of date. Run 'pnpm run build' in templates/DotnetMcpTemplate/ui and commit the result."
65+
exit 1
66+
fi

‎CHANGELOG.md‎

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,33 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [1.0.0] - 2026-09-27
44

5-
### Changed
6-
7-
- Folder layout: tools, resources and prompts live in `Capabilities/`; external API clients in `Integrations/`;
8-
plumbing in `Core/` (`ApiClients` base, `Auth`, `Mcp` with `Filters/` and `Apps/`, `Validation`, `Common`,
9-
`ServerInfo`). Namespaces follow folders and tests mirror the source tree. `AddApiClients` is now `AddIntegrations`.
10-
- `global.json` pins the .NET SDK (CI and Docker use it).
11-
- Watch mode: `dotnet watch` + `pnpm run watch` documented in getting-started; in dev, MCP App bundles are read
12-
from the project's `ui_dist/`, so rebuilt bundles are served without a restart.
13-
- Startup logs the public URL (`Server running at ...`) next to Kestrel's `http://[::]:port` bind address.
14-
- Consent page redesigned after the FastMCP OAuth proxy screen: server and client names, the redirect URI in a
15-
"Credentials will be sent to" box, collapsible advanced details (client id, scopes), Allow Access / Deny, a
16-
"Why am I seeing this?" tooltip and dark mode. The error page uses the same style.
17-
- Docs: index at `docs/README.md`, a scopes section and OIDC callback / discovery troubleshooting in the auth guide,
18-
and updated paths in every guide.
19-
- Scopes are defined in code (`Core/Auth/AppScopes.cs`) instead of `AUTH_SCOPES_SUPPORTED`; the dev user always has
20-
every scope and the `admin` role (`AUTH_DEV_USER_ROLES` removed).
21-
22-
### Fixed
23-
24-
- A client that requests no scope now gets no extra permissions instead of every scope.
25-
- The server listens on IPv4 and IPv6, so `localhost` works when it resolves to `::1`.
26-
- `Microsoft.Extensions.Http.Resilience` pinned to 10.9.0 (10.8.3 does not exist, NU1603).
27-
- CS0114 warning on `ApiClientException.Source`, and a unit test that did not compile.
28-
29-
## [1.0.0] - 2026-09-26
5+
First release.
306

317
### Added
328

33-
- MCP server on ASP.NET Core with ModelContextProtocol.AspNetCore 2.0 (stateless Streamable HTTP).
34-
- Tools, resources (templated, direct, MCP App `ui://`) and prompts, grouped by component, on a service layer.
35-
- Argument validation for tools and prompts (data annotations, nested objects, `IValidatableObject`).
36-
- Auth: OIDC proxy for any identity provider (DCR, consent, PKCE, ID token + userinfo merge, rotating refresh
37-
tokens, encrypted IdP tokens), `jwt` resource-server provider, extensible `IAuthProvider`, dev mode.
38-
- Typed API clients (`ApiClient` base) with env-configured auth, retries, circuit breaker, timeouts, correlation ids.
39-
- MCP Apps with the SDK extension, `ui/` React + Vite workspace and a placeholder bundle.
40-
- `.env` settings (DotNetEnv + validated typed settings), `APP_PORT`, minimal JSON server info at `/`.
41-
- Unit, integration (real MCP client, full OAuth flow against a fake IdP) and architecture tests.
42-
- Dockerfile (optional UI build stage), Compose, CI, ADRs and guides.
9+
- **MCP server** on ASP.NET Core (.NET 10) with ModelContextProtocol.AspNetCore 2.0: stateless Streamable HTTP;
10+
tools, resources (templated, direct, MCP App `ui://`) and prompts discovered from attributes; structured content.
11+
- **Layout:** `Capabilities/` (tools, resources, prompts), `Services/`, `Models/`, `Integrations/` (external API
12+
clients), `Core/` (auth, MCP setup and filters, validation, settings, errors, server info). Namespaces follow
13+
folders; tests mirror the source tree; architecture tests enforce the call direction and auth on every item.
14+
- **Validation** of every tool and prompt call (data annotations, nested objects, `IValidatableObject`), returned as
15+
errors the model can fix.
16+
- **Auth:** OIDC proxy for any OIDC identity provider (dynamic client registration, PKCE, ID token + userinfo merge,
17+
rotating refresh tokens, encrypted IdP tokens, memory or Redis store), consent page in the style of the FastMCP
18+
OAuth proxy, `jwt` resource-server provider, extensible `IAuthProvider`, dev mode. Scopes are defined in code
19+
(`Core/Auth/AppScopes.cs`); clients get only the scopes they request.
20+
- **MCP Apps UI** (`ui/`): ext-apps 2, React 19, Vite 8, Tailwind 4. One folder per view, shared host connection
21+
(`useToolOutput`, `useCallTool`, `useMcpApp`), host theme tokens, zod-validated tool outputs, failed / cancelled
22+
results shown in the view, dev sandbox with sample data (left out of production bundles), per-entry or full
23+
builds. Sample `projects-dashboard` with drill-down through `list_projects`.
24+
- **Typed API clients** (`ApiClient` base) with env-configured auth (`bearer`, `api_key`, `basic`,
25+
`client_credentials`, user token), retries, circuit breaker, timeouts, correlation ids.
26+
- **Settings:** `.env` (DotNetEnv) plus typed settings validated at startup; `APP_PORT`, `APP_URL`; dual-stack
27+
listening (`localhost`, `127.0.0.1`, `[::1]`); startup logs the public URL; minimal JSON server info at `/`.
28+
- **Dev loop:** `dotnet watch` and `pnpm run watch`; rebuilt MCP App bundles are served without a restart.
29+
- **Tests:** unit, integration (a real MCP client, including the full OAuth flow against a fake IdP) and
30+
architecture tests.
31+
- **Ship:** multi-stage Dockerfile (optional UI build), Compose, GitHub Actions (build, test, UI bundle check,
32+
Docker), `global.json`, central package management.
33+
- **Docs:** 10 guides indexed by task (`docs/development/README.md`) and 4 ADRs.

‎README.md‎

Lines changed: 117 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,143 @@
11
# .NET MCP Server Template
22

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+
[![NuGet](https://img.shields.io/nuget/v/Code4mk.McpServer.Template?logo=nuget&label=NuGet)](https://www.nuget.org/packages/Code4mk.McpServer.Template)
4+
[![Downloads](https://img.shields.io/nuget/dt/Code4mk.McpServer.Template?label=downloads)](https://www.nuget.org/packages/Code4mk.McpServer.Template)
5+
[![template-ci](https://github.com/code4mk/dotnet-mcp-server-template/actions/workflows/template-ci.yml/badge.svg)](https://github.com/code4mk/dotnet-mcp-server-template/actions/workflows/template-ci.yml)
6+
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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.
511

612
```bash
713
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
915
```
1016

11-
## What you get
17+
Built on the official [MCP C# SDK](https://github.com/modelcontextprotocol/csharp-sdk) 2.0 and .NET 10.
1218

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+
| | |
1438
| --- | --- |
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+
| ![MCP App: projects dashboard with drill-down](https://raw.githubusercontent.com/code4mk/dotnet-mcp-server-template/main/assets/mcp-app-dashboard.png) | ![Consent page shown before a new MCP client connects](https://raw.githubusercontent.com/code4mk/dotnet-mcp-server-template/main/assets/consent-page.png) |
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
2643

2744
```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
3151
```
3252

33-
## 2. Create a project
53+
`-n` is the .NET name (PascalCase), `-o` the folder. Namespaces, the solution, Docker names and GUIDs follow it.
3454

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.
3658

37-
```bash
38-
dotnet new code4mk-mcp -n NexusRE.Mcp -o nexusre-mcp
39-
```
59+
## What you get
4060

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
4375

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+
```
4590

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+
}
52106
```
53107

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
56109

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.
59113

60-
## Repository layout
114+
## Documentation
61115

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
68133
```
69134

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
71142

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

Comments
 (0)