Getting started
Expose an existing ASP.NET Core Web API as a Model Context Protocol server in four steps: reference the library, register it, mark the actions you want to publish, and talk to it.
1. Reference the library
From NuGet:
dotnet add package Nabu.Mcp.AspNetCore
Or as a project reference while working from source:
<ProjectReference Include="path/to/src/Nabu.Mcp.AspNetCore/Nabu.Mcp.AspNetCore.csproj" />
2. Register and mount it
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(/* ... */);
builder.Services.AddAuthorization(/* ... */);
builder.Services.AddNabuMcp(options =>
{
options.ServerName = "my-api";
options.RequireAuthorization = true; // protect the MCP endpoint itself
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseNabuMcp(); // mount after authentication so the endpoint sees the caller
app.MapControllers();
app.Run();
UseNabuMcp() serves the MCP endpoint at /mcp by default. The route can be
changed either through options.Path or directly at the mount —
app.UseNabuMcp("/agent/mcp") — with the argument winning when both are set. Where you
place it only affects the MCP endpoint itself — tool calls always traverse the whole pipeline from
the top, regardless of position.
3. Mark the actions you want to publish
[ApiController]
[Route("api/todos")]
[Authorize]
public class TodosController : ControllerBase
{
/// <summary>Lists the todo items belonging to the signed-in user.</summary>
/// <param name="search">Case-insensitive substring matched against the title and notes.</param>
[HttpGet]
[McpTool]
public ActionResult<TodoPage> List([FromQuery] string? search, [FromQuery] int page = 0) => ...
}
That is the whole setup. The XML <summary> becomes the tool description and the
<param> text becomes the argument descriptions, so a well-documented API produces a
well-described tool set with no extra work. (Set
<GenerateDocumentationFile>true</GenerateDocumentationFile> to enable it.)
4. Talk to it
curl -X POST http://localhost:5000/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Each published action is advertised with a generated schema and MCP behaviour annotations:
{
"name": "todos_get_by_id",
"title": "Get By Id",
"description": "Fetches a single todo item by its identifier.",
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid", "description": "Identifier of the item." }
},
"required": ["id"]
},
"annotations": {
"readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false
}
}
Run the sample application
The repository ships samples/Nabu.Sample.TodoApi — a working API with JWT bearer
authentication, a role-based policy, model validation and XML documentation. Two accounts exist,
both with the password password: alice (user) and root
(user + admin).
dotnet run --project samples/Nabu.Sample.TodoApi
# then POST JSON-RPC to http://localhost:5000/mcp
It is wired up for per-caller tool visibility, so the ordinary authorization attributes are visible
at work: connect without a token and tools/list returns only the anonymous tools; sign
in as alice and the todo tools appear; only root sees
todos_delete, because it carries [Authorize(Policy = "AdminOnly")].
Trying it with MCP Inspector
docker-compose.yml runs both samples together with the official
MCP Inspector,
preconfigured to demonstrate per-caller tool exposure on both protocol layers:
docker compose up --build
# then open http://localhost:6274?MCP_INSPECTOR_API_TOKEN=nabu-local-dev
A one-shot init container signs in as alice and root on both samples and
writes an Inspector config with three connections per sample. Switching between
todo-anonymous, todo-alice-user and todo-root-admin in the UI
shows the tool list grow from 6 anonymous tools to alice's 13 to root's 14. The Todo API is
published on http://localhost:5080/mcp and the book catalog (served through the
official MCP SDK) on http://localhost:5081/books/mcp.
The same comparison from the terminal, via the Inspector CLI:
docker compose run --rm inspector --cli --config /shared/mcp-servers.json \
--server todo-anonymous --method tools/list
docker compose run --rm inspector --cli --config /shared/mcp-servers.json \
--server todo-root-admin --method tools/list
docker compose up again regenerates them.Next steps
- Attributes & tools — the full attribute surface, publishing one action as several tools, argument mapping and schema generation.
- Minimal APIs — publishing route handlers with
.McpTool(). - Authentication & security — the two enforcement layers and the recommended production configuration.
- Configuration & protocol — every option on
NabuMcpOptionsand the supported protocol revisions.