Skip to content

MCP Server#1022

Open
busbyk wants to merge 11 commits intomainfrom
mcp-server
Open

MCP Server#1022
busbyk wants to merge 11 commits intomainfrom
mcp-server

Conversation

@busbyk
Copy link
Copy Markdown
Collaborator

@busbyk busbyk commented Apr 3, 2026

Description

Integrates the Payload MCP plugin (@payloadcms/plugin-mcp) to expose a Model Context Protocol server at /api/mcp. This allows AI tools like Claude Code to query CMS content directly using API key authentication, with full RBAC enforcement.

It has been configured to only allow read access to collections and to only allow super admins to manage API keys.

Related Issues

None

Key Changes

  • MCP plugin integration (src/plugins/index.ts): Configures mcpPlugin with read-only access to 16 collections and the nacWidgetsConfig global. Includes server instructions that describe the multi-tenant data model and common query patterns to MCP clients.
  • Patch for @payloadcms/plugin-mcp (patches/@payloadcms__plugin-mcp.patch): Adds two features not yet upstream:
    • authDepth — controls population depth when authenticating API key users (our RBAC needs depth 3 to resolve globalRoleAssignments.docs[].globalRole.rules)
    • instructions — passes the MCP protocol's instructions field through to clients during initialization
  • isUser type guard and getUser helper (src/utilities/isUser.ts): The MCP plugin adds a second auth collection (payload-mcp-api-keys), which widens req.user to a union type. These utilities let existing code safely narrow back to User without type assertions. We can't narrow this at the Payload config level - this is a result of having multiple "auth" collections.
  • Access control for MCP API Keys: Locked to super admins only via hasSuperAdminPermissions on all CRUD operations.
  • E2E tests (__tests__/e2e/admin/collections/mcp-api-keys.e2e.spec.ts): Tests that super admin can access the MCP API Keys collection and all other roles (7 roles) are denied.
  • Migration (src/migrations/20260403_204010.ts): Adds the payload_mcp_api_keys table and related schema.
  • Documentation (docs/mcp-server.md, CLAUDE.md): Setup guide, security model, troubleshooting, and MCP querying tips for developers.

How to test

  1. pnpm install to apply the patch
  2. pnpm seed to set up the database with the new migration
  3. pnpm dev to start the dev server
  4. Log in as super admin
  5. Navigate to Admin > MCP API Keys, create a new key associated with your user
  6. Configure your AI tool's MCP server settings per docs/mcp-server.md
  7. Verify you can query collections (e.g., findTenants, findPosts) - ask something like "List the posts for NWAC"
  8. Verify non-super-admin users cannot access the MCP API Keys collection

Screenshots / Demo video

https://www.loom.com/share/ed05450621d9421baf0a5b356bd467a5

Migration Explanation

Adds the payload_mcp_api_keys table with columns for API key management (label, user relationship, API key hash, enabled collections, etc.). This is a purely additive migration — no existing tables are modified.

Future enhancements / Questions

  • Open upstream PRs on @payloadcms/plugin-mcp for the authDepth and instructions features to eliminate the patch
  • Consider enabling selective write operations (e.g., create on posts) for AI-assisted content workflows
  • Consider adding custom MCP tools for non-CRUD operations (e.g., triggering revalidation, running reports)

busbyk and others added 11 commits April 2, 2026 12:13
Adds @payloadcms/plugin-mcp to enable MCP server access to the local
Payload CMS database, useful for exploring content structures during
development.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pin mcp-handler to 1.0.7 via pnpm overrides to work around a bug in
@payloadcms/plugin-mcp@3.78.0 where handler(request) expects a return
value but mcp-handler returns a (req, res) callback. Also exclude
payload-mcp-api-keys from TenantScopedCollectionWithHash in upsert.ts
since the MCP API key collection lacks tenant/contentHash fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend the @payloadcms/plugin-mcp patch to pass the `instructions` field
through to the underlying MCP SDK. Configure serverInfo and instructions
in the plugin to describe the multi-tenant data model and common query
patterns to MCP clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configure avyweb-payload-local (localhost:3000) and avyweb-payload-prod
(avy-fx.org) MCP servers in .mcp.json with env var placeholders for
API keys. Set AVYWEB_MCP_API_KEY_LOCAL and AVYWEB_MCP_API_KEY_PROD in
your environment to authenticate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add setup instructions, when to use/not use guidance, and querying tips
for the avyweb-payload-local and avyweb-payload-prod MCP servers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace individual denied-role tests with a parameterized loop over all
non-super-admin roles: providerManager, multiTenantAdmin, singleTenantAdmin,
singleTenantForecaster, singleTenantStaff, providerUser, multiProviderUser.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nges

Document the read-only-by-configuration design, custom tools extensibility,
.mcp.json setup, server instructions feature, and the combined patch for
authDepth and instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on and isUser guards

Revert addRandomSuffix and zod bump (unrelated to MCP feature). Tighten
E2E denied-role assertion by removing weak hasNoCreateButton fallback.
Remove dangling upstream PR doc reference. Add isUser type guards to
hasProviderAccess and globalRoleAssignmentsForUser to handle the new
PayloadMcpApiKey auth collection type. Generate migration for
payload_mcp_api_keys table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces getUser(req) that extracts a User from the request, returning
null if unauthenticated or authenticated as a different collection type
(e.g., PayloadMcpApiKey). Replaces the awkward `!user || !isUser(user)`
pattern at 8 call sites. The bare isUser() type guard remains for the 3
sites that receive user as a standalone argument from Payload callbacks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@busbyk busbyk deployed to Preview April 3, 2026 23:38 — with GitHub Actions Active
@busbyk busbyk self-assigned this Apr 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Migration Safety Check

Found 3 potential issues:

20260403_204010.ts

Warning (line 32): DELETE keyword detected - review for data loss

FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE set null

Warning (line 45): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`payload_locked_documents_rels\` ADD \`payload_mcp_api_keys_id\` integer REFERENCES payload_mcp_api_keys(id);`,

Warning (line 51): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`payload_preferences_rels\` ADD \`payload_mcp_api_keys_id\` integer REFERENCES payload_mcp_api_keys(id);`,

Review these patterns and add backup/restore logic if needed. See docs/migration-safety.md for guidance.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Preview deployment: https://mcp-server.preview.avy-fx.org

@busbyk busbyk requested a review from rchlfryn April 4, 2026 00:17
@busbyk busbyk marked this pull request as ready for review April 4, 2026 00:17
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.

1 participant