You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(mcp): serve site_list_documents headlessly over MCP (CoreBunch#186)
* fix(mcp): serve site_list_documents headlessly over MCP
The site-scope `site_list_documents` handler reads `ctx.snapshot`, which is
null over MCP (no chat turn, no editor snapshot). `asSnap` is an unchecked
cast, so `asSnap(null).currentDocument` throws a TypeError — any MCP client
calling the tool crashes instead of receiving the document catalog.
Add a headless `documentMcpTools` set that assembles the catalog directly from
the draft site document (`getDraftSiteDocument` + `describeAgentDocuments`) and
order it ahead of `siteTools` in the MCP registry so it wins the name de-dup,
mirroring how `styleMcpTools` already shadows `site_list_breakpoints`. No editor
focus exists server-side, so nothing is marked current; `get_context` still
reports the live editor.
* fix(mcp): scope site_list_documents to site.read and use a valid current-doc sentinel
Address review feedback on the headless site_list_documents:
- `requiredCapabilities` matches ANY-OF (see `toolAllowedForCapabilities`), so
the previous five-capability list let a caller holding only an edit capability
(e.g. `pages.edit`) but not `site.read` list documents. Narrow it to
`['site.read']`, matching the site-scope tool it shadows.
- The "no current document" sentinel used an empty id, violating
`AgentDocumentRefSchema` (`minLength: 1`). Use a clearly-nonexistent,
non-empty id instead so it never matches a real document and stays
schema-valid if surfaced.
* fix(mcp): finalize headless document catalog
---------
Co-authored-by: DavidBabinec <hello@davidbabinec.com>
Copy file name to clipboardExpand all lines: docs/features/mcp-connectors.md
+7-8Lines changed: 7 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ The server is implemented with the official `@modelcontextprotocol/sdk`. That pa
12
12
13
13
-**Instatic is an MCP server.** One Streamable-HTTP endpoint at `/_instatic/mcp` serves both local and remote clients (local is just `localhost`).
14
14
-**Thin adapter over the existing tool engine.** No tool logic is duplicated. MCP is a new *caller* alongside the built-in agent and the plugin host; tool dispatch reuses `executeAiTool`.
15
-
-**Tool surface = the full catalog.** Server-resolved tools (content reads + `site_read_styles`) run headless — no editor needed. Every browser-execution tool the agent panel has (structure edits, insert HTML, apply CSS, assign classes, set design tokens, manage pages, content CRUD, code assets, live-DOM reads) is exposed too, **relayed to an open editor via the live editor bridge** — the single source of truth for page editing. If the connector owner has no editor open, those tools return a clear "open the editor" error; the headless reads still work.
15
+
-**Tool surface = the full catalog.** Server-resolved tools (content reads + `site_list_documents` + `site_read_styles`) run headless — no editor needed. Every browser-execution tool the agent panel has (structure edits, insert HTML, apply CSS, assign classes, set design tokens, manage pages, content CRUD, code assets, live-DOM reads) is exposed too, **relayed to an open editor via the live editor bridge** — the single source of truth for page editing. If the connector owner has no editor open, those tools return a clear "open the editor" error; the headless reads still work.
16
16
-**Bearer-token auth, one secret per connector.** The token is shown once on creation and stored only as a SHA-256 hash. New tokens expire after 90 days by default; admins can choose a custom TTL or explicitly create a non-expiring token. Revocable.
17
17
-**Capability-gated.** A connector carries a granted capability subset; the same gate the built-in agent uses (`toolAllowedForCapabilities`) filters the toolset. An MCP caller can never invoke a tool the granting capabilities couldn't authorize over HTTP.
18
18
-**Privilege floor.** An admin can only grant capabilities they themselves hold.
@@ -36,9 +36,9 @@ server/ai/mcp/server.ts low-level SDK Server; tools filtered by ca
36
36
│
37
37
server/ai/mcp/registry.ts AiTool registry → MCP tools (TypeBox inputSchema sent verbatim as JSON Schema)
|`transports/http.ts`| Mounts the SDK's Web-standard Streamable-HTTP transport; stateless per request (`enableJsonResponse`). |
49
49
|`auth.ts`| Bearer resolution → `{ connectorId, userId, capabilities }`; spec-correct 401 with an RFC 9728 `resource_metadata` pointer. |
50
50
|`server.ts`| Builds a capability-scoped low-level `Server` (`ListTools` / `CallTool` handlers). Uses the low-level `Server`, not `McpServer.registerTool`, because the latter needs Zod (banned) — this lets the TypeBox `inputSchema` pass through verbatim. |
51
-
|`registry.ts`| The exposable toolset = full catalog (content + site + page-tree), deduped by name, filtered by `toolAllowedForCapabilities`. |
51
+
|`registry.ts`| Headless reads plus the browser-relayed site/content catalog, deduped by name and filtered by `toolAllowedForCapabilities`. |
52
+
|`tools/documentTools.ts`|`site_list_documents` — pages, templates, and visual components, headless from the DB. |
52
53
|`tools/styleTools.ts`|`site_read_styles` — the design system as a CSS stylesheet, headless from the DB. |
53
54
|`editorBridge.ts`| Per-user live editor bridge registry + `createEditorBridgeStream`; `getEditorBridgeForUser` routes browser tools to the owner's open editor. |
54
55
|`handlers/editorBridge.ts`|`GET /admin/api/ai/editor-bridge` — the NDJSON stream the editor holds open. |
|`handlers/connectors.ts`|`/admin/api/ai/mcp/connectors` CRUD, gated by `ai.providers.manage`. |
57
58
58
-
The headless page-tree path (load → `applyTreeOperation` → persist) lives in `server/ai/content/treeService.ts` and is shared with the plugin RPC `cms.content.tree.mutate` — neither caller duplicates the engine. Gated by `plugin-content-tree-via-engine.test.ts`.
59
-
60
59
---
61
60
62
61
## Tool surface
@@ -68,6 +67,7 @@ MCP exposes the **full tool catalog** (deduped by name), capability-filtered. To
68
67
**Headless (server-resolved) — work with no editor open:**
69
68
- Content reads — list/read collections, entries, data rows, media.
70
69
-`get_context({ entryId? })` — orientation in one call: is a live editor connected (browser tools need it), which "everywhere"/post-type templates wrap pages, site name. Call it first if a browser tool returns "open the editor."
70
+
-`site_list_documents` — editable pages, templates, and visual components with document references, root node ids, template metadata, and summaries. Nothing is marked active/current because headless calls have no editor focus.
71
71
-`site_read_styles({ format?, className?, includeTokens? })` — the design system as a **CSS stylesheet**: design tokens (CSS custom properties) + every class/ambient rule, read straight from the DB via the publisher's emitters. `format:"summary"` returns a compact class catalog (selector + referenced token vars, no declarations) to scan first. Symmetric with reading pages as HTML / writing CSS via `site_apply_css`. Replaces the old snapshot-dependent `list_tokens`.
72
72
-`site_list_breakpoints` — configured viewport ids/labels/widths (the first is the base), so `site_render_snapshot` can target one deliberately. Headless version replaces the snapshot-dependent one.
73
73
@@ -155,7 +155,6 @@ An admin cannot grant a capability they do not hold (enforced in `handlers/conne
155
155
## Tests
156
156
157
157
-`server/ai/mcp/connectors/{token,store}.test.ts` — token hashing, expiry, and store CRUD.
'List editable documents: pages, templates, and visual components. Use the returned document refs with site_read_document/site_open_document. Each item includes rootNodeId, template metadata, and a short summary. Headless — no editor needed.',
0 commit comments