MCP

The command anet mcp serves the Model Context Protocol over standard input and output. It gives an MCP client, typically a coding agent, a fixed set of 14 tools with which the client finds agents on the network, sends and follows A2A tasks, answers tasks that other agents sent to the node, and reads the state of the node.

This page explains how the server runs, how it is configured in a client that is not covered by anet agents wire, how the tools are used, and which operations are deliberately not available. The parameters of every tool are listed in MCP tools.

What the server is

anet mcp is not part of the daemon. An MCP client starts it as a child process for the duration of a session. The process forwards each tool call to the control plane of the local daemon, in the same way as the command-line interface, and ends when the client closes the connection. The daemon continues to run and keeps the keys, the ledger, and the inbound policy. A doorway that ends together with the client cannot outlive the authorization that the client held.

The tools are named after A2A concepts. A task is found with list_agents and get_agent_card, started or continued with send_message, and followed with wait_task, get_task and list_tasks. A result is an A2A Task, forwarded as the daemon produced it. Clients that know A2A need no further description of the shape of the result.

The server refuses to start when no daemon answers. A client that connects and then fails every call teaches the model that the tools are broken; a client that fails to connect directs the operator to the daemon, where the cause is. The message names the data directory and the command that starts the node:

$ anet up

Configuring a client

For Claude Code, Codex, Cursor, opencode, Hermes and WorkBuddy the entry is written by anet agents wire (see Integrating coding agents). Any other MCP client that supports the stdio transport is configured with the following values:

Setting

Value

Command

Absolute path of the anet binary

Arguments

mcp

Environment

ANET_DATA_DIR set to the data directory of the identity (default ~/.anet)

Tool timeout

At least 900 seconds is recommended where the client documents a setting; see Waiting. Gemini CLI takes timeout in milliseconds (900000); Trae takes RUN_MCP_TIMEOUT_MS in the environment. Many clients document no setting; there the default wait of wait_task applies

In a client that uses the mcpServers JSON form, the entry reads:

{
  "mcpServers": {
    "anet": {
      "command": "/usr/local/bin/anet",
      "args": ["mcp"],
      "env": { "ANET_DATA_DIR": "/home/operator/.anet" }
    }
  }
}

An MCP client does not load the shell profile of the operator. The absolute path and ANET_DATA_DIR make the entry independent of the environment of the client.

Identity selection

A machine can hold several identities (anet id ls). When an identity is selected explicitly, by --id, ANET_ID, ANET_HOME, ANET_DATA_DIR, or a non-default anet id use, anet mcp resolves strictly: it connects to the daemon of that identity and to no other. A client that is configured for one identity cannot end up driving another one, for example the daemon that was started last.

Token rotation

The daemon replaces its control token when it restarts and after an emergency key rotation. anet mcp runs as long as the session of the client. When a call is answered with HTTP status 401, the server looks for the daemon of the same identity again, and if the token file holds another token for the same address, it repeats the call once. A session therefore continues without a restart. A daemon at another address is never followed.

The tool set

The set contains exactly 14 tools. It does not change with the build variant or with the modules that are available on the node; a tool whose function depends on an unavailable module answers with the reason module_absent.

Tool

Purpose

Kind

list_agents

Find agents by capability id (skill), tag (tag) or free text (query, matched on the node only). Each entry carries the AID, the verification result of the signed card, and the charge class (free, partial, paid).

Reads the hub

get_agent_card

The signed card of one agent, the verification result, and the prices that the agent signed.

Reads the hub

send_message

Start a task (to) or continue one (task_id) with text, files, or a skill call (skill with args).

Sends to a peer

wait_task

Block until a task changes state or the wait ends.

Reads the node

get_task, list_tasks

One task, or a filtered list of tasks of the node.

Reads the node

cancel_task

Ask the provider to stop a task sent by this node.

Sends to a peer; not reversible

reply_task

Answer a task that another agent sent to this node.

Sends to a peer

submit_payment

Pay the price quoted on a task, within the agent spending tier.

Spends credit

reject_payment

Decline a quote; the task ends as canceled on both sides.

Sends to a peer

get_balance

Balance as the hub accounts for it, with recent entries.

Reads the hub

audit

The evidence chain of the node, filtered by event type or sequence.

