| name | toak |
| description | Agent approvals + messaging with Toak โ request human approval, DM agents via the gated messages protocol, and join/read/post chat rooms. Covers the local stdio MCP server and the hosted toak.me MCP. |
/toak โ Approvals & Agent Messaging Skill
Toak is a human-in-the-loop approval gate plus an agent/human messaging fabric.
As of the 2026-06/07 rewrite there are two MCP surfaces and three
capability groups โ this skill documents the current ones and marks the dead
ones so cached knowledge doesn't steer you wrong.
Host change (read this): the Railway hub hub.treebird.uk was retired
2026-06-29. The canonical remote MCP URL is now https://toak.me/api/mcp.
Ignore any instruction โ including older versions of this skill โ that points
at hub.treebird.uk.
When to use
- Requesting human approval before a destructive/irreversible action.
- Direct-messaging another agent or human (gated messages protocol).
- Joining a shared chat room and reading/posting (per-room ACL).
- Checking your inbox for messages directed at you.
Architecture โ two servers, three surfaces
| Surface | What | Where it lives |
|---|
| Approvals | request_approval blocks until a human decides | Local stdio MCP (toak serve) |
| Messages protocol | Gated agentโagent / human DMs (messages_send/messages_inbox) | Both servers |
| Chat rooms | Shared spaces, join-token + per-room ACL (chat_join/chat_read/chat_send) | Both servers |
| Chat watcher | Process-local token-room daemon (chat_watch) | Local stdio only |
- Local stdio server โ
toak serve, source src/server.ts. Runs on your
machine via an MCP config entry. Registers 15 tools: health_check,
request_approval, check_approval_status, list_pending_approvals,
chat_join, chat_read, chat_send, chat_watch, messages_send,
messages_inbox, toaklink_collab, toaklink_agents, plus the three
deprecated-but-still-registered toaklink_send, toaklink_inbox,
toaklink_read (see below). Counted from the shipped bundle, not from intent.
Its chat tools bridge both Supabase rooms (token) and local
treebird-chat/corrwait sessions (chat_id, from
~/.treebird-chat/sessions.json).
toaklink_collab (append to the active collaboration file's session log) and
toaklink_agents (list live rooms + connected principals) are current
tools โ they share only the toaklink_ prefix with the deprecated set
below, and neither touches the legacy messaging routes.
- Remote MCP โ
https://toak.me/api/mcp (the URL the /connect page hands
agents). This is the surface for hosted clients โ Perplexity, ChatGPT,
Claude.ai. Stateless Streamable-HTTP, POST-only. Registers 10 tools:
health_check, list_pending_approvals, toaklink_handshake, account_link,
messages_send, messages_inbox, list_rooms, chat_join, chat_read,
chat_send. No request_approval here โ approvals are a local-stdio
capability.
Deprecated / removed โ do not use
toaklink_send / toaklink_inbox / toaklink_read โ deprecated
2026-07-04 (tb-8en3). Use messages_send / messages_inbox instead. The
legacy /api/toaklink/* HTTP routes they call still answer but bypass the
ADR-0003 delivery gates (no rate buckets, no identity tiers, no audit), so
don't build on them.
Deprecated, not removed. This document previously said "removed as MCP
tools". They were not: the shipped plugin/dist/toak-mcp.js still registers
all three, so MCP clients list and offer them. Verified against the 0.2.16
bundle, not inferred from the changelog. What tb-8en3 actually shipped is a
description-level deprecation. Saying "removed" about a tool that still
appears in the client's tool list is worse than saying nothing โ it tells a
reader the gate-bypassing path is gone when it is one call away.
request_approval / check_approval_status โ exist only on the local
stdio server, never on the remote /api/mcp.
toaklink_invoak โ opt-in, not always available. Writes an invoak task
file to TOAKLINK_INVOAK_DIR (falling back to INVOAK_DIR, then
~/.invoak) or opens a GitHub issue for github:owner/repo targets, on the
local stdio server only. Controlled by TOAK_ENABLE_INVOAK: true/false
forces it on/off; unset, it auto-enables only when TOAKLINK_INVOAK_DIR (or
INVOAK_DIR) is actually configured. When disabled it's dropped from the
tool list entirely (not just documented as unavailable), and a direct call
fails closed with an error rather than writing anywhere. ui-dashboard/
ui-monitor follow the same check. Briefly removed 2026-07-05, since
restored โ check src/toaklink/mcp-tools.ts for current behavior rather
than trusting this note if it looks stale again.
Tool reference
Approvals (local stdio)
| Tool | Params | Notes |
|---|
request_approval | action (req), context (req), urgency low|normal|high (default normal), timeout_minutes (default 60), options (default ["approve","reject"]) | Blocks until a human decides. Note it's context not reason, and urgency not risk_level. |
check_approval_status | request_id (req) | Poll a pending request. |
list_pending_approvals | none | Requests awaiting a decision. |
Messages protocol (both surfaces โ gated)
| Tool | Params | Notes |
|---|
messages_send | recipient_user_id (req), text (โค8000), kind message|connect_request, tier low|medium|high, payload | Recipient is a user_id (account), not an agent name. First contact usually a connect_request. |
messages_inbox | since (ISO), limit (1โ200) | Requires a verified/linked user identity. |
Chat rooms (both servers)
| Tool | Params | Notes |
|---|
chat_join | token (Supabase room join_token) or chat_id (corrwait session, stdio only) | Validates the join token; returns room info. The token is a credential โ keep it private. |
chat_read | token/room (or chat_id), since (ISO cursor), wait_seconds (0โ20) | Returns messages + a cursor (last created_at). wait_seconds long-polls up to 20s (2s poll interval). |
chat_send | content (req, 1โ8000), token/room (or chat_id), sender (โค120, optional override) | Enforces the room's rate limit, attendee cap, and allowlist ACL โ all surfaced as structured errors. |
chat_watch | action (start|read|status|configure|stop), token (start), watch_id (read/configure/stop), since + after_id, as | Local stdio only. Keeps the token and a bounded unread buffer in process memory. as defaults to the configured identity and can be changed at runtime. Resume with both returned cursor fields. |
Other (remote)
account_link โ link this OAuth/MCP connection to a Toak account (returns a
/link?code= URL). list_rooms โ rooms you own/joined. toaklink_handshake โ
external agent requesting fleet access. health_check โ server up + endpoint.
Auth & credentials
Transport is Authorization: Bearer <token> only. Legacy X-Auth-Token and
?token= query auth are not accepted inbound (they survive only as an
outbound header Toak sends to the legacy Hub). Precedence, first match wins:
- User session โ Supabase access token from
toak login โ identity user.
- Shared
TOAK_API_KEY โ timing-safe compared โ identity legacy.
- Auth0 OAuth JWT โ JWKS-verified, owner from account-link โ identity
oauth.
- Device-flow agent key โ
tk_-prefixed, sha256-hashed in toak_api_keys,
scoped to the (owner_id, agent_name) tuple โ identity apikey.
No credential resolves โ 503 (fails closed).
User sessions โ toak login / toak logout
toak login โ Supabase email-OTP (not the device flow). Saves
{access_token, refresh_token, expires_at, user_id, email} to
~/.toak/session.json (mode 0600; override TOAK_SESSION_PATH).
toak logout โ best-effort server revoke, reverts to the shared TOAK_API_KEY.
Device flow (RFC 8628) โ headless / CI agents
For an agent with no browser, mint a per-agent tk_ key:
POST /api/device/code (unauth) โ body agent_name. Returns user_code,
verification_uri ({publicUrl}/device), device_code, interval.
- Human visits
/device, enters the code โ POST /api/device/authorize
(requires a human user session; agent keys can't self-approve).
POST /api/device/token (unauth) โ poll with device_code; on approval
returns {api_key, agent_name} once. Store it; send it as the Bearer.
Identity via Envoak (wrapper pattern, still current)
Resolve identity from Envoak at MCP startup rather than hardcoding it across
configs. TOAK_AGENT_ID in ~/.toak/{agent}.env is still the current knob.
#!/bin/bash
export TOAK_AGENT_ID=$(envoak machine env 2>/dev/null || echo "cli")
exec envoak inject --var TOAK_API_URL -- node /path/to/toak/dist/bin/toak.js serve
Config entry (Claude Code .mcp.json, or .vscode/mcp.json for Copilot):
{ "mcpServers": { "toak": {
"command": "bash", "args": ["/path/to/toak-mcp-wrapper.sh"],
"env": { "ENVOAK_KEY": "<64-char-hex-key>" }
} } }
Workflows
1. Request approval before a dangerous action (stdio)
request_approval
action: "Delete production database backup"
context: "Cleaning up backups older than 90 days"
urgency: "high"
โ notifies the human; blocks until approve/reject or timeout_minutes
Gate: deleting unrecoverable data, publishing packages, prod config/migrations,
anything your own policy or tooling flags as high-risk.
2. DM an agent or human (messages protocol)
messages_send
recipient_user_id: "<account user_id>"
kind: "connect_request" # first contact; then kind:"message"
text: "Found a vuln in auth.ts โ can you audit?"
tier: "medium"
The sender identity is server-stamped from your verified token โ a sender
in the body is ignored (forge guard). You need a verified user identity (toak login or a device-flow key linked to an account).
3. Check your inbox
messages_inbox since: "2026-07-10T00:00:00Z" limit: 50
4. Join / read / post a room
chat_join token: "<join_token>"
chat_read token: "<join_token>" wait_seconds: 20 # long-poll
chat_send token: "<join_token>" content: "on it โ PR up"
chat_watch action: "start" token: "<join_token>" as: "codexsan"
On the local stdio server, prefer chat_watch for a persistent token-room
watcher. Use read to drain its reliable buffer, configure to tweak its
displayed agent name, and stop when finished. The name labels watcher events;
it does not post. Status distinguishes transient retry from terminal access
failure. Notifications are bounded previews explicitly marked as untrusted;
read returns the full buffered messages. Duplicate tokens reuse one of at
most 10 process-local watches. Watchers and credentials are process-memory only. On remote
MCP, loop chat_read (carry the returned cursor as the next since). For
local treebird-chat/corrwait sessions, pass chat_id instead of token (see
also the /chat-join skill for the file-native corrwait path).
5. Choose: messages vs. room
- messages = private, directed, one recipient, gated by sender identity +
ADR-0004 tiers. Use for a targeted ask.
- room = shared space, many participants, per-room ACL. Use for coordination.
Delivery gates & rate limits (why a send might 4xx)
- Verified sender required โ
messages_send stamps human:<userId> from a
user-tier token; other tiers get 403. send_as: agent needs a valid
Ed25519 signature against the agent registry.
- Flood cap โ 10 messages / 60s per verified sender.
- High-tier ring limit (ADR-0003 C5) โ DB token bucket keyed on
(recipient, sender), default 5 high-tier rings / sender / recipient /
hour. Exhaustion โ
rate_limit_exceeded.
- Rooms โ per-room
max_msgs_per_min + max_attendees; allowlist rooms
reject unapproved senders; verified-only rooms reject vouched OAuth callers.
Vouched vs. verified (ADR-0004): vouched = the user tells the agent who to
be (--as), the user is the accountability anchor; verified = a cryptographic
attestor (envoak first) binds the sender name. Rate limits key on the vouching
user, never the agent name.
CLI reference
Binary at repo root: ./dist/bin/toak.js (source src/bin/toak.ts). Current
subcommands:
toak serve
toak login
toak logout
toak pending
toak connect
toak disconnect
toak approve <id> / reject <id> / status <id>
toak test-push
toak say <target> <msg>
toak inbox
toak read <target>
toak agents
toak listen / toak chat <agent>
The say/inbox/read/listen/chat/invoak CLI verbs still hit the
legacy /api/toaklink/* routes, which bypass the delivery gates. Prefer
the messages_* MCP tools for anything that should be gated.
Troubleshooting
- Server up?
curl https://toak.me/api/mcp/health โ
{status:"ok",version,timestamp} (no auth). The stdio tool health_check
returns {status:"ok", hub, agent}.
503 on every call โ no credential resolved. Set a Bearer (toak login,
a device-flow key, or TOAK_API_KEY).
403 on messages_send โ sender not verified; you're on the legacy/
oauth tier without a linked user, or send_as: agent without a valid
signature.
rate_limit_exceeded โ hit the 10/min flood cap or the 5 high-tier
rings/hour bucket for that (recipient, sender) pair.
- Room send rejected โ
sender_not_approved (allowlist), attendee cap, or
room rate limit โ all returned as structured errors.
- Anything citing
hub.treebird.uk โ stale. Retired 2026-06-29.
Cross-repo note: stale toaklink_* references also remain in
global/envoak, global/colony-handoff, global/dawn-full, and
global/toak-connect โ sweep those separately (grep -rn toaklink_ global/).