A demonstration MCP (Model Context Protocol) server in C#/.NET, exposing tools and resources over the Streamable HTTP transport. Built as a practice project — see PLAN.md for the full design rationale, security checklist, and open decisions.
- .NET 8 (LTS)
ModelContextProtocol.AspNetCore1.4.0- QuestPDF (Community license) for the synthetic product manual PDF
src/
McpServerExample.Server/ ASP.NET Core host, MCP tools/resources, validation
McpServerExample.Clients.JsonPlaceholder/ Typed REST client for jsonplaceholder.typicode.com
McpServerExample.Clients.Countries/ Typed GraphQL client for countries.trevorblades.com
tests/
McpServerExample.Server.Tests/ xUnit tests (validation, snapshot, integration)
dotnet run --project src/McpServerExample.Server
The server listens on http://localhost:5048 (see Properties/launchSettings.json) and exposes
the MCP endpoint at POST /mcp (Streamable HTTP transport, per the
MCP spec).
No authentication; intended for localhost use only.
| Tool | Source | Notes |
|---|---|---|
get_post |
JSONPlaceholder (REST) | Read-only |
list_posts_by_user |
JSONPlaceholder (REST) | Read-only |
get_user |
JSONPlaceholder (REST) | Read-only |
create_post |
JSONPlaceholder (REST) | Simulated write — JSONPlaceholder doesn't persist it |
get_country |
Countries (GraphQL) | Read-only |
list_countries_by_continent |
Countries (GraphQL) | Read-only |
Every tool argument is validated twice: declaratively (JSON Schema constraints — range, pattern,
length, enum, additionalProperties: false) and imperatively (guard clauses in
Validation/Guards.cs), because the SDK does not enforce the declared schema at runtime — it only
publishes it. See PLAN.md §5 for the full rationale.
| URI | MIME type | Content |
|---|---|---|
demo://catalog/products |
application/json |
Fictional ~20-product catalog |
demo://docs/product-manual |
application/pdf |
Synthetic 2-3 page manual (blob), generated at first access via QuestPDF |
demo://docs/product-manual/text |
text/plain |
Same manual as plain text |
Resources are served from a fixed set of [McpServerResource] methods with static URIs — there is
no dynamic path composition, so an unknown URI is rejected before any file/asset lookup happens.
Logging is handled by Serilog, fully configured from the Serilog section
in appsettings.json / appsettings.Development.json (sinks, minimum levels, enrichers, output
templates — no logging setup lives in code beyond wiring UseSerilog() up in Program.cs).
- Console: colored, human-readable single-line-per-entry output.
- File:
logs/mcpserverexample-.log, rolling daily, 14 days retained, enriched with the application name, environment, machine name, process ID, and thread ID on every line. - Tool arguments are logged at
Debug(never full response bodies — seePLAN.md§5); guard-clause rejections atWarning; the one write tool (create_post) and one-time PDF generation atInformation; unexpected upstream failures atError. appsettings.Development.jsonbumps the server's own namespace toVerbosefor local debugging.
The plan proposed StrawberryShake for the GraphQL client (§4). At implementation time,
StrawberryShake.Server was found stuck at v12.15.3 while the underlying HotChocolate ecosystem is
at v16.x — a strong signal the codegen tooling is unmaintained relative to current .NET tooling.
Instead, McpServerExample.Clients.Countries uses a small hand-written typed HTTP client with the
two GraphQL queries committed as .graphql files (embedded resources) and typed variables — this
preserves the plan's actual goal (precompiled queries, no model-supplied query fragments, no GraphQL
injection surface) without the fragile dependency.
dotnet test
26 tests covering:
ToolValidationTests— hostile input (out-of-range, wrong type, injection-shaped strings) against the real guard clauses.ToolDefinitionSnapshotTests— golden-file test overtools/listandresources/listoutput (Snapshots/*.snapshot.json). Any change to a tool/resource's name, description, schema, or annotations fails the build until the snapshot is updated in an explicit, reviewable commit — this is the anti-rug-pull mechanism from PLAN.md §6.McpIntegrationTests— end-to-end calls through an in-processWebApplicationFactory<Program>+ a realMcpClient, exercising every tool and resource (hits the real JSONPlaceholder/Countries APIs over the network).
npx @modelcontextprotocol/inspector --cli http://localhost:5048/mcp --method tools/list
npx @modelcontextprotocol/inspector --cli http://localhost:5048/mcp --method tools/call --tool-name get_country --tool-arg countryCode=IT
npx @modelcontextprotocol/inspector --cli http://localhost:5048/mcp --method resources/read --uri demo://catalog/products
(Requires the server to be running separately — dotnet run --project src/McpServerExample.Server.)