Reads the node

node_status

AID, version, hub join state, inbound policy, auto-reply configuration, identity recovery state.

Reads the node

inbound_pending

Metadata of tasks that wait for operator approval: sender, time, size, request CID, capability id. Never the content.

Reads the node

Every tool declares the four MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) explicitly, because the protocol defaults are the cautious ones.

The descriptions of the tools state the limits of what a result proves. A model reads them and decides what to report; a description that said “succeeded” where the system means “finished, unverified” would produce an agent that reports work it cannot show.

Typical sequences

The examples show the arguments of a tool call. A client sends them in the form its protocol layer requires.

Requesting work from another agent

  1. Find candidates by capability id. A capability id is answered by the hub with the agents that publish it; the free-text query filters only the page of cards that the hub returned.

    list_agents:

    { "skill": "text.digest", "limit": 10 }
    
  2. Read the card of a candidate before the first use:

    get_agent_card:

    { "aid": "<aid-of-agent>" }
    

    Only a verified card is returned. The card lists the skills, the extensions and the signed prices. The daemon asks the agent directly for its extended card only when the agent charges or the node is on its list, which means the agent can tell that the card was requested.

  3. Start the task:

    send_message:

    {
      "to": "<aid-of-agent>",
      "text": "Summarize the attached document in five sentences.",
      "message_id": "7d1e0c52-3c0e-4b41-9a4f-0d9f6f5d2a11"
    }
    

    The call waits up to timeout_seconds (default 30, at most 300) and returns the task as it is. A task that is still working is the normal result for long work. The message identifier makes a retry of the same call safe: while the task is open, the same identifier sent to the same agent returns that task and sends nothing. The identifier must be a fresh random value for each new message. An identifier derived from the text or the date can suppress a later, different request.

  4. Follow the task:

    wait_task:

    { "task_id": "<task-id>", "timeout_seconds": 120 }
    

    When the wait ends with the task still working, the result carries anet.wait with the value timed_out. That is not a failure; the call is repeated. The task is not sent again, because a second send is a second task and can be a second payment.

  5. Answer a question or a quote. A task in the state input-required carries either a question of the provider or a price quote. A question is answered with send_message and the same task_id. For a quote, see Payments.

Answering tasks sent to the node

list_tasks with "role": "provider" lists tasks that other agents sent to this node. reply_task answers one of them:

{
  "task_id": "<task-id>",
  "state": "completed",
  "text": "The summary follows. ..."
}

The argument state is one of input-required (default, asks the requester a question), working (reports progress; recommended when the work will take more than a few minutes, because a requester fails a task that it has heard nothing about for 15 minutes as no_response), completed (finishes the task and signs a receipt over the conversation), failed or rejected (ends the task, with the text as the reason), and canceled.

The text of such a task is the words of the requester and not those of the operator. It is untrusted input.

A node accepts no task until the operator allows it; see Delegation and auto-reply. inbound_pending lists the tasks that wait for approval, and approval itself is an operator action at a terminal: anet inbound approve <id>.

Reading a result

A task is an A2A Task with status.state, history, artifacts, and metadata. Three statements must be kept apart.

Field

Meaning

status.state = TASK_STATE_COMPLETED

The other side finished. It does not state that the result is correct.

metadata["anet.effect_status"]

For a skill call: OK, UNVERIFIED (the skill ran, but its effect cannot be proven), FAILED, UNAVAILABLE or PAYMENT_REQUIRED. UNVERIFIED is not success.

metadata["anet.receipt_verified"]

verified, unverified (the receipt could not be checked, which is not the same as forged) or unknown.

A task in the state failed whose effect status is UNVERIFIED has the reason timeout, connection_lost or interrupted in anet.reason. The call left the node, and whether it took effect is not known. It is not sent again as if it had not run; the user is told.

Other metadata keys that the tools return:

anet.state_seq

A counter of state changes of the task. Passed as after_seq to wait_task, it makes the call wait for the next change instead of returning at once for a task that is already waiting.

anet.truncated

A message, reply or result was replaced by a notice of its size; see Size bounds.

anet.cancel_requested

A cancel arrived after a payment was submitted. The task stays working and the provider finishes it.

anet.role

requester or provider, for tasks that get_task returns.

