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
145
planned
1,361
shipped

Motir · Work items

MOTIR-3362Done

Integration test — add_lesson through the real MCP transport: gated, tenant-only, near-duplicate surfaced

The gate proved wired rather than merely written. Enter at app/api/mcp/route.ts with a token, the way an agent does — not by calling the tool function, which skips exactly the layers that could be wrong.

Four things:

  • A token without the lesson-library-change permission is refused, and no upstream request is made. Assert the absence of the call, not just the refusal — a check that runs after the request has gone out reads identically from the return value.
  • A token with it creates the lesson, and the request that reaches motir-ai names the acting project.
  • No caller can produce a global lesson. There is no argument for it, and a body carrying scope is refused. Since MOTIR-3342 (motir-core#2241) that refusal comes from lib/mcp/strictInput.ts, which rewrites every tool's inputSchema to strict at the registration seam — so the assertion here is that add_lesson inherits it, not that the tool implements anything. It would stop inheriting it the moment someone declared the schema .passthrough(), which is exactly the change this test exists to catch.
  • The near-duplicate refusal survives the whole way out — the existing lesson's id and title are in the tool result the agent receives, not flattened into a generic failure on one of the four hops.

A token bound to one project cannot write a lesson to another.

Provenance is stored, not displayed here: the hand-added sourceRef shows on the detail view MOTIR-3329 builds, and the list does not distinguish. That is deliberate, and if it turns out to matter it is a change to the list, not to this test.

Acceptance criteria

  • Every case enters through the MCP route with a token, following tests/mcp/route.test.ts and permission-gate.test.ts.
  • The unauthorized case asserts no upstream request was issued, and fails if one is.
  • The authorized case asserts the outgoing request names the acting project.
  • A request carrying scope is refused by the strict-input seam, naming the key; no row is created. The test fails if the tool's schema is ever loosened to .passthrough().
  • A token bound to project A cannot create a lesson on project B.
  • The near-duplicate response reaches the caller with the existing lesson's id and title intact.
  • Upstream is stubbed at the HTTP boundary, not by mocking motirAiClient's exported function — a mock at that level would not exercise the body this feature's contract depends on.

Context refs

  • motir-core tests/mcp/route.test.ts, tests/mcp/permission-gate.test.ts, tests/mcp/write-tools.test.ts — the entry shape and token fixtures.
  • motir-core lib/mcp/strictInput.ts — the registration-time schema rewrite that refuses an unknown key; landed in #2241, and the reason the scope case needs no tool-level code.
  • motir-core lib/mcp/permissionGate.ts, lib/mcp/rateLimitGate.ts — the layers under test.
  • MOTIR-3361 — the tool.
  • MOTIR-3360 — the seam whose refusal ordering this pins.
  • MOTIR-3339 — the sibling seam test under MOTIR-3329; same argument, read side.