feat(plugin-mcp)!: use generic CRUD tools#16962
Open
AlessioGr wants to merge 13 commits into
Open
Conversation
AlessioGr
commented
Jun 10, 2026
| dataSchema: sanitizeEntitySchema(collectionSchema), | ||
| }), | ||
| input: z.object({ | ||
| data: z.record(z.string(), z.unknown()).describe('The document fields to create'), |
Member
Author
There was a problem hiding this comment.
data is validated inside the handler
Contributor
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
Contributor
|
Hi @AlessioGr, I really welcome this change. Besides the advantages you already pointed out, this also solves another issue I ran into in larger projects with many collections: the tool list could become very long and consume quite a lot of tokens. I think it would also be useful to add MCP tool annotations (like Maybe this is also a good time to add the currently missing |
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.
BREAKING:
The MCP plugin no longer registers a separate CRUD tool for every collection and global. Instead of tools like
createPosts,findPosts, andupdateSiteSettings, it now registers stable generic tools likecreateDocument,findDocuments, andupdateGlobal. The target collection/global is passed ascollectionSlug/globalSlug.Why
The old shape worked well for a static config, but it broke an important agent workflow we want MCP to support: asking an agent to add a collection and then immediately seed data into it.
Example: after the agent adds a
catscollection, Payload can exposecreateCats, but the MCP client still has to refreshtools/listbefore the agent can call it. Some clients handlenotifications/tools/list_changed, but not all of them do (e.g. codex does not implement the full mcp spec). In those clients, the agent gets stuck until the MCP server is manually refreshed.Generic tools avoid that failure mode.
createDocumentalready exists beforecatsexists, so after HMR the agent only needs to call it withcollectionSlug: 'cats'. The exact write schema is still available throughgetCollectionSchema, and Payload still validates the data before writing.What changed
getConfigInfotool lists the collection and global slugs visible to the MCP client, so agents can discover what to pass ascollectionSlug/globalSlug. When the request has a user, it mirrors what that user would see in the admin panel (admin.hidden+ read access); without a user it lists the full config.getCollectionSchema,findDocuments,createDocument,updateDocument, anddeleteDocuments.getGlobalSchema,findGlobal, andupdateGlobal.auth,login,forgotPassword,resetPassword,unlock,verify) and takecollectionSlug.collectionSlug/globalSlugas top-level handler args. Payload adds the slug string to the MCP input schema automatically - intentionally not an enum, so slugs added after HMR keep working in clients that cached the old tool schema.createDocument,updateDocument, andupdateGlobalvalidatedataagainst the entity schema before writing. Validation and cast errors return that schema, so agents can fix the data and retry without an extra round trip.find,create,update, etc.), even though the MCP wire tool names are generic.updateGlobalnow runs the same point-field data transformation as the collection write tools.How to migrate
Per-collection tool names are gone:
Global tool names are generic too:
For custom collection/global tools, do not add the slug to
inputyourself:defineCollectionTool({ description: 'Publish a draft post by ID.', - input: z.object({ collectionSlug: z.string(), id: z.string() }), + input: z.object({ id: z.string() }), }).handler(async ({ collectionSlug, input, req }) => { // ... })Function-form
input(deriving the input from the collection/global JSON schema) is removed -inputis a static schema now. If your tool accepted arbitrary document data, define a plain object in the input schema instead, and validate it in the handler:defineCollectionTool({ description: 'Bulk-import posts.', - input: ({ collectionSchema }) => ({ - type: 'object', - properties: { docs: { type: 'array', items: collectionSchema } }, - required: ['docs'], - }), + input: z.object({ docs: z.array(z.record(z.string(), z.unknown())) }), })