payloadcms / payloadcms/payload
plugin-mcp: updating a localized array in one locale breaks other locales when row ids are echoed from a read
@AlessioGr is already working on this.
Since Sep 11, 2026.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
When a document with a localized array field is updated through @payloadcms/plugin-mcp, the update breaks as soon as the MCP client echoes array row ids that were read from a different locale — which is exactly what every LLM client naturally does (read the document, translate/modify the rows, write them back to another locale).
Rows of a localized array exist once per locale, keyed by their row id. The plugin's findDocuments tool (called without locale) returns the default locale's rows including their row ids, and the collection input schema declares items[].id as a settable field. So when the model then calls updateDocument with locale: "es", it sends the English rows' ids under the Spanish locale. Result:
- Postgres / SQLite: the whole update fails with the misleading error
The following field is invalid: id. The array rows live in a single table whose primary key is the rowidwith a_localecolumn, so writing the same id under a second locale is a unique constraint violation (23505), whichhandleUpsertErrorconverts into that ValidationError. The model gets no hint about the real cause, retries with different shapes, and in my case ended up rewriting the array in ways that destroyed the other locale's rows and their relationships. - MongoDB: the update "succeeds" but silently stores the same row id under two locales — corrupt data that can never be written to a relational adapter again.
Nothing in the tool schema or descriptions tells the client that row ids are per-locale, and the plugin passes the ids straight through to payload.update, so any MCP client that follows the normal read → modify → write loop hits this.
I've opened a PR with a failing-test reproduction and a fix: the update tools now drop incoming row ids inside localized arrays/blocks that don't belong to a row of the locale being updated (ids that do belong to the target locale are kept, so partial row updates still merge correctly, and ids of non-localized arrays — which are required to keep localized subfields attached to their rows — are never touched).
Link to the code that reproduces this issue
(Integration test against the monorepo's test/plugin-mcp suite. On current main, the tests should generate new row ids when an update echoes row ids read from another locale and should strip row ids inside localized arrays when updating by where clause fail on Postgres with The following field is invalid: id, and the row-id assertion fails on MongoDB.)
Reproduction Steps
Config: a localized collection with a localized array containing a relationship:
// localization: { defaultLocale: 'en', locales: ['en', 'es', 'fr'], fallback: true }
{
slug: 'localized-items',
fields: [
{ name: 'title', type: 'text' },
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'label', type: 'text' },
{ name: 'rel', type: 'relationship', relationTo: 'users' },
],
},
],
}
- Create a document with English content:
await payload.create({ collection: 'localized-items', data: { title: 'demo', items: [{ label: 'english label', rel: user.id }] }, locale: 'en', }) - Through the MCP endpoint, call the
findDocumentstool for that document without alocale(what LLM clients do). The response contains the English rows with their row ids, e.g.{"items":[{"label":"english label","rel":1,"id":"6a95360f851349b6a0032645"}]}. - Call the
updateDocumenttool for another locale, echoing those rows back — again what LLM clients do:{ "collectionSlug": "localized-items", "id": 1, "locale": "es", "data": { "items": [ { "id": "6a95360f851349b6a0032645", "label": "spanish label", "rel": 1 } ] } } - On Postgres/SQLite the tool returns
Error updating document in collection "localized-items": The following field is invalid: idand the Spanish locale is never written. On MongoDB,payload.findByID({ locale: 'all' })now shows the same row id underitems.enanditems.es.
Runnable version of the same steps, from the monorepo:
git fetch https://github.com/tgriek/payload fix/plugin-mcp-cross-locale-row-ids
git checkout FETCH_HEAD -- test/plugin-mcp
git stash push packages/plugin-mcp # only needed if you also fetched the fix
pnpm run test:int:postgres plugin-mcp/localizedArrays
Which area(s) are affected?
plugin: mcp, db: postgres, db: sqlite, db: mongodb
Environment Info
Reproduced on payload main (4.0.0-canary.14), packages built from source
@payloadcms/plugin-mcp: 4.0.0-canary.14
@payloadcms/db-postgres / db-mongodb: 4.0.0-canary.14
Node.js: v25.8.0
OS: macOS (darwin 24.6.0)
Databases: PostgreSQL (ghcr.io/payloadcms/postgis-vector:latest) and MongoDB via the repo's docker compose
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.