M2 delivers the read-only MCP capability broker gateway and four Day-1 infrastructure adapters (Proxmox, SSH/Linux, GitHub, Gitea). 13 REQs (015-027) all pass. 656 tests green. M1 non-regression verified. MCP spec 2025-06-18 conformance verified (PROTOCOL.md + 7 tests). Defense-in-depth SSH (broker layer 1 + Relay Agent layer 2 + no-shell exec). Two-track LLM smoke (Track A mock-path P0 gate passes). CI: Gitea Actions (.gitea/workflows/ci.yml) with Postgres 16 + RLS verification. Phases shipped: P0 pre-execution v0.1.0 P1 Wave F — MCP gateway v0.1.1 P2 Wave G — Proxmox v0.1.2 P3 Wave H — SSH/Linux v0.1.3 P4 Wave I — Git adapters v0.1.4 P5 Wave J — SSE+smoke+UI v0.1.5 P6 Final — review+ship v0.1.6 ← milestone release ---ci--- phase: 6 milestone: v0.2 status: complete phase_role: final milestone_complete: true requirements: covered: [REQ-015, REQ-016, REQ-017, REQ-018, REQ-019, REQ-020, REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026, REQ-027] partial: [] ---/ci---
13 KiB
Requirements
Source: CoreCI Chat v0.1 M2 Engineering Specification v1.0 (.ciagent/steer-m2-spec.md), Sarah Chen (PO), locked 2026-08-25. All 9 open questions resolved. D-006 (GitHub scopes) and D-007 (MCP transport) recorded as deviations.
Milestone type: Feature (new MCP adapters are feat: phases). Tags run on the v0.1.x patch line (M1's previous minor): phase 0 seeds v0.1.0.
Predecessor M1 (COMPLETE): REQ-001..014, 038, 039, 040 (17 REQs, all PASS, shipped v0.0.1..v0.0.7).
M2 Requirements (this milestone — REQ-015..027, 13 REQs — COMPLETE, shipped v0.1.6)
All acceptance criteria verbatim from M2 spec §4. Every REQ inherits M1 invariants: INV-1 (auth gateway ordering), INV-2 (withTenant + RLS), INV-3 (SecretProvider only), INV-4 (audit completeness), INV-7 (read-only). The broker is the load-bearing safety boundary for INV-7.
MCP Capability Broker Gateway
- REQ-015 (J2, High) Define abstract MCP tool schema — Given the broker exposes the closed read-only tool set, when a tool is registered, then it has
name,description,inputSchema(JSON Schema) per MCP standard, the schema is in the broker's tool registry before any adapter invocation, any tool call with arguments not matchinginputSchemareturns HTTP 400 with a schema-validation error, and the tool registry is closed and enumerated with per-tenant policy able to disable individual tools but never add new ones. M2 starter set is locked at:proxmox.list_vms(inventory),proxmox.get_vm_status(live),proxmox.get_node_metrics(live),ssh.run_whitelisted_command(live),github.list_repos(inventory),github.get_recent_ci_runs(live),github.get_workflow_run(live),gitea.list_repos(inventory),gitea.get_recent_ci_runs(live). - REQ-01[5-9] (J1, J2, High) Route abstract MCP calls to tenant-specific adapter — Given an MCP tool call request with a tenant-scoped adapter binding
(tenant_id, adapter_type, target_id), when the broker receives the call, then the call is routed to the adapter resolved by that tuple, the response is returned as an SSE stream, and routing errors return HTTP 404 with a structured error. - REQ-01[5-9] (J2, High) Stream tool execution output to chat UI via SSE — Given the broker invokes an adapter capability, when the adapter returns partial or complete output, then the broker emits an SSE stream on
GET /api/mcp/stream/:correlationIdwithContent-Type: text/event-streamand each event hasid,event,datafields per the SSE specification; the stream terminates with a terminal event (doneorerror) on completion or error. Per-call lifecycle: one stream per capability invocation; correlation ID = ULID minted atPOST /api/mcp/invoke. Client disconnect (Edge 8) cancels in-flight adapter call; no audit event for client-side cancellation. - REQ-01[5-9] (Edge 1, Edge 7, High) Enforce read-only at MCP gateway proxy layer — Given a write-capable method per the adapter's known write surface (Proxmox: POST/PUT/DELETE; SSH: non-whitelist commands; GitHub: scopes outside
metadata:read+actions:read; Gitea: POST/PUT/DELETE/PATCH on all endpoints), when the request reaches the broker, then the broker rejects with HTTP 403, appendsadapter.write_rejectedaudit event, and never invokes the adapter; verified by a test per adapter at the M2 gate. The broker is the load-bearing safety boundary (INV-7 enforcement at the gateway, not at the adapter). - REQ-01[5-9] (Edge 5, High) Apply token-bucket rate limit per user and per tenant — Given a user has exceeded 60 req/min OR a tenant has exceeded 300 req/min, when any subsequent capability invocation is attempted, then the broker returns HTTP 429 with
Retry-Afterheader and no adapter call is made; rate limit state is process-local in M2 (in-memory token-bucket, capacity = rate, refill 1/sec user / 5/sec tenant).
Adapters — Day 1 Integrations
- REQ-020 (J1, J2, High) Implement read-only Proxmox MCP adapter — Given a Proxmox adapter is configured with a
PVEAuditor-scoped API token, when an MCP tool call routes to it, then the adapter calls only Proxmox GET endpoints (e.g.,/api2/json/nodes,/api2/json/qemu,/api2/json/nodes/{node}/qemu/{vmid}/status/current) and never mutates state; supported capabilities includeproxmox.list_vms(inventory),proxmox.get_vm_status,proxmox.get_node_metrics. - REQ-021 (J1, J2, High) Implement read-only SSH/Linux Server MCP adapter — Given an SSH adapter is configured via M1 Relay Agent (REQ-026), when an MCP tool call routes to it, then the adapter invokes only commands from the fixed whitelist subset (
uptime,df -h,free -m,systemctl status <svc>,journalctl -n <N>,systemctl list-units --type=service) via the M1 Relay whitelist hook; the broker validatescommandagainst this subset BEFORE dispatch to the Relay Agent (defense-in-depth layer 1); the Relay AgentCheckCommandis the second enforcement layer (layer 2); non-whitelist commands return HTTP 403 (REQ-026). - REQ-022 (J1, J2, High) Implement read-only GitHub MCP adapter — Given a GitHub adapter is configured with a fine-grained PAT (
metadata:read+actions:readminimum per D-006), when an MCP tool call routes to it, then the adapter calls only GitHub REST GET endpoints and rejects any token lacking required scopes; supported capabilities includegithub.list_repos(inventory),github.get_recent_ci_runs,github.get_workflow_run. - REQ-023 (J1, J2, High) Implement read-only Gitea MCP adapter — Given a Gitea adapter is configured with a read-only token, when an MCP tool call routes to it, then the adapter calls only Gitea REST GET endpoints and rejects any token lacking required read scopes; version-aware validation: Gitea ≥1.22 requires
read:repositoryscope; Gitea <1.22 accepts any token with broker-side write-method blocklist (POST/PUT/DELETE/PATCH) as security backstop; supported capabilities mirror the GitHub adapter (gitea.list_repos,gitea.get_recent_ci_runs). - REQ-024 (Edge 3, High) Scope MCP queries to explicitly selected target in multi-target tenants — Given a tenant has multiple adapters of the same type configured (e.g., 2 Proxmox hosts), when a capability invocation is received without an explicit
target_idfor that adapter type, then the broker returns HTTP 400 "target required" with a list of available targets; the UI surfaces a target picker.
Adapter Authentication
- REQ-025 (J1, High) Authenticate to Proxmox via scoped API token + PVEAuditor — Given a Proxmox adapter config submission, when the token is submitted, then the broker verifies the token's role on the target is
PVEAuditorbefore persisting; tokens withoutPVEAuditorreturn HTTP 422 with role-violation error and no config is persisted; the token is stored viaSecretProvider.set(INV-3). - REQ-026 (J1, Edge 7, High) Authenticate to Linux servers via SSH key + whitelist execution — Given an SSH adapter config submission, when the Relay registration token is stored via
SecretProvider.set(INV-3), then all subsequent SSH commands are validated against the fixed whitelist subset at two layers: (1) broker validatescommandbefore dispatch, (2) M1 Relay AgentCheckCommandvalidates at execution; non-whitelisted commands return HTTP 403 with a structured error andadapter.write_rejectedaudit event is appended. - REQ-027 (J1, High) Authenticate to GitHub and Gitea via scoped API tokens — Given a GitHub or Gitea adapter config submission, when the token is submitted, then the broker validates token scopes before persisting (GitHub: fine-grained PAT with
metadata:read+actions:readminimum per D-006; Gitea ≥1.22:read:repositoryminimum; Gitea <1.22: any token accepted with broker-side write-method blocklist as security backstop); insufficient scopes return HTTP 422 with scope-violation error and no config is persisted; the token is stored viaSecretProvider.set(INV-3).
M1 Requirements (predecessor — COMPLETE, for non-regression reference)
All 17 M1 REQs (001-014, 038, 039, 040) are COMPLETE and must remain passing through M2. See M1 REQUIREMENTS.md history (git log) for the verbatim acceptance criteria. M2 adds no breaking changes to M1 systems except additive (new tables, new audit event types).
Deferred to M3 (REQ-028 → REQ-037, REQ-041 → REQ-044 — Chat, Orchestration, Hardening)
Listed for traceability; NOT in M2 scope. M3 acceptance gate: Operator can ask a natural-language diagnostic question, see streamed tool execution, and receive a cited, evidence-backed answer within 5 min p95. Async workflows persist and rejoin correctly. Usage is metered. SOC 2 controls are instrumented. All v0.1 release gates pass.
REQ-028 (chat UI), REQ-029 (NL input), REQ-030 (streaming response + citations), REQ-031 (tool traces), REQ-032 (conversation history), REQ-033 (reason about tools), REQ-034 (multi-step workflows), REQ-035 (≤20 step limit), REQ-036 (durable execution), REQ-037 (rejoin workflow), REQ-041 (SOC2 posture page), REQ-042 (Vanta instrumentation), REQ-043 (usage metering), REQ-044 (usage dashboard).
M2→M3 contract freeze (M2 spec §9): M2's gateway (5 endpoints: GET /api/mcp/tools, POST /api/mcp/invoke, GET /api/mcp/stream/:correlationId, POST /api/mcp/adapter, PATCH/DELETE /api/mcp/adapter/:id) is a stable contract from M2's acceptance gate onward. M3 treats these as a stable API. M3 token-streaming SSE endpoint (for LLM output tokens) is a separate design — not in M2 scope, but documented as the M3 chat orchestration requirement.
Out of Scope (M2 — do not build)
| Feature | Reason |
|---|---|
| LLM chat UI / orchestration | M3 (M2 spec §2.2) |
| Trigger.dev task execution | M3 (M2 spec §2.2) |
| Any write capability | INV-7 hard; broker rejects 100% of write attempts (M2 spec §2.2) |
| S3 Object Lock WORM audit | M3 per D-004 (M2 spec §2.2) |
| Vanta evidence collection | M3 (M2 spec §2.2) |
| New Postgres tables for MCP caching | Q4 decision: in-memory only (M2 spec §2.2) |
| 5+ Day-1 adapters | Only Proxmox, SSH, GitHub, Gitea (M2 spec §2.2) |
| Persistence of MCP results beyond audit events | No snapshot/time-series tables (M2 spec §2.2) |
| Cross-tenant adapter sharing | Adapters are per-tenant (M2 spec §2.2) |
| Custom MCP server authoring tools for customers | v1.2+ (M2 spec §2.2) |
| Custom capability tool set per tenant | Fixed tool registry; per-tenant policy may disable but not add (M2 spec §2.2) |
| Write actions (apply/delete/scale/restart/VM start-stop) | v1.1 (M1 spec §2.2) |
| Hosted LLM inference | Never — BYOM permanent (M1 spec §2.2) |
| Kubernetes / ArgoCD / Helm | Not planned (M1 spec §2.2) |
| Slack / Teams / Discord / CLI / mobile chat surfaces | v1.1+ / not planned (M1 spec §2.2) |
| Approval-gated remediation, Senior Approver persona | v1.1 (M1 spec §2.2) |
| RAG over historical incidents | v1.1 (M1 spec §2.2) |
| SOC 2 Type 1 final certification | Audit-in-progress posture only (M1 spec §2.2) |
| Custom RBAC roles beyond Admin/Operator/Viewer | v1.2+ (M1 spec §2.2) |
| BYOK / customer-managed encryption keys | v1.2+ (M1 spec §2.2) |
| Multi-region deployment | Single region MVP (M1 spec §5) |
| Windows server management | Not planned v1.x (M1 spec §2.2) |
| Fine-tuning, custom model deployments | Not planned (M1 spec §2.2) |
| Anything not in the spec | Flag as spec-time scope question; never silently add |
Traceability
| Requirement | Milestone | Phase | Status |
|---|---|---|---|
| REQ-001 | M1 | Wave B | complete |
| REQ-002 | M1 | Wave B | complete |
| REQ-003 | M1 | Wave B | complete |
| REQ-004 | M1 | Wave B | complete |
| REQ-005 | M1 | Wave B | complete |
| REQ-006 | M1 | Wave C | complete |
| REQ-007 | M1 | Wave C | complete |
| REQ-008 | M1 | Wave C | complete |
| REQ-009 | M1 | Wave C | complete |
| REQ-010 | M1 | Wave D | complete |
| REQ-011 | M1 | Wave D | complete |
| REQ-012 | M1 | Wave D | complete |
| REQ-013 | M1 | Wave D | complete |
| REQ-014 | M1 | Wave E | complete |
| REQ-015 | M2 | Phase 1 (Wave F) | complete |
| REQ-016 | M2 | Phase 1 (Wave F) | complete |
| REQ-017 | M2 | Phase 1+5 (Wave F+J) | complete |
| REQ-018 | M2 | Phase 1+2+3+4 (Wave F+G+H+I) | complete |
| REQ-019 | M2 | Phase 1 (Wave F) | complete |
| REQ-020 | M2 | Phase 2 (Wave G) | complete |
| REQ-021 | M2 | Phase 3 (Wave H) | complete |
| REQ-022 | M2 | Phase 4 (Wave I) | complete |
| REQ-023 | M2 | Phase 4 (Wave I) | complete |
| REQ-024 | M2 | Phase 1 (Wave F) | complete |
| REQ-025 | M2 | Phase 2 (Wave G) | complete |
| REQ-026 | M2 | Phase 3 (Wave H) | complete |
| REQ-027 | M2 | Phase 4 (Wave I) | complete |
| REQ-028 | M3 | — | deferred |
| REQ-029 | M3 | — | deferred |
| REQ-030 | M3 | — | deferred |
| REQ-031 | M3 | — | deferred |
| REQ-032 | M3 | — | deferred |
| REQ-033 | M3 | — | deferred |
| REQ-034 | M3 | — | deferred |
| REQ-035 | M3 | — | deferred |
| REQ-036 | M3 | — | deferred |
| REQ-037 | M3 | — | deferred |
| REQ-038 | M1 | Wave A | complete |
| REQ-039 | M1 | Wave A | complete |
| REQ-040 | M1 | Wave A | complete |
| REQ-041 | M3 | — | deferred |
| REQ-042 | M3 | — | deferred |
| REQ-043 | M3 | — | deferred |
| REQ-044 | M3 | — | deferred |