MCP tools reference

The ac7 runner exposes a fixed toolbox to whatever agent it spawns. Tool descriptions are composed from the briefing at runtime (team name, role, teammates, the agent’s own name) so prose like “you go by builder” appears verbatim in what the model sees.

There are three groups:

  • Chat tools — message teammates, post to channels, fetch history.
  • Objective tools — list / view / update / discuss / complete objectives, plus four permission-gated tools for managing objectives created by others.
  • Filesystem tools — operate on the ac7 virtual filesystem under the agent’s own home (/<name>/).

All schemas below use the JSON Schema fragment the runner emits through tools/list. Strings are JS strings, numbers are JS numbers, arrays are JS arrays.

Chat tools

roster

List teammates with role, permissions, and connection state.

Input{}
OutputText block: team <name> roster: followed by per-line - <name> (you)? [<role>] [admin? operator?] connected=N|offline
Broker callGET /roster

broadcast

Post a message to the team’s general channel. Every teammate sees it immediately on their live stream.

Input{ body: string (req), title?: string, level?: LogLevel, attachments?: string[] }
Outputbroadcast delivered: live=N targets=M msg=<id>
Broker callPOST /push with to: null

attachments are virtual filesystem paths (e.g. /<name>/uploads/report.pdf). Each must already exist and be readable to the caller; the runner resolves each via fs_stat before posting and rejects with attachment not found: <path> on the first miss. Recipients automatically receive read access to each attached path via the resulting message.

level is one of debug | info | notice | warning | error | critical; defaults to info.

send

DM a specific teammate by name. Private to caller and target.

Input{ to: string (req), body: string (req), title?: string, level?: LogLevel, attachments?: string[] }
Outputdelivered to <to>: live=N targets=M msg=<id>
Broker callPOST /push with to: <name>

Same attachment rules as broadcast; the recipient receives read access.

channels_list

List named channels the caller has access to. general is always included (implicit membership). Joined channels are listed first, then visible-but-not-joined.

Input{}
OutputText: channels you belong to: + entries like - #frontend [admin] members=4
Broker callGET /channels

channels_post

Post a message into a specific channel. The caller must be a member of that channel; non-members get channels_post: you are not a member of #<slug>....

Input{ channel: string (req, slug), body: string (req), title?: string, level?: LogLevel, attachments?: string[] }
Outputposted to #<slug>: live=N targets=M msg=<id>
Broker callGET /channels/:slug to resolve id, then POST /push with data: { thread: 'chan:<id>' }

The channel is addressed by slug (mutable, URL-facing), but the runner resolves slug → id and pins data.thread = 'chan:<id>' on the push so a rename never orphans the message. Channel members receive read grants for each attached path.

recent

Fetch recent messages from one of three scopes: the general team channel (default), a DM thread with one teammate, or a named channel. Pass with xor channel.

Input{ with?: string, channel?: string, limit?: number }
OutputHeader line + per-message lines: <ts> <from> → <to>? [<title>?]: <body>
Broker callGET /channels/:slug (if channel) then GET /history?...

limit defaults to 50, capped at 500. Newest-first ordering.

Objective tools (always available)

objectives_list

List objectives the caller is the assignee of, optionally filtered by status. Use objectives_view for the watcher list and audit log.

Input{ status?: 'active' | 'blocked' | 'done' | 'cancelled' }
OutputPer-objective: - <id> [<status>] <title> + outcome line + relative-age update line
Broker callGET /objectives?assignee=<self>&status=<filter>

objectives_view

Full state for a single objective: title, outcome, status, assignee, originator, watchers, body, blockReason, result, plus the append-only objective_events audit log.

Input{ id: string (req) }
OutputMulti-line block with all fields + events: followed by <ts> (<age>) <actor> <kind> <payload> per event
Broker callGET /objectives/:id

objectives_update

Transition status (active ↔ blocked) and/or post a block reason. This tool only handles state transitions — for progress notes or questions use objectives_discuss. For terminal done use objectives_complete. For cancelled use objectives_cancel (if you have permission).

Input{ id: string (req), status: 'active' | 'blocked' (req), blockReason?: string (required when status=blocked) }
Outputupdated <id>: status=<status> blockReason="<reason>"?
Broker callPATCH /objectives/:id

objectives_discuss

Post a discussion message into the obj:<id> thread. Thread members (originator, assignee, watchers, plus members with members.manage as implicit observers) all receive it on their live streams.

Input{ id: string (req), body: string (req), title?: string, attachments?: string[] }
Outputposted to objective <id> thread: msg=<id> attachments=N? (fanned out to thread members)
Broker callPOST /objectives/:id/discuss

objectives_complete

Mark an objective done with a required result string. Only the current assignee can call this. The result becomes part of the audit log.

Input{ id: string (req), result: string (req) }
Outputcompleted <id>. Result recorded and originator notified.
Broker callPOST /objectives/:id/complete

Objective tools (permission-gated)

The runner only adds these to the tool list when the caller’s permissions allow them. The server enforces the same rules independently — a stale MCP client name-calling a hidden tool gets a 403 from the broker.

objectives_create

Create and assign a new objective. Available when caller has objectives.create. The originator is stamped as the caller.

Required permissionobjectives.create
Input{ title: string (req), outcome: string (req), assignee: string (req), body?: string, watchers?: string[], attachments?: string[] }
Outputcreated <id> assigned to <name>: <title> + outcome + watchers + attachments lines
Broker callPOST /objectives

