Skip to content

MCP server

This site ships an MCP server so an AI agent can build with Brouter from the library it actually has, instead of from what it remembers. The page below is a live MCP client: it handshakes with /mcp, lists the tools, resources and prompts, calls them, and prints every JSON-RPC message it sends and receives.

Model Context Protocol is how an agent discovers and calls capabilities at runtime. A server offers three kinds: tools the model may call, resources a client can attach to a conversation, and prompts a person can pick as a ready-made workflow. This one answers from the shipped Bit.Brouter assembly, its README and this site's own pages - so its answers move with the library rather than aging next to it.

Handshake

Every session opens with initialize: the two sides agree on a protocol revision - a server without the requested one refuses with -32022 and names the revisions it has - and the server states which capabilities it offers. The client answers with notifications/initialized, a message with no id and therefore no reply, and only after that may it call anything.

This server runs the transport in its stateless mode, which is the default: no Mcp-Session-Id comes back, every POST stands on its own, and the deployment can sit behind a load balancer without session affinity. What each later request does have to carry is the negotiated MCP-Protocol-Version header.

Endpoint
POST /mcp · Streamable HTTP
Protocol
- (asked for 2025-11-25)
Server
-
Session id
none - stateless
Capabilities
-

Tools

tools/list returned 0 tools, each with the JSON schema of its arguments - that schema is all a model gets, which is why the descriptions read like instructions. Pick one, fill the arguments and call it: the request and the response land in the wire log at the bottom of the page.

The badges beside a selected tool's name are what it declares about itself. read-only is the one that changes a client's behavior: a tool that cannot modify anything is one an agent may run without stopping to ask a person first. closed world says the answers come from a known body of material rather than from the internet, and structured output means the tool publishes an outputSchema and answers with a validated object, not only with text.

Only two of them carry that last badge, and deliberately: a structured answer goes over the wire twice - once as the object, once as the JSON text the spec asks a server to send on its behalf for clients that cannot read one - so it earns its cost where the answer is something a caller acts on rather than reads. A ranked search hit carrying its own follow-up call, and a parse verdict, are that; a page of documentation is not, and comes back as Markdown, which is also smaller than either half of the same thing as JSON.

The reference tools share one rule worth knowing before you try them: each takes the key of the thing you want - a type name, a slug, a heading, a path - and leaving it out asks for the index of what there is. That is why there is no separate "list" tool beside any of them: a tool costs its name, description and schema in every single request a client makes, forever, and an optional argument says the same thing for nothing.

No tools yet - the session has not listed them.

Resources

Resources are addressed by URI rather than called, so a client can attach them to a conversation up front or let a person browse and pin them. Fixed ones come from resources/list; the parameterised ones come from resources/templates/list and are filled in before reading.

No resources yet - the session has not listed them.

Prompts

A prompt is a workflow a person picks - in most clients they appear as slash commands. What prompts/get returns is the message that gets put in front of the model, and these four spend their words on the order to call the tools in, because an agent with a handful of tools usually fails by sequence rather than by ignorance.

No prompts yet - the session has not listed them.

The wire log

Every message this page has exchanged with the server, oldest first - the JSON-RPC envelope that went out and the one that came back (indented for reading, otherwise as they were), with the HTTP status, the response's content type and how long the round trip took. Notifications have no id, so the server answers them with 202 and an empty body.

Nothing sent yet.

Point your own client at it

The endpoint is plain Streamable HTTP, so any MCP client connects to it with a URL and nothing else. Against a local run of this demo:

// .mcp.json (Claude Code, VS Code, and anything else reading this shape)
{
  "mcpServers": {
    "brouter": {
      "type": "http",
      "url": "https://localhost:7185/mcp"
    }
  }
}

Or drive it from a terminal - one POST is a whole handshake:

curl -sN https://localhost:7185/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
       "protocolVersion":"2025-11-25","capabilities":{},
       "clientInfo":{"name":"curl","version":"1.0"}}}'

The same tools over plain HTTP

McpController is an MVC controller as well as an MCP tool type: every tool is also a GET under /api/mcp/…. That is a debugging convenience - open one in a browser tab and read exactly what an agent would be handed, with no client in the way.

How the server is put together

Three files and one registration. The tools are ordinary methods - the attributes are what make them callable, and the [Description] text is the whole interface a model sees.

// Server/Program.cs
builder.Services.AddMcpServer(options =>
{
    options.ServerInfo = new Implementation { Name = "bit-brouter", Version = BrouterServerInstructions.BrouterVersion };

    // Read once, before any tool is called - the only text that reaches an incurious agent.
    options.ServerInstructions = BrouterServerInstructions.Text;
})
    .WithHttpTransport()
    .WithToolsFromAssembly()
    .WithResourcesFromAssembly()
    .WithPromptsFromAssembly()
    // What belongs in a {slug} or a {typeName}: no client can guess it, so it asks.
    .WithCompleteHandler((request, ct) => ValueTask.FromResult(new CompleteResult
    {
        Completion = BrouterCompletions.Complete(request.Params?.Argument?.Name, request.Params?.Argument?.Value)
    }))
    .WithListResourcesHandler((request, ct) => ValueTask.FromResult(new ListResourcesResult
    {
        Resources = [.. McpResources.ListDocumentationPages()]
    }));

app.MapControllers();
app.MapMcp("/mcp");

// Server/Controllers/McpController.cs - a tool and an HTTP endpoint at once
[HttpGet]
[McpServerTool(Name = nameof(InspectBrouterRouteTemplates), Title = "Inspect route templates",
             ReadOnly = true, OpenWorld = false, UseStructuredContent = true)]
[Description("Parses a route template with Bit.Brouter's own parser and reports what it means…")]
public BrouterRouteAnalysisDto InspectBrouterRouteTemplates(string templates)
    => BrouterTemplateInspector.Analyze(SplitTemplates(templates), brouterOptions.Value.Constraints);