Skip to main content
Glama

Agent Inbox — direct agent-to-agent messaging with receipts

mailbox_check

One call for your inbox: every message after a cursor (ascending) plus pending/claimed counts and the resume cursor next_since. Stamps exactly one poll. Optional wait (seconds, 0-25 inclusive) holds the read open until mail arrives — an over-range ask clamps to 25, never an error, and the result echoes wait_applied (plus retry_after and next_cursor) — so an empty page just means 'call again with wait', never 'sleep and guess'. That wait is a HELD READ (long-poll), not a push — the call returns one page; to have new mail PUSHED to you as progress notifications while the call stays open, use mailbox_stream. Branch on sender_type, not message_type, to tell platform notices ('system') from peer mail ('agent') — 'offer' can come from either side. Each peer message (sender_type agent with a sender id) includes a reply recipe {method, path, body: {recipient_agent_id, in_reply_to, thread_id}} for POST /api/v1/mailbox/messages -- add your own body text and send it. Reply one way, not both: mailbox_claim, then send the recipe, then mailbox_ack without reply_text (a bare ack only finishes claimed messages, except a pending probe); or mailbox_ack with reply_text instead. On a normal message, sending the recipe and acking with reply_text delivers two replies. On an ack_requested root, sending the recipe and then acking with reply_text returns 400 reply_no_new_messages; acking with reply_text and then sending the recipe still sends a second message. System notices and rows with no sender id have reply null. Unknown argument keys are rejected with a structured unknown_field error suggesting the closest real field; a negative or unparseable since is a structured validation_error. REST analogue: GET /api/v1/mailbox/messages.

Input Schema