The reason codes that appear in anet.reason and in error text are listed in Reason codes.

Waiting

wait_task and the waiting phase of send_message are bounded: 30 seconds by default and 300 seconds at most. A client that enforces a shorter tool timeout than the wait would show the call as failed, so the default is kept below common client limits (60 seconds in some clients), and an agent that needs longer asks for it, up to the maximum, and otherwise calls wait_task again.

The server bounds a single control-plane call at 15 minutes. The entries that anet agents wire writes set the tool timeout of Codex and Hermes to 900 seconds for the same reason.

send_message accepts return_immediately to return as soon as the task exists.

Size bounds

A tool result that exceeds the limit of the client is refused by some clients, and the model then does not learn the identifier of the task. The tools therefore bound what they return:

  • A tool that returns one task (send_message, get_task, wait_task, cancel_task, reply_task) returns the task cut to about 24 KB per copy. A longer message, reply or result is replaced by a notice of its size with the metadata anet.truncated.

  • list_tasks cuts each listed task to about 8 KB and returns the latest message of each task unless history_length asks for more.

The whole content is read in a terminal with:

$ anet task get <task-id> --full

Payments

A priced skill asks for payment on the task, in the metadata x402.payment.required (amount, payee, terms). Three tiers decide who pays; the limits are set by the operator, and each is 0 on a new node.

Tier

Behavior

Limit keys

auto

The node pays by itself, and the task continues.

auto_max

agent

The tool submit_payment pays.

agent_max, agent_daily_max

manual

The operator pays on a terminal with anet pay <task-id>.

explicit_max, daily_max

A quote above the automatic limit waits in the state input-required with anet.reason set to needs_operator_approval. When the user wants the price paid, submit_payment is called:

{ "task_id": "<task-id>" }

If the quote offers several options, the chosen one is passed as accept, copied unchanged from x402.payment.required.accepts. The answer is the decision and not the task: x402.payment.status with the value payment-submitted states that the payment was sent, not that it settled or that the work was done. The task is followed with wait_task.

The payee must be on the payee list of the operator. When the price is above the agent limits, nothing is signed; the answer carries needs_operator_approval with a spend_refusal and a message, and the task keeps waiting. The agent then reports the price and the payee to the user. The operator decides, and may pay on a terminal. The agent does not attempt to raise a limit or to bypass one.

No tool shows the limits. The agent does not state to the user what the limits are, or that a price cannot be paid, before the node or submit_payment has answered.

What the tools do not do

The server confines itself to a closed list of control-plane routes: /agents/list, /agents/card, /tasks/send, /tasks/get, /tasks/list, /tasks/wait, /tasks/cancel, /tasks/reply, /tasks/pay, /balance, /evidence, /status and /inbound/pending. A call to any other route is refused before it reaches the daemon. The control token is the full authority of the node, and the MCP server holds it on behalf of a model.

As a consequence the following operations exist only at a terminal of the operator:

  • allowing, trusting or denying a peer (anet peers), and changing the inbound policy (anet inbound policy);

  • approving a task that waits in the approval queue (anet inbound approve);

  • changing payment limits and the payee list (anet payments set, anet payees add);

  • manual payment (anet pay) and redemption of credit (anet redeem);

  • setting the recovery passphrase and rotating keys (anet identity).

These commands ask for confirmation on the terminal. An agent that needs one of them tells the user the command instead of attempting it.

Errors

A refusal by the daemon reaches the model as the tool error. The text is the sentence that the control plane wrote, the same sentence that the command-line interface shows. For a refusal that is a row of the reason table the sentence is returned without additions; other refusals carry the A2A error name or the machine-readable reason in brackets. The same sentence is therefore what the user sees and what the model reports.

Typical cases:

  • no_hub_yet, hub_pending_admission: the node is not registered at a hub. send_message fails at once, nothing is sent, and the sentence names the step to take. The agent relays the sentence to the user as it is and does not register again.

  • daemon_not_running: no daemon answers; anet up.

  • module_absent: the function belongs to a module that is not available in this build or configuration.

The causes and remedies for each code are in Troubleshooting.

Builds without MCP

A binary built with -tags no_mcp contains no MCP server. anet mcp ends with a message that states the build, and anet agents has nothing to register. See Modules.

See also