Skip to content

Commit ed72994

Browse files
committed
Initial commit: code4mk MCP server template
ASP.NET Core MCP server template (official C# SDK 2.0): tools, resources, prompts, MCP Apps, OIDC proxy auth for any IdP, typed API clients, validation, Docker, tests and docs. - Folder layout: Capabilities/ (tools, resources, prompts), Services/, Models/, Integrations/ (external API clients), Core/ (plumbing) - Scopes defined in code; clients get only the scopes they request - Dual-stack listening (localhost, 127.0.0.1, [::1]); startup logs the URL - Consent page redesigned after the FastMCP OAuth proxy screen - Watch mode for local development; global.json pins the SDK
0 parents  commit ed72994

154 files changed

Lines changed: 6853 additions & 0 deletions

File tree

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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: publish
2+
3+
# Publishes to nuget.org on tags like v1.2.0. Needs the repository secret NUGET_API_KEY (glob Code4mk.*).
4+
on:
5+
push:
6+
tags: ["v*.*.*"]
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-dotnet@v4
17+
with:
18+
dotnet-version: 10.0.x
19+
- name: Pack
20+
run: |
21+
VERSION="${GITHUB_REF_NAME#v}"
22+
dotnet pack TemplatePack.csproj -c Release -o artifacts -p:PackageVersion=$VERSION
23+
- name: Push to nuget.org
24+
run: >
25+
dotnet nuget push "artifacts/*.nupkg"
26+
--source "https://api.nuget.org/v3/index.json"
27+
--api-key "${{ secrets.NUGET_API_KEY }}"
28+
--skip-duplicate

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: template-ci
2+
3+
# Proves the template works: install it, create a project, build and test that project.
4+
on:
5+
push:
6+
branches: [main]
7+
pull_request:
8+
9+
jobs:
10+
verify:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-dotnet@v4
15+
with:
16+
dotnet-version: 10.0.x
17+
18+
- name: Install template from source
19+
run: dotnet new install ./templates/DotnetMcpTemplate
20+
21+
- name: Create a sample project
22+
run: dotnet new code4mk-mcp -n Sample.Mcp -o "$RUNNER_TEMP/sample-mcp"
23+
24+
- name: Build and test the sample project
25+
working-directory: ${{ runner.temp }}/sample-mcp
26+
run: |
27+
dotnet build -c Release
28+
dotnet test -c Release --no-build
29+
30+
- name: Check that no placeholder names are left
31+
working-directory: ${{ runner.temp }}/sample-mcp
32+
run: |
33+
if grep -rIi --exclude-dir=bin --exclude-dir=obj "dotnetmcptemplate" .; then
34+
echo "Found leftover placeholder"; exit 1
35+
fi
36+
37+
- name: Pack the template
38+
run: dotnet pack TemplatePack.csproj -c Release -o artifacts

‎.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Bb]in/
2+
[Oo]bj/
3+
.vs/
4+
.idea/
5+
*.user
6+
*.nupkg
7+
artifacts/
8+
TestResults/
9+
node_modules/
10+
.DS_Store

