Skip to content

Add MCP write tools: create / connect / update-docs / move (#19)#55

Merged
CuriouslyCory merged 2 commits into
mainfrom
feat/mcp-write-tools
Jun 1, 2026
Merged

Add MCP write tools: create / connect / update-docs / move (#19)#55
CuriouslyCory merged 2 commits into
mainfrom
feat/mcp-write-tools

Conversation

@CuriouslyCory

@CuriouslyCory CuriouslyCory commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Closes #19.

Summary

Four single-op MCP write tools — create_component, connect_components, update_component_docs, move_component — over the existing service layer, plus a new moveNode service for reparenting. Each tool is a thin adapter, wrapped in db.$transaction; the agent receives a short text confirmation with the affected row id (so it can chain calls without an intermediate read).

moveNode (ADR-0024) writes only parentId — non-cascading by design. Cycle-creating moves reject as ValidationError; moves that would orphan an active incident Connection reject as ConflictError with details.conflictingEdgeIds. The cross-scope refinement falsification case was considered and shown to be unreachable under current routeFlow / connectNodes constraints — the analysis lives in ADR-0024, and the placement for a future reject is named.

A new generic toMcpWriteError keeps NOT_FOUND/FORBIDDEN indistinguishable (read posture, ADR-0022) while preserving CONFLICT/BAD_REQUEST messages and carrying ConflictError.details through as data.archDetails — the AI-readable self-correction channel future Flow tools (#40 / #42) reuse unchanged.

The tool catalog is plain data (WRITE_TOOLS in src/server/mcp/tool-catalog.ts), so SDK registration, tools/list, and /llms.txt all render from one source.

Scope boundary (held)

Verification

  • pnpm check — eslint + tsc clean.
  • pnpm test — 256 / 256 (12 test files). 11 new moveNode cases (happy paths, idempotent no-op, cycle, missing/soft-deleted/foreign parent, non-owner, incident-edge reject with structured details, soft-deleted edges ignored, descendants travel intact) + 8 toMcpWriteError cases (non-disclosure parity, message preservation, details envelope, Zod, opaque internal, McpError passthrough).
  • 🔲 MCP Inspector round-trip — see the PR comment with manual testing steps.

Docs (travel-with-code)

  • New: docs/adr/0024-movenode-reparent-reject-orphaning.md.
  • Updated: CONTEXT.md — new MCP tool glossary entry (canonical write-surface term); Node entry reflects realized reparenting; status notes in Agent and MCP path.

Test plan

  • Mint an API token from /connect and confirm tools/list over the MCP Inspector enumerates exactly the four tools — no delete tool listed.
  • create_component round-trips through a read of the architecture://project/{projectId} resource (the acceptance-criterion round-trip).
  • connect_components between two created Components; duplicate call returns a ConflictError carrying archDetails.conflictingEdgeIds.
  • update_component_docs persists; /p/{slug} shows the new docs.
  • move_component to a new parent succeeds; moving onto self or a descendant returns BAD_REQUEST; moving a Component with active Connections returns CONFLICT carrying archDetails.conflictingEdgeIds.
  • Unauthenticated request to /api/mcp returns 401.
  • /llms.txt lists all four tools by name.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Reparenting: move nodes within projects with cycle detection, orphaning prevention, and idempotent no-op behavior.
    • Write-tool discovery: discovery endpoint now exposes write-tool metadata and richer access/error guidance.
  • Improvements

    • Consistent error mapping for tools with structured conflict details and non-disclosure of sensitive messages.
    • Centralized write-tool catalog and transactional tool invocation.
  • Documentation

    • Glossary and ADR updated to clarify milestones, tool semantics, and reparenting policy.
  • Tests

    • Added suites validating reparent behavior and MCP error translations.

Four thin-adapter MCP tools over existing services, plus a new moveNode service
for reparenting (cycle prevention + incident-edge orphan reject). Each tool is
a single-op write wrapped in db.$transaction; service errors translate through
a generic toMcpWriteError that carries ConflictError.details as data.archDetails
so an agent can self-correct on a duplicate Connection or a blocked move
(ADR-0010 named pattern). No destructive tool is exposed.

The moveNode service writes only parentId — non-cascading by design (ADR-0024).
Cycle-creating moves reject as ValidationError; moves that would orphan an
incident Connection reject as ConflictError with details.conflictingEdgeIds.
A cross-scope FlowRoute falsification check was considered and shown to be
unreachable under current routeFlow/connectNodes constraints (analyzed in
ADR-0024); the placement for the check is named explicitly so a future
constraint loosening lands locally.

The catalog is plain data (WRITE_TOOLS in src/server/mcp/tool-catalog.ts), so
the SDK registration loop and /llms.txt render from one source — #40 / #42's
Flow / FlowRoute tools and #20's apply_graph batch tool plug in additively.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
infinite-docs Ready Ready Preview, Comment Jun 1, 2026 3:46am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9337186a-402c-4c8d-ac01-407fdea21090

📥 Commits

Reviewing files that changed from the base of the PR and between eed2d87 and 5e51d1e.

📒 Files selected for processing (3)
  • CONTEXT.md
  • src/lib/schemas.ts
  • src/server/architecture/node.service.ts

📝 Walkthrough

Walkthrough

Adds a moveNode reparent service with cycle and orphaning guards, a typed moveNode input schema and tests, MCP write-tool catalog and registration (four tools), MCP write error translation, and an updated /llms.txt discovery document exposing write-tool metadata and structured conflict details.

Changes

MCP write tools feature

Layer / File(s) Summary
Specification and input contracts
CONTEXT.md, docs/adr/0024-movenode-reparent-reject-orphaning.md, src/lib/schemas.ts
Glossary and ADR clarify moveNode and MCP adapter semantics; adds exported moveNodeInput Zod schema and MoveNodeInput type.
moveNode service implementation and comments
src/server/architecture/node.service.ts
Adds exported moveNode(db, actor, input) implementing load-then-authorize, idempotent no-op, parent liveness check, recursive-subtree cycle rejection (ValidationError), incident-edge orphan rejection (ConflictError with conflictingEdgeIds), and a non-cascading parentId update; updates related comments.
moveNode test suite
src/server/architecture/__tests__/node.service.test.ts
Comprehensive tests covering success cases, root moves, idempotency, cycle prevention, authorization, soft-deleted/cross-project parent rejection, not-found/forbidden cases, conflict detection with conflictingEdgeIds, soft-deleted edge ignoring, and subtree travel semantics.
MCP write-tool error translation
src/server/mcp/errors.ts, src/server/mcp/__tests__/errors.test.ts
toMcpWriteError maps service/Zod errors into MCP McpError for WRITE handlers: non-disclosure for NOT_FOUND/FORBIDDEN (shared InvalidParams message), BAD_REQUEST/CONFLICT surface messages, CONFLICT forwards details as data.archDetails, Zod → InvalidParams generic, fallback → InternalError. Tests validate mappings.
Tool catalog definition
src/server/mcp/tool-catalog.ts
Exports McpWriteToolDescriptor generics, ToolInvocationResult, and WRITE_TOOLS array of four tools (create_component, connect_components, update_component_docs, move_component) each with input schema and invoke calling the corresponding service and returning a confirmation message.
Tool registration and handler wiring
src/server/mcp/tools.ts, src/server/mcp/handler.ts
registerArchitectureTools registers each WRITE_TOOL on the per-request McpServer, validates input, derives actor from extra.authInfo, runs descriptor.invoke inside db.$transaction, returns text content, and normalizes errors via toMcpWriteError; handler registers tools alongside resources.
Discovery document refresh
src/app/llms.txt/route.ts
/llms.txt now renders WRITE_TOOLS in a “What you can change” section, clarifies tokens can read and mutate the minting user’s data, tightens owner-only scope/trust-boundary language, and documents state-conflict archDetails payload in Errors.

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant MCP_Tool
  participant Authorization
  participant Database
  participant Service
  
  Agent->>MCP_Tool: invoke move_component {id, parentId}
  MCP_Tool->>MCP_Tool: validate schema
  MCP_Tool->>Authorization: load and authorize node
  Authorization-->>MCP_Tool: actor can write
  MCP_Tool->>Database: start transaction
  MCP_Tool->>Service: moveNode(db, actor, args)
  Service->>Database: load node and parent
  Service->>Database: compute moving subtree
  Service->>Database: check incident edges
  alt cycle detected
    Service-->>MCP_Tool: ValidationError
  else orphaning detected
    Service-->>MCP_Tool: ConflictError with conflictingEdgeIds
  else valid
    Service->>Database: UPDATE node.parentId
    Service-->>MCP_Tool: updated node
  end
  alt error
    MCP_Tool->>MCP_Tool: toMcpWriteError mapping
    MCP_Tool-->>Agent: McpError with ErrorCode, archDetails if CONFLICT
  else success
    MCP_Tool-->>Agent: {content: [{type: text, text: confirmation message}]}
  end
  MCP_Tool->>Database: commit transaction
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐇 I nudge a node and hop aside,
Trees move gently where branches abide,
Tools hum in transactions, neat and spry,
Errors whisper ArchDetails when they cry,
A rabbit cheers, the graph grows wide!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding four MCP write tools (create, connect, update-docs, move) with a reference to the related issue #19.
Linked Issues check ✅ Passed All acceptance criteria from issue #19 are met: tools exist for create/connect/update-docs/move (#19), invariants and authorization are reused from services (#19), errors surface as readable text (#19), no delete tools are exposed (#19), and MCP Inspector round-trip capability is documented (#19).
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing issue #19: MCP write tools, tool catalog, error mapping, moveNode service, and supporting documentation (ADR-0024, CONTEXT.md, /llms.txt). No unrelated refactoring, style changes, or out-of-scope features are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-write-tools

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/server/mcp/tool-catalog.ts (1)

45-54: ⚡ Quick win

Trim field comments that restate the type names.

The per-field doc comments here are largely “what” comments; keep only rationale-level notes (why constraints/shape exist) to match project comment policy.

♻️ Suggested change
 export interface McpWriteToolDescriptor<Schema extends z.ZodType> {
-  /** MCP tool name; what the agent calls. snake_case to match MCP convention. */
   name: string;
-  /** Human/agent-facing one-liner (shown in `tools/list` and `llms.txt`). */
   title: string;
-  /** Multi-paragraph agent guidance; carries the prompt-injection note. */
   description: string;
-  /** Zod schema validating the tool's input. */
   inputSchema: Schema;
-  /** Service-layer call; the registry wraps it in `db.$transaction`. */
+  /** Kept as a callback so registration/discovery stay data-driven and in sync. */
   invoke: (
     db: Db,
     actor: Actor,
     args: z.input<Schema>,
   ) => Promise<ToolInvocationResult>;
 }

As per coding guidelines: Only write comments to explain why code exists, not what it does; avoid comments that restate the code or serve as coverage metrics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/mcp/tool-catalog.ts` around lines 45 - 54, Remove or shorten the
per-field doc comments on the MCP tool interface that merely restate the type
(fields: name, title, description, inputSchema, invoke); keep only
rationale-level notes explaining why a field exists or any non-obvious
constraints (for example note that name is snake_case to match MCP convention or
that invoke is wrapped in db.$transaction). Update the comments for name, title,
description, inputSchema, and invoke to be minimal and explanatory (drop “what
it is” phrasing that duplicates the type) so the docs explain intent/constraints
not types.
src/server/mcp/__tests__/errors.test.ts (1)

12-12: ⚡ Quick win

Use ~/* alias for src-local imports in tests too.

Please replace ../errors with the ~/* alias form to keep import style consistent with repo rules.

♻️ Suggested change
-import { toMcpWriteError } from "../errors";
+import { toMcpWriteError } from "~/server/mcp/errors";

As per coding guidelines: Use the path alias ~/* to reference src/* throughout the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/mcp/__tests__/errors.test.ts` at line 12, Replace the relative
import of toMcpWriteError in the test file with the repo path-alias form; locate
the import statement that currently reads `import { toMcpWriteError } from
"../errors";` and change it to use the `~/*` alias (e.g. `import {
toMcpWriteError } from "~server/mcp/errors";`) so tests follow the src-local
import convention.
src/server/mcp/handler.ts (1)

4-6: ⚡ Quick win

Switch src-local imports to ~/* aliases.

Please convert these relative imports to alias imports for consistency with repo TS import rules.

♻️ Suggested change
 import { db } from "~/server/db";
-import { makeVerifyMcpToken } from "./auth";
-import { registerArchitectureResources } from "./resources";
-import { registerArchitectureTools } from "./tools";
+import { makeVerifyMcpToken } from "~/server/mcp/auth";
+import { registerArchitectureResources } from "~/server/mcp/resources";
+import { registerArchitectureTools } from "~/server/mcp/tools";

As per coding guidelines: Use the path alias ~/* to reference src/* throughout the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/mcp/handler.ts` around lines 4 - 6, Replace the three relative
imports in handler.ts with project path-alias imports: change import sources
"./auth", "./resources", and "./tools" to the src alias paths (e.g. use
~/server/mcp/auth, ~/server/mcp/resources, and ~/server/mcp/tools) while keeping
the imported symbols makeVerifyMcpToken, registerArchitectureResources, and
registerArchitectureTools unchanged so the module resolution follows the
repository's ~/* convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CONTEXT.md`:
- Around line 504-505: The paragraph referencing the MCP tool contains "`#40` /
`#42`" which Markdown can treat as headings; update the MCP tool paragraph (the
line containing "`#40` / `#42`" near the mention of `apply_graph` and `/llms.txt`)
to prevent heading parsing by escaping the hashes or rendering them as inline
code (e.g., replace "`#40`" and "`#42`" with escaped hashes or backticked text) so
they appear as plain text rather than Markdown headers.

In `@src/lib/schemas.ts`:
- Around line 211-213: Update the moveNodeInput docstring to remove the outdated
statement that moves are rejected for "falsify a refinement FlowRoute" and
instead document the actual enforced behavior per ADR-0024: state that
moveNodeInput rejects moves that would orphan an Incident or Connection with
ValidationError, and that ConflictError is used only for the currently enforced
conflict cases; remove or reword any clause claiming FlowRoute-falsification as
a reject path. Locate the wording in the moveNodeInput docstring and replace the
incorrect clause so the description matches the current service enforcement.

In `@src/server/architecture/node.service.ts`:
- Around line 866-867: The current read uses db.node.findUnique which can return
a soft-deleted ("tombstoned") row; change the lookup to enforce live-row
semantics by excluding soft-deleted records — replace db.node.findUnique(...)
with db.node.findFirst({ where: { id: node.id, deletedAt: null } }) or, if your
soft-delete flag is different, add the appropriate where clause (e.g., deleted:
false) so that the subsequent if (!current) truly represents not-found rather
than a tombstone.

In `@src/server/mcp/tools.ts`:
- Around line 3-7: The module currently accepts a typed PrismaClient parameter
and uses relative imports; change it to import and use the singleton DB instance
from ~/server/db instead of taking a PrismaClient arg, and convert all relative
imports (e.g., actorFromAuthInfo, toMcpWriteError, WRITE_TOOLS) to use the path
alias ~/server/...; update any function signatures that accept a PrismaClient to
remove that parameter and reference the singleton DB inside those functions, and
ensure error handling via toMcpWriteError still works with the singleton DB
usage.

---

Nitpick comments:
In `@src/server/mcp/__tests__/errors.test.ts`:
- Line 12: Replace the relative import of toMcpWriteError in the test file with
the repo path-alias form; locate the import statement that currently reads
`import { toMcpWriteError } from "../errors";` and change it to use the `~/*`
alias (e.g. `import { toMcpWriteError } from "~server/mcp/errors";`) so tests
follow the src-local import convention.

In `@src/server/mcp/handler.ts`:
- Around line 4-6: Replace the three relative imports in handler.ts with project
path-alias imports: change import sources "./auth", "./resources", and "./tools"
to the src alias paths (e.g. use ~/server/mcp/auth, ~/server/mcp/resources, and
~/server/mcp/tools) while keeping the imported symbols makeVerifyMcpToken,
registerArchitectureResources, and registerArchitectureTools unchanged so the
module resolution follows the repository's ~/* convention.

In `@src/server/mcp/tool-catalog.ts`:
- Around line 45-54: Remove or shorten the per-field doc comments on the MCP
tool interface that merely restate the type (fields: name, title, description,
inputSchema, invoke); keep only rationale-level notes explaining why a field
exists or any non-obvious constraints (for example note that name is snake_case
to match MCP convention or that invoke is wrapped in db.$transaction). Update
the comments for name, title, description, inputSchema, and invoke to be minimal
and explanatory (drop “what it is” phrasing that duplicates the type) so the
docs explain intent/constraints not types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d70eeac6-1916-42f0-8475-7b49d13ccdff

📥 Commits

Reviewing files that changed from the base of the PR and between a660abc and eed2d87.

📒 Files selected for processing (11)
  • CONTEXT.md
  • docs/adr/0024-movenode-reparent-reject-orphaning.md
  • src/app/llms.txt/route.ts
  • src/lib/schemas.ts
  • src/server/architecture/__tests__/node.service.test.ts
  • src/server/architecture/node.service.ts
  • src/server/mcp/__tests__/errors.test.ts
  • src/server/mcp/errors.ts
  • src/server/mcp/handler.ts
  • src/server/mcp/tool-catalog.ts
  • src/server/mcp/tools.ts

Comment thread CONTEXT.md Outdated
Comment thread src/lib/schemas.ts Outdated
Comment thread src/server/architecture/node.service.ts Outdated
Comment thread src/server/mcp/tools.ts
Comment on lines +3 to +7
import type { PrismaClient } from "../../../generated/prisma/client";
import { actorFromAuthInfo } from "./auth";
import { toMcpWriteError } from "./errors";
import { WRITE_TOOLS } from "./tool-catalog";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Align DB access and imports with TypeScript repository rules.

This adapter should use the singleton DB and ~/* imports, rather than taking a typed client parameter and using relative src paths.

♻️ Suggested change
 import { type McpServer } from "`@modelcontextprotocol/sdk/server/mcp.js`";
 
-import type { PrismaClient } from "../../../generated/prisma/client";
-import { actorFromAuthInfo } from "./auth";
-import { toMcpWriteError } from "./errors";
-import { WRITE_TOOLS } from "./tool-catalog";
+import { db } from "~/server/db";
+import { actorFromAuthInfo } from "~/server/mcp/auth";
+import { toMcpWriteError } from "~/server/mcp/errors";
+import { WRITE_TOOLS } from "~/server/mcp/tool-catalog";
@@
 export function registerArchitectureTools(
-  server: McpServer,
-  db: PrismaClient,
+  server: McpServer,
 ): void {

As per coding guidelines: Always access the database through the singleton at ~/server/db and Use the path alias ~/* to reference src/* throughout the codebase.

Also applies to: 30-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/mcp/tools.ts` around lines 3 - 7, The module currently accepts a
typed PrismaClient parameter and uses relative imports; change it to import and
use the singleton DB instance from ~/server/db instead of taking a PrismaClient
arg, and convert all relative imports (e.g., actorFromAuthInfo, toMcpWriteError,
WRITE_TOOLS) to use the path alias ~/server/...; update any function signatures
that accept a PrismaClient to remove that parameter and reference the singleton
DB inside those functions, and ensure error handling via toMcpWriteError still
works with the singleton DB usage.

@CuriouslyCory

Copy link
Copy Markdown
Owner Author

Fixes Applied Successfully

Fixed 3 file(s) based on 3 of 4 CodeRabbit feedback item(s).

Files modified:

  • CONTEXT.md — rephrased to prevent #40 from being parsed as a markdown heading.
  • src/lib/schemas.ts — updated moveNodeInput docstring to match enforced behavior (the refinement-FlowRoute falsification reject was removed before commit; it's named as future follow-up in ADR-0024).
  • src/server/architecture/node.service.ts — no-op move path now filters deletedAt: null so a concurrent soft-delete cannot return a tombstoned row under READ COMMITTED.

Declined:

  • src/server/mcp/tools.ts — the suggestion (use the singleton + ~/* aliases for siblings) contradicts the sibling resources.ts adapter, which takes db as a parameter and uses relative imports for same-directory siblings. Matching resources.ts keeps the two adapters consistent. The PrismaClient type import path is identical to the one ~/server/db itself uses; the ~/* alias doesn't reach generated/.

Commit: 5e51d1e77701450aa9cd9994b8e2a75bb65a30d8

The latest autofix changes are on the feat/mcp-write-tools branch.

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.

MCP write tools

1 participant