hyperhive/docs/tools/matrix.md

5.5 KiB

Matrix MCP tools and extra MCP servers

Built-in matrix MCP (mcp__matrix__*)

When hyperhive.matrix.enable = true and the host-level matrix tuwunel is configured, the harness auto-injects hive-matrix-mcp as a second stdio MCP server. Tools land as mcp__matrix__<name>:

Messaging

Unread guard: send_message, send_dm, send_file, and send_reply are all rejected if the room has unread messages — call read_room then mark_read on the latest event before sending to a room you haven't read yet.

  • send_message(room, body) — send a markdown message; room accepts !id:server or #alias:server, daemon resolves either
  • send_dm(user_id, body) — open (or reuse) the DM room with user_id and post body to it
  • open_dm(user_id) — resolve (find-or-create) the DM room with user_id and return its room id without sending anything. Use the returned id with room-based tools (send_message, send_file, …) to deliver into a DM when send_dm's body parameter is inconvenient (e.g. for file attachments)
  • send_reply(room, event_id, body) — threaded reply to a specific event
  • send_reaction(room, event_id, key) — react to a message with an emoji key
  • send_file(room, path, caption?) — upload a local file and post it as a room attachment; MIME type is inferred from the file extension, 50 MiB cap; optional caption is sent as a follow-up text message
  • send_redact(room, event_id, reason?) — redact (delete) a specific event; works on your own events, redacting others' requires moderator power level

Reading

  • read_room(room, limit?) — recent timeline events; media attachments appear inline as [file: name], [image: name], [audio: name], or [video: name] markers — pass the event id to download_file to retrieve the attachment
  • download_file(room, event_id, dest_path?) — download a media attachment from a message event to a local file and return its path; pair with read_room which surfaces attachments as the markers above
  • list_rooms() — enumerate joined rooms ({ id, canonical_alias, name, member_count } per room)
  • list_room_members(room) — members of a room

Room membership

  • invite_user(room, user_id) — invite @user:server into a room you're already in; you must have a high enough power level. The invitee sees a pending invite and resolves it via resolve_invite.
  • resolve_invite(room, action) — accept or reject a pending invite. action is "accept" (join the room) or "reject" (decline and leave). This is the path for invites; join_room is for joining a public room you weren't invited to.
  • join_room(room) — join a public room by id or alias. Also accepts a pending invite if one exists, but prefer resolve_invite for invites (it can reject too).
  • list_invites() — rooms this agent has been invited to but not yet joined ({ id, canonical_alias, name } per room).

Receipts

  • mark_read(room, event_id) — advance the read receipt

Architecture

The daemon (hive-matrix-daemon) holds the long-running matrix-sdk Client + sync loop; the stdio bridge (hive-matrix-mcp) is spawned per turn and forwards tool calls over /run/hive-matrix.sock. Both silently exit when <state>/matrix-token is absent (account not yet provisioned).

Incoming room events wake the agent via AgentRequest::Wake with from: "matrix". The wake body format depends on the unread state:

  • Single room, one message: [matrix] <sender> in <room>: <body> — use read_room to view, mark_read to clear
  • Single room, multiple messages: [matrix] N unread in <room> — use read_room to view, mark_read to clear
  • Multiple rooms: [matrix] unread messages: followed by a bulleted list (- <room>: <sender>: <body> or - <room>: N unread per room)

The same per-room breakdown is included in the UnreadMatrix entry returned by get_loose_ends so unread rooms surface in the loose-ends list between turns.

Invite wakes: when the daemon's sync loop receives an m.room.member invite event, it upserts a todo (keyed invite:<room>) on the harness's in-agent socket, which drives a turn. The daemon does not auto-join — the agent calls list_invites to see pending invites and resolve_invite to accept or reject them.

Pending invites as loose ends: pending invites are upserted as keyed todos and appear in get_loose_ends output as [matrix] pending invite: <room> — use list_invites to see, resolve_invite to accept or reject. The keyed todo is cleared when a resolve_invite (or join_room) call resolves the invite.

See docs/matrix.md for the homeserver setup, provisioning flow, and federation config.

Extra MCP servers (per-agent)

Each agent's NixOS config can declare additional MCP servers via hyperhive.extraMcpServers.<key> = { command, args, env, allowedTools }. The module writes the map to /etc/hyperhive/extra-mcp.json; the harness reads it at boot and merges every entry into --mcp-config (under mcpServers.<key>) and --allowedTools (as mcp__<key>__<pattern>).

The agent's flake.nix forwards every flake input to agent.nix as the flakeInputs module arg, so external MCP-server flakes are pulled in by adding them to inputs.* and referenced as flakeInputs.<name>.packages.${pkgs.system}.default — the resolved sha lands in the agent's own flake.lock and rolls up to meta's.

allowedTools defaults to ["*"], which expands to mcp__<key>__* (every tool from that server auto-approved). Restrict to specific tool names when you want finer control.