feat(mcp): add safe schema update tools - #2354
Conversation
🦋 Changeset detectedLatest commit: b628703 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scope checkThis PR changes 964 lines across 12 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
This PR is a focused, well-scoped feature addition: two new MCP tools (schema_update_collection, schema_update_field) that delegate to the existing REST handlers and validation, plus the underlying registry changes needed to make partial updates safe and concurrent-friendly.
Approach: The approach is sound and fits EmDash. Reusing updateCollectionBody/updateFieldBody for the MCP input schemas keeps the REST and MCP surfaces in sync, and the registry rewrite of updateCollection/updateField moves the read-modify-write logic inside the transaction so omitted fields are preserved. The narrowing of in-place type changes to the safe string/text/slug aliases (even when the SQLite column type happens to match) is the right conservative choice for a "safe update" tool.
What I checked:
- Diff and full changed files in
packages/core/src/{api,mcp,schema,query}. - Caching/invalidation paths: field writes now call both
invalidateCollectionCacheandinvalidateSchemaCache; collection writes go throughinvalidateUrlPatternCache()which also busts the schema object-cache namespace. - Validation:
updateCollectionBodyandupdateFieldBodyare used by both REST and MCP; newurlPatternValueandfieldValidationrefinements close validation gaps without increasing the logged-out query count. - Security: schema writes require
schema:write+Role.ADMIN; SQL is built with Kysely/parameterized values; no interpolation into SQL. - Tests: new integration tests cover happy paths, migration errors, invalid validation, concurrent partial updates, and permission gating.
I did not find any blocking issues. The changeset, docs, and tests are all in place, and the implementation matches the PR's stated goals.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
What does this PR do?
Adds
schema_update_collectionandschema_update_fieldMCP tools so agents can update existing schemas without deleting tables, columns, content, or stored field values.Both tools reuse the canonical REST handlers and validation, require
schema:writeplus the Admin role, preserve omitted properties during concurrent partial updates, invalidate affected caches, and return explicit migration errors for changes that cannot be represented safely as metadata-only updates. The MCP reference documents the new surface and migration limits.Closes #1681
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain. N/A: no admin UI strings are changed.AI-generated code disclosure
Screenshots / test output
No visual changes.
pnpm typecheck— passedpnpm lint:json | jq '.diagnostics | length'—0git diff --check— passed/var/...with its/private/var/...resolved pathpublintpassed;attw0.18.2 exited with its internalCannot read properties of undefined (reading 'filename')error