ROOST

For agents · Model Context Protocol

Your agent proposes.
ROOST decides.

Connect an agent over MCP. It can read the rules it is bound by and submit an action to be checked against them — and nothing else. There is no tool that signs, and none can be added, because the API it talks to does not offer one.

7tools exposed
0that can move value

The division of labour

Three things, never two.

Conflating any pair of these is the mistake this page exists to prevent.

01your agent

Propose

“Here is what I want to do.” Only the intent — never the balances or budget used to judge it.

02ROOST

Decide

Policy runs on the server against state it loads itself. Still nothing has moved.

03the owner

Sign

The only step where value actually moves. ROOST holds no key and cannot do this for anyone.

Connect

One config block.

.mcp.json
{
  "mcpServers": {
    "roost": {
      "command": "node",
      "args": ["/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}

The credential

Issued by an owner from the Agents screen. It is shown once and only its hash is stored, so a lost one means a new one.

Revoking an agent invalidates it on the very next request — not at the next refresh, not on a timer.

If a tool reports the credential is invalid, stop and say so. Do not look for another route to the same outcome.

Quickstart

Three steps, any client.

The parts people get wrong are the absolute path to the entry point and the config key, which is not the same in every client. Neither should be typed by hand.

  1. 1

    Get a credential

    Sign in, open Agents, create one, copy it. It is shown once and stored only as a hash — a lost one means issuing a new one.

  2. 2

    Generate the config

    Let the repo write it, with the right path and the right key for your client.

    npm run build -w @mandate/mcp
    npm run mcp:config -- codex --token rst_…

    Takes claude-code, claude-desktop, codex, cursor, vscode or windsurf.

  3. 3

    Restart and check

    Ask the client to call roost_whoami. It reports the agent, the network, the mandates it is bound to, and what it may not do.

Claude Codeverified

.mcp.json in the project root, or ~/.mcp.json for every project

{
  "mcpServers": {
    "roost": {
      "command": "node",
      "args": ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}
Codex CLIverified

~/.codex/config.toml

[mcp_servers.roost]
command = "node"
args = ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"]

[mcp_servers.roost.env]
ROOST_BASE_URL = "https://app.roostagent.xyz"
ROOST_AGENT_TOKEN = "rst_…"
Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "roost": {
      "command": "node",
      "args": ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}
Cursor

.cursor/mcp.json in the project, or ~/.cursor/mcp.json

{
  "mcpServers": {
    "roost": {
      "command": "node",
      "args": ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}
VS Code (Copilot)

.vscode/mcp.json — note the key is servers, not mcpServers

{
  "servers": {
    "roost": {
        "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}
Windsurf

~/.codeium/windsurf/mcp_config.json

{
  "mcpServers": {
    "roost": {
      "command": "node",
      "args": ["/absolute/path/to/roost/mcp/dist/mcp/src/index.js"],
      "env": {
        "ROOST_BASE_URL": "https://app.roostagent.xyz",
        "ROOST_AGENT_TOKEN": "rst_…"
      }
    }
  }
}

Client marks are their owners’ trademarks, shown only to identify which configuration belongs to which tool. Codex has no mark here, so it carries a neutral one.

A credential belongs to one ROOST instance. Each has its own database and its own hashes, so one issued locally is meaningless against production.

The surface

Seven tools. None of them sign.

Read tools cost nothing and can be called freely. The one write tool records a decision and may reserve budget — but it cannot itself move value.

roost_whoami

Which agent this is, which chain it operates on, which mandates it is bound to, and exactly what it may not do.

read
roost_list_mandates

Every rule in human units, plus how much daily budget is left. Call this before proposing, so the proposal is shaped to fit the rules rather than discovered to break them.

read
roost_propose_action

Submit a transfer to be checked. Returns the decision and the full rule-by-rule trace. It does not move funds and does not broadcast anything.

write
roost_get_action

Lifecycle, latest trace and any chain execution. Only a status of “confirmed” means the chain accepted it.

read
roost_list_actions

Recent proposals, for reconciling what was actually decided against what the agent believes it did.

read
roost_get_receipt

A signed receipt: payload, signature and signing key id.

read
roost_verify_receipt

Check a receipt against the published key. Needs no credential — that is the point of it.

read

Authority

Narrow on purpose.

An agent that can approve its own actions has no approval step. The restriction lives in the API, not in the MCP server’s good manners.

An agent can

  • read own identity
  • read bound mandates
  • propose actions
  • read own decisions and receipts

An agent cannot

  • approve or reject actions
  • dispatch, sign or broadcast transactions
  • pause or resume a mandate
  • create or edit a mandate
  • read another agent or owner data

Reading a decision

Three answers, and one trap.

The trap is reporting an allowed decision as a completed transfer. It is not one, and the tools say so on every call.

ALLOWED

Policy was satisfied. No funds have moved.

The owner must still dispatch and sign, and everything is re-checked first. Never report this as sent, paid or complete.

NEEDS HUMAN APPROVAL

Within policy, but a person must decide.

The request expires. Poll the action rather than assuming approval because time passed.

BLOCKED

The mandate refused. Nothing was reserved and nothing will execute.

Read the failing rules and stop. Do not retry unchanged, split the amount to slip under a cap, or try another destination.

The skill

What the agent is taught.

skills/roost/SKILL.md
You propose. ROOST decides.
The owner signs.

Look before you propose.
Read the decision honestly.
When something is blocked, stop.
Only “confirmed” means it happened.

The skill exists because the failure mode is not a crash — it is an agent that is confidently wrong about what happened.

So it spends most of its words on two things: never calling a policy decision a settlement, and never treating a refusal as an obstacle to route around. Splitting an amount to slip under a cap is not resourcefulness. It defeats the thing the agent was asked to operate within.

no retry loopsno amount splittingno substituted destinations

Honest limits

What this cannot do.

Stated as limits, not as failures. An agent should report them rather than work around them.

Transfers only
Native and ERC-20. No swaps, quotes or routing, so the liquidity and slippage rules report “does not apply” rather than a pass for a check that never ran.
USD needs a live rate
Prices are cross-checked across several public venues. If too few answer, or they disagree beyond tolerance, the action blocks rather than being judged against a guess.
No position tracking
A mandate that sets a daily loss limit blocks with a reason, instead of passing a guard that is not really there.
No constrained signer
ROOST can refuse to authorise, and can prove afterwards that a transaction did not match its authorisation. It cannot stop an owner signing elsewhere.