@expanse-ade/mcp 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -0
- package/dist/index.d.ts +336 -0
- package/dist/index.js +1028 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# canvas-ade-mcp
|
|
2
|
+
|
|
3
|
+
The **MCP (Model Context Protocol) layer** for Canvas ADE — the tool/resource/prompt contract that
|
|
4
|
+
lets AI coding agents running _inside_ Canvas ADE boards orchestrate the canvas itself. This is what
|
|
5
|
+
turns the canvas from a human-driven cockpit into an **AI-orchestratable swarm environment** with a
|
|
6
|
+
**command board** (an orchestrator agent) driving a fleet of worker agents.
|
|
7
|
+
|
|
8
|
+
> **Status:** planning / docs only. No code yet. Build order lives in [`docs/roadmap.md`](docs/roadmap.md).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What this is (and is NOT)
|
|
13
|
+
|
|
14
|
+
- **IS:** a **standalone package with its own git repo**, living at `Z:\canvas-ade-mcp` — a
|
|
15
|
+
**sibling of the Canvas ADE repo, NOT nested inside it**. It owns the MCP _contract_ — tool +
|
|
16
|
+
resource + prompt schemas, the streamable-HTTP transport, auth (per-board bearer tokens), and the
|
|
17
|
+
capability tier-factory (orchestrator vs worker).
|
|
18
|
+
- **IS NOT:** a standalone _app_. The MCP server has nothing to orchestrate on its own — every tool
|
|
19
|
+
needs live access to PTYs, git worktrees, `WebContentsView`s, and the canvas store, all of which
|
|
20
|
+
live in **Canvas ADE's Electron MAIN process**. Canvas ADE **consumes this package as a dependency**
|
|
21
|
+
(a local linked / path dependency during dev; published + pinned later) and MAIN binds the contract
|
|
22
|
+
to the real implementations.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Z:\
|
|
26
|
+
├─ Canvas ADE\ ← the Electron app (its own git repo) — CONSUMES canvas-ade-mcp as a dep
|
|
27
|
+
└─ canvas-ade-mcp\ ← THIS repo (separate git repo, sibling — never nested in Canvas ADE)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Why separate:** keeps the MCP contract versioned + testable on its own, and avoids nesting a second
|
|
31
|
+
git repo inside the Canvas ADE working tree. The two repos are wired only by a dependency edge + the
|
|
32
|
+
live test harness.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
┌──────────────────────────── Canvas ADE (Electron) ────────────────────────────┐
|
|
36
|
+
│ │
|
|
37
|
+
│ MAIN process │
|
|
38
|
+
│ ┌─────────────────────────┐ ┌──────────────────────────────────────┐ │
|
|
39
|
+
│ │ CanvasOrchestrator │◄──────►│ canvas-ade-mcp (THIS PACKAGE) │ │
|
|
40
|
+
│ │ (board state · git · │ binds │ · tool/resource/prompt schemas │ │
|
|
41
|
+
│ │ PTY control · status) │ impls │ · streamable-HTTP transport (/mcp) │ │
|
|
42
|
+
│ └─────────────────────────┘ │ · per-board token auth + tier factory│ │
|
|
43
|
+
│ ▲ └──────────────────────────────────────┘ │
|
|
44
|
+
│ │ IPC ▲ │
|
|
45
|
+
│ ┌────────┴───────────┐ │ loopback HTTP (127.0.0.1) │
|
|
46
|
+
│ │ renderer (human UI) │ │ Bearer <per-board token> │
|
|
47
|
+
│ │ glyph · queue · diff │ ┌────────────┴───────────────┐ │
|
|
48
|
+
│ └─────────────────────┘ │ Terminal boards' agents │ │
|
|
49
|
+
│ │ (Claude Code / Codex / …) │ │
|
|
50
|
+
│ │ = MCP CLIENTS │ │
|
|
51
|
+
│ │ command board = orchestrator│ │
|
|
52
|
+
│ │ others = workers │ │
|
|
53
|
+
│ └──────────────────────────────┘ │
|
|
54
|
+
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Two views of one core:** the same `CanvasOrchestrator` is exposed to **humans** (renderer UI over
|
|
58
|
+
IPC) and to **agents** (this MCP server over loopback HTTP). The MCP is the second interface.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## The non-negotiable rule: every tool is tested against Canvas ADE
|
|
63
|
+
|
|
64
|
+
No MCP tool/resource ships until **both** layers are green. This is the project's whole point —
|
|
65
|
+
it keeps the agent↔canvas connection real, not theoretical.
|
|
66
|
+
|
|
67
|
+
1. **Contract test** — the tool against a mock `CanvasOrchestrator`. Fast, isolated, validates the
|
|
68
|
+
schema + auth + tier gating + error shapes.
|
|
69
|
+
2. **Live test** — drive the **real running Canvas ADE** (reuse the `CANVAS_SMOKE=e2e` in-process
|
|
70
|
+
harness; later the planned Playwright `_electron` harness) and assert the tool actually moved the
|
|
71
|
+
canvas: a board appeared, a prompt landed in a PTY, a diff came back, the camera flew.
|
|
72
|
+
|
|
73
|
+
Each phase ends **runnable + committed**, with its live-against-Canvas-ADE test passing — mirroring
|
|
74
|
+
the Canvas ADE roadmap convention.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Core architectural decisions (locked by research, 2026-05-30)
|
|
79
|
+
|
|
80
|
+
These are validated against real prior art (Claude Code Agent Teams, Cursor 3, Warp, Anthropic's
|
|
81
|
+
multi-agent research system, the MCP spec). Full reasoning in the sibling Canvas ADE repo's
|
|
82
|
+
`docs/feature-proposals.md` research + the two research workflows that produced this.
|
|
83
|
+
|
|
84
|
+
| Decision | Why |
|
|
85
|
+
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| **Transport = streamable-HTTP on a loopback `localServer` in MAIN** | stdio is process-per-client; we need ONE shared bus for many board-agents → one MAIN. Spec defines streamable-HTTP exactly for this. |
|
|
87
|
+
| **Control plane only — PTY data stays on MessagePort** | MessagePort isn't reachable on `127.0.0.1`; avoids head-of-line blocking of high-volume terminal bytes on shared SSE. |
|
|
88
|
+
| **Capability tiers enforced SERVER-SIDE by token, never by annotation/prompt** | Tool annotations "don't enforce anything." Build a fresh `McpServer` per session from a **factory that registers only the allowed tier's tools** (orchestrator vs worker), decided from the bearer token at `initialize`. |
|
|
89
|
+
| **Command board = orchestrator (elevated tools); other boards = workers (read-only/scoped)** | Canonical lead/subagent pattern. Workers cannot dispatch, spawn, or do git writes. |
|
|
90
|
+
| **Read-only context = resources; mutating actions = tools** | Spec-correct. `boards/status/attention/diff/output/console` are resources; `spawn/send_prompt/commit/merge` are tools with accurate destructive/openWorld annotations. |
|
|
91
|
+
| **Risky tools gated: human-confirm + nonce + audit_log** | `send_prompt`/`answer_permission`/git-writes can write into another agent's shell or touch git. Human-in-the-loop is a MUST, not a SHOULD. |
|
|
92
|
+
| **No OAuth discovery endpoints** | We're a trusted local server with static per-board tokens; advertising `/.well-known/oauth-*` makes Claude Code falsely flag "needs authentication." |
|
|
93
|
+
|
|
94
|
+
### Security model (inherited + extended)
|
|
95
|
+
|
|
96
|
+
The orchestrator board **is the lethal trifecta** at the system level: it reads untrusted worker
|
|
97
|
+
output + holds dispatch power + has external comms (commit/PR/navigate). Therefore:
|
|
98
|
+
|
|
99
|
+
- **Treat all worker-originated resources as TAINTED.** Never let worker output _auto-trigger_
|
|
100
|
+
`send_prompt`/`broadcast`/`commit`/`open_pr` — always human-confirm.
|
|
101
|
+
- **`answer_permission` = unconditional human-confirm, no auto-answer.** (Approving a prompt inside
|
|
102
|
+
another agent's shell is irreversible.)
|
|
103
|
+
- **Provenance tagging** on every cross-agent message (unspoofable "this came from the orchestrator,
|
|
104
|
+
not your operator") + worker instruction hardening. No single defense suffices.
|
|
105
|
+
- **Replay protection:** single-use nonce + monotonic sequence per dispatch; bind to **opaque
|
|
106
|
+
server-issued board ids, never agent-chosen labels.**
|
|
107
|
+
- **Hard runtime boundary:** every git/file op scoped to the board's own `canvas-ade/<board-id>`
|
|
108
|
+
worktree, server-enforced. Validate `Origin` (403 on bad → blocks DNS-rebinding); bind
|
|
109
|
+
`127.0.0.1`, never `0.0.0.0`.
|
|
110
|
+
- Preserves Canvas ADE's locked invariant: **Browser-board content must never reach the PTY write
|
|
111
|
+
channel**; orchestrator prompt text is trusted-user-only and fully audit-logged.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Dependency on Canvas ADE phases
|
|
116
|
+
|
|
117
|
+
The MCP build can **start now** for the transport/auth/observation layers, but the dispatch + git
|
|
118
|
+
layers bind to features still on the Canvas ADE roadmap:
|
|
119
|
+
|
|
120
|
+
| MCP phase | Needs from Canvas ADE |
|
|
121
|
+
| --------------------------------------------- | ------------------------------------------------------------------ |
|
|
122
|
+
| 0–3 (transport, auth, observation, lifecycle) | nothing hard — board state + spawn exist today |
|
|
123
|
+
| 4 (dispatch) | the `pty.write` channel (exists); persistence for audit (Phase 3) |
|
|
124
|
+
| 6 (git tools) | **git-worktrees-per-board + per-board ports (Canvas ADE Phase 3)** |
|
|
125
|
+
| 8 (best-of-N + merge queue) | worktrees + Duplicate/fan-out (Canvas ADE Phase 3) |
|
|
126
|
+
|
|
127
|
+
See [`docs/roadmap.md`](docs/roadmap.md) for the full phase-by-phase plan.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Layout (planned)
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Z:\canvas-ade-mcp\ ← its own git repo (sibling of Z:\Canvas ADE)
|
|
135
|
+
README.md ← this file
|
|
136
|
+
docs/
|
|
137
|
+
roadmap.md ← phased build plan (0 → full), per-phase test gate
|
|
138
|
+
decisions/ ← ADRs specific to the MCP layer (transport, auth, safety)
|
|
139
|
+
src/ ← (added Phase 0) schemas · transport · auth · tier-factory
|
|
140
|
+
test/
|
|
141
|
+
contract/ ← tool-vs-mock-orchestrator tests
|
|
142
|
+
live/ ← tool-vs-real-Canvas-ADE tests (drives Z:\Canvas ADE: CANVAS_SMOKE=e2e / Playwright)
|
|
143
|
+
package.json ← (added Phase 0) standalone package, MCP SDK dep
|
|
144
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { Server } from 'node:http';
|
|
2
|
+
import { Express } from 'express';
|
|
3
|
+
|
|
4
|
+
/** Capability tier of a board's MCP session. */
|
|
5
|
+
type Tier = 'orchestrator' | 'worker';
|
|
6
|
+
/** A coarse permission scope string (refined in later phases). */
|
|
7
|
+
type Scope = string;
|
|
8
|
+
/** Opaque server-issued board id. */
|
|
9
|
+
type BoardId = string;
|
|
10
|
+
/** A row in the per-board token store: what a minted bearer token grants. */
|
|
11
|
+
interface AuthRow {
|
|
12
|
+
boardId: BoardId;
|
|
13
|
+
tier: Tier;
|
|
14
|
+
scopes: Scope[];
|
|
15
|
+
/** Seconds since epoch. Set to board lifetime so long agent runs don't expire. */
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Minimal board projection exposed to agents. */
|
|
20
|
+
interface BoardSummary {
|
|
21
|
+
id: BoardId;
|
|
22
|
+
/** Board type — 'terminal' | 'browser' | 'planning' (open string for forward types). */
|
|
23
|
+
type: string;
|
|
24
|
+
/** User-facing board title (trusted-user content; no page/whiteboard content). */
|
|
25
|
+
title: string;
|
|
26
|
+
status: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One capped, tail-anchored page of a board's plain-text scrollback (T1.4). The
|
|
30
|
+
* host strips ANSI escape codes server-side (control-sequence injection surface)
|
|
31
|
+
* and slices the last `MAX_OUTPUT_PAGE` chars; older content is reached by passing
|
|
32
|
+
* the returned `nextCursor` back as the next read's cursor. NEVER the raw ring.
|
|
33
|
+
*/
|
|
34
|
+
interface BoardOutput {
|
|
35
|
+
/** ANSI-stripped scrollback for this page, in chronological order. */
|
|
36
|
+
text: string;
|
|
37
|
+
/** Total chars currently available (the full clean ring length). */
|
|
38
|
+
total: number;
|
|
39
|
+
/** Chars in this page (`text.length`; always ≤ `MAX_OUTPUT_PAGE`). */
|
|
40
|
+
returned: number;
|
|
41
|
+
/**
|
|
42
|
+
* Tail-anchored cursor (chars-from-end already consumed) for the NEXT, OLDER
|
|
43
|
+
* page; absent once the oldest available char has been returned.
|
|
44
|
+
*/
|
|
45
|
+
nextCursor?: number;
|
|
46
|
+
/**
|
|
47
|
+
* True when older output has been discarded by the host's capped ring (honest
|
|
48
|
+
* truncation — the buffer was saturated, so the head is gone for good).
|
|
49
|
+
*/
|
|
50
|
+
droppedOlder: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A board's structured last result (T1.5) — references and a verdict, NOT raw logs
|
|
54
|
+
* (raw scrollback is `BoardOutput`). v1 is an observational shell: until M4's
|
|
55
|
+
* `write_result` tool lets a worker record one, every board reads `{ present: false }`.
|
|
56
|
+
* Designed so M4 fills the optional fields without changing the contract.
|
|
57
|
+
*/
|
|
58
|
+
interface BoardResult {
|
|
59
|
+
/** Whether a result has been recorded for this board (false until M4 writes one). */
|
|
60
|
+
present: boolean;
|
|
61
|
+
/** Verdict of the last completed task, e.g. 'success' | 'failure' (open string). */
|
|
62
|
+
status?: string;
|
|
63
|
+
/** One-line human summary (references, not raw logs). */
|
|
64
|
+
summary?: string;
|
|
65
|
+
/** Structured references the worker produced — file paths, PR/issue URLs, etc. */
|
|
66
|
+
refs?: string[];
|
|
67
|
+
/** ISO-8601 timestamp when the result was recorded. */
|
|
68
|
+
at?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A coarse per-board status change (M5 event-driven attention). `status` is a status
|
|
72
|
+
* bucket value (`idle`/`running`/`awaiting-review`/`blocked`/`failed`/`static`) or
|
|
73
|
+
* `'gone'` when the board left the canvas. `result` is attached by the host when the
|
|
74
|
+
* board settles to `idle` and a `write_result` exists (so a barrier can return it).
|
|
75
|
+
*/
|
|
76
|
+
interface BoardStatusChange {
|
|
77
|
+
id: BoardId;
|
|
78
|
+
status: string;
|
|
79
|
+
result?: BoardResult;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The fields a worker may record for its OWN board via `write_result` (T4.4) — a verdict,
|
|
83
|
+
* a one-line summary, and structured references (NOT raw logs). All optional. The host
|
|
84
|
+
* stamps `present: true` + `at` and binds the board id to the caller's token, producing
|
|
85
|
+
* the {@link BoardResult} the `canvas://board/{id}/result` resource then serves.
|
|
86
|
+
*/
|
|
87
|
+
interface BoardResultInput {
|
|
88
|
+
/** Verdict of the last completed task, e.g. 'success' | 'failure' (open string). */
|
|
89
|
+
status?: string;
|
|
90
|
+
/** One-line human summary (references, not raw logs). */
|
|
91
|
+
summary?: string;
|
|
92
|
+
/** Structured references the worker produced — file paths, PR/issue URLs, etc. */
|
|
93
|
+
refs?: string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A read-only slice of the project's persistent memory (T1.7) — the project index
|
|
97
|
+
* (`canvas://memory`) or a per-board summary (`canvas://board/{id}/summary`). It is
|
|
98
|
+
* produced by the sibling Brain/Memory engine's `.canvas/memory/`; 🔒 PASSIVE context
|
|
99
|
+
* only — it grants no action. When that subsystem hasn't written anything (it ships on
|
|
100
|
+
* a separate track), the doc is the empty shell `{ present: false, text: '' }` — the
|
|
101
|
+
* resource gracefully empties, never errors.
|
|
102
|
+
*/
|
|
103
|
+
interface MemoryDoc {
|
|
104
|
+
/** Whether the memory engine has produced this document. */
|
|
105
|
+
present: boolean;
|
|
106
|
+
/** The markdown content (empty when absent). */
|
|
107
|
+
text: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Durable per-type config an orchestrator may change on a board (T3.3). All optional —
|
|
111
|
+
* only the supplied fields are applied. Host-side, off-type/identity keys are dropped
|
|
112
|
+
* (the canvas patch is filtered by the board's patchable keys), so this is the safe set.
|
|
113
|
+
*/
|
|
114
|
+
interface BoardConfig {
|
|
115
|
+
/** Terminal shell (Win: pwsh|powershell|cmd; *nix: $SHELL/zsh/bash). */
|
|
116
|
+
shell?: string;
|
|
117
|
+
/** First PTY line written after the shell starts (the agentic CLI / command). */
|
|
118
|
+
launchCommand?: string;
|
|
119
|
+
/** Working directory for the board. */
|
|
120
|
+
cwd?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The canvas control surface injected by Canvas ADE MAIN. Phase 0 defines the
|
|
124
|
+
* shape; tools wire to it in later phases. Keeping it an interface (with a mock)
|
|
125
|
+
* lets canvas-ade-mcp build + test standalone, with no Electron dependency.
|
|
126
|
+
*/
|
|
127
|
+
interface Orchestrator {
|
|
128
|
+
listBoards(): Promise<BoardSummary[]>;
|
|
129
|
+
spawnBoard(input: {
|
|
130
|
+
type: string;
|
|
131
|
+
prompt?: string;
|
|
132
|
+
cwd?: string;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
id: BoardId;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Close a board (T3.2). The host drains the board's PTY gracefully (not an abrupt
|
|
138
|
+
* SIGKILL) before removing it from the canvas. Idempotent: closing an absent board
|
|
139
|
+
* resolves. The dirty-worktree prompt arrives with Feature Workspaces (M6).
|
|
140
|
+
*/
|
|
141
|
+
closeBoard(boardId: BoardId): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Change a board's durable config (T3.3) — shell / launchCommand / cwd. Only the
|
|
144
|
+
* supplied fields change; the host filters to the board type's patchable keys.
|
|
145
|
+
*/
|
|
146
|
+
configureBoard(boardId: BoardId, config: BoardConfig): Promise<void>;
|
|
147
|
+
dispatchPrompt(boardId: BoardId, text: string): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* 🔒 Record the calling worker's OWN board result (M4 T4.4, worker-tier WRITE). The
|
|
150
|
+
* `boardId` is the caller's token-bound board (never client-supplied), so a worker can
|
|
151
|
+
* only write its own result. Feeds the `canvas://board/{id}/result` resource (T1.5).
|
|
152
|
+
*/
|
|
153
|
+
writeResult(boardId: BoardId, result: BoardResultInput): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* 🔒 Interrupt the target terminal board (M4 T4.5) — send Ctrl-C (`\x03`) to its PTY to
|
|
156
|
+
* stop a runaway/long-running command. Orchestrator-tier only. The host gates it behind
|
|
157
|
+
* a single-use nonce + a mandatory human confirm + an audit entry, and rejects any
|
|
158
|
+
* non-terminal target (Browser/Planning never reach a PTY). Content-less.
|
|
159
|
+
*/
|
|
160
|
+
interrupt(boardId: BoardId): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* 🔒 Agent-to-agent relay (M4 T4.6) — dispatch `text` from board `sourceId` to board
|
|
163
|
+
* `targetId`, expressed by an ORCHESTRATION connector `sourceId → targetId` (the cable
|
|
164
|
+
* is the route + intent). Orchestrator-tier only. The host validates the directed edge
|
|
165
|
+
* exists and is **terminal → terminal** (never Browser → PTY), then gates the write
|
|
166
|
+
* behind a single-use nonce + a mandatory human confirm + an audit entry. Fire-and-forget.
|
|
167
|
+
*/
|
|
168
|
+
relayPrompt(sourceId: BoardId, targetId: BoardId, text: string): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* 🔒 Blocking hand-off (M4 T4.3): write `text` into the target terminal board's PTY,
|
|
171
|
+
* wait until it goes idle, and return its structured last result. Orchestrator-tier
|
|
172
|
+
* only. The host gates it behind a single-use nonce + a mandatory human confirm +
|
|
173
|
+
* an audit entry, and rejects any non-terminal target (Browser/Planning content
|
|
174
|
+
* never reaches a PTY). Resolves to the {@link BoardResult} the target produced.
|
|
175
|
+
*/
|
|
176
|
+
handoffPrompt(boardId: BoardId, text: string): Promise<BoardResult>;
|
|
177
|
+
gitDiff(boardId: BoardId): Promise<string>;
|
|
178
|
+
boardStatus(boardId: BoardId): Promise<string>;
|
|
179
|
+
/**
|
|
180
|
+
* Read one capped page of a board's scrollback (T1.4, read-only). `cursor` is the
|
|
181
|
+
* tail-anchored offset from a prior page's `nextCursor`; omit for the newest tail.
|
|
182
|
+
*/
|
|
183
|
+
boardOutput(boardId: BoardId, opts?: {
|
|
184
|
+
cursor?: number;
|
|
185
|
+
}): Promise<BoardOutput>;
|
|
186
|
+
/**
|
|
187
|
+
* Read a board's structured last result (T1.5, read-only). Returns the empty shell
|
|
188
|
+
* `{ present: false }` until a result has been recorded (M4 `write_result`).
|
|
189
|
+
*/
|
|
190
|
+
boardResult(boardId: BoardId): Promise<BoardResult>;
|
|
191
|
+
/**
|
|
192
|
+
* Read the project memory index (T1.7, 🔒 read-only passive context). Empty shell
|
|
193
|
+
* when the memory engine is absent — graceful, never an error.
|
|
194
|
+
*/
|
|
195
|
+
projectMemory(): Promise<MemoryDoc>;
|
|
196
|
+
/**
|
|
197
|
+
* Read a board's memory summary (T1.7, 🔒 read-only passive context). Empty shell
|
|
198
|
+
* when absent.
|
|
199
|
+
*/
|
|
200
|
+
boardSummary(boardId: BoardId): Promise<MemoryDoc>;
|
|
201
|
+
/**
|
|
202
|
+
* Subscribe to per-board coarse status changes (M5). MAIN forwards
|
|
203
|
+
* `boardRegistry.ts`'s `subscribeBoardStatus`, attaching the last result when a board
|
|
204
|
+
* settles to `idle`. Returns an unsubscribe fn. Barriers + the attention notifier wake
|
|
205
|
+
* on this instead of polling. SYNCHRONOUS (returns the unsubscribe directly, not a Promise).
|
|
206
|
+
*/
|
|
207
|
+
subscribeStatus(listener: (change: BoardStatusChange) => void): () => void;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* In-memory per-board token store. Tokens are minted out-of-band when a board
|
|
212
|
+
* spawns and revoked when it closes — no OAuth flow. In-memory by design:
|
|
213
|
+
* sessions die on MAIN restart (correct for a single-user desktop app).
|
|
214
|
+
*/
|
|
215
|
+
declare class TokenStore {
|
|
216
|
+
private readonly rows;
|
|
217
|
+
mint(token: string, row: AuthRow): void;
|
|
218
|
+
revoke(token: string): void;
|
|
219
|
+
get(token: string): AuthRow | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface McpServerDeps {
|
|
223
|
+
orchestrator: Orchestrator;
|
|
224
|
+
tokens: TokenStore;
|
|
225
|
+
/**
|
|
226
|
+
* Optional single command-orchestrator board id (BUG-021). When set, `relay_prompt` is
|
|
227
|
+
* restricted to the token bound to this board, so a second orchestrator-tier token can't
|
|
228
|
+
* drive orchestration cables it doesn't own. Omit to keep the prior open-to-any-orchestrator
|
|
229
|
+
* behaviour (correct for a single-orchestrator-token deployment).
|
|
230
|
+
*/
|
|
231
|
+
commandBoardId?: BoardId;
|
|
232
|
+
}
|
|
233
|
+
interface RunningMcpServer {
|
|
234
|
+
app: Express;
|
|
235
|
+
httpServer: Server;
|
|
236
|
+
port: number;
|
|
237
|
+
setAllowedOrigins(origins: readonly string[]): void;
|
|
238
|
+
close(): Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Creates the loopback streamable-HTTP MCP server and starts listening on an
|
|
242
|
+
* ephemeral 127.0.0.1 port. Pipeline: originGuard -> requireBearerAuth -> /mcp.
|
|
243
|
+
* NO OAuth discovery routes are mounted (resourceMetadataUrl is left unset), so
|
|
244
|
+
* MCP clients use the static per-board bearer token without a "needs auth" flag.
|
|
245
|
+
*/
|
|
246
|
+
declare function createMcpHttpServer(deps: McpServerDeps): Promise<RunningMcpServer>;
|
|
247
|
+
|
|
248
|
+
interface MintedToken {
|
|
249
|
+
token: string;
|
|
250
|
+
row: AuthRow;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Mint a crypto-random per-board bearer token, store it, and return token + row.
|
|
254
|
+
* Scopes come from the tier (ADR 0002, D2). Always carries a board-lifetime
|
|
255
|
+
* `expiresAt` (the SDK requires a numeric expiry); override with `ttlSeconds`.
|
|
256
|
+
*/
|
|
257
|
+
declare function mintBoardToken(store: TokenStore, input: {
|
|
258
|
+
boardId: BoardId;
|
|
259
|
+
tier: Tier;
|
|
260
|
+
ttlSeconds?: number;
|
|
261
|
+
}): MintedToken;
|
|
262
|
+
|
|
263
|
+
declare const SCOPE_READ: Scope;
|
|
264
|
+
declare const SCOPE_DISPATCH: Scope;
|
|
265
|
+
declare const SCOPE_SPAWN: Scope;
|
|
266
|
+
declare const SCOPE_GIT_WRITE: Scope;
|
|
267
|
+
declare const SCOPE_ANSWER_PERMISSION: Scope;
|
|
268
|
+
/** Default scopes granted to a freshly-minted token of the given tier. */
|
|
269
|
+
declare function defaultScopesFor(tier: Tier): Scope[];
|
|
270
|
+
|
|
271
|
+
interface McpJson {
|
|
272
|
+
mcpServers: {
|
|
273
|
+
'canvas-ade': {
|
|
274
|
+
type: 'http';
|
|
275
|
+
url: string;
|
|
276
|
+
headers: {
|
|
277
|
+
Authorization: string;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Pure builder for a board worktree's project-scoped .mcp.json: a single loopback
|
|
284
|
+
* streamable-HTTP server entry carrying the board's bearer token. No OAuth
|
|
285
|
+
* discovery is referenced (ADR 0001) — the static bearer token is the authority.
|
|
286
|
+
*/
|
|
287
|
+
declare function buildMcpJson(port: number, token: string): McpJson;
|
|
288
|
+
/**
|
|
289
|
+
* Write .mcp.json into a board's worktree dir. Returns the written file path.
|
|
290
|
+
* Written owner-only (0600): the file embeds a plaintext bearer token, so it must
|
|
291
|
+
* not be world-readable on a multi-user box. (POSIX mode is a no-op on Windows.)
|
|
292
|
+
*/
|
|
293
|
+
declare function writeMcpJson(dir: string, port: number, token: string): string;
|
|
294
|
+
|
|
295
|
+
/** A no-op Orchestrator for contract tests and standalone runs. */
|
|
296
|
+
declare class MockOrchestrator implements Orchestrator {
|
|
297
|
+
listBoards(): Promise<BoardSummary[]>;
|
|
298
|
+
spawnBoard(_input: {
|
|
299
|
+
type: string;
|
|
300
|
+
prompt?: string;
|
|
301
|
+
cwd?: string;
|
|
302
|
+
}): Promise<{
|
|
303
|
+
id: BoardId;
|
|
304
|
+
}>;
|
|
305
|
+
closeBoard(_boardId: BoardId): Promise<void>;
|
|
306
|
+
configureBoard(_boardId: BoardId, _config: BoardConfig): Promise<void>;
|
|
307
|
+
dispatchPrompt(_boardId: BoardId, _text: string): Promise<void>;
|
|
308
|
+
writeResult(_boardId: BoardId, _result: BoardResultInput): Promise<void>;
|
|
309
|
+
interrupt(_boardId: BoardId): Promise<void>;
|
|
310
|
+
relayPrompt(_sourceId: BoardId, _targetId: BoardId, _text: string): Promise<void>;
|
|
311
|
+
handoffPrompt(_boardId: BoardId, _text: string): Promise<BoardResult>;
|
|
312
|
+
gitDiff(_boardId: BoardId): Promise<string>;
|
|
313
|
+
boardStatus(_boardId: BoardId): Promise<string>;
|
|
314
|
+
boardOutput(_boardId: BoardId, _opts?: {
|
|
315
|
+
cursor?: number;
|
|
316
|
+
}): Promise<BoardOutput>;
|
|
317
|
+
boardResult(_boardId: BoardId): Promise<BoardResult>;
|
|
318
|
+
projectMemory(): Promise<MemoryDoc>;
|
|
319
|
+
boardSummary(_boardId: BoardId): Promise<MemoryDoc>;
|
|
320
|
+
/** @internal subscribers for the M5 status stream. */
|
|
321
|
+
private readonly statusListeners;
|
|
322
|
+
subscribeStatus(listener: (change: BoardStatusChange) => void): () => void;
|
|
323
|
+
/** Test seam: drive a status change through the subscription fan-out. */
|
|
324
|
+
__emitStatus(change: BoardStatusChange): void;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Hard cap on the chars returned by ONE `canvas://board/{id}/output` page (T1.4 🔒).
|
|
329
|
+
* The MCP output budget is ~25k; we never emit a larger page even if the host
|
|
330
|
+
* over-returns. MUST match the app accessor's page size (`MAX_OUTPUT_PAGE` in
|
|
331
|
+
* `src/main/ptyOutput.ts`) so the tail-anchored cursor math lines up across the
|
|
332
|
+
* two repos. Unit = UTF-16 code units (JS `String.length`), matching the host ring.
|
|
333
|
+
*/
|
|
334
|
+
declare const MAX_OUTPUT_PAGE = 25000;
|
|
335
|
+
|
|
336
|
+
export { type AuthRow, type BoardId, type BoardOutput, type BoardResult, type BoardResultInput, type BoardStatusChange, type BoardSummary, MAX_OUTPUT_PAGE, type McpJson, type McpServerDeps, type MemoryDoc, type MintedToken, MockOrchestrator, type Orchestrator, type RunningMcpServer, SCOPE_ANSWER_PERMISSION, SCOPE_DISPATCH, SCOPE_GIT_WRITE, SCOPE_READ, SCOPE_SPAWN, type Scope, type Tier, TokenStore, buildMcpJson, createMcpHttpServer, defaultScopesFor, mintBoardToken, writeMcpJson };
|