Problem/Motivation

On multilingual sites an article is one node with linked translations. The tool input schemas generated by ToolApiSchemaConverter::inputDefinitionsToSchema() never emit additionalProperties: false, and ToolApiCallToolHandler only copies declared inputs to the plugin — so an unknown argument passes Opis validation and is silently dropped.

Real-world consequence (hit during a production content migration): mcp_delete_content {"nid": 416, "language": "it"} — the caller intended to delete only the Italian translation. The undeclared language key was ignored and the tool deleted the entire node with all three translations. destructive: TRUE on the Tool attribute is only a client-side hint (destructiveHint), so nothing guards the destructive default at runtime.

Related gaps in the content tools:

  • mcp_update_content always writes the source-language entity — there is no way to target a specific translation, and unknown field names in updates are silently skipped (the call reports success without applying anything).
  • There is no way to delete a single translation while keeping the other language versions.
  • AuditLogger::sanitizeDetails() throws a TypeError under strict_types when details contain list values (strtolower() on integer array keys).

Steps to reproduce

  1. Enable content_translation and create a node with one or more translations.
  2. Call mcp_delete_content with {"nid": N, "language": "xx"}.
  3. The call passes input validation, the language key is dropped before reaching the plugin, and the whole node — every translation — is deleted.

Proposed resolution

Patch attached (rolled against 1.0.0-beta14):

  1. Fail loudly on unknown parameters, for every tool: emit additionalProperties: false in each generated input schema (also visible to MCP clients in tools/list); ToolInputValidator appends the allowed parameter list to additionalProperties errors. A typo or unsupported key can no longer fall through to a destructive default.
  2. mcp_delete_content: new optional language deletes only that translation — saved as a new revision so earlier revisions retain the removed translation as a recovery path; the node's source language is protected while translations exist. A bare {"nid": N} is now rejected: deleting the whole node requires an explicit confirm_delete_all: true, and the response lists the removed language versions. Passing both language and confirm_delete_all is rejected as ambiguous. The response is trash-aware (ModuleHandler injected) instead of always claiming the delete is permanent.
  3. mcp_update_content: new optional language routes the update to that translation via the mcp_tools_translate submodule proposed in #3611087 (soft dependency — a clear error is returned when it is not installed). Unknown field names in updates are rejected before anything is saved, and the response names the affected language and the updated fields.
  4. AuditLogger: cast array keys to string before strtolower().
  5. Unit tests (schema converter, input validator, ContentService) and Kernel tests (full delete-guard matrix, translation delete with revision retention, trash integration, fail-loud update validation) included.

The translation-side write mechanics (replacing an existing translation via an explicit overwrite flag, field-level per-language updates including paragraph content) live in the submodule proposed in #3611087.

Remaining tasks

  • Review.

API changes

  • Behavior change: unknown tool parameters now return a validation error (previously accepted and ignored).
  • Behavior change: mcp_delete_content with only nid is rejected; pass confirm_delete_all: true for the previous full-delete behavior.
  • New optional inputs: language + confirm_delete_all (DeleteContent), language (UpdateContent).
  • ContentService::deleteContent() gained optional parameters; the service constructor gained ModuleHandlerInterface (services.yml updated in the patch).

Comments

julien created an issue. See original summary.

julien’s picture

mowens’s picture

Assigned: Unassigned » mowens

shipped in beta15 - sorry this cost you a node. Thanks for turning an incident into a proper fix.

mowens’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.