Enterprise-grade MCP enablement for ASP.NET Core APIs.
ZeroMCP lets teams expose existing controller and minimal API endpoints as MCP (Model Context Protocol) tools, resources, templates, and prompts, without creating a second service or duplicating logic.
- What it solves: Connect LLM clients to established ASP.NET Core APIs safely and quickly.
- How it works: Annotate endpoints (
[Mcp],[McpResource],[McpTemplate],[McpPrompt]) or use minimal API metadata (.AsMcp,.AsResource,.AsTemplate,.AsPrompt), then map one MCP route. - Why enterprises adopt it: Keeps existing auth, policy, validation, observability, and release controls in place.
- In-process dispatch through your real ASP.NET Core pipeline
- Streamable HTTP MCP endpoint (
GETandPOST) - Optional stdio transport for local/desktop MCP clients
- Streaming tool results via
IAsyncEnumerable<T> - Tools, resources, templates, and prompts in one framework
- Per-tool governance (roles, policies, filters)
- Observability hooks (correlation, logs, metrics sink, OpenTelemetry tags)
- Inspector endpoints for discovery and controlled testing
- Versioned MCP routes for phased client migration
- API startup discovers MCP metadata from controllers and minimal APIs.
- ZeroMCP builds schemas and endpoint descriptors.
- MCP clients call
/mcpusing JSON-RPC methods. - ZeroMCP dispatches in-process to your original endpoint.
- Response is normalized back into MCP-compatible output.
This model preserves middleware behavior and avoids "shadow implementations."
<PackageReference Include="ZeroMCP" Version="1.*" />builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddZeroMCP(options =>
{
options.ServerName = "Orders Platform";
options.ServerVersion = "1.0.0";
});
var app = builder.Build();
app.MapControllers();
app.MapZeroMCP(); // registers GET+POST /mcp
app.Run();[HttpGet("{id:int}")]
[Mcp("get_order", Description = "Retrieves an order by ID.")]
public IActionResult GetOrder(int id) => Ok(new { id });
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" }))
.AsMcp("health_check", "Returns API health status.");- Protect MCP endpoint using existing authentication/authorization:
app.MapZeroMCP().RequireAuthorization("McpPolicy");- Enforce least privilege with
RolesandPolicyon tool metadata. - Keep inspector endpoints disabled or restricted outside trusted environments.
- Forward only required security headers via
ForwardHeaders.
ToolFilter: discovery-time exclusion by name/environment.ToolVisibilityFilter: per-request dynamic visibility based on context.- Roles/policies are enforced at listing and invocation boundaries.
- Use versioned routes to run controlled cutovers across client populations.
- Correlation IDs via configurable header propagation.
- Structured logging around MCP request lifecycle and tool calls.
IMcpMetricsSinkfor custom telemetry export.- Optional OpenTelemetry enrichment for traces.
- SSE-based keep-alive behavior for long-lived MCP connections.
- Set endpoint auth and rate limiting policies at the ASP.NET Core layer.
- Treat
/mcpas a production API surface with normal SLO/SLA controls. - Keep inspector UI behind environment checks or internal access controls.
- Validate key tool flows with integration tests before client rollout.
builder.Services.AddZeroMCP(options =>
{
options.ServerName = "Orders Platform";
options.ServerVersion = "2.3.0";
options.RoutePrefix = "/mcp";
// Core behavior
options.IncludeInputSchemas = true;
options.ForwardHeaders = ["Authorization"];
// Governance
options.ToolFilter = name => !name.StartsWith("internal_");
options.ToolVisibilityFilter = (name, ctx) =>
ctx.User.IsInRole("Admin") || !name.StartsWith("admin_");
// Observability
options.CorrelationIdHeader = "X-Correlation-ID";
options.EnableOpenTelemetryEnrichment = true;
// Optional MCP capabilities
options.EnableResources = true;
options.EnablePrompts = true;
options.EnableToolInspector = false;
options.EnableToolInspectorUI = false;
});initializetools/list,tools/callresources/list,resources/templates/list,resources/readresources/subscribe,resources/unsubscribe(when enabled)prompts/list,prompts/get- notification flows such as list-changed updates (when enabled)
GET /mcpfor metadata and SSE scenariosPOST /mcpfor JSON-RPC methods
if (args.Contains("--mcp-stdio"))
{
await app.RunMcpStdioAsync();
return;
}Useful for local-first MCP clients that spawn your service process directly.
{
"mcpServers": {
"orders-api": {
"command": "dotnet",
"args": ["run", "--project", "ZeroMCP.Sample", "--", "--mcp-stdio"]
}
}
}For full client setup options (stdio and HTTP), see wiki/Connecting-Clients.md.
GET /mcp/tools: JSON inventory of tools and schemasGET /mcp/ui: browser-based invocation UI
Recommended usage: enable in development and internal test environments only.
- Semantic versioning policy is defined in
VERSIONING.md. - MCP protocol behavior is implemented with explicit compatibility tests.
- Versioned endpoint support allows non-breaking migration paths for clients.
ZeroMCP/: core framework package (NuGet artifact source)ZeroMCP.Sample/: reference host with practical patternsZeroMCP.Tests/: integration and schema/compatibility testsexamples/: focused scenario samples:MinimalWithAuthWithEnrichmentWithStdioWithRateLimitingEnterprise
wiki/: implementation and operations documentationprogress.md: persistent engineering change log
[Mcp] supports a required tool name plus optional metadata used for discoverability and governance.
[Mcp(
"create_order",
Description = "Creates an order.",
Tags = new[] { "orders", "write" },
Category = "orders",
Examples = new[] { "Create order for Alice, quantity 2" },
Hints = new[] { "idempotent", "cost=low" },
Roles = new[] { "Admin" },
Policy = "RequireEditor",
Version = 2
)]Full details: wiki/The-Mcp-Attribute.md.
Use these when exposing MCP resources and prompts from controller actions:
[McpResource("catalog://info", "catalog_info",
Description = "Returns catalog metadata.",
MimeType = "application/json")]
[McpTemplate("catalog://products/{id}", "product_resource",
Description = "Returns a product by ID.",
MimeType = "application/json")]
[McpPrompt("restock_recommendation_prompt",
Description = "Generates a restock recommendation prompt.")]Minimal API equivalents:
app.MapGet("/api/catalog/info", () => Results.Ok(...))
.AsResource("catalog://info", "catalog_info", "Returns catalog metadata.", mimeType: "application/json");
app.MapGet("/api/catalog/products/{id:int}", (int id) => Results.Ok(...))
.AsTemplate("catalog://products/{id}", "product_resource", "Returns a product by ID.", mimeType: "application/json");
app.MapGet("/api/catalog/prompts/restock/{productId:int}", (int productId) => Results.Ok(...))
.AsPrompt("restock_recommendation_prompt", "Generates a restock recommendation prompt.");Full details: wiki/Resources-and-Prompts.md.
dotnet build ZeroMCP.slnx -v detailed
dotnet test ZeroMCP.Tests/ZeroMCP.Tests.csproj -v detailedIntegration tests include MCP streamable HTTP behaviour (e.g. GET /mcp with Accept: text/event-stream and Mcp-Session-Id, resources/subscribe with session header, and notifications/resources/updated on the SSE channel). See McpResourceSubscriptionTests and McpClientCompatibilityTests in ZeroMCP.Tests/.
- Package README:
ZeroMCP/README.md - Configuration:
wiki/Configuration.md - Security model:
wiki/Security-Model.md - Enterprise usage:
wiki/Enterprise-Usage.md - Tool versioning:
wiki/Tool-Versioning.md - Resources and prompts:
wiki/Resources-and-Prompts.md
Contributions are welcome, especially around protocol compatibility hardening, minimal API binding parity, and production-focused samples. Please include integration tests and documentation updates with each functional change.