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.
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);