---
cuppy_hub: cuppy
cuppy_path: guides/agent-ergonomics.md
cuppy_section: docs
cuppy_doc: JS1Q-RBCN
cuppy_doc_uuid: 07b71057-4a20-48f4-8886-d85811860ac7
cuppy_permalink: https://cuppy.io/cuppy/d/JS1Q-RBCN
cuppy_version: 1
cuppy_updated: 2026-10-17T06:32:45.511Z
cuppy_canonical: https://cuppy.io/cuppy/docs/guides/agent-ergonomics
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 36e1036513a0c29a97989bb297055007f4dd2ae939de716d76cad7cefb9a8f0b
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---

# MCP Agent Ergonomics — second-pass analysis & improvements

Companion to `TOOL_NAMING_STANDARD.md`. Goal: make Sonnet- and Haiku-class agents
succeed on first attempt **without deep reasoning** — the tool surface itself should do
the thinking. Findings are from a full read of `mcp/handler.ts` (1,138 lines),
`mcp/tools.ts` (209 defs), and `services/hello.ts`.

## Measured findings

| # | Finding | Evidence |
|---|---|---|
| F1 | **Zero parameter-level descriptions.** 209 `description:` fields = exactly one per tool; every parameter is a bare `{ type: 'string' }`. | `tools.ts` grep |
| F2 | **One `enum` in the entire catalog**, despite ≥8 closed-set parameters (export profile, job type, override type, secret kind, report kind, CRUD collection, pattern status, source type). | `tools.ts` grep |
| F3 | **Errors don't teach.** `"projectId is required."`, `"Pattern not found."` — no recovery path. (Exception that proves the rule: `account.elevate`'s email error includes a copy-pasteable example and is excellent.) | `handler.ts:198,594,1016,1085` |
| F4 | **Pass-through state objects.** ~10 tools require `modelPlan` / `entityPlan` / `workflowPlan: {type:'object'}` — the agent must fetch a large JSON and echo it back intact. Small models mangle round-tripped objects. | `tools.ts:41–48,52–54,58` |
| F5 | **Boot payload is oversized and partly human-themed.** `_hello` returns ASCII banners, progress bars, "Choose your adventure"; `agentBootContext.toolNamespaces` dumps **all 209 tools with full descriptions, unfiltered by role**; 14 dense `criticalRules`; a flat 24-step `recommendedWorkflow` with no conditions. | `hello.ts:35–148` |
| F6 | **Upsell footer is injected into tool results mid-task** (2nd call, then every 5th) for free tiers. Small models sometimes respond to injected marketing instead of continuing the task. | `handler.ts:77–88,138` |
| F7 | **Wire response omits `title`, `outputSchema`, and MCP tool `annotations`** — only name/description/inputSchema are served. `initialize` returns no `instructions` and pins `protocolVersion: '2024-11-05'`. | `handler.ts:118,121` |
| F8 | **Guidance is internally inconsistent.** Prompts/quickStart reference alias names (`semarchy.catalog.search_patterns` → actually runs `list_patterns`; `semarchy.deploy.upload_csv_data`), while criticalRules reference the flat names. `serverInfo.version` (0.25.1) ≠ hello version (0.24.0). | `handler.ts:66–73`, `hello.ts:41–49,150` |
| F9 | **UI-speak in descriptions.** "for right-panel preview or full-screen edit modal", "rich ASCII job telemetry" — meaningless or misleading to an agent deciding whether to call. | `tools.ts:163–165,133` |
| F10 | Descriptions say **what**, almost never **when** — no trigger conditions, no "typically follows X", no "don't use for Y". | catalog-wide |

## Improvements, in priority order

### P1 — Schemas that remove choices (biggest small-model lift)

1. **Enum every closed set.** A Haiku given `profile: {type:'string'}` invents
   `"seml"`; given `enum: ['accelerator','seml_only','docs_only','validation','prompt_pack','deployment']`
   it cannot be wrong. Apply to: `workspace-export_profile.profile`,
   `jobs-queue.type`, `report-submit.kind`, `security-store_secret.kind`,
   `model-add_override.type`, `crud-*.collection`, `learning-list_patterns.status`,
   `docs-add_source.type`, `data-browse.collection` (if listable at boot).
2. **Describe every parameter, with an example value in the description.**
   `projectId: { type:'string', description: "Modeling project id, e.g. 'project-G17BOeBt'. List with semarchy-project-status." }`
   This single pattern collapses the two most common small-model failures
   (wrong format, wrong source of the value).
3. **`additionalProperties: false`** on every inputSchema, defaults declared in-schema.
4. **One vocabulary for shared params** — `projectId`, `query`, `limit` mean the same
   thing everywhere (already mostly true; lock it in the validator).

### P2 — Stop making agents carry state (F4)

Every tool that takes `modelPlan`/`entityPlan`/`workflowPlan` objects should accept
`projectId` and load the stored plan server-side (the server already persists plans per
project). Keep the object param as an optional override for advanced callers. The agent
contract becomes: **"identifiers travel; documents stay on the server."** This is the
single biggest reliability win for Haiku-class models in the generation pipeline.

