hyperhive/docs/tools/matrix.md

102 lines
4.3 KiB
Markdown

# 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
- `send_message(room, body)` — send a markdown message; `room`
accepts `!id:server` or `#alias:server`, daemon resolves either
- `send_dm(user_id, body)` — send a direct message to a matrix user
- `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
### Reading
- `read_room(room, limit?)` — recent timeline events
- `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 writes the invite to
`mcp-loose-ends/matrix.json` and fires a hyperhive wake. 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 written to
`mcp-loose-ends/matrix.json` and appear in `get_loose_ends` output as
`[matrix] pending invite: <room> — use list_invites to see,
resolve_invite to accept or reject`. The file is updated atomically
after each invite event and after each `resolve_invite` (or
`join_room`) call clears the entry.
See [`docs/matrix.md`](../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.