{
  "schema_version": "0.1",
  "id": "ARCH-002",
  "title": "MCP server architecture",
  "status": "approved",
  "source_prd_id": "PRD-002",
  "source_request_id": "REQ-052",
  "created_at": "2026-07-02",
  "updated_at": "2026-07-13",
  "summary": "Single self-contained CommonJS file (mcp/server.cjs) speaking newline-delimited JSON-RPC (MCP stdio). State I/O is native Node with lock-guarded registry allocation, agent-scoped tracking branches, serialized canonical promotion, and indentation-preserving atomic writes. Read-heavy tools (status, validate) shell out to the canonical Python helpers so outputs stay identical to /prd-status and prd_gate.",
  "components": [
    {
      "id": "ARCH-COMP-008",
      "name": "JSON-RPC stdio loop",
      "responsibility": "ndjson framing, initialize/tools-list/tools-call dispatch, stdout reserved for protocol, diagnostics to stderr.",
      "satisfies": [
        "PRD-REQ-009"
      ]
    },
    {
      "id": "ARCH-COMP-009",
      "name": "State store",
      "responsibility": "Read/modify/write one record at a time in .prd_plugin/state/*.json; detect and preserve file indentation; temp-file+rename atomicity; server-side UTC dates.",
      "satisfies": [
        "PRD-REQ-010",
        "PRD-REQ-013",
        "PRD-REQ-012"
      ]
    },
    {
      "id": "ARCH-COMP-010",
      "name": "Registry allocator",
      "responsibility": "Exclusive-lock (mkdir lock with stale takeover) around registry read-bump-write; prefix whitelist from config ids.required_prefixes.",
      "satisfies": [
        "PRD-REQ-011"
      ]
    },
    {
      "id": "ARCH-COMP-011",
      "name": "Python bridge",
      "responsibility": "Locate a runnable python + .prd_plugin/scripts (downstream) or scripts/ (hub); run prd_status/prd_gate for status/validate parity.",
      "satisfies": [
        "PRD-REQ-009"
      ]
    },
    {
      "id": "ARCH-COMP-012",
      "name": "Gate & guard alignment",
      "responsibility": "Commit-gate error messages point offenders at the MCP tools; stop-guard closed-status set and status normalization share one vocabulary with the server.",
      "satisfies": [
        "PRD-REQ-015"
      ]
    },
    {
      "id": "ARCH-COMP-017",
      "name": "Bounded state query",
      "responsibility": "Scan the small canonical state files and tracking-branch directory on demand and return filtered, limited records; persist no index.",
      "satisfies": ["PRD-REQ-021", "PRD-NFR-011"]
    },
    {
      "id": "ARCH-COMP-018",
      "name": "Generic record mutations",
      "responsibility": "Reuse the state map, lock, allocator, schema checks, and atomic writer for create/update/link operations.",
      "satisfies": ["PRD-REQ-022", "PRD-REQ-023", "PRD-NFR-011"]
    },
    {
      "id": "ARCH-COMP-019",
      "name": "Agent tracking branch store and promoter",
      "responsibility": "Pre-create one DBR JSON file per worker, enforce the configured worker environment and owner-scoped branch update boundary, expose DBR discovery through existing read tools, and serialize conflict-checked idempotent promotion into TRK state.",
      "satisfies": ["PRD-REQ-024", "PRD-NFR-012"]
    }
  ],
  "interfaces": [
    {
      "id": "ARCH-IF-005",
      "between": [
        "agent",
        "server"
      ],
      "contract": "Eighteen MCP tools: bounded prd_find/prd_get; locked prd_create/prd_update/prd_link; pre-created prd_open_tracking_branch/prd_update_tracking_branch/prd_promote_tracking_branch for parallel worktrees; and the existing specialised status, allocation, goal, request, evidence, decision, change, and validation operations. Underscore names (MCP name charset forbids dots)."
    },
    {
      "id": "ARCH-IF-006",
      "between": [
        "prd-install",
        ".mcp.json"
      ],
      "contract": "mcpServers['prd-plugin'] = {command: 'node', args: ['.prd_plugin/mcp/server.cjs']}; additive merge; server file copied to .prd_plugin/mcp/.",
      "satisfies": [
        "PRD-REQ-014"
      ]
    },
    {
      "id": "ARCH-IF-011",
      "between": ["agent", "state store"],
      "contract": "prd_find(kind?, status?, linked_id?, query?, limit?) and prd_get(id) include DBR tracking branches; prd_create(kind, fields), prd_update(id, fields), and prd_link(left_id, right_id) cover canonical records; all results are bounded and canonical writes are locked."
    },
    {
      "id": "ARCH-IF-012",
      "between": ["lead agent", "worker worktrees", "canonical state"],
      "contract": "The lead calls prd_open_tracking_branch before fan-out and sets PRD_WORKER_SESSION, PRD_TRACKING_BRANCH_ID, and PRD_TRACKING_BRANCH_OWNER. With require_for_parallel_agents enabled, the server rejects every canonical state/registry write and any branch ID/owner mismatch; the worker calls only prd_update_tracking_branch. After merge the lead applies deferred canonical records and prd_promote_tracking_branch serially."
    }
  ],
  "decisions": [
    {
      "id": "ARCH-DEC-004",
      "summary": "Hand-rolled JSON-RPC over stdin/stdout; zero npm runtime deps.",
      "rationale": "MCP stdio is simple ndjson; dependency-light is a hard constraint and precedented.",
      "consequences": [
        "Own the framing/error paths",
        "No SDK upgrades to track"
      ]
    },
    {
      "id": "ARCH-DEC-005",
      "summary": "CommonJS, no top-level await, single file.",
      "rationale": "Future .mcpb packaging (Electron utilityProcess cannot load ESM); single file copies cleanly into .prd_plugin/mcp/.",
      "consequences": [
        "Slightly longer file",
        "Phase 3 ready"
      ]
    },
    {
      "id": "ARCH-DEC-006",
      "summary": "status/validate shell out to the canonical Python helpers.",
      "rationale": "Acceptance requires parity with /prd-status and the gate; reimplementing invites drift. Python is already required by the plugin's hooks.",
      "consequences": [
        "Needs python on PATH for those two tools; clear error otherwise"
      ]
    },
    {
      "id": "ARCH-DEC-007",
      "summary": "Underscore tool names (prd_open_goal), canonical status vocabulary open/active/complete/resolved/deferred/parked/superseded with legacy read-mapping.",
      "rationale": "MCP tool-name charset excludes dots; one vocabulary ends the drift that hid records downstream.",
      "consequences": [
        "Docs use underscore names",
        "Guard/status/report normalize legacy values"
      ]
    },
    {
      "id": "ARCH-DEC-011",
      "summary": "Extend the existing MCP state server with five tools and no new persistence layer.",
      "rationale": "This is the smallest sufficient design that removes routine JSON scans while preserving current proven invariants.",
      "source_ids": ["PRD-REQ-021", "PRD-REQ-022", "PRD-REQ-023", "DEC-003"]
    },
    {
      "id": "ARCH-DEC-012",
      "summary": "Reuse DBR IDs as pre-allocated per-worker tracking branches and allocate canonical TRK/DBR-MERGE IDs only during lead promotion.",
      "rationale": "Separate Git worktrees cannot share a checkout-local lock or live registry. Distinct committed files merge cleanly, while the existing allocator remains the single canonical authority.",
      "source_ids": ["REQ-087", "DBR-001", "PRD-REQ-024", "PRD-NFR-012"]
    }
  ],
  "risks": [
    {
      "id": "ARCH-RISK-004",
      "summary": "Concurrent writers (agent + subagents) racing state files.",
      "mitigation": "Single lock serializes registry allocation; record writes are read-modify-write + rename; parallel-work rule keeps one writer."
    },
    {
      "id": "ARCH-RISK-005",
      "summary": "Indentation preservation fails on hand-mangled files.",
      "mitigation": "Indent detection with 2-space fallback; tests cover foreign-indent files; worst case is a formatting-only diff, never data loss."
    },
    {
      "id": "ARCH-RISK-008",
      "summary": "Generic update/create tools could weaken typed workflow rules or permit ID mutation.",
      "mitigation": "Support only record-backed kinds, use per-kind allowed fields, make IDs and server timestamps immutable, and retain specialised goal/evidence tools for guarded transitions."
    },
    {
      "id": "ARCH-RISK-009",
      "summary": "Workers independently allocate branch or canonical IDs from stale worktree registries.",
      "mitigation": "The lead pre-allocates and commits branches before fan-out and supplies the worker/branch/owner environment. The MCP dispatcher rejects canonical writes and mismatched branch updates, the default gate installs and requires state consistency validation, and numeric allocation happens only in serialized lead operations."
    }
  ]
}
