fix(mcp): name the HTML body in the tool descriptions #37

Merged
jlxq0 merged 1 commit from fix/tool-descriptions-name-html into main 2026-09-01 17:32:48 +00:00
Owner

Lucy read the descriptions after v0.2.20 landed and both were stale in the direction that hides the capability.

send_email  "Send a plain-text email."
read_email  "headers, plain-text body, attachments"

send_email's is the worse of the two: a caller reading it has no reason to look for body_html and would conclude the tool cannot send HTML at all.

The cost is measured rather than hypothetical. On Saturday she read the summary rather than the properties and told two people the parameter did not exist, then retracted it. The description is what an agent reads to decide whether a capability exists, and a stale one is a claim about the system that the system does not make.

What they say now

read_email: names the HTML body, says body_html is null when there is no HTML part and never a fallback to the text, and that it is escaped for the same injection tokens but not wrapped, since the wrapper would stop it being valid HTML. Both bodies named as untrusted; suspicious described as covering either.

send_email: body_text always required, body_html optional, both parts go out so a client that does not render HTML still shows something, and HTML with an empty body_text is refused.

Nothing outside the repo moves

Tool names, titles and annotations untouched. Diffed for exactly those lines:

git diff -- src/mcp.rs | grep -E "^[+-].*(annotations|read_only_hint|destructive_hint|idempotent_hint|async fn )"
  (no output)

So tool-scope.sh generates the same deny lines and no mount's scope moves.

Why this carries a version bump

A tool description only reaches a caller through the running server, so the fix is not real until deployed. Cargo.toml is at 0.2.21.

Gate

Toolchain read from ci.yml: 1.93.0, through build-slot.sh. fmt rc=0, clippy -D warnings rc=0, test rc=0, 195 passed.

Lucy read the descriptions after `v0.2.20` landed and both were stale in the direction that **hides** the capability. send_email "Send a plain-text email." read_email "headers, plain-text body, attachments" **`send_email`'s is the worse of the two**: a caller reading it has no reason to look for `body_html` and would conclude the tool cannot send HTML at all. **The cost is measured rather than hypothetical.** On Saturday she read the summary rather than the properties and told two people the parameter did not exist, then retracted it. The description is what an agent reads to decide whether a capability exists, and a stale one is a claim about the system that the system does not make. ## What they say now `read_email`: names the HTML body, says `body_html` is `null` when there is no HTML part and **never a fallback to the text**, and that it is escaped for the same injection tokens but **not wrapped**, since the wrapper would stop it being valid HTML. Both bodies named as untrusted; `suspicious` described as covering either. `send_email`: `body_text` always required, `body_html` optional, both parts go out so a client that does not render HTML still shows something, and HTML with an empty `body_text` is refused. ## Nothing outside the repo moves Tool names, titles and annotations untouched. Diffed for exactly those lines: ``` git diff -- src/mcp.rs | grep -E "^[+-].*(annotations|read_only_hint|destructive_hint|idempotent_hint|async fn )" (no output) ``` So `tool-scope.sh` generates the same deny lines and no mount's scope moves. ## Why this carries a version bump A tool description only reaches a caller through the running server, so the fix is not real until deployed. `Cargo.toml` is at 0.2.21. ## Gate Toolchain read from `ci.yml`: `1.93.0`, through `build-slot.sh`. `fmt` rc=0, `clippy -D warnings` rc=0, `test` rc=0, 195 passed.
fix(mcp): name the HTML body in the tool descriptions
All checks were successful
CI / tag-ancestry (pull_request) Successful in 3s
CI / cargo (pull_request) Successful in 1m23s
CI / docker (pull_request) Successful in 1m11s
ed74743574
Lucy read the descriptions after v0.2.20 landed and both were stale in the
direction that hides the capability.

send_email's read "Send a plain-text email", so a caller has no reason to look
for body_html and would conclude the tool cannot send HTML. read_email's listed
"headers, plain-text body, attachments" and did not mention body_html at all.

The cost is measured rather than hypothetical: on Saturday she read the summary
rather than the properties and told two people the parameter did not exist,
then retracted it. The description is what an agent reads to decide whether a
capability is there, and a stale one is a claim about the system that the system
does not make.

read_email's now says body_html is null when there is no HTML part and never a
fallback to the text, and that body_html is escaped for the same injection
tokens but not wrapped, because the wrapper would stop it being valid HTML.
Both bodies are named as untrusted and the suspicious flag is described as
covering either.

send_email's now says body_text is always required, that supplying body_html
sends both parts so a client that does not render HTML still shows something,
and that HTML with an empty body_text is refused.

Tool names, titles and annotations are untouched, so tool-scope.sh generates
the same deny lines and no mount's scope moves. Verified by diffing for those
lines: no output.

Version bumped to 0.2.21, because a tool description only reaches a caller
through the running server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jlxq0 merged commit 316ed7cca8 into main 2026-09-01 17:32:48 +00:00
jlxq0 deleted branch fix/tool-descriptions-name-html 2026-09-01 17:32:48 +00:00
Sign in to join this conversation.
No reviewers
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/jmap-mcp!37
No description provided.