arizuko › reference › MCP tools
MCP tools reference
Every Model Context Protocol tool the arizuko platform registers on a per-group unix socket. Generated from ipc/ipc.go and ipc/inspect.go. The tool set a given session sees is filtered by grants.MatchingRules against the group's rule list — tools whose name doesn't match any rule are not registered. A handful of tools also gate registration on tier (e.g. list_acl is tier 0–1; get_web_presence is tier 0–1).
["*"]). Tier 1 = inner group (full platform-scoped send + group ops). Tier 2 = guest (basic send + folder-scoped routes). Tier 3+ = stranger (reply/send_file/like/edit only). Defaults live in grants/grants.go DeriveRules. Many tools also do inline auth.Authorize checks past the grant filter.
Contents
messages
post tier 0–2
Create a new top-level post on a platform (mastodon toot, bluesky post, discord channel message, reddit submission). Use for broadcast/announcement content that isn't replying to anyone. Not for replies (reply), direct messages (send), or file delivery (send_file).
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Target chat JID; grant-checked + folder-authorized. |
| content | string | yes | Post body. |
| media | array | no | Filepaths under ~/; validated by mountsec. |
Returns: JSON {"ok": true, "id": "<platformId>"}; on adapter "unsupported", returns error text with platform hint.
reply
The default way to respond — use for virtually every answer to the user. Delivers a message threaded to the conversation (quote/reply UI on the platform; on Slack it lands in the thread, not the channel root). Omit replyToId to thread to the current conversation automatically, or pass it to target a specific earlier message. Only reach for send when you deliberately need a fresh top-level message that is NOT a reply.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Target chat JID. |
| text | string | yes | Reply body. |
| replyToId | string | no | Platform message id to thread to; defaults to db.GetLastReplyID(jid, ""). |
Returns: text "ok"; outbound recorded into messages with the returned platform id.
send
Deliver a fresh top-level message that is NOT a reply to the current conversation. Use ONLY when you explicitly need that — a proactive/unprompted notification, or a message to a different chat. For responding to the user (the normal case) use reply, which threads. Not for threaded replies (reply) or file delivery (send_file — its caption replaces this call).
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Target chat JID; grant-checked + folder-authorized via authorizeJID. |
| text | string | yes | Message body. |
Returns: text "ok"; outbound recorded into messages with the platform-returned id.
send_file
Deliver a file from the group workspace (~/) to a chat. Works on every platform whose channel registered the tool. The caption IS the accompanying message — never follow with send. Not for inline text the user can read in-chat.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Target chat JID. |
| filepath | string | yes | Must be under /home/node/ (the group workspace mount). |
| filename | string | no | Display filename in chat. |
| caption | string | no | Message text accompanying the file — this IS the message; don't output separate text. |
Returns: text "ok".
send_voice
Deliver text as a synthesized voice message on the platform — push-to-talk on Telegram/WhatsApp, audio attachment on Discord. voice defaults to PERSONA.md frontmatter or the instance default; pass an explicit name to override.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Target chat JID. |
| text | string | yes | Text to synthesize; non-empty, ≤ 5000 chars. |
| voice | string | no | Voice name (e.g. af_bella for Kokoro). Omit to use PERSONA.md or instance default. |
Returns: JSON {"ok": true, "id": "<platformId>"}; on adapter unsupported, error text with platform hint.
social actions
All registered via the regSocial/socialAct helper at ipc/ipc.go:1114. All take string args; the first arg (or jidArg) is grant-checked + folder-authorized. All adapter errors are rendered through toolMaybeUnsupported, surfacing the platform hint to the agent.
delete
Delete a post/message previously created by this agent (platform enforces authorship). Use to retract an incorrect or superseded post. No edit tool — delete and re-post.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id of message to delete. |
Returns: text "ok".
dislike
Endorse-negative on a message (Discord 👎 reaction). Native only — Mastodon, Bluesky, and most platforms have no native downvote and return unsupported with a hint.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id to downvote. |
Returns: text "ok".
edit
Modify a message previously sent by this agent in-place (Discord, Mastodon, Bluesky, Telegram own bot messages). Preserves the platform message id. Email is unsupported.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id of message to edit. |
| content | string | yes | New content. |
Returns: text "ok".
forward
Redeliver an existing message to a different chat with provenance preserved (Telegram forward, WhatsApp forward, email Fwd:). Not for replying within the same chat (reply) or amplifying on a public feed (repost / quote).
| name | type | req | description |
|---|---|---|---|
| sourceMsgId | string | yes | Platform id to forward. |
| targetJid | string | yes | Destination chat JID; grant-checked. |
| comment | string | no | Optional accompanying text. |
Returns: JSON {"ok": true, "id": "<platformId>"}.
like
Like an existing message (unicode emoji on Discord, favourite on Mastodon, like on Bluesky). Use when acknowledging or endorsing a specific earlier message without sending text. Platform decides what reaction strings are valid; unsupported platforms return an error.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id to react to. |
| reaction | string | yes | Platform-specific reaction (emoji on Discord; like/favorite on Mastodon/Bluesky). |
Returns: text "ok".
quote
Republish a message on your own feed with added commentary (Bluesky quote, X quote-tweet). Native only — Mastodon has no quote primitive and returns unsupported with a hint to use post(content=..., source_url=...).
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Destination feed JID. |
| sourceMsgId | string | yes | Message to quote. |
| comment | string | yes | Your commentary. |
Returns: JSON {"ok": true, "id": "<platformId>"}.
repost
Amplify a message on your own feed without added text (Mastodon boost, Bluesky repost, X retweet). Use to endorse-and-share. Not for commentary (quote) or sending a copy to a different chat (forward).
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Destination feed JID. |
| sourceMsgId | string | yes | Message to amplify. |
Returns: JSON {"ok": true, "id": "<platformId>"}.
pin_message
Pin a message to a chat/channel (Slack pins.add, Discord channel pin, Telegram pinned message). Use to mark a live status surface (deploy progress, standup link) or anchor a reference. Targets any message in the chat — not author-scoped like edit/delete. Mastodon, Bluesky, Reddit, email, WhatsApp return unsupported.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id of the message to pin. |
Returns: text "ok".
unpin_message
Remove the pin on a specific message (Slack pins.remove, Discord channel unpin, Telegram unpinChatMessage). Use to retire a status surface or rotate the pinned reference. Not for clearing every pin (unpin_all) or deleting the message (delete).
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat the message lives in. |
| targetId | string | yes | Platform id of the message to unpin. |
Returns: text "ok".
unpin_all
Clear every pin in a chat/channel (Slack iterates pins.list + pins.remove; Telegram unpinAllChatMessages). Use when wholesale resetting a channel's pinned set. Discord has no bulk primitive — call unpin_message per id.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | Chat whose pins to clear. |
Returns: text "ok".
history & inspection
fetch_history
Pull authoritative conversation history from the channel adapter and cache it. Use to reconstruct context before replying, especially on first contact or after a reset_session. Falls back to local cache if the adapter is down. Not for DB/routing audits (inspect_messages) or single-thread slices (get_thread).
| name | type | req | description |
|---|---|---|---|
| chat_jid | string | yes | Chat to fetch. |
| limit | number | no | Default 100; clamped to (0, 200]. |
| before | string | no | RFC3339 cursor. |
Returns: JSON {messages, count, oldest, source, cap}. source is platform / platform-capped / cache-only / unsupported / cache; messages rendered by router.FormatMessages.
get_thread
Return rows from local messages.db scoped to one thread (chat_jid + topic). Use when a chat fans out into per-topic conversations (Telegram forum topics, web-chat topics).
| name | type | req | description |
|---|---|---|---|
| chat_jid | string | yes | Chat to query. |
| topic | string | yes | Topic / thread key. |
| limit | number | no | Default 50; clamped to (0, 100]. |
| before | string | no | RFC3339 cursor. |
Returns: JSON {messages, count, oldest, source:"local-db"}.
inspect_messages
Return rows from the local messages.db for one chat_jid, including outbound/bot rows and errored entries. Use for routing/delivery audits or to verify what the store recorded.
| name | type | req | description |
|---|---|---|---|
| chat_jid | string | yes | Chat to query. |
| limit | number | no | Default 100; clamped to (0, 200]. |
| before | string | no | RFC3339 cursor. |
Returns: JSON {messages, count, oldest, source:"local-db"}.
find_messages
Full-text search over local messages.db using SQLite FTS5. query takes FTS5 syntax — a bare token, a "quoted phrase", a OR b, a NOT b, prefix*, or NEAR(a b, 5). Use to find a past message by what it said. Not for whole-chat scroll (inspect_messages), single-thread slices (get_thread), or platform-truth fallback (fetch_history).
| name | type | req | description |
|---|---|---|---|
| query | string | yes | FTS5 query — token, phrase, OR/NOT, prefix*, NEAR(...). Malformed syntax surfaces the SQLite error. |
| scope | string | no | One chat_jid (contains :) or a folder subtree (no :; matches routed_to and its children). |
| sender | string | no | Exact match on the sender column. |
| since | string | no | RFC3339 lower bound (inclusive) on timestamp. |
| limit | number | no | Max rows; default 20, clamped to (0, 200]. |
Returns: JSON {messages, count, source:"local-db"}. Each row carries chat_jid, sender, timestamp, content (a snippet() fragment with «» around the hit, not the full body), and rank (BM25 — lower is a better match). Ordered by rank, then newest first.
inspect_routing
Return routes visible to this group plus the errored-chat aggregate; pass jid to also resolve that JID to its folder. Use when a message isn't reaching the expected group, or to triage delivery failures.
| name | type | req | description |
|---|---|---|---|
| jid | string | no | JID to resolve; non-root callers may only resolve JIDs routed to their folder. |
| limit | number | no | Default 50; clamped to (0, 200]. |
Returns: JSON {routes, errored?, jid?, resolved_folder?}.
inspect_session
Return current session_id for this group/topic plus recent session_log rows (message counts, last error, last reset). Use to check whether context is healthy. Not for clearing state (reset_session).
| name | type | req | description |
|---|---|---|---|
| topic | string | no | Topic key; default empty (root topic). |
| limit | number | no | Default 10; clamped to (0, 100]. |
Returns: JSON {folder, topic, session_id, recent}.
inspect_tasks
Return tasks visible to this group; pass task_id for that task's recent run logs (durations, errors). Use to debug why a scheduled prompt didn't fire or failed.
| name | type | req | description |
|---|---|---|---|
| task_id | string | no | Specific task; if omitted, returns the full visible list with no runs. |
| limit | number | no | Default 50; clamped to (0, 200]. |
Returns: JSON {tasks, runs?}.
groups & delegation
delegate_group tier 0–1
Hand a prompt down to a specific child group for async execution; the child runs in its own session and workspace. Depth capped at 1.
| name | type | req | description |
|---|---|---|---|
| group | string | yes | Target child folder. |
| prompt | string | yes | Prompt body delivered as a synthetic message. |
| chatJid | string | yes | JID used as ForwardedFrom provenance. |
| depth | number | no | Caller-passed depth counter; rejected when ≥ 1. |
Returns: JSON {"queued": true}; enqueues a message-check for the target.
escalate_group tier 0–1
Hand a prompt up to this group's parent folder; the parent responds back through this child. Use when the request exceeds this group's authority or needs operator review. Depth capped at 1.
| name | type | req | description |
|---|---|---|---|
| prompt | string | yes | Prompt to bubble up. |
| chatJid | string | yes | Originating chat JID (for reply_to in the wrap). |
| depth | number | no | Caller-passed depth counter; rejected when ≥ 1. |
Returns: JSON {"queued": true, "parent": "<folder>"}.
refresh_groups tier 0–2
Return folder/name/parent for every registered group. Use to discover delegation targets or audit the group tree.
Params: none. Returns: JSON array of {folder, name, parent?}.
register_group tier 0–1
Create a child agent group and route a JID to it. Use when onboarding a new chat into its own isolated workspace, or when spinning up a sub-agent from this group's prototype/ (fromPrototype=true).
| name | type | req | description |
|---|---|---|---|
| jid | string | yes | Source chat JID to route into the new group. |
| name | string | no | Display name; defaults to the JID. |
| fromPrototype | boolean | no | If true, spawn a child via SpawnGroup(parentFolder, jid); folder is derived. |
| folder | string | no | Required when fromPrototype=false. Target folder path. |
| parent | string | no | Parent folder (for tree). |
Returns: JSON {"registered": true, "folder": "<f>", "jid": "<jid>"}; SetupGroup seeds skills/settings/tasks.
set_group_open tier 0–1
Toggle this group's visibility to sibling folders. When open=true, sibling folders' ambient messages surface in this group's <observed> block (and this group's messages surface to open siblings). Default: open. Use to seal off a sensitive folder from cross-folder ambient context. Spec: 7/F. Concept: scopes.
| name | type | req | description |
|---|---|---|---|
| open | bool | yes | true to expose to siblings, false to seal off. |
Returns: JSON {"ok": true, "open": <bool>}.
set_observe_window
Override this group's ambient observe-window caps (messages and/or chars). Per-group caps win over instance env defaults (OBSERVE_WINDOW_MESSAGES, OBSERVE_WINDOW_CHARS); per-route caps still win over both. Pass -1 to clear an override; omit a field to leave it unchanged. Spec: 7/F.
| name | type | req | description |
|---|---|---|---|
| messages | number | no | Max ambient messages surfaced per turn; -1 clears override. At least one of messages / chars must be present. |
| chars | number | no | Max ambient chars per turn; -1 clears override. |
Returns: JSON {"ok": true, "messages": <n>, "chars": <n>}.
sessions & work
get_work
Read this group's work.md — current work, blockers, next steps. Use at the start of a turn to recover what was in-flight. Returns empty content when the file doesn't exist.
Params: none. Returns: JSON {content, exists}.
inject_message
Write a synthetic inbound message into the store as if received from chat, triggering the normal agent loop. Use for programmatic prompts, tests, or scheduling one-off runs from tool code.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | JID the synthetic message is bound to. |
| content | string | yes | Message body. |
| sender | string | no | Defaults to "system". |
| senderName | string | no | Defaults to "system". |
Returns: JSON {"injected": true, "id": "<mid>"}.
fork_topic
Branch a topic from another’s current state. The gateway copies the parent’s Claude Code session file to a fresh session_id, so the child resumes natively with the parent’s history already in its session — no prompt-injection block. Use when starting a focused side-conversation that needs the parent’s recent state but should not pollute it. Pass force=true to overwrite an existing child. Spec: 7/F.
| name | type | req | description |
|---|---|---|---|
| parent | string | yes | Parent topic to fork from ("" = main). |
| child | string | yes | New child topic name. |
| force | bool | no | Overwrite the child if it already exists. |
Returns: JSON {"folder", "parent_topic", "child_topic"}. Error "topic_exists" when child exists and force=false.
pane_set_prompts
Slack only — stage suggested-prompt buttons shown at the bottom of the assistant pane after your next reply lands. Fire-and-forget; the buttons appear once and persist until your next call. Use after a reply when you can anticipate the user’s likely follow-ups (e.g. “dig deeper”, “summarise”, “export”). 3-4 prompts is the visible cap. Adapters without pane semantics return chanlib.ErrUnsupported. Spec: 7/D. Concept: slack pane.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | JID of the pane DM channel (slack DM). |
| prompts | [{title, message}] | yes | Title shows on the button; message is sent as user input on click. |
Returns: text "ok".
pane_set_title
Slack only — override the title shown at the top of the assistant pane. Fires after your next reply lands. Use to reflect the active topic (e.g. “atlas — debugging the build”). Defaults to <assistant> — chat when never set. Spec: 7/D.
| name | type | req | description |
|---|---|---|---|
| chatJid | string | yes | JID of the pane DM channel. |
| title | string | yes | Title (max 256 chars; non-empty). |
Returns: text "ok".
engage
Mark (jid, topic) engaged for the ENGAGEMENT_TTL window so subsequent inbounds fire even when the route table wouldn't route them here. Use before a scheduled or autonomous turn, or to recover after a failed reply. Caller folder must already own the conversation (last reply here, or default route here). Spec: 5/G.
| name | type | req | description |
|---|---|---|---|
| jid | string | yes | Chat JID to engage. |
| topic | string | no | Topic to engage. Defaults to main topic when omitted. |
Returns: JSON {"ok": true}.
disengage
Clear engagement for (jid, topic). Subsequent inbounds need a fresh mention to re-fire. Use when the bot is done helping or when a corrective fork is closing. Caller folder must own the conversation. Spec: 5/G.
| name | type | req | description |
|---|---|---|---|
| jid | string | yes | Chat JID to disengage. |
| topic | string | no | Topic to disengage. Defaults to main topic when omitted. |
Returns: JSON {"ok": true}.
reset_session
Drop the Claude session for a group so the next message starts fresh context. Use when the user asks for /new, when context is confused/polluted, or before a topic switch. Not for injecting content (inject_message).
| name | type | req | description |
|---|---|---|---|
| groupFolder | string | yes | Folder whose session to clear. |
Returns: text "ok".
set_work tier 0–2
Overwrite this group's work.md with a fresh snapshot of current work, blockers, and next steps. Use at turn end to checkpoint state. This replaces the file — read with get_work first if merging.
| name | type | req | description |
|---|---|---|---|
| content | string | yes | New work.md contents. |
Returns: text "ok".
scheduling
cancel_task tier 0–1
Permanently delete a scheduled task. Use when the task is no longer wanted. Not for temporary suspension (pause_task) — this cannot be undone.
| name | type | req | description |
|---|---|---|---|
| taskId | string | yes | Task to delete. |
Returns: JSON {"ok": true}.
list_tasks tier 0–1
Return scheduled tasks visible to this group. Use for a plain task dump; prefer inspect_tasks when you also want task_run_logs or per-task history.
Params: none. Returns: JSON array of core.Task.
pause_task tier 0–1
Mark a scheduled task paused so it stops firing but is preserved. Use when suspending a task temporarily.
| name | type | req | description |
|---|---|---|---|
| taskId | string | yes | Task to pause. |
Returns: JSON {"ok": true}.
resume_task tier 0–1
Re-activate a paused task so it resumes firing on its schedule. Use to undo pause_task. No effect on already-active or cancelled tasks.
| name | type | req | description |
|---|---|---|---|
| taskId | string | yes | Task to resume. |
Returns: JSON {"ok": true}.
schedule_task tier 0–1
Create a scheduled prompt that fires against a target chat. cron accepts a 5-field cron expression, an integer millisecond interval, or an RFC3339 one-shot timestamp.
| name | type | req | description |
|---|---|---|---|
| targetJid | string | yes | Chat the task fires against; folder derived via DefaultFolderForJID. |
| prompt | string | yes | Prompt body delivered at fire time. |
| cron | string | no | 5-field cron, millis interval, or RFC3339 one-shot. |
| contextMode | string | no | group (default) or isolated. |
Returns: JSON {"taskId": "task-<ms>-<uuid8>"}.
routing
add_route tier 0–1
Append one routing rule. Preferred over set_routes for everything except full rewrites.
| name | type | req | description |
|---|---|---|---|
| route | string (JSON) | yes | JSON object: seq (int), match (key=glob pairs; keys: platform, room, chat_jid, sender, verb), target (folder path, or folder: / daemon: / builtin: prefix). |
Returns: JSON {"id": <routeId>}.
delete_route tier 0–1
Remove one routing rule by id. Use after list_routes/inspect_routing to surgically drop a rule. Cannot delete the caller's own default route (seq=0, target=own folder).
| name | type | req | description |
|---|---|---|---|
| id | number | yes | Route id. |
Returns: JSON {"deleted": true, "id": <id>}.
list_routes tier 0–1
Return the routing table rows this group can see. Use for a raw route dump; prefer inspect_routing when you also want JID→folder resolution or errored-chat context.
Params: none. Returns: JSON {"routes": [...]}.
set_routes tier 0–1
Bulk-overwrite the full routing table for this folder subtree. Use only for wholesale reconfiguration; prefer add_route/delete_route for targeted edits.
| name | type | req | description |
|---|---|---|---|
| routes | string (JSON) | yes | JSON array of route objects (same shape as add_route). |
Returns: JSON {"updated": true, "count": <n>}.
egress
Manage the egress allowlist crackbox enforces. The proxy is default-deny — a folder only reaches hosts it (or an ancestor) has been allowed. Rules cascade down the tree: a rule at a parent folder opens the host for every descendant. All three gate on auth.AuthorizeStructural: tier 0 (root) is unrestricted; tier 1 is confined to its own folder and descendants; tier 2+ cannot manage egress. Backed by the network_rules table. Spec: 5/5 (egress_allowlist). Agent-only by design — there is no REST twin, and network_rules isn’t mounted at /v1/network_rules or listed in routd’s /openapi.json.
network_allow tier 0–1
Append an allowlist rule so folder and every descendant can reach host. Use when an agent needs a host the default-deny proxy blocks — a vendor API, say. host is a bare hostname (no scheme, no path), e.g. example.com. The rule cascades to all children.
| name | type | req | description |
|---|---|---|---|
| folder | string | yes | Folder the rule applies to; covers this folder and all descendants. |
| host | string | yes | Bare hostname, e.g. example.com — no scheme or path. |
Returns: JSON {"allowed": true, "folder": "<f>", "host": "<host>"}.
network_deny tier 0–1
Remove an allowlist rule set on folder itself, closing access to host. Only drops a rule owned by this folder — a host inherited from an ancestor must be removed at that ancestor, not here.
| name | type | req | description |
|---|---|---|---|
| folder | string | yes | Folder the rule was set on. |
| host | string | yes | Host to remove from this folder's own rules. |
Returns: JSON {"denied": true, "folder": "<f>", "host": "<host>"}.
network_list tier 0–1
Read the egress allowlist for folder. resolved is every host the folder can reach — its own rules, those inherited from ancestors, and the instance base. own is only the rules set on folder itself. Use to see why a host is reachable (own rule vs inherited) before adding or removing one.
| name | type | req | description |
|---|---|---|---|
| folder | string | yes | Folder to read. |
Returns: JSON {"folder": "<f>", "resolved": [...], "own": [...]}.
acl
list_acl tier 0–1
Audit the unified acl rows whose scope equals the target folder. Subsumes the pre-v0.38.0 get_grants/set_grants surface; writes now happen via arizuko grant or the dashd UI, not over MCP.
| name | type | req | description |
|---|---|---|---|
| folder | string | yes | Folder to read. |
Returns: JSON {"folder": "<f>", "acl": [{principal, action, scope, effect, params, predicate}, ...]}.
add_acl tier 0–1
Grant a principal access to a folder scope (writes an acl row). scope='**' confers the operator role. You can only grant within your own authority. Not for routes (add_route) or invites (invite_create). REST twin: POST /v1/acl.
| name | type | req | description |
|---|---|---|---|
| principal | string | yes | Subject receiving access, e.g. user:alice. |
| scope | string | yes | Folder path or ** for operator role. |
| action | string | no | Grant action; defaults to the authd GrantACL default. |
| effect | string | no | Grant effect; defaults to allow. |
Returns: JSON {"ok": true, "principal": "<p>", "scope": "<s>"}.
remove_acl tier 0–1
Revoke a principal's access to a folder scope (drops the acl row). scope='**' revokes the operator role. You can only revoke within your own authority. REST twin: DELETE /v1/acl.
| name | type | req | description |
|---|---|---|---|
| principal | string | yes | Subject whose access to remove. |
| scope | string | yes | Folder path or **. |
| action | string | no | Action filter for the row to remove. |
| effect | string | no | Effect filter for the row to remove. |
Returns: JSON {"ok": true, "principal": "<p>", "scope": "<s>"}.
invites
invite_create tier 0–1
Issue an invite token granting access to a path glob. The recipient accepts via /invite/<token> and gets an acl(admin, target_glob) row. The agent's authority must cover target_glob — you can't issue access you don't have.
| name | type | req | description |
|---|---|---|---|
| target_glob | string | yes | Path glob the invite confers access to. |
| max_uses | number | no | Default 1; min 1. |
| expires_at | string | no | RFC3339 timestamp. |
Returns: JSON {token, target_glob, max_uses, expires_at?, accept_url?}.
invite_list tier 0–1
List the invite tokens this agent has issued — token, target glob, expiry, use count. Read-only; shows only this folder's invites. Use to audit outstanding invites before revoking. REST twin: GET /v1/invites.
Params: none. Returns: JSON {"invites": [{token, target_glob, max_uses, used_count, expires_at?}, ...]}.
invite_revoke tier 0–1
Revoke an invite token you issued so it can no longer be redeemed. You may only revoke tokens issued by your own folder. Use invite_list to find your tokens. REST twin: DELETE /v1/invites/{token}.
| name | type | req | description |
|---|---|---|---|
| token | string | yes | Token to revoke (from invite_list or invite_create). |
Returns: JSON {"ok": true, "token": "<t>"}.
web routes & web presence
Each group folder has a derived public hostname (<folder>.<HOSTING_DOMAIN>) that 302-redirects to its /pub/<folder>/ slot. No vhosts.json, no per-host config — the host is purely folder + "." + HOSTING_DOMAIN. An explicit WEB_VHOST_ALIASES entry overrides for the rare case where the folder label and the desired hostname differ. Use get_web_presence to discover the canonical host. The web_routes table (managed by set_web_route/del_web_route) is the inner layer: it controls access and redirects within /pub/<folder>/ after the vhost 302 lands.
del_web_route
Delete a web route by path. Only routes owned by this folder may be deleted (operators can delete any).
| name | type | req | description |
|---|---|---|---|
| path | string | yes | Path prefix to remove. |
Returns: text "ok", or error if not found / not owned.
get_web_presence tier 0–1
Report a folder's public web presence: derived canonical hostname, public/private base URLs, and the /pub/<folder>/ path that works even without HOSTING_DOMAIN. Read-only. Tier-1+ may only query own folder or descendants; tier-0 any. REST twin: GET /v1/web_presence?folder=<f>.
| name | type | req | description |
|---|---|---|---|
| folder | string | no | Folder to query; defaults to caller's folder. |
Returns: JSON {hosting_domain, derived_host, alias_host?, canonical_host, public_base_url, private_base_url, pub_path}. pub_path is the vhost-independent canonical path.
list_web_routes
List all web routes owned by this folder.
Params: none. Returns: JSON array of {path_prefix, access, redirect_to, folder, created_at}.
set_web_route
Upsert a web route: control whether a URL path is public, auth-gated, denied, or redirected. Scoped to the caller's folder.
| name | type | req | description |
|---|---|---|---|
| path | string | yes | URL path prefix; must start with /. |
| access | string | yes | One of public / auth / deny / redirect. |
| redirect_to | string | no | Required when access=redirect. |
Returns: text "ok".
identity
inspect_identity
Resolve a platform sender sub (e.g. tg:123, discord:abc) to its canonical identity and the full set of subs claimed by that identity. Advisory only — agents query, never enforce. Returns {identity:null, subs:[]} when the sub is unclaimed.
| name | type | req | description |
|---|---|---|---|
| sub | string | yes | Platform sender sub to resolve. |
Returns: JSON {sub, identity, subs}.
system / JSON-RPC
submit_turn
Per-turn agent result delivery. Not a tool — a JSON-RPC method on the same MCP unix socket. Hidden from tools/list; the agent loop calls it directly with the turn payload. Idempotency enforced by routd on (folder, turn_id).
| name | type | req | description |
|---|---|---|---|
| turn_id | string | yes | Unique per-turn id; enforces dedupe. |
| session_id | string | no | Claude session id. |
| status | string | no | success or error. |
| result | string | no | Final result text (on success). |
| error | string | no | Error text (on failure). |
Returns: JSON-RPC {"result": {"ok": true}}, or a JSON-RPC error.
cost accounting
log_external_cost
Record one non-Anthropic LLM call against the folder's daily budget. Call this AFTER invoking an external model (e.g. codex exec --json for /oracle). Anthropic costs are captured automatically via submit_turn — this tool is for everything else the agent shells out to. Skipping it hides the call from cost-caps; the only operator-visible drift becomes the provider's own invoice. Spec 5/34. Registration is conditional on db.LogExternalCost being wired.
| name | type | req | description |
|---|---|---|---|
| provider | string | yes | openai, codex, other. |
| model | string | yes | Model identifier, e.g. gpt-5, codex-mini. |
| cost_usd | number | yes | USD cost reported by the provider; gateway rounds to cents. |
| input_tokens | number | no | Input token count (0 if unknown). |
| output_tokens | number | no | Output token count (0 if unknown). |
Returns: JSON {"ok": true, "cents": N}.
external REST tools
Beyond the hand-written MCP tools above, arizuko can expose a REST API as an MCP tool from a declarative [[ext]] TOML descriptor — no Go code per endpoint. A descriptor maps a tool name to one HTTP call; when the agent invokes it, routd makes the request directly on the host, injects the credential from the secrets table, and hands back only the response body. The token never enters the container. Loader: routd/ext.go; dispatcher: ipc/extcall.go CallExtTool.
the descriptor
Each provider is one [[ext]] block: a base URL, an [ext.auth] method that names the secret-store key to inject, and one [[ext.tool]] per endpoint. The tool's MCP name is <provider>_<tool> (e.g. cloudflare_dns_list). Path params in {braces} are filled from the call arguments; remaining args become the query string on GET/DELETE or the JSON body on POST/PUT/PATCH.
# connectors.toml (operator) or a built-in extproviders/*.toml
[[ext]]
name = "cloudflare"
base = "https://api.cloudflare.com/client/v4"
[ext.auth]
method = "bearer"
secret = "CF_API_TOKEN"
[[ext.tool]]
name = "dns_list"
description = "List DNS records for a Cloudflare zone."
scope = "ext:cloudflare:dns:read"
method = "GET"
path = "/zones/{zone_id}/dns_records"
credential injection
The [ext.auth] method decides how the resolved secret reaches the upstream. Five shapes, all in CallExtTool:
| method | where the secret goes |
|---|---|
| bearer | Authorization: Bearer <secret> |
| apikey-header | a named header (header = "X-Api-Key") |
| apikey-query | a named query param (param = "apikey") |
| basic | Authorization: Basic base64(<secret>) |
| json-body | injected into the request body under a named key (two keys via secret2/header2) |
The secret value is looked up per call for the triggering user (their own key overrides the folder default), never held in the descriptor, and scrubbed out of the response text before it returns to the agent.
built-in DNS providers
arizuko ships descriptors for four DNS registrars, ready to use once the operator sets the provider's token: Cloudflare (CF_API_TOKEN), Porkbun, Gandi, and Namecheap. Each covers the list/create/delete DNS operations. Operators add their own providers by dropping an [[ext]] block into connectors.toml in the instance dir.
grant gating
Every [[ext.tool]] carries a scope of the form ext:<service>:<operation> (e.g. ext:cloudflare:dns:write). Registration is grant-filtered like every other tool: the tool joins tools/list only when Authorize(folder, scope) passes, so an agent sees an external tool only where its folder is granted that scope. A call-time grant check stays as defense in depth.
notes & gotchas
- Registration is filtered by grants.
registerRawconsultsgrants.MatchingRules(rules, name); if no rule matches the tool name, the tool is never added totools/list. The matched rules are appended to the tool description asgrants: [...]so the agent sees what it has been granted. - Tier-gated registration on top of grants.
refresh_groups(tier ≤ 2),set_work(tier ≤ 2), andlist_acl(tier ≤ 1) wrap theAddToolcall in a tier check — even at tier 0 with rules["*"], tier-bracketing trumps glob match. - Inline auth.Authorize past the grant filter. Most JID-targeted tools call
authorizeJIDafter the grant check: a tier ≥ 1 caller cannot dispatch to a JID whoseDefaultFolderForJIDresolves outside the caller's folder subtree, even when grants would otherwise permit it. - The secret broker is live. A connector or
[[ext]]REST tool that needs a capability token has it resolved on the host at call time for the triggering user (their own key overrides the folder default), so the token never enters the container. Registration itself is per-call:registerRaw,granted,regSocial, rawsrv.AddTool. - Optional registration paths. Several tools are nil-guarded on the backing store function and silently absent if not wired:
inspect_routing,inspect_tasks,inspect_session,inspect_identity,inspect_messages,find_messages(requiresdb.FindMessages),get_thread,fetch_history, all*_tasktools (requiredb.GetTask). - Cold-tier resources are a separate surface. Groups,
acl, routes,web_routes, secrets, scheduled tasks and the like are not per-group MCP tools — they are CRUD resources driven by theresregengine and reachable over REST, MCP, YAML manifests, and/openapi.jsonfrom one struct. The registry lives inroutd. See OpenAPI. - Route tools are granted at tier 1.
tier1FixedActionslistslist_routes,set_routes,add_route,delete_route— the names match the registered tools, so tier-1 callers get them by default. Seegrants/grants.go:160.