Skip to content

feat: redesign mcp surface as 8 ref-first tools#170

Merged
FrkAk merged 39 commits into
mainfrom
feat/PYZ-290-mcp-v2-surface
Jul 4, 2026
Merged

feat: redesign mcp surface as 8 ref-first tools#170
FrkAk merged 39 commits into
mainfrom
feat/PYZ-290-mcp-v2-surface

Conversation

@FrkAk

@FrkAk FrkAk commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Task Reference: [PYZ-290]

Replaces the 6-tool MCP surface (piyaz_project/task/edge/query/context/analyze) with 8 ref-first tools (piyaz_workspace/search/get/create/edit/link/map/activity) in one breaking change. Refs (PYZ-42, PYZ) are first-class on every parameter with server-side resolution (org-bounded, candidate lists on ambiguity, near-miss copy on a miss; the near-miss lookup runs only when the ref misses). Task editing becomes operation-based (str_replace/append/set on text, by-id ops on collections, ifUpdatedAt compare-and-swap) and overwriteArrays is removed. piyaz_edit reads the task row FOR UPDATE, so concurrent edits to the same task serialize and a stale ifUpdatedAt fails deterministically; edit ops enforce the create-path per-field size caps, tag writes return the same variant/taxonomy hints as the create path, link update patches only the supplied fields, and delete_task rejects ifUpdatedAt. set prUrl guarantees a pull_request-kind link at that URL on any host (one shared helper serves the web and MCP paths): a same-URL link of another kind is converted in place keeping its label, retries are no-ops, a task can carry multiple PR links, and null clears them; status hints derive the prior status and links from the write transaction instead of extra reads. Task creation becomes an idempotent batch (1-25 tasks + edges, exact-title dedup; duplicate edges within one payload dedupe to their first occurrence; assignees are membership-checked once per batch over the to-create items, before any write). New read capabilities: cross-project filtered search with keyset cursors (forged non-UUID cursor ids degrade to page 1), single-field reads (fields=[...], framed with the untrusted-content notice), a record lens, 1-2 hop neighbors walks, and a keyset activity feed with since for resume. Context lenses now render category, work-so-far execution records, assignees (agent lens), and relates_to neighbors; lens bundles width-cap edge/prerequisite/downstream/related lists and upstream record blocks with narrowing guidance. piyaz_workspace gains members (one team's directory — name, user UUID, role — the UUID source for assigneeIds/assignee ops/filters, backed by the membership-gated team_members_visible SECURITY DEFINER read) and rename_category/delete_category (vocabulary edits that cascade to task rows atomically, reusing the web data functions; plain update categories=[...] replaces the list without touching tasks and now says so). Every list-shaped response is budgeted with truncation guidance, including piyaz_workspace teams/projects/members backstops. MCP gets dedicated rate-limit tiers (RATE_LIMIT_MCP 100/60, RATE_LIMIT_MCP_HEAVY 20/60 enforced per tool shape in wrapTool; heavy covers deep lenses, overview, critical_path, hops-2 neighbors, task- or edge-heavy batches, and the category cascades) and a structured per-call log line. Task statuses and the heavy lenses derive from single shared lists (TASK_STATUSES in lib/types.ts, DEEP_LENSES in the schema module). Plugin doctrine (all four brands) and generated docs are rewritten against the new surface.

The write surface is now project-lifecycle-phase aware (brainstorming → decomposing → active → archived). Task/edge writes in a brainstorming project and execution-status flips (in_progress/in_review/done) in a decomposing project return advisory _hints naming the exact promote call; active is silent. archived is hard read-only on the task surface: piyaz_create, piyaz_edit (including delete_task preview=false; preview stays a read), piyaz_link, and the category cascades throw a new ProjectArchivedError in the data layer before any write, translated to an error naming the reopen call. Reads and piyaz_workspace update stay allowed (the un-archive path); metadata-only updates and identifier renames on an archived project carry the read-only hint. piyaz_workspace update status=... returns transition hints for forward skips, backward moves, and the archived read-only consequence, ordered by a shared PROJECT_STATUS_ORDER in lib/types.ts. Status reads cost zero extra queries: the task access gate joins projects (single-row PK join) for projectStatus/projectIdentifier, and updateProject/getProjectCategories/createTasksBatch/applyTaskEdit/createEdge surface the phase from reads they already perform. The previously dead decomposing phase is wired into the decompose and onboarding agents (flip at task-creation start, active on validation, decomposing counts as resume mode); lifecycle.md gains a project-lifecycle section and the schema .describe() prose documents the phases for docs:gen.

Type of change

  • New feature

Testing

  • Tested locally with bun run dev
  • Linting passes (bun run lint)
  • Typecheck passes (bun run typecheck)
  • Full suite green (bun test), including a 17-test MCP read-surface regression suite over the InMemoryTransport client (tests/regression/) and a concurrency test proving one winner + one StaleWriteError on contended ifUpdatedAt edits
  • piyaz_workspace action='update' covered end to end: field persistence, no-op rejection, malformed identifier fails before any write, identifier-rename taskRef cascade, rename+fields in one call, rename conflict (clean 23505), member-role rename refusal
  • members covered: directory with roles and assignment steering, foreign-team 404 shape, multi-team ambiguity; rename_category/delete_category covered: task cascade, unknown/colliding names, uncategorize
  • Heavy-tier budget gate asserted over the InMemoryTransport client: heavy shapes (agent lens, critical_path, hops-2 neighbors, 6-task batch, rename/delete_category) rejected with the retry copy before any write; light shapes and plain workspace updates pass under an exhausted heavy budget
  • set prUrl contract covered: kind forced to pull_request on non-GitHub hosts, same-URL conversion keeps the user label, second URL keeps both PR links, null clears; batch assignee checks covered for persist and non-member fail-before-any-write
  • Phase gating covered handler-level: brainstorming/decomposing hints, silent active phase, the full archived matrix (create/edit/link/delete/rename_category blocked, preview and reads allowed, unarchive restores writes, metadata updates and identifier renames hinted), and forward-skip/backward transition hints; also walked live over MCP against a throwaway local-dev project

Notes for reviewer

  • Deploys need bun run db:rls: three new SECURITY DEFINER functions in docker/rls-functions.sql (activity_actors_for_project_visible, current_user_profile, team_members_visible — reintroduced with a TS caller and a drop-first guard against the legacy signature).
  • lib/graph/tool-handlers.ts and its tests are deleted; assertions ported to tests/graph/tools-{read,write}.test.ts. New transport tests cover the InMemoryTransport pair and the route's 401/413 boundary.
  • createTask was refactored onto a createTaskTx seam for the batch path; the untouched pre-existing task test suite is the behavior-identity proof.
  • Ref resolution runs on the batched read path (no interactive transaction); each write tool still opens exactly one transaction.
  • wrangler.jsonc adds rate-limit namespaces 1003/1004 (prod) and 2003/2004 (dev).
  • docs-sync.yml now watches lib/graph/tools/** (the deleted tool-handlers.ts path is gone).

Docs impact

Generated MCP tool reference changes from 6 pages to 8 (Docs Sync opens the PR automatically). The 6 orphan pages reference/tools/{project,task,edge,query,context,analyze}.mdx in piyaz-docs need manual deletion; meta.json is regenerated.

BREAKING CHANGE: piyaz_project/task/edge/query/context/analyze are removed; the MCP surface is now piyaz_workspace/search/get/create/edit/link/map/activity. Once this deploys to prod, plugins still on 0.2.x call the removed tools and their skills carry stale prompts against the old surface. Users must update their plugin to 0.3.0.

@FrkAk FrkAk requested review from ZeyNor and ulascanzorer as code owners July 3, 2026 10:51
@FrkAk FrkAk self-assigned this Jul 3, 2026
@FrkAk FrkAk merged commit 576d2bd into main Jul 4, 2026
5 checks passed
@FrkAk FrkAk deleted the feat/PYZ-290-mcp-v2-surface branch July 4, 2026 16:31
@FrkAk FrkAk mentioned this pull request Jul 4, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants