Skip to content

fix(mcp): serve site_list_documents headlessly over MCP#186

Open
RemiAsselin42 wants to merge 2 commits into
CoreBunch:mainfrom
RemiAsselin42:fix/mcp-headless-list-documents
Open

fix(mcp): serve site_list_documents headlessly over MCP#186
RemiAsselin42 wants to merge 2 commits into
CoreBunch:mainfrom
RemiAsselin42:fix/mcp-headless-list-documents

Conversation

@RemiAsselin42

@RemiAsselin42 RemiAsselin42 commented Jul 5, 2026

Copy link
Copy Markdown

Summary

Adds a headless site_list_documents to the MCP server so external MCP clients can list editable documents (pages, templates, visual components) without an open editor.

The site-scope site_list_documents resolves from ctx.snapshot, the browser-posted editor snapshot. Over MCP there is no snapshot (ctx.snapshot === null), and asSnap is an unchecked cast, so snap.currentDocument throws a TypeError and 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 of siteTools in server/ai/mcp/registry.ts so it wins the name de-dup, the same pattern the existing styleMcpTools uses to shadow site_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 requiredCapabilities to ['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 against AgentDocumentRefSchema.

Verification

Run on Windows 11, Bun 1.3.14:

  • bun run build
  • bun test (covering suite bun test server/ai/mcp: 41 pass, 0 fail; full suite left to CI)
  • bun run lint
  • Docker/deployment check, if relevant (not applicable)

Checklist

  • Tests cover behavior changes. (documentTools.test.ts exercises the tool over a snapshotless MCP context.)
  • Docs were updated when behavior, config, deployment, or public surfaces changed. (Not touched; if you want docs/features/mcp-connectors.md to list the headless tool set explicitly, I can add it.)
  • No compatibility shim was added for old pre-release behavior.
  • No secrets, local databases, uploads, or generated artifacts are included.

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.
@RemiAsselin42 RemiAsselin42 marked this pull request as ready for review July 6, 2026 08:59
Copilot AI review requested due to automatic review settings July 6, 2026 08:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 documentMcpTools with a server-resolved, DB-backed site_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.

Comment thread server/ai/mcp/tools/documentTools.ts Outdated
Comment on lines +22 to +28
const SITE_READ_CAPS: readonly CoreCapability[] = [
'site.read',
'site.structure.edit',
'site.content.edit',
'site.style.edit',
'pages.edit',
]
Comment thread server/ai/mcp/tools/documentTools.ts Outdated
Comment on lines +30 to +32
// 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: '' }
Comment on lines +43 to +48
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.
@RemiAsselin42

RemiAsselin42 commented Jul 6, 2026

Copy link
Copy Markdown
Author

Addressed the two correctness comments in 4e432d9:

  • Capability scope : site_list_documents now requires only ['site.read']. Since requiredCapabilities is ANY-OF (toolAllowedForCapabilities), the previous five-capability list would have let an edit-only caller (e.g. pages.edit without site.read) list documents. It now mirrors the site-scope tool it shadows.
  • Sentinel id : replaced the empty-id NO_CURRENT_DOCUMENT with a clearly-nonexistent, non-empty id so it stays valid against AgentDocumentRefSchema (minLength: 1).

On the getDraftSiteDocument cost: leaving it as-is here. Every site read path (the in-editor snapshot, transactional save, and the other read tools) reconstructs the full draft document the same way, so a metadata-only projection is a broader repository/publisher optimization better done in its own PR than folded into this crash fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants