feat(mcp): MCP Server plugin — expose content as AI agent tools (#784)#991
Merged
Conversation
Implements a Model Context Protocol server as a first-class SonicJS plugin, exposing content collections as JSON-RPC 2.0 tools for AI agents (Claude Code, Cursor, etc). - POST /mcp endpoint — initialize, tools/list, tools/call, resources/* - Auth via existing API Keys plugin (Bearer sk_… header) - Per-collection tools: list_, get_, create_, update_, publish_, delete_ - Auto-populates `user` type fields (e.g. author) with API key owner - GET /admin/mcp dashboard — endpoint URL, exposed collections, Claude Code / Cursor integration snippets - Sidebar "Author" field resolves created_by UUID to email in all content list and edit views - Mounted at /mcp (not /api/mcp) to avoid /api/:collection catch-all - E2E spec: tests/e2e/93-mcp-admin.spec.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…TOU, N+1) - R8 violation: add escapeHtml to Author sidebar in admin-content-form - D1 100-param limit: chunk auth_user IN-clause lookup into 99-param batches - execGet slug: replace O(N) list scan with direct getBySlug() repo query - execDelete TOCTOU: reuse existence-check result instead of re-fetching - execListCollections: remove N serial findById DB calls; types already resolved active by resolveMcpConfig — now a sync O(1) map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…phase flags Fresh-eyes review follow-ups on the MCP plugin: - redactFields now stripped from create/update input, not just read output — a client can no longer populate a field it can never read back. - getBySlug falls back to the current draft so a never-published doc is reachable by slug (parity with getById); published still preferred. - PHASE_FLAGS hoisted to a single exported const in tools/registry so the admin dashboard tool list can't drift from what the endpoint serves. - execList documents its post-ACL truncation tradeoff. - Corrected stale /api/mcp endpoint comments (actual mount is /mcp). - Integration tests for slug-draft resolution + redact-on-write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives POST /mcp the way an agent does: mints a one-time API key via the api-keys admin route, then Bearer-auths JSON-RPC. Pins the review fix that get_* by slug resolves a never-published draft. Covers unauthenticated rejection, initialize handshake, and tools/list advertising. Verified locally against a running dev server (4/4). CI runs it on PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erver # Conflicts: # my-sonicjs-app/wrangler.toml # packages/core/src/db/migrations-bundle.ts # packages/core/src/plugins/core-plugins/index.ts # packages/core/src/plugins/manifest-registry.ts # packages/core/src/routes/admin-content.ts # packages/core/src/templates/pages/admin-content-form.template.ts
The lockfile was authored by npm 11 (node 24), which resolves optional platform deps differently than the CI matrix (node 20/22 → npm 10). npm ci on CI rejected it as out of sync (missing better-sqlite3@12.11.1, @emnapi/*, esbuild@0.28.1). Regenerated under node 22 / npm 10 so the lock matches what CI installs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mcpPlugin()) that exposes SonicJS content collections as Model Context Protocol JSON-RPC 2.0 tools for AI agents (Claude Code, Cursor, etc.)/admin/mcpdashboard showing the endpoint URL, exposed collections, and copy-paste integration config for Claude Code and Cursorcreated_byUUID to human-readable email viaauth_userJOINChanges
New:
packages/core/src/plugins/core-plugins/mcp-plugin/index.ts— plugin entry (mcpPlugin(options?)) + sidebar menu entry at order 87routes/mcp.ts—POST /mcphandler (JSON-RPC 2.0: initialize, tools/list, tools/call, resources/list, resources/read)admin/routes.ts+admin/templates.ts—/admin/mcpdashboardconfig.ts—McpConfigInputschema +resolveMcpConfig()(expands collections from registry)tools/registry.ts—buildToolRegistry()— generates per-collection list/get/create/update/publish/delete toolstools/mutations.ts— write executors; auto-populatesusertype schema fields (e.g.author) with API key owner's userId on createtools/documents.ts— read executors (list, get)tools/static.ts—list_collectionsstatic toolschema/field-to-jsonschema.ts— converts SonicJSCollectionConfigschema to JSON Schema for toolinputSchemaresources/schemas.ts— MCP resources exposing collection schemasjsonrpc.ts— JSON-RPC 2.0 request/response helpers__tests__/— 5 test files (unit + integration, 9 cases total)Modified: core
plugins/core-plugins/index.ts+index.ts— exportmcpPluginmanifest-registry.ts— plugin manifest supportroutes/admin-content.ts— resolvecreated_byUUID → email viaauth_userJOIN in content list (2 queries) and version historytemplates/pages/admin-content-form.template.ts— Author sidebar falls back tocreatedByemail when contentdata.authoris absentApp wiring
my-sonicjs-app/src/index.ts— addsmcpPlugin()toplugins.registermy-sonicjs-app/wrangler.toml— no functional changesDocs + tests
docs/ai/plans/mcp-plugin-plan.md— updated with completed phases + mount point rationaletests/e2e/93-mcp-admin.spec.ts— 8 Playwright cases for/admin/mcpdashboardArchitecture notes
POST /mcp(not/api/mcp) — avoidsapiRoutes.post('/:collection')catch-all registered before user plugin routes inapp.tsAuthorization: Bearer sk_…) — MCP adds zero privilege; every tool call runs as the key's owning user through normal document ACLincludeWrite: true,includeSearch: false(search_content deferred to Phase 4)Testing
npm test)npm run type-check)93-mcp-admin.spec.ts— CI validatesCloses
Resolves #784
🤖 Generated with Claude Code