MCP server
This site is also an MCP server: the same documentation, API reference and browser-support data, exposed as tools an AI agent can call. The sections below are a working client - they speak the protocol to this very server and show you both halves of every exchange.
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.
{
"mcpServers": {
"bit-butil": {
"type": "http",
"url": "https://butil.bitplatform.dev/mcp"
}
}
}POST https://butil.bitplatform.dev/mcpGET https://butil.bitplatform.dev/api/mcp/{toolName}?arg=valueEvery 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.
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" }
}
}Connecting...
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.
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }Nothing sent yet.
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.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "SearchButil",
"arguments": { "query": "copy text to clipboard" }
}
}Pick a tool and send it.
4. Prompts and resources
prompts/list, prompts/get, resources/list, resources/templates/list, resources/readTools 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.
{ "jsonrpc": "2.0", "id": 4, "method": "resources/read",
"params": { "uri": "butil://support" } }Nothing sent yet.
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.
{ "jsonrpc": "2.0", "id": 5, "method": "completion/complete",
"params": {
"ref": { "type": "ref/resource", "uri": "butil://docs/{slug}" },
"argument": { "name": "slug", "value": "stor" }
} }Pick an argument and start typing.
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.
curl "https://butil.bitplatform.dev/api/mcp/PlanButilFeature?apis=WakeLock"Nothing sent yet.
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.
GetButilSetupGuide does not fill the screen. The byte count above each exchange is what
actually arrived.
What the server exposes
over MCP and as GET /api/mcp/{tool}readOnlyHint, idempotentHint, destructiveHint, openWorldHintone text block per callreturned by initializeprompts/listbutil://guide, butil://guide/{heading}, butil://api, butil://api/{typeName}, butil://docs/{slug}, butil://source/{path}, butil://supportcompletion/completePOST /mcp (streamable HTTP, stateless)