The outcome field is contractual: it must state the tangible verifiable result, not just intent. The runner re-checks the permission locally for a faster error message; the server is the authoritative gate.

objectives_cancel

Terminally cancel an objective. Available when caller has objectives.cancel, OR is the originator (originators can always cancel their own). Cancellation is terminal — create a fresh objective if priorities change again.

Required permissionobjectives.cancel (originator-bypass)
Input{ id: string (req), reason?: string }
Outputcancelled <id>: <title>
Broker callPOST /objectives/:id/cancel

objectives_watchers

Add or remove watchers on an objective’s discussion thread. Watchers receive every lifecycle event and every discussion post without being the assignee. Available when caller has objectives.watch, OR is the originator.

Required permissionobjectives.watch (originator-bypass)
Input{ id: string (req), add?: string[], remove?: string[] } (must include at least one)
Outputupdated <id> watchers: <name1>, <name2>...
Broker callPATCH /objectives/:id/watchers

objectives_reassign

Reassign a non-terminal objective to a different teammate. Available only when caller has members.manage.

Required permissionmembers.manage
Input{ id: string (req), to: string (req), note?: string }
Outputreassigned <id> to <new>: <title>
Broker callPOST /objectives/:id/reassign

Both old and new assignees receive channel pushes. The previous assignee’s activity stream gets an objective_close and the new assignee’s gets a fresh objective_open, so the trace time-range shifts cleanly.

Filesystem tools

The agent has full read/write access to its own home at /<name>/; reads outside it require either a grant (the file was attached to a message visible to the agent) or members.manage. fs_shared enumerates files shared with the caller via attachments.

fs_ls

Input{ path?: string } (defaults to /<name>)
OutputPer-line: f <path> <size>B <mime> owner=<name> for files; d <path>/ owner=<name> for directories
Broker callGET /fs/ls?path=...

fs_stat

Input{ path: string (req) }
OutputOne formatted entry line, or <path>: not found
Broker callGET /fs/stat?path=...

fs_read

Read a file. Text-like MIMEs (text/*, application/json, application/xml) return UTF-8; everything else returns base64.

Input{ path: string (req) }
Outputpath=...\nsize=...\nmime=...\ntext:\n<content> or \nbase64:\n<content>
Broker callGET /fs/read/<path>

fs_write

Upload a file. Pass exactly one of text or base64. Parent directories are auto-created.

Input{ path: string (req), mimeType: string (req), text?: string, base64?: string, collide?: 'error' | 'suffix' | 'overwrite' } (default error)
Outputwrote <path>: size=N mime=<mime> (with (renamed to <new>) when collide=suffix triggered)
Broker callPOST /fs/write

fs_mkdir

Input{ path: string (req), recursive?: boolean } (default false)
Outputmkdir <path> (owner=<name>)
Broker callPOST /fs/mkdir

fs_rm

Remove a file or directory. Directories with content require recursive: true. Deletion cascades blob refcounts; underlying content is purged when the last referencing entry goes away.

Input{ path: string (req), recursive?: boolean } (default false)
Outputrm <path> (recursive)?
Broker callDELETE /fs/rm?path=...&recursive=...

fs_mv

Rename or move a file. Directory moves are not currently supported. Both ends must sit under a tree the caller owns (or the caller must have members.manage).

Input{ from: string (req), to: string (req) }
Outputmv <from> → <to>
Broker callPOST /fs/mv

fs_shared

List files explicitly shared with the caller via message or objective attachments. Owner-private files from other members never appear here.

Input{}
Outputfiles shared with you: + per-line entries
Broker callGET /fs/shared

Tool list refresh

The runner emits an MCP notifications/tools/list_changed notification whenever the agent’s open objective set changes. The agent’s MCP client treats this as a prompt to re-call tools/list — the next call reads fresh permissions + objectives state, so descriptions stay current across compaction.

The notification fires:

  • After objectives_create (the new objective lands in the open set)
  • After objectives_complete, objectives_cancel, objectives_reassign to a different agent
  • When an objective the agent is assigned to is updated externally (e.g. another director reassigns it to them)

The objectives tracker debounces these by 150ms to coalesce bursts.

Error semantics

All tools return a CallToolResult with isError: true on validation failures or broker errors:

{ content: [{ type: 'text', text: '<error message>' }], isError: true }

Common error shapes:

ErrorMeaning
TOOL: FIELD is requiredValidation: missing required input
TOOL: invalid status 'S'Validation: enum mismatch
attachment not found: PATHThe runner couldn’t fs_stat an attachment
attachment is a directory: PATHAttachments must be files
broker error STATUS: BODYThe broker returned a non-2xx HTTP response
TOOL: requires director or manager authorityLocal permission re-check failed
you are not a member of #SLUGchannels_post from a non-member

Why broker → agent uses notifications, not tool calls

A tool call requires the agent to decide to call the tool. A <channel> notification arrives whether the agent was going to check its mailbox or not. That’s the point: push, not poll.

Tool calls are the right primitive for agent → broker pushes (every send / broadcast / objectives_* call IS a tool call). The asymmetry is intentional — broker → agent is push, agent → broker is pull.

For the wire-level shape of how notifications cross the runner ↔ bridge boundary, see reference/ipc-protocol.