reticle mcp, and the tools appear in the agent’s surface. That has one property you cannot always live with. Many clients read their MCP tool list once, at startup. If the daemon was not running when the client launched, the reticle_* tools are absent for the rest of that session, and the fix is to restart the client.
The daemon also speaks MCP over HTTP, on the same port as everything else. Nothing has to reload for a client to reach it, so this is the transport for scripted runs, CI, a language with no MCP client library, and any editor that will not pick up tools mid-session.
It is the same tool surface. Not a subset, not a simplified one. reticle_snapshot, reticle_act_and_wait, reticle_assert and the rest behave exactly as they do over stdio, because they are the same server behind a different transport.
The two endpoints
The daemon listens on port 4400 by default (
RETICLE_PORT, or reticle serve --port N). Run reticle status to confirm which port is live.
The handshake
The one shape worth reading before you write any code: the POST does not answer your request. It replies202 Accepted with no result in it, and the actual JSON-RPC reply arrives on the SSE stream you opened first. A client that waits on the POST body waits forever.
-
GET /mcp/sse. Hold the response open. -
The first frame is the endpoint announcement, and it carries the session id:
Use that
datavalue verbatim as the path you POST to. Do not build it yourself: the session id is minted per connection. -
POSTtheinitializerequest there, then thenotifications/initializednotification. -
POSTtools/list,tools/call, and anything else. Match each reply to its request by the JSON-RPCid, off the SSE stream.
Errors
404 and 400 are kept apart on purpose: reconnecting fixes one and nothing about the other.
Authorization
Two tiers, the same ones the WebSocket bridge uses. Local clients need no token. A request is trusted when its peer address, itsHost header, and its Origin/Referer (when present) are all loopback. All three are required, because a DNS-rebound page reaches the daemon as a loopback peer while carrying the attacker’s Host, so peer address alone would not be a check.
Anything else must present the pairing token, which is required whenever you bind beyond loopback with RETICLE_HOST. Either form works:
RETICLE_TOKEN. With no token configured, the daemon binds loopback-only and non-local requests are refused outright rather than falling back to trust.
A minimal client
No MCP library. Node’s standard library is enough. This opens a session, lists the tools, and takes a snapshot.curl is enough for the first half:
202 comes back here; the tool list appears in the first shell.
What this does not change
The transport carries the tools. It does not replace the rest of the setup. The app still has to be instrumented and connected, andreticle_sessions is still what tells you whether a session is there to drive. A verdict reached over HTTP is a verdict reached the usual way: only verified: "yes" is a pass.
The endpoints, the handshake, the three status codes and the 202-then-SSE shape are pinned by packages/server/src/mcp-http-transport.test.ts, which drives them over raw HTTP with no client library, so the contract this page describes fails the build if it changes.