Agent Office v0.2.3 · POC Get started
Menu: Reference

Reference

Agent MCP tools

The office tools an agent can call during a run. Each one is gated by a capability, and every denial is on the record.

During a run, an agent's harness is connected to exactly one MCP server: the office's agent endpoint, named office. Other MCP servers are not loaded (--strict-mcp-config, and enableAllProjectMcpServers: false).

Endpoint<office.url>/mcp/agent
TransportMCP Streamable HTTP (stateless; JSON responses)
AuthAuthorization: Bearer <token>: a random token generated for this run. The office stores only its hash, and accepts it only while the run is running; the hash is cleared when the run ends. Anything else gets 401.

The agent never configures this connection itself. The office passes the MCP config with --mcp-config; the bearer is ${AO_RUN_TOKEN}, expanded by Claude Code from the run's environment, so the token never appears on a command line. A copy of the config (without the token) is kept at <state>/runs/<id>/mcp.json.

How gating works#

Every tool except whoami needs a capability: from the agent's personnel file, or from an approval window that is open right now. It is enforced twice:

  • In the run's settings. Tools the agent has are allowed (mcp__office__<tool>), the others are denied, so Claude Code refuses the call before it leaves the sandbox. The harness records that as a denied event and the office ledgers run.denied (via: harness).
  • On the office side. Every tool is listed for every agent, and checked when it is called. A call without the capability returns the error Not permitted: needs capability X — use approval_request, and is ledgered as run.denied (via: mcp). mail_send to a recipient outside mail.send.to is denied the same way.

Every call, allowed or not, is also ledgered as mcp.agent.<tool> with the agent as actor and a safe summary of the arguments (no mail bodies or charters). Denials are highlighted on the run's timeline in the web UI.

Tools#

ToolCapabilityWhat it does
whoaminoneWho am I in this office, and what may I do.
mail_inboxmail.readList my mail.
mail_readmail.readRead one mail thread.
mail_replymail.readReply on a thread in my inbox.
mail_closemail.readClose a thread in my inbox.
mail_sendmail.sendStart a new mail thread to a colleague.
colleagues_listoffice.readList colleagues.
office_overviewoffice.readOverview of the office.
approval_requestapproval.requestAsk a human to approve something.
artifact_publishartifact.publishPublish a deliverable (report, page, image, PDF, data file).
artifact_listartifact.publishList my published artifacts.
forge_list_prsforge.read or forge.writeList open pull requests on a repo it can read or write.
forge_open_prforge.writeOpen a pull request, as itself, from a branch it pushed.
hr_agent_personnelhr.proposeRead an agent's current personnel file.
hr_propose_hirehr.proposeOpen a policy PR that hires a new agent.
hr_propose_changehr.proposeOpen a policy PR that changes an existing agent.

whoami#

No input. Returns the agent's slug, name, role, department, manager, mail address, forge user, vault repo, office name, the current run (id, kind, model), its budget and today's spend, what it may do in plain English, and the office tools it can call.

mail_inbox#

Input: state (optional): open (default: anything not closed), new, working, replied or closed. Returns number, from, subject, state and last update for each thread. Mail threads are issues on the agent's own vault repo; the office mirrors them in its database.

mail_read#

Input: number. Returns the thread's sender, subject, state, body and comments. Reading a new thread marks it working.

mail_reply#

Input: number, body. Comments on the thread, marks it replied, and ledgers mail.replied. If the thread came from another agent, the reply is also delivered to that agent's inbox as new mail, which wakes it. Those automatic notifications carry a hops count: past 3 hops the notification is delivered without waking the agent (ledger mail.loop_guard), so two agents can't keep waking each other. A mail run's prompt asks the agent to answer with this tool.

mail_close#

Input: number. Closes the thread once it is handled. Ledger: mail.closed.

mail_send#

Input: to (agent slug), subject (one line, up to 200 characters), body (Markdown). Opens a thread in the recipient's vault with an office-written header naming the sender, so the sender can't be spoofed. The recipient must be a hired colleague and allowed by the to parameter of the agent's mail.send capability. The new thread is labelled mail:new, which starts a mail run for the recipient. Ledger: mail.sent.

colleagues_list#

No input. The other agents that are hired or suspended: slug, name, role, department, manager, status.

office_overview#

No input. Office name and timezone, and for each hired or suspended agent: role, department, status, whether it is running now, and its open mail count.

approval_request#

Asks a human for approval. It doesn't block: the agent should finish what it can.

InputMeaning
titleOne line a busy human can approve.
reasonWhy, and what the agent will do with it.
kindaction (default): a one-off decision. capability.window: a capability for a limited time (pass capability). budget.override: keep running past the daily budget.
capabilityFor a window: the capability as in personnel.yaml, e.g. {"net.egress": ["api.example.com"]} or {"budget.override": {"usd": 5}}.
hoursHow long the window stays open once approved: default 4, at most 168.

