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
The demo tokens live for 24 hours; docker compose up again regenerates them.

Next steps