fix(mcp): serve site_list_documents headlessly over MCP#186
fix(mcp): serve site_list_documents headlessly over MCP#186RemiAsselin42 wants to merge 2 commits into
Conversation
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.
There was a problem hiding this comment.
Pull request overview
Adds a headless implementation of site_list_documents to the MCP tool registry so external MCP clients can list pages/templates/visual-components without relying on an in-browser editor snapshot (ctx.snapshot), preventing the current MCP crash path when ctx.snapshot === null.
Changes:
- Introduces
documentMcpToolswith a server-resolved, DB-backedsite_list_documents. - Adds a Bun test ensuring the MCP registry exposes the headless tool and it runs without a snapshot.
- Updates the MCP registry ordering so the headless tool shadows the snapshot-based site tool by name de-dup.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| server/ai/mcp/tools/documentTools.ts | Adds headless site_list_documents implemented via DB reads + describeAgentDocuments. |
| server/ai/mcp/tools/documentTools.test.ts | Seeds an in-memory SQLite DB and verifies the MCP-exposed tool works without a snapshot. |
| server/ai/mcp/registry.ts | Registers documentMcpTools ahead of siteTools so it wins name de-dup in MCP. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const SITE_READ_CAPS: readonly CoreCapability[] = [ | ||
| 'site.read', | ||
| 'site.structure.edit', | ||
| 'site.content.edit', | ||
| 'site.style.edit', | ||
| 'pages.edit', | ||
| ] |
| // A document ref that `documentRefEquals` never matches against a real id, so | ||
| // the headless listing marks nothing current — there is no editor focus here. | ||
| const NO_CURRENT_DOCUMENT: Parameters<typeof describeAgentDocuments>[2] = { type: 'page', id: '' } |
| handler: async (_input, ctx: ToolContext) => { | ||
| const site = await getDraftSiteDocument(ctx.db) | ||
| if (!site) return { ok: false, error: 'No site found.' } | ||
| return { | ||
| currentDocument: null, | ||
| documents: describeAgentDocuments(site, '', NO_CURRENT_DOCUMENT), |
…ent-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.
|
Addressed the two correctness comments in 4e432d9:
On the |
Summary
Adds a headless
site_list_documentsto the MCP server so external MCP clients can list editable documents (pages, templates, visual components) without an open editor.The site-scope
site_list_documentsresolves fromctx.snapshot, the browser-posted editor snapshot. Over MCP there is no snapshot (ctx.snapshot === null), andasSnapis an unchecked cast, sosnap.currentDocumentthrows aTypeErrorand the tool crashes for every MCP caller.This PR adds
documentMcpTools(server/ai/mcp/tools/documentTools.ts), which builds the catalog from the DB (getDraftSiteDocument+describeAgentDocuments) and orders it ahead ofsiteToolsinserver/ai/mcp/registry.tsso it wins the name de-dup, the same pattern the existingstyleMcpToolsuses to shadowsite_list_breakpoints. Nothing is marked current because there is no server-side editor focus. The in-editor chat path is unchanged.Review follow-ups (4e432d9): scoped
requiredCapabilitiesto['site.read'](ANY-OF semantics would otherwise let an edit-only caller list documents), and replaced the empty-id sentinel with a non-empty, clearly-nonexistent id so it stays valid againstAgentDocumentRefSchema.Verification
Run on Windows 11, Bun 1.3.14:
bun run buildbun test(covering suitebun test server/ai/mcp: 41 pass, 0 fail; full suite left to CI)bun run lintChecklist
documentTools.test.tsexercises the tool over a snapshotless MCP context.)docs/features/mcp-connectors.mdto list the headless tool set explicitly, I can add it.)