MCP surface

The harness ships an embedded MCP server (rmcp 1.7). The built-in hyperhive surface is served over streamable HTTP by a persistent hive-mcp-http daemon (loopback, 127.0.0.1:<hyperhive.mcp.httpPort>, per-container private netns). Claude connects to its stable URL via --mcp-config rather than respawning a stdio child each turn, so the URL survives the per-turn claude re-spawn (and a host-side hive-c0re restart) — there is no per-turn MCP re-registration race for the built-in surface. HTTP is the sole transport for it (no stdio fallback). Extra servers (hyperhive.extraMcpServers) pick their own transport per entry (type = "stdio" | "http", default "stdio"): matrix stays a stdio bridge, bash runs its own persistent streamable-http listener (hive-bash-daemon) — same reasoning as the built-in surface. The server name is hyperhive, so the tools land in claude as mcp__hyperhive__<tool>.

Tool access is gated by tool groups (HIVE_TOOL_GROUPS). The default preset (AGENT_DEFAULT) includes messaging, meta, inbox, and execution. Privileged groups (lifecycle, approvals, scheduling, diagnostics) are opt-in via the P3RM1SS10NS tab.

Core tools (always available)

Messaging (messaging group): send(to, body, in_reply_to?), recv(wait_seconds?, max?), ask(question, options?, multi?, ttl_seconds?, to?), answer(id, answer), ack_until(up_to).

System messages (from sender system): lifecycle and Q&A events delivered as regular inbox messages (same recv path; body is a JSON object with an event discriminant field). The submitting agent (the root agent for top-level containers; an agent with the approvals tool group for its own subtree) receives lifecycle events (spawned, rebuilt, killed, destroyed, container_crash, needs_login, logged_in, config_ready, needs_update, approval_resolved). Any agent receives Q&A events when it is the declared target (question_asked) or the asker (question_answered). Full payload shapes and routing logic in docs/approvals.md § Helper events.

Inbox (inbox group): get_loose_ends(agent?), cancel_loose_end(kind, id), remind(message, delay_seconds? | at_unix_timestamp?), request_next_turn().

Meta (meta group): set_status(text), get_agent_meta(name?).

Always-on, no tool group (like set_status): compact().

Privileged tools (by tool group)

Waking the agent from inside the container

External MCP servers (and any other in-container process) can inject a wake-up event into the agent's inbox via the per-agent socket at /run/hive/mcp.sock. Speak the wire protocol directly — JSON-line over the unix socket: {"cmd":"wake","from":"matrix","body": "new dm from @alice"}\n. Same shape as any other AgentRequest; see hive-sh4re::AgentRequest::Wake. (An earlier hive-agent-wake CLI wrapper existed for this but was removed — no shipped co-process daemon actually shelled out to it; every one that wakes the harness (matrix, bash) dials the socket directly, so the raw protocol is the only path now.)

The wake event lands in the broker as {from:<label>, to:<agent>, body}, waking whatever recv call the harness is currently blocked on. The next turn fires with the wake prompt formed from that message.

Identity = socket: anything that can connect to /run/hive/mcp.sock is implicitly trusted to inject these — the bind-mount is the agent's own container only.

Authoritative state

hive_ag3nt::events::Bus carries the current turn-loop state in addition to the broadcast channel and the events history. Variants:

The harness flips state at the relevant transitions (set_state(Thinking) before drive_turn, set_state(Idle) after; set_state(Compacting) around an idle operator compact in turn::run_pending_compact). Exposed via /api/state.turn_state + turn_state_since (unix seconds); the agent page renders this rather than deriving from SSE events.

Tool envelope

mcp::run_tool_envelope: every MCP tool handler logs the request, runs the body, logs the result. Pre-/post-log only — the inbox status hint moved to the wake prompt + UI header.

Tool whitelist (mcp_config::ALLOWED_BUILTIN_TOOLS)

Bash is disallowed for the autonomous harness — shell execution goes through mcp__bash__run (background tasks with structured output + task-id tracking) instead of an interactive shell. The harness gate is --tools / --allowedTools (Bash absent from ALLOWED_BUILTIN_TOOLS), so Bash never exists in a harness turn regardless of managed settings. Bash is deliberately not in the managed-settings deny so that the operator-driven hivectl choom session — which passes neither --tools nor --allowedTools — gets claude's built-in synchronous Bash tool (inline, human-approved). That sidesteps the async mcp__bash__run completion wake landing in the wrong session (the harness inbox) for a choom-started task (#2356); choom is an operator (root) action, so built-in shell there stays within the existing trust boundary. The bash MCP server (run / status / kill) uses allowedTools = ["*"] so all mcp__bash__* tools are always available regardless of tool groups.

WebFetch / WebSearch are off by default; enable the web_tools tool group in the P3RM1SS10NS tab and rebuild the agent to enable them.