Skip to content

feat(mcp): MCP Server plugin — expose content as AI agent tools (#784)#991

Merged
lane711 merged 6 commits into
mainfrom
lane711/sonicjs-mcp-server
Jul 2, 2026
Merged

feat(mcp): MCP Server plugin — expose content as AI agent tools (#784)#991
lane711 merged 6 commits into
mainfrom
lane711/sonicjs-mcp-server

Conversation

@lane711

@lane711 lane711 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a first-class MCP Server plugin (mcpPlugin()) that exposes SonicJS content collections as Model Context Protocol JSON-RPC 2.0 tools for AI agents (Claude Code, Cursor, etc.)
  • Adds a /admin/mcp dashboard showing the endpoint URL, exposed collections, and copy-paste integration config for Claude Code and Cursor
  • Fixes Author field display across all content list and edit views — resolves created_by UUID to human-readable email via auth_user JOIN

Changes

New: packages/core/src/plugins/core-plugins/mcp-plugin/

  • index.ts — plugin entry (mcpPlugin(options?)) + sidebar menu entry at order 87
  • routes/mcp.tsPOST /mcp handler (JSON-RPC 2.0: initialize, tools/list, tools/call, resources/list, resources/read)
  • admin/routes.ts + admin/templates.ts/admin/mcp dashboard
  • config.tsMcpConfigInput schema + resolveMcpConfig() (expands collections from registry)
  • tools/registry.tsbuildToolRegistry() — generates per-collection list/get/create/update/publish/delete tools
  • tools/mutations.ts — write executors; auto-populates user type schema fields (e.g. author) with API key owner's userId on create
  • tools/documents.ts — read executors (list, get)
  • tools/static.tslist_collections static tool
  • schema/field-to-jsonschema.ts — converts SonicJS CollectionConfig schema to JSON Schema for tool inputSchema
  • resources/schemas.ts — MCP resources exposing collection schemas
  • jsonrpc.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 — export mcpPlugin
  • manifest-registry.ts — plugin manifest support
  • routes/admin-content.ts — resolve created_by UUID → email via auth_user JOIN in content list (2 queries) and version history
  • templates/pages/admin-content-form.template.ts — Author sidebar falls back to createdBy email when content data.author is absent

App wiring

  • my-sonicjs-app/src/index.ts — adds mcpPlugin() to plugins.register
  • my-sonicjs-app/wrangler.toml — no functional changes

Docs + tests

  • docs/ai/plans/mcp-plugin-plan.md — updated with completed phases + mount point rationale
  • tests/e2e/93-mcp-admin.spec.ts — 8 Playwright cases for /admin/mcp dashboard

Architecture notes

  • Endpoint is POST /mcp (not /api/mcp) — avoids apiRoutes.post('/:collection') catch-all registered before user plugin routes in app.ts
  • Auth delegates entirely to the API Keys plugin (Authorization: Bearer sk_…) — MCP adds zero privilege; every tool call runs as the key's owning user through normal document ACL
  • Phase flags: includeWrite: true, includeSearch: false (search_content deferred to Phase 4)

Testing

  • 9 unit/integration tests pass (npm test)
  • TypeScript clean (npm run type-check)
  • Live-tested on port 9274: initialize, tools/list (25 tools), create→publish→list round-trip
  • E2E spec 93-mcp-admin.spec.ts — CI validates

Closes

Resolves #784

🤖 Generated with Claude Code

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>
@lane711 lane711 merged commit 67408e2 into main Jul 2, 2026
1 of 2 checks passed
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.

Feature request: MCP plugin

1 participant