MCP tools are built to create, not to maintain — add a design checklist #7

Open
opened 2026-08-10 09:37:48 +00:00 by mathias · 0 comments
Owner

Observation

Three separate tool gaps blocked work in a single session on 2026-08-10, and they share one shape.

Gap Server Effect
No label_create gitea-mcp Labels had to be hand-made in the UI before any could be applied
No issue_unlabel gitea-mcp Stale blocked/* labels could not be cleared; the board only accumulated
capture with a tickets array fails brain-mcp Fell back to individual issue_create calls — 20 instead of 1

The first two are now gitea-mcp#61. This issue is about the pattern behind them.

The pattern

Our MCP servers are built to create, not to maintain.

issue_create, label_list, issue_label, file_write_branch, brain_write — the create-and-read half is well covered. The half that lets an agent correct something it or someone else got wrong is thin or missing.

That has a specific consequence for agentic work. An agent reconciling desired state against actual state needs to remove as well as add. Without removal it can only append, so the state it manages drifts steadily away from reality — and the tracker starts describing a world that no longer exists. In this session that meant three issues carrying blockers that had already cleared, discovered only because someone read the labels sceptically during sprint planning.

State that can only be added is not state, it's sediment.

Why it happens

Tools get specified from the motivating case, and the motivating case is almost always a creation: file the issue, write the note, apply the label. The maintenance path is the second session, by which point the tool is "done".

Proposal — a design checklist for MCP tool specs

Before a tool is considered complete, answer:

  1. How does an agent undo this? If the answer is "the UI", the tool is half-built.
  2. Is it idempotent? Removing something already absent, or adding something already present, should be a no-op rather than an error — reconciliation loops do this constantly.
  3. Does a mutation return the resulting state? Self-verifying results remove a whole class of read-back calls and let an agent confirm rather than assume.
  4. What is the blast radius, and does the description say so? label_delete strips a label from every issue in a repo. An agent cannot infer that from the name.
  5. Does batching work at realistic sizes? brain:capture accepts a tickets array that fails in practice, which is worse than not offering it — the agent tries, fails, and retries smaller before giving up.

Not a rule that every tool needs a full CRUD surface. Some things should stay human-only — repo deletion, for instance. But that should be a recorded decision with reasoning, not an accident of which case came first.

Done when

  • Checklist agreed and recorded somewhere tool specs actually get written against
  • Existing MCP servers audited against it — gaps filed rather than fixed wholesale
  • gitea-mcp#61 closed as the first application

Note

Filed here rather than on a specific server because it cuts across all of them. If there is a better home — an ADR, or mcp-chassis — move it and close this.

Risk: low — a convention, not a change
Blocked on: nothing

## Observation Three separate tool gaps blocked work in a single session on 2026-08-10, and they share one shape. | Gap | Server | Effect | |---|---|---| | No `label_create` | gitea-mcp | Labels had to be hand-made in the UI before any could be applied | | No `issue_unlabel` | gitea-mcp | Stale `blocked/*` labels could not be cleared; the board only accumulated | | `capture` with a `tickets` array fails | brain-mcp | Fell back to individual `issue_create` calls — 20 instead of 1 | The first two are now [gitea-mcp#61](https://git.d-ma.be/mathias/gitea-mcp/issues/61). This issue is about the pattern behind them. ## The pattern **Our MCP servers are built to create, not to maintain.** `issue_create`, `label_list`, `issue_label`, `file_write_branch`, `brain_write` — the create-and-read half is well covered. The half that lets an agent *correct* something it or someone else got wrong is thin or missing. That has a specific consequence for agentic work. An agent reconciling desired state against actual state needs to remove as well as add. Without removal it can only append, so the state it manages drifts steadily away from reality — and the tracker starts describing a world that no longer exists. In this session that meant three issues carrying blockers that had already cleared, discovered only because someone read the labels sceptically during sprint planning. **State that can only be added is not state, it's sediment.** ## Why it happens Tools get specified from the motivating case, and the motivating case is almost always a creation: file the issue, write the note, apply the label. The maintenance path is the second session, by which point the tool is "done". ## Proposal — a design checklist for MCP tool specs Before a tool is considered complete, answer: 1. **How does an agent undo this?** If the answer is "the UI", the tool is half-built. 2. **Is it idempotent?** Removing something already absent, or adding something already present, should be a no-op rather than an error — reconciliation loops do this constantly. 3. **Does a mutation return the resulting state?** Self-verifying results remove a whole class of read-back calls and let an agent confirm rather than assume. 4. **What is the blast radius, and does the description say so?** `label_delete` strips a label from every issue in a repo. An agent cannot infer that from the name. 5. **Does batching work at realistic sizes?** `brain:capture` accepts a `tickets` array that fails in practice, which is worse than not offering it — the agent tries, fails, and retries smaller before giving up. Not a rule that every tool needs a full CRUD surface. Some things *should* stay human-only — repo deletion, for instance. But that should be a recorded decision with reasoning, not an accident of which case came first. ## Done when - [ ] Checklist agreed and recorded somewhere tool specs actually get written against - [ ] Existing MCP servers audited against it — gaps filed rather than fixed wholesale - [ ] gitea-mcp#61 closed as the first application ## Note Filed here rather than on a specific server because it cuts across all of them. If there is a better home — an ADR, or `mcp-chassis` — move it and close this. **Risk:** low — a convention, not a change **Blocked on:** nothing
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/homelab#7