Executor version
1.6.8
How do you run Executor?
CLI (executor daemon run)
Operating system
macOS (Apple Silicon)
Integration involved
MCP server (browser elicitation / elicitation_mode=browser)
What happened
A tool's human-readable approval copy is reachable only by declaring the tool destructive, so a truthfully non-destructive tool is forced to choose between honest metadata and a legible approval prompt.
In packages/plugins/mcp/src/sdk/plugin.ts, toToolDef populates approvalDescription only when destructiveHint is true:
const destructive = entry.annotations?.destructiveHint === true;
const annotations: StampedAnnotations = {
requiresApproval: destructive,
...(destructive ? { approvalDescription: entry.annotations?.title ?? entry.toolName } : {}),
mcp: stamp,
};
enforceApproval in packages/core/sdk/src/executor.ts then falls back to the tool address when that field is absent:
const message = annotations?.approvalDescription
? annotations.approvalDescription
: policyForcesApproval && policy.pattern
? `Approve ${address}? (matched policy: ${policy.pattern})`
: `Approve ${address}?`;
Concretely, an MCP server exposing a reminder-creation tool with accurate annotations — readOnlyHint: false, destructiveHint: false (creation is additive, per the MCP spec's "additive updates" wording), title: "Create an Apple Reminder" — gets this on the browser approval page:
Approve tools.apple-reminders.org.default.reminders_create? (matched policy: apple-reminders.org.default.reminders_create)
while sibling update and completion tools, which are truthfully destructiveHint: true, correctly render "Update an Apple Reminder" and "Complete an Apple Reminder".
The tool address and the verbatim arguments are shown either way, so approval integrity is intact — this is about whether the human approving can tell what they are approving.
What you expected
title to be honored as approval copy independently of destructiveHint, since the two express unrelated things: one is how to describe the action to a human, the other is whether the action destroys state. Gating the first on the second creates an incentive to mislabel additive tools as destructive purely to obtain readable copy — which then degrades any client that uses destructiveHint for confirmation or risk UI.
Either of these resolves it:
- Populate
approvalDescription from entry.annotations?.title whenever a title is present, regardless of destructiveHint, keeping requiresApproval: destructive as-is.
- Honor an explicit MCP
annotations.approvalDescription passthrough, letting a server supply approval copy directly.
Steps to reproduce
- Register an MCP server (stdio) whose tool declares
annotations: { title: "Create an Apple Reminder", readOnlyHint: false, destructiveHint: false }.
- Add a
require_approval policy row matching that tool, with owner: user.
- Connect with
?elicitation_mode=browser and invoke the tool.
- Open the approval page. It reads
Approve <address>? (matched policy: <pattern>); the declared title is absent.
- Repeat with
destructiveHint: true and the title appears — the only difference.
Diagnostics / logs
Source read at tag v1.6.8: packages/plugins/mcp/src/sdk/plugin.ts (toToolDef) and packages/core/sdk/src/executor.ts (enforceApproval). No logs attached; the behavior is fully described by the two excerpts above and reproduces deterministically from the annotations. All identifiers above are from a local test integration.
Before you submit
Executor version
1.6.8
How do you run Executor?
CLI (
executor daemon run)Operating system
macOS (Apple Silicon)
Integration involved
MCP server (browser elicitation /
elicitation_mode=browser)What happened
A tool's human-readable approval copy is reachable only by declaring the tool destructive, so a truthfully non-destructive tool is forced to choose between honest metadata and a legible approval prompt.
In
packages/plugins/mcp/src/sdk/plugin.ts,toToolDefpopulatesapprovalDescriptiononly whendestructiveHintis true:enforceApprovalinpackages/core/sdk/src/executor.tsthen falls back to the tool address when that field is absent:Concretely, an MCP server exposing a reminder-creation tool with accurate annotations —
readOnlyHint: false,destructiveHint: false(creation is additive, per the MCP spec's "additive updates" wording),title: "Create an Apple Reminder"— gets this on the browser approval page:while sibling update and completion tools, which are truthfully
destructiveHint: true, correctly render "Update an Apple Reminder" and "Complete an Apple Reminder".The tool address and the verbatim arguments are shown either way, so approval integrity is intact — this is about whether the human approving can tell what they are approving.
What you expected
titleto be honored as approval copy independently ofdestructiveHint, since the two express unrelated things: one is how to describe the action to a human, the other is whether the action destroys state. Gating the first on the second creates an incentive to mislabel additive tools as destructive purely to obtain readable copy — which then degrades any client that usesdestructiveHintfor confirmation or risk UI.Either of these resolves it:
approvalDescriptionfromentry.annotations?.titlewhenever a title is present, regardless ofdestructiveHint, keepingrequiresApproval: destructiveas-is.annotations.approvalDescriptionpassthrough, letting a server supply approval copy directly.Steps to reproduce
annotations: { title: "Create an Apple Reminder", readOnlyHint: false, destructiveHint: false }.require_approvalpolicy row matching that tool, withowner: user.?elicitation_mode=browserand invoke the tool.Approve <address>? (matched policy: <pattern>); the declared title is absent.destructiveHint: trueand the title appears — the only difference.Diagnostics / logs
Source read at tag v1.6.8:
packages/plugins/mcp/src/sdk/plugin.ts(toToolDef) andpackages/core/sdk/src/executor.ts(enforceApproval). No logs attached; the behavior is fully described by the two excerpts above and reproduces deterministically from the annotations. All identifiers above are from a local test integration.Before you submit