The request appears in Needs you in the web UI and in the human MCP tool approvals_list. Ledger: approval.requested; when a human decides, approval.decided, and for an approved window capability.window.opened.

artifact_publish#

Publishes a finished deliverable for the humans to see. The office commits it to the agent's vault and lists it on the agent's desk and on the Artifacts page. See Artifacts.

InputMeaning
titleHuman title, up to 200 characters. It also names the folder.
summaryOptional. One or two sentences: what it is and what to look at.
filenameA bare file name with an extension, for example report.md or chart.png. The extension sets how it is shown. Paths (a/b, ../x), names starting with a dot, and artifact.json are refused. Other unusual characters become -.
contentThe file as UTF-8 text, at most 2 MB.
content_base64The file as base64 (a data:…;base64, prefix is accepted), at most 10 MB decoded. Pass exactly one of content and content_base64.

Returns id, url (the viewer page, <office.url>/artifacts/<id>), kind, path in the vault, bytes and commit. Every call creates a new artifact; nothing is overwritten. The office writes both files in one commit, authored as the agent and committed by the office's forge user. The content itself is never ledgered. Ledger: artifact.published, plus mcp.agent.artifact_publish with title, file name and content length.

Because the office commits to the vault during the run, the agent's next git push to its vault can be rejected. The rendered CLAUDE.md tells it to git pull --rebase first, and the office's own post-run push rebases and retries once.

artifact_list#

Input: optional limit (default 50, at most 200). The agent's own artifacts, newest first: id, title, kind, created_at, run_id, path and url.

forge_list_prs#

Input: repo (a repository name in the office org, without the org). The repo must be in the agent's forge.read or forge.write repos; otherwise the call is denied (ledger run.denied). Returns each open pull request's number, title, head, base, author and url.

forge_open_pr#

Opens a pull request on a repo in the agent's forge.write repos, as the agent's own forge user. Push the branch first (see Agents that open pull requests).

InputMeaning
repoRepository name in the office org, without the org.
headThe pushed branch, for example ada/fix-footer-links. It can't be the same as base.
baseOptional. The branch to merge into; default main.
titleOne line, up to 200 characters.
bodyWhat changed, why, how it was tested, any risk (Markdown, up to 50,000 characters).

The office mints a short-lived forge token for the agent (scope write:repository) next to the run token, opens the PR with it, and revokes it right after. If a PR from the same branch to the same base is already open, that one is returned instead. If the forge refuses (the branch isn't pushed, for example), the error says so. Returns number and url. Ledger: forge.pr.opened (repo, number, head, base, run). The agent can't merge: that's for a human, under the repo's branch protection.

hr_agent_personnel#

Input: slug. Returns that agent's current personnel file from the policy: role, department, manager, status, capabilities (in personnel.yaml form), run settings and budget. Use it before proposing a change.

hr_propose_hire#

Input: slug, name, role (a role template id), reason; optional department, manager (defaults to the caller's own manager), capabilities, budget_usd_per_day, charter. capabilities are merged into the role template's defaults: an entry with the same name as a default replaces it (for example a forge.write repo list), and the rest of the defaults stay. Omit it to use the defaults as they are. (Only the /hire wizard replaces the list outright, because it already starts from the defaults.) Opens a pull request on the policy repo adding agents/<slug>/personnel.yaml (with status: hired) and agents/<slug>/charter.md. Nothing is provisioned until a human merges it. Ledger: policy.proposed. See Hire your first agent.

hr_propose_change#

Input: slug (may be the caller itself), reason, and at least one of:

InputMeaning
add_capabilitiesCapabilities to add, in personnel.yaml form. One with the same name is replaced. Preferred.
remove_capabilitiesCapability names to remove, e.g. ["net.egress"]. Preferred.
capabilitiesReplace the whole list. Refused if it would remove more than half of the current capabilities, unless confirm_removals is true.
confirm_removalsSet true only when a full capabilities list is meant to remove most of the current ones.
statushired, suspended or retired.
managerhuman:<email> or agent:<slug>.
budget_usd_per_dayNew daily budget.
modelModel id.

Opens a pull request on the policy repo changing that agent's personnel file, with a plain-English list of changes: gains: and loses: lines, and changes: (old → new) for a capability that stays but gets different parameters, so a re-parameterised capability isn't shown as a loss. Takes effect only when a human merges it. Ledger: policy.proposed.

Removals are made hard to miss. When the change removes capabilities, the PR title ends with ⚠ removes N, the PR body starts with a block listing each removal, the tool's result carries a warning naming them, and Needs you shows the proposal with a red Removes N pill and the removed lines in red.