‎CHANGELOG.md‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Changelog
2+
3+
## [Unreleased]
4+
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
30+
31+
### Added
32+
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.

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Code4mk
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# .NET MCP Server Template
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.
5+
6+
```bash
7+
dotnet new install Code4mk.McpServer.Template
8+
dotnet new code4mk-mcp -n NexusRE.Mcp -o nexusre-mcp
9+
```
10+
11+
## What you get
12+
13+
| Area | Included |
14+
| --- | --- |
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
26+
27+
```bash
28+
dotnet new install Code4mk.McpServer.Template # from nuget.org
29+
# or from source:
30+
git clone https://github.com/code4mk/dotnet-mcp-template && dotnet new install ./dotnet-mcp-template/templates/DotnetMcpTemplate
31+
```
32+
33+
## 2. Create a project
34+
35+
Run it outside this repository:
36+
37+
```bash
38+
dotnet new code4mk-mcp -n NexusRE.Mcp -o nexusre-mcp
39+
```
40+
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.
43+
44+
## 3. Run
45+
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
52+
```
53+
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/`.
56+
57+
Hidden files (`.env.example`, `.gitignore`, `.github/`, `.template.config/`) start with a dot: show hidden files
58+
in Finder (`Cmd+Shift+.`) or Explorer.
59+
60+
## Repository layout
61+
62+
```text
63+
dotnet-mcp-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
68+
```
69+
70+
## Maintaining
71+
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`.

‎TemplatePack.csproj‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<!-- Packs templates/DotnetMcpTemplate into a NuGet template package: dotnet pack TemplatePack.csproj -->
4+
<PropertyGroup>
5+
<PackageType>Template</PackageType>
6+
<PackageId>Code4mk.McpServer.Template</PackageId>
7+
<PackageVersion>1.0.0</PackageVersion>
8+
<Title>Code4mk MCP Server</Title>
9+
<Authors>Code4mk</Authors>
10+
<Description>ASP.NET Core MCP server template (official C# SDK 2.0): tools, resources, prompts, MCP Apps, OIDC proxy auth for any IdP, typed API clients, validation, Docker, tests and docs.</Description>
11+
<PackageTags>dotnet-new;templates;mcp;model-context-protocol;aspnetcore;oidc</PackageTags>
12+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
13+
<PackageProjectUrl>https://github.com/code4mk/dotnet-mcp-template</PackageProjectUrl>
14+
<RepositoryUrl>https://github.com/code4mk/dotnet-mcp-template</RepositoryUrl>
15+
<PackageReadmeFile>README.md</PackageReadmeFile>
16+
17+
<TargetFramework>net10.0</TargetFramework>
18+
<IncludeContentInPack>true</IncludeContentInPack>
19+
<IncludeBuildOutput>false</IncludeBuildOutput>
20+
<ContentTargetFolders>content</ContentTargetFolders>
21+
<NoDefaultExcludes>true</NoDefaultExcludes>
22+
<EnableDefaultItems>false</EnableDefaultItems>
23+
<NoWarn>$(NoWarn);NU5128;NU5110;NU5111</NoWarn>
24+
</PropertyGroup>
25+
26+
<ItemGroup>
27+
<Content Include="templates/**/*"
28+
Exclude="templates/**/bin/**;templates/**/obj/**;templates/**/.vs/**;templates/**/TestResults/**;templates/**/node_modules/**" />
29+
<None Include="README.md" Pack="true" PackagePath="/" />
30+
</ItemGroup>
31+
32+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
**/bin/
2+
**/obj/
3+
**/.vs/
4+
**/.idea/
5+
**/TestResults/
6+
**/node_modules/
7+
.git/
8+
.github/
9+
docs/
10+
tests/
11+
.env
12+
.env.*
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
indent_style = space
9+
indent_size = 4
10+
11+
[*.{json,yml,yaml,props,csproj,xml,ts,tsx,html,css}]
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
17+
[*.cs]
18+
csharp_style_namespace_declarations = file_scoped:warning
19+
dotnet_sort_system_directives_first = true
20+
csharp_style_var_for_built_in_types = true:suggestion
21+
csharp_style_var_when_type_is_apparent = true:suggestion
22+
csharp_style_prefer_primary_constructors = true:suggestion
23+
dotnet_style_prefer_collection_expression = true:suggestion
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Example settings: the only env file committed to git. Copy it to start:
2+
# cp .env.example .env
3+
# Variables already set in the environment (Docker, CI, shell) always win over .env.
4+
# Keep only placeholders and local-only defaults here, never real secrets.
5+
6+
# ---------------------------------------------------------------- App
7+
# dev | stage | prod (maps to Development | Staging | Production)
8+
APP_ENV=dev
9+
# Port the server listens on (also used by Docker Compose).
10+
APP_PORT=5080
11+
# Public base URL clients use (behind a proxy: https://mcp.example.com). Empty = http://localhost:${APP_PORT}
12+
APP_URL=
13+
14+
# ---------------------------------------------------------------- MCP
15+
MCP_SERVER_NAME=DotnetMcpTemplate
16+
MCP_PATH=/mcp
17+
# Optional instructions sent to clients (what this server is for).
18+
MCP_INSTRUCTIONS=
19+
# required: every MCP request needs a token (clients log in on connect)
20+
# mixed: anonymous clients can connect; [Authorize] is checked per tool/resource/prompt
21+
# none: no login, a fixed developer user is signed in (only allowed when APP_ENV=dev)
22+
MCP_AUTH_MODE=required
23+
# Where built MCP App bundles live. Empty = ui_dist next to the app.
24+
MCP_UI_DIST_DIR=
25+
26+
# ---------------------------------------------------------------- Auth
27+
# oidc (default, proxy for any OIDC identity provider) | jwt (validate IdP tokens directly) | your own provider
28+
AUTH_PROVIDER=oidc
29+
30+
# --- OIDC identity provider (any IdP: Google, Entra ID, Auth0, Okta, Keycloak, Zitadel, ...)
31+
# Register ONE app in your IdP with redirect URI: ${APP_URL}/oauth/callback
32+
OIDC_DISCOVERY_URL=https://accounts.google.com/.well-known/openid-configuration
33+
OIDC_CLIENT_ID=change-me
34+
# Empty = public client (PKCE only).
35+
OIDC_CLIENT_SECRET=
36+
# client_secret_basic | client_secret_post
37+
OIDC_CLIENT_AUTH_METHOD=client_secret_basic
38+
OIDC_SCOPES=openid,profile,email
39+
# auto: call userinfo when the IdP advertises it | always | never
40+
OIDC_USERINFO=auto
41+
# Claim holding roles; dot paths work for nested claims, e.g. realm_access.roles
42+
OIDC_ROLE_CLAIM=roles
43+
OIDC_GROUPS_CLAIM=groups
44+
# Extra IdP claims to copy into the server token (comma-separated), e.g. tenant_id,department
45+
OIDC_TOKEN_CLAIMS=
46+
# jwt provider only: accepted token audience(s). Empty = the MCP URL (APP_URL + MCP_PATH).
47+
OIDC_AUDIENCE=
48+
49+
# --- OIDC proxy (the MCP server is the OAuth server for MCP clients)
50+
# Signs server-issued tokens and encrypts stored IdP tokens. 32+ chars: openssl rand -base64 48
51+
AUTH_TOKEN_SIGNING_KEY=development-only-signing-key-change-me-0123456789
52+
AUTH_TOKEN_LIFETIME_MINUTES=60
53+
AUTH_REFRESH_TOKEN_LIFETIME_DAYS=30
54+
# Redirect URIs MCP clients may register (comma-separated; * allowed in port and path).
55+
AUTH_ALLOWED_REDIRECT_URIS=http://localhost:*,http://127.0.0.1:*,https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback
56+
# Remember a user's consent for a client for N days (0 = always ask).
57+
AUTH_CONSENT_REMEMBER_DAYS=30
58+
# memory (single instance) | redis (several instances)
59+
AUTH_STORE=memory
60+
REDIS_URL=
61+
62+
# ---------------------------------------------------------------- API clients (one prefix per data source)
63+
# {PREFIX}_BASE_URL, _TIMEOUT_SECONDS, _RETRY_COUNT, _RETRY_UNSAFE_METHODS, _HEADERS,
64+
# _AUTH = none | bearer | api_key | basic | client_credentials | user
65+
SAMPLE_API_BASE_URL=https://jsonplaceholder.typicode.com/
66+
SAMPLE_API_TIMEOUT_SECONDS=15
67+
SAMPLE_API_RETRY_COUNT=3
68+
SAMPLE_API_AUTH=none
69+
SAMPLE_API_HEADERS=

0 commit comments

Comments
 (0)