Skip to content

Build1 publisher3 min readPublished

A CLI's 7 browser tools reach agents through 60 hand-written lines of JSON-RPC

xbrowser shipped a stdio MCP server with no new dependencies, implementing three protocol methods by hand instead of adopting the official SDK. The first smoke run found two bugs, both at the CLI boundary.

The Engineer · Build desk

Illustration accompanying A CLI's 7 browser tools reach agents through 60 hand-written lines of JSON-RPC

What happened

  • xbrowser v1.23.1 added `xbrowser mcp`, a stdio MCP server exposing seven browser tools from a CLI that carries 57 commands, more than 130 site plugins and a self-healing replay engine.
  • The first smoke run caught the CLI treating the protocol as input: the entrypoint reads piped stdin, so the incoming initialize message was executed as a browser command.
  • The second bug was a shutdown race, where EOF from a file-piped test triggered process.exit(0) while the browser was still launching for a tools/call.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision For anyone maintaining a globally installed CLI, MCP support is now a dependency decision with a measured floor: three methods over stdio, with the SDK's resources, prompts and typed helpers as the part you are paying for.
  • constraint The 60-line figure only holds for a stdio server that answers three methods; the moment a maintainer wants an HTTP transport or resource endpoints, the hand-rolled switch statement stops being 60 lines.
  • exposure Any CLI whose entrypoint collects piped stdin has the same collision waiting for its first initialize frame, and the symptom shows up as a browser command being executed.
  • capability Exposing the accessibility-tree snapshot and the replay engine as single calls gives an agent the parts of the tool that were previously reachable only by composing CLI chains.

Sixty lines is a claim about one server's requirements. xbrowser's MCP layer implements initialize, tools/list and tools/call over newline-delimited JSON-RPC 2.0 on stdio, plus an error path, and the post puts the whole thing at roughly 60 lines including the framing [5]. That figure transfers only if your server needs those three methods and nothing else. The post says the SDK's value sits in the long tail of resources, prompts, transports and typed helpers, which a seven-tool server does not touch [6]. It also says, plainly, "The official @modelcontextprotocol/sdk is fine software" [7]. The stated reasons for skipping it are install weight on a globally installed CLI and a protocol loop small enough to debug at 2am [8][9].

The interesting decision sits under the line count. Every tool call bottoms out in the same executeChain and executeCommand functions the CLI already uses; the MCP server rewrote no automation logic [10]. browser_navigate builds the string `goto <url>` and appends any follow-up chain after a `&&` [11]. So the code an agent reaches is the code the CLI's 4000-test suite exercises [12].

The seven tools were picked for agent ergonomics [14]. An agent gets browser_navigate, browser_act, browser_read, browser_snapshot (the accessibility tree, offered as the token-efficient page view), browser_screenshot, browser_network and browser_replay, with the self-healing replay engine behind a single call [13]. That is seven of 57 commands, so 50 stay CLI-only [23].

Both bugs the first smoke run caught showed up where the CLI meets the protocol. The CLI entrypoint reads stdin when piped, which is how `echo "goto x && title" | xbrowser` works, and to an MCP client that same stdin is the protocol channel; the first initialize was handed to the command executor as a browser command [15]. The fix short-circuits stdin collection in the mcp path so protocol lines and command lines never mix [16]. That failure lives in the CLI's own stdin handling, upstream of anything that parses JSON-RPC. The post says neither bug appears in the SDK's examples because both sit at the boundary between an existing CLI and the protocol [19].

The second is a lifecycle bug. The smoke test piped requests from a file, so EOF arrived while the browser was still launching for a tools/call, and the stdin-end handler called process.exit(0) on an in-flight request; real clients keep stdin open and would not hit it [17]. The harness hung up faster than any client would. Fixing it took an inflight counter, with exit deferred until stdin is closed and nothing is pending [18]. The post does not test whether the SDK's stdio transport drains pending work on EOF, so it settles nothing about the SDK either way.

You can check conformance without launching a client. A 50-line script spawns the server, writes four JSON-RPC lines (initialize, tools/list, an unknown method, an unknown tool) and asserts server info, exactly seven tools, -32601 for the bad method and isError content for the bad tool; it is in the repo as scripts/mcp-smoke.mjs and runs in two seconds [20]. Registration with Claude is `claude mcp add xbrowser -- xbrowser mcp` [21].

The framing around all of this is the post's own: "If Claude Desktop, Cursor, or any MCP-capable client can list your tool's capabilities and call them, your tool exists for agents. If not, it doesn't" [22]. The post gives no adoption or usage numbers behind that claim [22].

What to watch

  • Whether xbrowser adds MCP resources, prompts or a non-stdio transport, which is where the hand-written switch statement stops being the cheap option.
  • Whether the smoke script grows past its four JSON-RPC lines to cover a tools/call against a live browser.
  • Whether more of the 57 CLI commands surface as tools, or the seven stay the agent-facing set.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories