loading

Connect a client

/mcp

The server speaks MCP over streamable HTTP at /mcp - one POST endpoint, JSON-RPC in the body. Point any MCP client at it with a config like this one; the URL below is the origin you are reading this page on. Every tool is also a plain GET under /api/mcp/..., which is what makes it inspectable from a browser address bar.

JSON
{
  "mcpServers": {
    "bit-butil": {
      "type": "http",
      "url": "https://butil.bitplatform.dev/mcp"
    }
  }
}
Live sample
MCP POST https://butil.bitplatform.dev/mcp
HTTP mirror GET https://butil.bitplatform.dev/api/mcp/{toolName}?arg=value

1. Handshake

initialize

Every session opens with an initialize call: the client states the protocol version it speaks and what it supports, and the server answers with the same for itself - here, tools, prompts, resources, completions and logging. Each of those comes back as an empty object, which is the protocol working as intended: the key's presence is the declaration, and what goes inside it is only the optional extras, such as a promise to notify the client when the list changes. A capability a server does not have is absent, not empty. The answer also carries instructions - the one block of text a server writes straight into the model's context before it has called anything - so read it in the response pane below; it is what tells an agent to search before it guesses. Nothing here hands back a session id: streamable HTTP is stateless, so every request stands on its own and any replica can answer it. The client then sends an initialized notification, which takes no id and gets no result back. This page does both automatically when it loads.

HTTP
POST /mcp
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {},
    "clientInfo": { "name": "bit-butil-docs", "version": "1.0" }
  }
}
Live sample

Connecting...

2. Discover the tools

tools/list

tools/list is how an agent learns what a server can do: every tool comes back with its name, the description the model reads to decide whether to call it, and a JSON Schema for its arguments. Nothing about this list is configured on the client - which is the point of the protocol.

JSON
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
Live sample

Nothing sent yet.

3. Call a tool

tools/call

The one that does the work. Pick a tool, edit its arguments, and send - the response pane shows the JSON-RPC envelope the client receives, and the pane under it shows the text inside it, which is what an agent would actually put in front of the model. Try SearchButil with a task described in your own words, or PlanButilFeature with the APIs a feature would need. The retrieval tools take no required argument: send GetButilDocsPage with an empty object and the index of every page comes back instead of one page.

JSON
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "SearchButil",
    "arguments": { "query": "copy text to clipboard" }
  }
}
Live sample

Pick a tool and send it.

4. Prompts and resources

prompts/list, prompts/get, resources/list, resources/templates/list, resources/read

Tools are for an agent that has decided what it needs. Prompts are ready-made workflows a person picks from a menu - 'add Butil to an app', 'debug a call that does nothing' - and resources are documents a client can attach to a conversation up front or let someone browse. This server exposes four prompts and seven resources: three fixed ones, which is what resources/list returns, and four URI templates, which come back from resources/templates/list. Resources are addressed by URI, so butil://support is the whole page index - which is also the browser-support matrix - in one read. prompts/list only names a prompt; prompts/get is where the workflow itself comes back, with the argument you passed already written into it.

JSON
{ "jsonrpc": "2.0", "id": 4, "method": "resources/read",
  "params": { "uri": "butil://support" } }
Live sample

Nothing sent yet.

5. Completing an argument

completion/complete

The half of the protocol that exists for the person rather than the model. A prompt argument and a resource template's placeholder are both free text, and every one of this server's is drawn from a closed set - four hosting models, the 143 docs slugs, every public type name. completion/complete is how a client fills the picker: it sends what has been typed so far and gets back what would be valid, prefix matches first. Type below and watch the values change with each keystroke.

JSON
{ "jsonrpc": "2.0", "id": 5, "method": "completion/complete",
  "params": {
    "ref": { "type": "ref/resource", "uri": "butil://docs/{slug}" },
    "argument": { "name": "slug", "value": "stor" }
  } }
Live sample

Pick an argument and start typing.

6. The same tools over plain HTTP

/api/mcp/{tool}

Every tool is also an ordinary GET endpoint returning ordinary JSON - no handshake, no session, no JSON-RPC envelope. That is not a second implementation: it is the same method on the same controller, attributed twice. It exists so a tool can be checked in a browser tab, curl or a test, which is a much shorter loop than wiring up an MCP client to find out what one returns.

Shell
curl "https://butil.bitplatform.dev/api/mcp/PlanButilFeature?apis=WakeLock"
Live sample

Nothing sent yet.

Note:
Why a documentation site hosts one An agent writing Butil code has the same problem a person does: more than a hundred services, and the browser's name for a capability is rarely the name the task suggests. The tools answer from the assembly loaded in this process and from these very pages - GetButilApiDetails reflects over the shipped Bit.Butil, GetButilDocsPage renders the same component you are reading - so there is no second copy of the documentation to fall out of date with the library.
Warning:
Responses are clipped here, not on the wire A real client gets the whole payload; this page shows the first few thousand characters of each pane so one GetButilSetupGuide does not fill the screen. The byte count above each exchange is what actually arrived.

What the server exposes

Member
Signature
Description
Tools
over MCP and as GET /api/mcp/{tool}
Search, the exact API of every service, what an API or a whole feature needs from the page, the setup per hosting model, the docs pages, the guide and the demo's own source files. Seven of them: a listing is not a tool here, it is what a retrieval tool answers when called with no argument, and the docs index doubles as the browser-support matrix.
Tool annotations
readOnlyHint, idempotentHint, destructiveHint, openWorldHint
Every tool here reads, reads only from this process, and answers the same way twice. A client that is told so can consult the documentation without stopping to ask permission first.
Answers
one text block per call
A tool that reads a document answers with Markdown; a tool that answers with data - search, the API reference, a plan - answers with the JSON of it. No tool publishes an outputSchema, and that is a size decision rather than a typing one: declaring one makes the SDK send the object in structuredContent AND the identical JSON in the text block the protocol wants there anyway, so every one of those answers used to cross the wire twice.
Instructions
returned by initialize
The one block of text the server writes into the model's context before it has called anything: which tool to reach for first, and the four rules that separate Butil code that compiles from Butil code that works.
Prompts
prompts/list
Ready-made workflows, each spending its words on the order to call the tools in and the mistakes that compile.
Resources
butil://guide, butil://guide/{heading}, butil://api, butil://api/{typeName}, butil://docs/{slug}, butil://source/{path}, butil://support
The same knowledge as documents a client can attach or browse, addressed by URI.
Completions
completion/complete
Every prompt argument and template placeholder is drawn from a closed set - the hosting models, the docs slugs, the type names - so a client's picker can offer them rather than ask someone to type one blind.
Transport
POST /mcp (streamable HTTP, stateless)
JSON-RPC in the body; responses arrive as a text/event-stream frame. No session id - SEP-2567 removed them from this transport, so any replica can answer any request. Mapped by MapMcp in the host's Program.cs, with CORS so browser-based clients can reach it.
An unhandled error has occurred. Reload 🗙