Configuration & protocol
Every option lives on NabuMcpOptions. This page also covers the supported
MCP protocol revisions, the official SDK adapter, target frameworks and known limitations.
Options reference
| Option | Default | Meaning |
|---|---|---|
Path | /mcp | Endpoint path. |
ServerName, ServerVersion, Instructions | entry assembly | Reported during initialize / server/discover. |
CacheTtlMilliseconds | 60000 | ttlMs freshness hint on cacheable 2026-07-28 results. |
ExposeAllActions | false | Publish every action and route handler, not just annotated ones. [McpIgnore] still wins. |
RequireAuthorization, AuthorizationPolicy, AuthenticationSchemes | off | Protects the MCP endpoint. |
ToolVisibility | All | Advertise every tool, or only the ones the caller is authenticated / authorized for. |
AnonymousAccess | None | How much of a protected endpoint an unauthorized caller may reach. |
ToolNameFactory | controller_action snake_case | Builds tool names. |
ToolFilter | none | Last-chance predicate to drop discovered tools. |
FlattenBodyParameter | true | Lift [FromBody] model properties to the top level. |
ExposeHeaderParameters | false | Publish [FromHeader] parameters as tool inputs. |
ForwardedHeaders, ForwardedHeaderPrefixes | credentials + tracing | Headers copied onto tool requests. |
ProtectedHeaders | credentials + proxy metadata | Headers a model-supplied argument may never override. |
PropagateUser | true | Seed the caller's principal onto the synthetic request. |
MaxResponseBytes | 1 MiB | Cap on the buffered response body; bounds memory, larger bodies are truncated and flagged. |
IncludeStructuredContent | true | Emit JSON responses as MCP structuredContent. |
TreatErrorStatusAsToolError | true | Map HTTP >= 400 to isError: true. |
MaxSchemaDepth | 8 | Nesting limit for generated schemas. |
UseXmlDocumentation | true | Read <summary>/<param> from the XML docs file. |
StringEnumsInRequestBody | auto-detect | Whether the API accepts enum names in JSON bodies. |
Protocol support
Streamable HTTP transport, JSON-RPC 2.0. The server is dual-era: it speaks the stateless revision
2026-07-28 (version and capabilities declared in _meta on every request,
no handshake, no sessions) and the legacy revisions 2025-06-18,
2025-03-26 and 2024-11-05 (negotiated at initialize). A
request that declares a protocol version in _meta — or calls
server/discover — is served under 2026-07-28 semantics; everything else
stays on the legacy path, so existing clients are unaffected.
On the 2026-07-28 path the mirrored request headers
(MCP-Protocol-Version, Mcp-Method, Mcp-Name) are validated
against the body (-32020 on mismatch), an unsupported version is answered with
-32022 and the supported list, results carry resultType, the server's
identity in _meta, and ttlMs/cacheScope on cacheable
results, and no session id is ever minted.
| Method | Behaviour |
|---|---|
server/discover | (2026-07-28) Supported versions, capabilities, identity and instructions. |
initialize | (legacy) Negotiates the version, advertises the tools capability, returns a session id. |
tools/list | Every discovered tool with its schema and annotations. |
tools/call | Replays the action; returns text content, structuredContent, and isError. |
ping | (legacy) Answered with an empty result. |
notifications/* | Accepted with 202 and no body. |
resources/list, resources/templates/list, prompts/list | Answered as empty for client compatibility. |
POST returns application/json, or a single server-sent event when the
client accepts only text/event-stream. Batched arrays are supported on the legacy
path. DELETE ends a legacy session (the server is stateless, so this is a no-op).
GET returns 405.
Failures are reported at the right layer: a bad tool name or a missing required argument is a
JSON-RPC error (-32602), while an HTTP error from the action — 400 from validation,
403 from a policy, 404 from the action — is a successful JSON-RPC response carrying
isError: true, which is what lets a model read and react to it.
Using the official MCP SDK as the protocol layer
Nabu's value is the ASP.NET Core bridge — controller discovery, schema generation,
authorization-aware visibility and pipeline replay — not the JSON-RPC plumbing. The protocol layer
is therefore replaceable. The Nabu.Mcp.ModelContextProtocol package serves Nabu's
tools through the official
ModelContextProtocol C# SDK
instead of the built-in layer:
services.AddNabuMcp(options => { ... })
.UseOfficialMcpProtocol();
app.UseNabuMcp(); // now mounts the official SDK's Streamable HTTP endpoint at the same path
The SDK owns the wire protocol — transport, protocol revisions, sessions — and Nabu answers
tools/list and tools/call behind it, with the same visibility rules and
pipeline replay as the built-in layer. The transport defaults to the SDK's stateless mode, matching
Nabu's design; pass a configuration delegate to change that or any other transport option:
services.AddNabuMcp().UseOfficialMcpProtocol(transport => transport.Stateless = false);
Choose the built-in layer for netstandard2.0/ASP.NET Core 2.x compatibility and zero extra
dependencies; choose the official layer (net8.0+) to track the protocol at the SDK's pace and pick
up its transport features. RequireAuthorization is honoured on the mapped endpoint;
the partial AnonymousAccess modes leave the endpoint anonymous and rely on the
replayed pipeline to authorize each call.
samples/Nabu.Sample.OfficialSdk is a complete runnable example: a small book-catalog
API published through the official SDK on a custom route
(app.UseNabuMcp("/books/mcp")), with the same JWT setup and per-caller tool exposure
as the Todo sample — showing that the authorization story is identical on both protocol layers.
Target frameworks
The library multi-targets netstandard2.0, net8.0 and net10.0:
- netstandard2.0 builds against the ASP.NET Core 2.x packages, so ASP.NET Core 2.1/2.2 apps can use it.
- net8.0 and net10.0 bind to the shared framework, so modern apps pull in no legacy assemblies.
Version-specific APIs are handled at runtime rather than by feature-flagging behaviour: the HTTP
verb is read through IActionHttpMethodProvider, which has been stable since 1.0, and
the enum representation is detected reflectively so the same code path serves System.Text.Json and
Newtonsoft.Json.
Limitations
- The synthetic request reproduces the HTTP application surface, not the server transport. Standard request/response semantics, connection information, request services, cancellation and identity are all present, but server-level features are not: TLS and client-certificate features, HTTP/2-specific server features, WebSockets, response upgrade, raw transport access and some IIS/Kestrel-specific features do not exist on a replayed request. Middleware that requires them will behave as it does when those features are absent.
- File uploads are supported:
IFormFileand form-data actions publish file arguments that carry the content base64-encoded, replayed as multipart/form-data. The content travels inside the JSON-RPC body, so uploads are bounded by the MCP endpoint's request-size limits — fine for documents and images, wrong for very large payloads. - Attribute routing is what discovery is built around. Conventionally routed actions fall back to
a
{controller}/{action}path with the remaining arguments in the query string. - The server is stateless: no server-initiated notifications, no
listChanged, no resumable streams. - Overloaded actions that generate the same tool name are disambiguated with a numeric suffix and
a warning; give them explicit
[McpTool(Name = "...")]names instead.
Releasing
Publishing to nuget.org is driven by tags — the tag is the single source of truth for the package version:
git tag v1.2.3
git push origin v1.2.3
GitHub Actions then restores, builds, tests, packs at 1.2.3, pushes the
.nupkg and its .snupkg symbol package to nuget.org via
NuGet Trusted Publishing
(no API key to store), and opens a GitHub release with generated notes. A tag containing a hyphen
(v1.2.3-rc.1) is published as a prerelease.