### P3 — Errors that contain the next action (F3)

Standardize every thrown error to: *what's wrong → what to call instead → example*.

```
projectId is required. Call semarchy-project-status {} to list projects,
or semarchy-fixtures-create_demo {"fixtureId":"..."} to create one.
```

- "Not found" errors list 2–3 valid candidates when cheap to compute.
- Unknown-tool errors (post-rename) suggest the nearest valid name (levenshtein over
  the 172 names is ~free) — this also softens the dotted→dash cutover.
- Role-denial errors already do this well (`elevationHint`) — extend the pattern.

### P4 — Descriptions that say *when*, not just *what* (F9, F10)

Rewrite descriptions in the form **capability + trigger + sequencing**, ≤ 2 sentences:

> *Before:* "Browse a data collection as rows suitable for list-table plus right-panel detail UX."
> *After:* "Browse rows of a local data collection (projects, jobs, audit, …). Use when the user asks what data exists; use semarchy-data-read_record to open one row."

Prescriptive "use when / use after / don't use for" clauses in descriptions give a
measurable lift in correct-tool selection for conservative models. Strip all UI-speak.
The rename pass touches every definition anyway — do both edits in the same sweep.

### P5 — Boot diet + protocol upgrades (F5, F7)

1. **`initialize.instructions`**: add the 3-sentence boot pointer ("Call
   `semarchy-_hello` first…"). Agents receive it before listing tools — the strongest
   first-tool guarantee available. Bump `protocolVersion` negotiation (2025-03-26+).
2. **Split `_hello` by audience.** Default (agent) response: role, project count,
   conditional next step, ≤6 critical rules, domain list (names only) — target < 1.5K
   tokens. The ASCII art/banners move behind `format: "visual"` (the `intent` param
   already exists; today it does nothing).
3. **Role-filter everything in the boot payload** — today `toolNamespaces` and tool
   counts include tools the caller cannot invoke; an agent that plans around a tool it
   can't call wastes its only shot.
4. **Make `recommendedWorkflow` conditional, not linear**: "no project → intake/fixtures;
   project planned → review; artifacts generated → validate; validated → deploy."
   Three branches beat 24 ordered steps.
5. **Serve `title` and MCP `annotations`** on tools/list:
   `readOnlyHint: true` on all dashboards/list/get tools (~half the catalog),
   `destructiveHint: true` on deploy/upload/update/store, `idempotentHint` where true.
   Clients gate dangerous calls and models get a free safety signal.

### P6 — Result hygiene (F6 + token economy)

1. **Lead the text result with one plain sentence** (outcome + next step); keep
   `structuredContent` as the machine-readable truth. ASCII boxes/bars/trees are
   decoration that costs tokens and adds no information for a model — reserve them for
   `format:"visual"` or drop in agent context.
2. **Every workflow tool's `structuredContent` carries `nextAction`**
   (`{ tool, args, reason }`) and `blockers[]`. A small model then runs the entire
   factory pipeline as a follow-the-pointer loop — `semarchy-project-next_action`
   already computes this; surface it on every step's result instead of requiring a
   separate call.
3. **Move the upsell out of mid-task results.** Restrict the footer to `_hello` and
   `cuppy-account-*` responses, or put it in `structuredContent.upgradeHint` where it
   can't be mistaken for task content. Keep the (good) elevation hints on role-denied
   errors.

### P7 — Consistency sweep (F8)

- All prompts, quickStart entries, criticalRules, and docs reference the **new canonical
  names only** (this falls out of the rename, but add a CI grep that fails on
  `semarchy\.` / `cuppy\.` / `account\.` in served strings).
- Single source for the version string (serverInfo and `_hello` currently disagree).

### P8 — Optional: a "core" surface for small models

172 tools is fine for Opus-class agents; it's working memory pressure for Haiku. The
role tiers already trim (public = 11). If you want more: add an opt-in per-key
`toolProfile: 'core'` that serves only the ~30 spine tools (hello, account, project,
flow, model, validation, workspace read, docs search, report) — everything else remains
reachable on the full profile. Cheap to implement (one field on `api_keys`, one filter
in tools/list); defer until you see small models flailing on selection.

## Validator additions (extends the naming validator)

Registration-time checks, server refuses to boot on violation:

1. Name grammar + uniqueness + sentinel rule (from TOOL_NAMING_STANDARD.md).
2. Every parameter has a non-empty `description`.
3. String params whose description lists fixed options must declare `enum`.
4. `additionalProperties: false` present.
5. Tool description ≤ 350 chars and contains no banned UI vocabulary
   (`modal`, `panel`, `ASCII`, `UX`).
6. Served strings (prompts, quickStart, criticalRules, elevation hints) contain no
   dotted tool names.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 18 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/cuppy/
<!-- /cuppy -->
