Skip to content

[bug] Approval copy is gated on destructiveHint, so a truthfully additive tool cannot have a readable approval prompt #2074

Description

@tkstang

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:

  1. Populate approvalDescription from entry.annotations?.title whenever a title is present, regardless of destructiveHint, keeping requiresApproval: destructive as-is.
  2. Honor an explicit MCP annotations.approvalDescription passthrough, letting a server supply approval copy directly.

Steps to reproduce

  1. Register an MCP server (stdio) whose tool declares annotations: { title: "Create an Apple Reminder", readOnlyHint: false, destructiveHint: false }.
  2. Add a require_approval policy row matching that tool, with owner: user.
  3. Connect with ?elicitation_mode=browser and invoke the tool.
  4. Open the approval page. It reads Approve <address>? (matched policy: <pattern>); the declared title is absent.
  5. 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

  • I searched the open issues for a duplicate.
  • I removed all keys, tokens, and credentials from this report.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions