fix: #3618726 Send tool messages to MCP clients as plain text

Closes #3618726 (closed)

ToolApi::execute() sent markup to MCP clients as raw HTML. Placeholders escape quotes and % placeholders add an <em> tag, so the model read text like this:

Successfully deleted node entity "Torture &quot;quoted&quot; title" (ID: 70)

This MR adds a plainText() helper. It runs PlainTextOutput::renderFromHtml() on markup, the same way tool:run and tool_ai_connector do since tool#3583042 (closed). Plain strings pass through unchanged, because rendering would strip text that looks like a tag and decode literal entities.

  • The result message from getFormattedResult() is always TranslatableMarkup, so it is rendered.
  • Each violation message from validateInputs() is converted on its own and sent one per line. Tool API wraps each one as Input @name: @message, and core constraint messages use %value. This also drops the typed data root prefix that #3618731 (closed) tracks, so !25 (closed) keeps only its tests after a rebase.
  • The access denial reason is sent as the tool gave it. getReason() always returns a string, and tool:run does the same.

Exception messages stay as they are.

Testing

A new mcp_server_test:quoted_message fixture puts its title input in the violation message, the access reason, and the result message. ToolApiFailureResultTest checks:

  • Violation and result messages arrive with literal quotes and no <em> tag. These fail on 1.x.
  • An access reason of About <us> &amp; "team" is locked. arrives unchanged. This fails if the reason is rendered from HTML.

🤖 Generated with Claude Code

Edited by Matt Glaman

Merge request reports

Loading
Loading