Documentation
¶
Overview ¶
Package server — HTTP gateway (ADR-014 Phase 2, v0.11).
`clawtool serve --listen :8080 --token-file <path>` mounts a thin HTTP surface that proxies prompts to the supervisor and exposes the agent registry. Every dispatch goes through Supervisor.Send (same call site as the CLI / MCP). Auth is bearer-token at the edge — non-negotiable; the relay opens an exec-arbitrary-code-on-host surface.
TLS is not terminated here. Operators front this with nginx / caddy / Cloudflare Tunnel — we do not invent a cert story (see ADR-014 Rationale).
MCP-over-HTTP Accept-header content negotiation shim.
The mark3labs/mcp-go StreamableHTTPServer (v0.49.0) always replies to a single-response /mcp POST with `Content-Type: application/json` and a bare JSON-RPC body, regardless of the client's Accept header (see streamable_http.go:546 in that release).
rmcp (the Rust MCP SDK that codex's HTTP client is built on) opens initialize with `Accept: text/event-stream` only. When the upstream answers with raw JSON the rmcp parser tries to decode the body as SSE-framed (`data: <json>\n\n`), finds no `event:` lines, and surfaces the misleading
Deserialize error: data did not match any variant of untagged enum JsonRpcMessage when send initialize request
MCP Streamable-HTTP (2025-06-18) says the server SHOULD honor the client's Accept by responding with `text/event-stream` framed as `data: <json>\n\n`; a single SSE event is a valid response shape.
mcpAcceptShim wraps the streamable handler and post-processes the outgoing response when the client asked for SSE — buffer the `application/json` body the inner handler emits, then write it back as a single `data: ...\n\n` SSE event with the right Content-Type. When the inner handler already chose `text/event-stream` (multi-event drain path, the upgradedHeader branch in mcp-go) we pass through unchanged.
Package server — `/v1/peers` REST surface (ADR-024 Phase 1).
Four endpoints, all bearer-authed by the same authMiddleware every other /v1/* path uses:
GET /v1/peers — list with status / backend / circle / path filters
POST /v1/peers/register — body: a2a.RegisterInput; returns the assigned Peer
POST /v1/peers/{peer_id}/heartbeat — refresh last_seen + status
DELETE /v1/peers/{peer_id} — explicit deregister on session end
Wire shape mirrors prassanna-ravishankar/repowire's /peers + /peers/by-pane endpoints so an existing repowire dashboard can be re-pointed at a clawtool daemon with a one-line URL change. Difference: clawtool's auth model is bearer-token (the daemon-wide token in ~/.config/clawtool/listener-token), not repowire's per-peer auth_token; we already have the daemon-shared token so a second layer is unnecessary at this phase.
Registry lifecycle: the handlers fetch a2a.GetGlobal() on every request. buildMCPServer's Phase-1 boot installs a registry into the global slot (with persistence at ~/.config/clawtool/peers.json); daemon shutdown clears it. Handlers return 503 when the global is nil so a misconfigured boot doesn't 500 — operator gets a clear "registry not initialised" hint instead.
Package server starts the clawtool MCP server.
Per ADR-004, clawtool exposes itself as one MCP server over stdio. Per ADR-006, core tools use PascalCase names (Bash, Read, Edit, ...). Per ADR-008, configured sources spawn as child MCP servers and their tools are aggregated under `<instance>__<tool>` wire names.
Boot order on every `clawtool serve`:
- Load config + secrets.
- Build sources.Manager and start each configured source. Failures on individual sources are non-fatal; their tools just don't show up.
- Build a search.Index from descriptors of every tool we plan to register: enabled core tools + ToolSearch + aggregated source tools. This index powers the ToolSearch primitive — see ADR-005 for why search-first is the prerequisite that lets a 50+ tool catalog scale.
- Register all tools on the parent MCP server. ToolSearch closes over the index reference; aggregated source-tool handlers route via the manager.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InitTokenFile ¶ added in v0.20.0
InitTokenFile generates a fresh 32-byte (256-bit) hex token and writes it to path with 0600. Used by `clawtool serve init-token` and by tests that need a working credential.
func ServeHTTP ¶ added in v0.20.0
func ServeHTTP(ctx context.Context, opts HTTPOptions) error
ServeHTTP runs clawtool as an HTTP gateway. Blocks until the listener returns. Mirrors ServeStdio's lifecycle: build the MCP server (so the same agents/recipes/tools are available), then route HTTP requests through it.
MCP-over-HTTP (`--mcp-http`) mounts the full toolset at /mcp via mark3labs/mcp-go's StreamableHTTPServer (the persistent shared daemon every host fans into; see internal/daemon).
func ServeStdio ¶
ServeStdio runs clawtool as an MCP server speaking over stdio. It blocks until stdin closes (the conventional MCP shutdown signal) or an unrecoverable error occurs.
Types ¶
type HTTPOptions ¶ added in v0.20.0
type HTTPOptions struct {
Listen string // ":8080" or "0.0.0.0:8080" — passed to http.ListenAndServe.
TokenFile string // path to a 0600 file containing the bearer token. Refused if missing/empty unless NoAuth is set.
MCPHTTP bool // when true, mount the MCP toolset at /mcp via mcp-go's Streamable HTTP transport.
// NoAuth runs the listener without bearer-token enforcement. The
// shared local daemon flips this on by default — the operator's
// machine is the trust boundary, codex / gemini hit /mcp over
// loopback without pre-setting CLAWTOOL_TOKEN. Daemon / relay
// deployments (multi-user, exposed beyond loopback) keep auth on
// by leaving NoAuth false and supplying TokenFile.
NoAuth bool
}
HTTPOptions configures the listener.