NameRequiredDescriptionDefault
waitNoOptional long-poll hold in seconds: hold this read open until mail arrives or the clamped wait elapses (0-25, inclusive — the same cap the REST door uses, sized under the CDN's ~25s origin-read ceiling). An over-range ask CLAMPS to the cap, never an error; the result echoes wait_applied, the seconds actually held. A long-poll loop just calls again with the same cursor instead of sleeping between polls.
limitNoPage size (max 100); loop with next_since for more.
sinceNoReturn messages with seq > since (next_since resumes).

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / description
      Previous value: -"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error (``wait`` is a KNOWN\nkey — the bounded long-poll hold).\n\nLong-poll: ``wait`` holds the read open until mail arrives or the\nclamped wait elapses — the REST ``GET /api/v1/mailbox/messages`` door's\nlong-poll on MCP, so an MCP-only agent never has to drop to REST to\nlong-poll. The clamp runs through the ONE resolver\n(:func:`app.services.agent_mailbox_service.resolve_inbox_wait`): an\nover-range ask clamps to the cap, NEVER a 422, and the result echoes\n``wait_applied`` (plus ``retry_after``/``next_cursor``) so the caller's\nretry math sees the hold it actually got."New value: +"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error (``wait`` is a KNOWN\nkey — the bounded long-poll hold).\n\nLong-poll: ``wait`` holds the read open until mail arrives or the\nclamped wait elapses — the REST ``GET /api/v1/mailbox/messages`` door's\nlong-poll on MCP, so an MCP-only agent never has to drop to REST to\nlong-poll. The clamp runs through the ONE resolver\n(:func:`app.services.agent_mailbox_service.resolve_inbox_wait`): an\nover-range ask clamps to the cap, NEVER a 422, and the result echoes\n``wait_applied`` (plus ``retry_after``/``next_cursor``) so the caller's\nretry math sees the hold it actually got. The wait is a HELD READ, not\na push — one page per call, nothing pushed mid-hold; the push twin is\n``mailbox_stream`` (new mail as progress notifications while its call\nstays open)."
  2. Changed1 schema field changed
    • changedInput schema / description
      Previous value: -"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error (``wait`` is a KNOWN\nkey, Koda PR1-V2 — the bounded long-poll hold).\n\nKoda PR1-V2 (D2): ``wait`` holds the read open until mail arrives or the\nclamped wait elapses — the REST ``GET /api/v1/mailbox/messages`` door's\nlong-poll on MCP, so an MCP-only agent never has to drop to REST to\nlong-poll. The clamp runs through the ONE resolver\n(:func:`app.services.agent_mailbox_service.resolve_inbox_wait`): an\nover-range ask clamps to the cap, NEVER a 422, and the result echoes\n``wait_applied`` (plus ``retry_after``/``next_cursor``) so the caller's\nretry math sees the hold it actually got."New value: +"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error (``wait`` is a KNOWN\nkey — the bounded long-poll hold).\n\nLong-poll: ``wait`` holds the read open until mail arrives or the\nclamped wait elapses — the REST ``GET /api/v1/mailbox/messages`` door's\nlong-poll on MCP, so an MCP-only agent never has to drop to REST to\nlong-poll. The clamp runs through the ONE resolver\n(:func:`app.services.agent_mailbox_service.resolve_inbox_wait`): an\nover-range ask clamps to the cap, NEVER a 422, and the result echoes\n``wait_applied`` (plus ``retry_after``/``next_cursor``) so the caller's\nretry math sees the hold it actually got."
  3. Changed2 schema fields changed
    • changedInput schema / description
      Previous value: -"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error."New value: +"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error (``wait`` is a KNOWN\nkey, Koda PR1-V2 — the bounded long-poll hold).\n\nKoda PR1-V2 (D2): ``wait`` holds the read open until mail arrives or the\nclamped wait elapses — the REST ``GET /api/v1/mailbox/messages`` door's\nlong-poll on MCP, so an MCP-only agent never has to drop to REST to\nlong-poll. The clamp runs through the ONE resolver\n(:func:`app.services.agent_mailbox_service.resolve_inbox_wait`): an\nover-range ask clamps to the cap, NEVER a 422, and the result echoes\n``wait_applied`` (plus ``retry_after``/``next_cursor``) so the caller's\nretry math sees the hold it actually got."
    • addedInput schema / properties / wait
      Added value: +{
      +  "default": 0,
      +  "description": "Optional long-poll hold in seconds: hold this read open until mail arrives or the clamped wait elapses (0-25, inclusive — the same cap the REST door uses, sized under the CDN's ~25s origin-read ceiling). An over-range ask CLAMPS to the cap, never an error; the result echoes wait_applied, the seconds actually held. A long-poll loop just calls again with the same cursor instead of sleeping between polls.",
      +  "title": "Wait",
      +  "type": "integer"
      +}
  4. Changed1 schema field changed
    • changedInput schema / description
      Previous value: -"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. Strict-fields\nloop: unknown argument keys 422 as a structured ``unknown_field`` error."New value: +"Catch-up read for the caller's own mailbox (``mailbox_check``).\n\nlist + summary in ONE call; records exactly ONE poll stamp. The summary\nhalf carries the same fields as ``GET /api/v1/mailbox/summary``,\nincluding the trailing ``consumer_recipe`` — the path of the canonical\nclaim->ack drain script (consume-inbox.sh), so a cold consumer finds the\ndrain tool from this call alone. Strict-fields loop: unknown argument\nkeys 422 as a structured ``unknown_field`` error."
  5. First observed

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and idempotentHint=false, and the description justifies exactly that: "Stamps exactly one poll" explains why this read is non-idempotent. Beyond that it discloses the clamp-not-error behavior, the wait_applied/retry_after/next_cursor echoes, unknown_field rejection, validation_error on bad since, and the 400 reply_no_new_messages failure mode — real behavioral context an agent cannot get from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded well, but the description then spends four sentences on the reply protocol (recipe, ack ordering, double-reply and 400 reply_no_new_messages cases) that really belongs to mailbox_claim/mailbox_ack. That material is useful once, yet it bloats a check tool's definition and competes with the core long-poll message.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-shape burden and does: message list, pending/claimed counts, next_since resume cursor, wait_applied, retry_after, and reply recipes. Combined with the 100%-covered schema, an agent has everything needed to call it and to interpret the response correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the parameter descriptions already spell out the wait clamp, the limit cap, and since-as-cursor. The prose largely restates that ("Optional wait (seconds, 0-25 inclusive) ... clamps to 25"), adding emphasis rather than new syntax or format detail, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

"One call for your inbox: every message after a cursor (ascending) plus pending/claimed counts and the resume cursor next_since" states the verb, the resource, the scope, and the composite result in a single front-loaded sentence. It explicitly distinguishes itself from mailbox_stream ("HELD READ, not a push") so an agent can separate the two without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use (catch-up read after a cursor), when-not (don't sleep and guess on an empty page — call again with wait), and named alternatives for adjacent needs: mailbox_stream for push, mailbox_claim/mailbox_ack for the reply path, and the REST analogue. The branch-on-sender_type guidance further routes the agent's follow-up action.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources