# Agent MCP Result Budget Contract

> Canonical path: `specs/daemon/agent-mcp-result-budget.md`
> Status: living-spec
> Scope: AgentTeam Desktop MCP result boundaries and local workspace filesystem browse surfaces.

## Purpose

Agent-facing MCP tools must not return or read data that grows without an explicit request or product bound. The final MCP protocol boundary must also protect model context if a tool implementation forgets to page its backend query or accidentally includes a large structured payload.

## Result-boundary invariant

Every `tools/call` result returned by AgentTeam Desktop MCP servers must pass through `desktop/internal/resultbudget` before the JSON-RPC response is written. This includes the first-party `agentteam` MCP surface and other registered MCP handlers.

The result budget is a fallback, not a substitute for domain paging. Domain tools must still use request-bound or product-limit-bound queries and must report `has_more`/`next_cursor` or an explicit top-N contract where more data exists. AgentTeam Exec has a separate typed-success rule: its exhaustive built-in action policy is maintained in [`agentteam-exec-action-inventory.json`](./agentteam-exec-action-inventory.json), and an over-64 KiB complete envelope is never projected into a partial typed success. Read-only actions report `result_budget_exceeded` with continuation guidance; a mutation that may already have committed reports a compact applied receipt in the bounded error metadata and never advises replay.

The result budget must bound:

- `content[].text` total length and per-block length;
- `structuredContent` list length, map key count, string length, nesting depth, and encoded size;
- raw proxied tool results so only standard MCP tool result top-level fields are preserved (`content`, `structuredContent`, `isError`, and `_meta`), with non-standard top-level fields omitted before the response is returned;
- metadata describing truncation, including `truncated`, `budget_kind`, `omitted_count`, and `omitted_paths`.

## Surface inventory

The canonical inventory is maintained in [`agent-mcp-tool-inventory.json`](./agent-mcp-tool-inventory.json). Guardrail and unit tests may consume this file directly, but the inventory must remain a durable spec-owned artifact rather than hidden test fixture data.

Each inventory row records the surface entry point, classification, default and maximum limits, cursor/`has_more` contract, per-item budget, final result budget, backend query bound, and structured-content policy.

## List versus search semantics

Agent-facing list tools are browse contracts: they return one bounded page ordered by a documented browse sort, and callers continue with a cursor when they intentionally want more of that browse sequence. A list tool must not be the recommended path for dedupe, locating existing work, or other query-driven lookup patterns.

When an agent needs to find an existing entity by text or identity, the surface must provide a search/lookup contract with required query input, explicit default/max limits, and the same final result budget. Current splits are:

| Entity | Browse/list surface | Search/lookup surface | Contract |
| --- | --- | --- | --- |
| messages | `message_history_read` | `message_search` | chronology reads stay on history; keyword lookup uses search |
| tasks | `list_tasks` | `search_tasks` | `list_tasks` rejects `q`/`query`/`search`; dedupe, locating, and checking existing tasks use `search_tasks` |
| task field/member options | `task_options` field option page | `task_options` member query mode | options lookup is bounded and role-scoped; it is not a task/work dedupe surface |

Future agent-facing reuse of internal member or review endpoints must preserve this separation: browse pages may remain `list_*`, but query-driven lookup/dedupe paths should be exposed as `search_*` or as an explicitly named lookup mode rather than hidden broad lists.

## Defaults

Unless a more specific product contract is documented in the inventory:

- discovery, search, and list defaults are 20 items, with max 100;
- detail sections default to 10 items, with max 100;
- local filesystem directory browse defaults to 50 entries, with max 200;
- hidden held-refresh reads from the daemon-local continuation inbox are capped at 50 messages;
- single-item tools must still pass through the result budget because single payloads such as skill content, app details, review context, or future artifact content can be large.

## Backend-read rules

Tools must not compute exact totals or summaries by scanning all history, members, refs, stashes, files, or local directories unless the inventory marks the count as cheap and indexed or bounded by configuration. Prefer `limit+1` reads to compute `has_more` and `next_cursor`.

Local git and filesystem surfaces must be treated as growth-sensitive:

- git log reads at most the requested page plus one extra commit and does not walk history for an exact total;
- refs, branches, tags, stashes, status files, untracked files, and conflict files are paged or capped before being formatted; git branch/tag/stash query filters operate within bounded candidate pages and use the cursor to continue candidate scanning rather than scanning all refs/stashes for sparse matches; MCP git status and merge-status must not fall back to unbounded full working-tree status scans;
- workspace directory browse is one-level and paged without first reading and sorting the whole directory; workspace scans page agent directories and summarize each selected directory with a bounded one-level scan only.

## Mutation payload rules

Mutation tools must reject oversized semantic payloads before writing. The MCP 4 MiB transport body limit is not a product contract. Each mutation must enforce tool-specific caps for array item counts, string byte/rune length, map/object size, and total payload size. Oversized mutation input returns an error and must not be partially applied. This includes workspace seed-file batches and local git mutation fields such as commit messages, tag messages, and ref names.
