Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
144
planned
1,362
shipped

Motir · Work items

MOTIR-1856Done

11.6 MCP tool payloads derive from the v1 response schemas — the two surfaces cannot drift

The story that closes the loop and makes the epic's architectural claim structurally true: the REST API and the MCP surface derive their payloads from ONE schema source, so they cannot disagree about what a work item is.

blocked_by 11.4 — those schemas are what MCP derives from — and blocked_by 11.7, which supplies the v1 schemas for the work-loop resources and the per-child edge field criterion 5 below reads on (see the amendment at the foot of this card).

What this is NOT

MCP tools are NOT re-pointed at HTTP routes. That would add an internal network hop, re-authenticate a request already inside the process, and make an in-process tool call depend on the server being reachable from itself. Both surfaces stay thin adapters over services — that is already true of MCP today and is the correct architecture. Agents keep MCP; the REST surface is for the CLI and third parties. This story changes nothing about who calls what.

What unifies them is the response SCHEMA, not the transport. Today each MCP tool hand-builds its structuredContent (lib/mcp/tools/listReady.ts spreads dependencies: edges[item.id] onto rows; getWorkItem.ts returns detail as unknown as Record<string, unknown>), so the same conceptual resource can be shaped two ways in two places — which is exactly how the epic's founding defect arose: list_ready and search_work_items carried a dependencies block while get_work_item did not, invisibly, until a card was planned on the assumption they matched (the planning bug).

What to build

  1. Re-express every MCP tool's structuredContent in terms of the shared resource schemas — the same shapes the REST responses use, rather than a per-tool hand-assembled object. A tool may still project a SUBSET (agents want less), but the subset must be a declared narrowing of the shared schema, never an independently-authored shape. The set is EVERY resource both surfaces expose — see the amendment below for why this is not the four resources this card originally named.
  2. A drift guard in CI — for every resource both surfaces expose, assert the MCP payload validates against the same schema the REST response does, so a field added on one side and forgotten on the other fails the build. Mirror the registry-driven totality pattern lib/mcp/scopes.ts already uses (an ungated tool is a compile error): a tool whose output resource has no declared schema should not typecheck.
  3. Keep the agent-shaped freedom that is legitimate. The tool's human-readable content text block, its tools/list description, its argument names and its scope stay MCP's own — those are prompt-engineering surface and SHOULD churn freely. This story freezes only the data shape, which is the half that has a second consumer. Say so explicitly in the code comments so a later prompt-tuning pass does not read the guard as a ban on rewording a description.
  4. get_work_item's child dependency edges are ASSERTED against the REST equivalent, not re-added. 7.9.16b shipped them on the MCP side; 11.7 adds the REST counterpart. This story asserts the two agree. Check origin/main before writing either.

Why it is worth its own story

Without it the epic delivers two parallel, independently-drifting descriptions of the same domain, and the drift is silent by construction — the failure mode this epic was created in response to. With it, the "agents churn freely / clients get stability" split is enforced by the type system rather than by discipline.

Scope BOUNDARY

Ends at MCP payloads deriving from the shared schemas, plus the CI guard. It does NOT rename, add, remove, split or merge any MCP tool; does NOT change any tool's arguments, scope or description text; does NOT route MCP through HTTP; does NOT change /api/v1 (if the alignment reveals that a REST schema is wrong, that is a corrective card in the owning story — 11.7 for the work-loop resources, which is live and can take it); and does NOT touch motir-ai, which consumes neither surface.

ONE repo — motir-core — for every deliverable.

Acceptance criteria

  • Every MCP tool whose output resource is ALSO exposed on /api/v1 builds its structuredContent from the shared resource schema — asserted per tool, not per module. The set is derived from the operation registry, not from a list in this card, so it cannot fall behind a resource added later.
  • For each resource exposed by BOTH surfaces, a CI test asserts the MCP payload and the REST response validate against the SAME schema; introducing a field on one side only fails the build (asserted by deliberately doing so in the test).
  • A tool whose resource has NO v1 counterpart is EXEMPT and the exemption is explicit — a named, enumerated list with a reason per entry, not silence. (validate_work_item, validate_sprint, get_project_state and claim_next_ready have no client asking for a REST form; 11.7's boundary records that.) An unexplained absence from the guard is the defect this criterion prevents.
  • A tool that projects a subset declares that subset as a narrowing of the shared schema; no tool hand-authors an independent shape.
  • Every existing MCP tool's behaviour is unchanged from a caller's perspective: arguments, scopes, tool names, tools/list descriptions and the human-readable content text are untouched — asserted by the existing MCP suites passing unmodified.
  • get_work_item's children carry dependency edges with key names IDENTICAL to the REST equivalent — which 11.7 creates; verify both against origin/main and ASSERT they agree rather than re-adding either side.
  • The frozen-vs-free split is documented in the code: data shape is shared and guarded, prose/arguments/scopes remain MCP's own.
  • The per-file coverage floor (≥90%) holds on every changed file.

Context refs

  • lib/mcp/tools/listReady.ts · lib/mcp/tools/searchWorkItems.ts · lib/mcp/tools/getWorkItem.ts — the three hand-assembled structuredContent shapes this story re-bases first.
  • lib/mcp/tools/dispatchPrompt.ts · markIntegrated.ts · completeSession.ts · expandItem.ts · planSession.ts · getPlan.ts · getWorkItemActivity.ts — the work-loop tools the widened scope adds, once 11.7 gives each a v1 schema to derive from.
  • lib/mcp/dependencyEdges.ts — the "ONE seam, TWO tools" comment that documents the partial coverage this story generalises.
  • lib/mcp/registry.ts + lib/mcp/scopes.ts — the totality pattern (missing entry ⇒ compile error) the drift guard should copy.
  • lib/api/v1/openapi/registry.ts — the v1 operation registry the "resource exposed by both surfaces" set is derived FROM, so the guard's coverage cannot be a stale hand-written list.
  • lib/dto/workItems.tsWorkItemSummaryDto / WorkItemDependencyEdgesDto / IssueDetailDto.
  • Producers: 11.4 (the shared schemas + the registry) and 11.7 (the work-loop schemas + the per-child edge field). Related: 7.9.16b (shipped the MCP-side edges) and the planning bug that motivated this story. Parent epic: the public REST API.

⚠️ Amended 2026-08-05 — the coverage set is EVERY shared resource, and criterion 5 was unsatisfiable

Two corrections, made when 11.7 was created and this card was re-read against it.

1. Criterion 5 could not pass as written. It asked that get_work_item's children carry dependency edges "IDENTICAL to the REST equivalent" — and there is no REST equivalent: on origin/main @ d4023b3b, workItemDetailSchema.children is workItemRefSchema[] with no dependencies block at all. The MCP side shipped with 7.9.16b; the REST side never did. 11.7 adds it, which is why this card is now blocked_by that story — a present, per-criterion dependency, not a completeness argument.

2. The coverage set was four resources; the title claims all of them. As authored, criterion 1 named "work item, sprint, project or ready row" while the card's title promises "the two surfaces cannot drift" — a claim over the whole surface. Once 11.7 lands, six more resources are exposed on BOTH surfaces (the dispatch prompt, a plan, a plan session, an activity page, a session-close result, an expansion handle), and under the original scope every one of them would sit outside the guard, hand-shaped on each side — the exact defect this story exists to prevent, recurring on the newest resources. So the set is now every resource both surfaces expose, DERIVED from the v1 operation registry rather than listed here, with an explicit enumerated exemption for the MCP-only tools. The title is now true.