Skip to content

fix(app): name the MCP tool and its input in the permission dock - #60548

Merged
usrnk1 merged 9 commits into
anomalyco:devfrom
bjck:fix/permission-dock-mcp-tool-name
Nov 4, 2026
Merged

usrnk1 merged 9 commits into
anomalyco:devfrom
bjck:fix/permission-dock-mcp-tool-name

Conversation

@bjck

@bjck bjck commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #51519

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

MCP tools ask with resources: ["*"] and have no settings.permissions.tool.<action>.description. So the web/desktop dock shows only *. You can't see which tool is asking or what it will do.

The dock now shows the action when there is no description. It also shows request.message when the permission.evaluate hook sets one. It lists metadata.input (sent since #51062) as key: value lines in their own row. Long arguments are not cut: the row scrolls once it is taller than 160px, because the user is approving these exact arguments. The resources row is unchanged, so the * still shows what Allow always covers. Tools with a description look the same as before.

The TUI also drops request.message. I'll fix that in a separate PR.

How did you verify your code works?

  • New e2e test in e2e/regression/session-request-docks.spec.ts. It fails on dev and passes with the fix. It checks the tool name, the hook message, the full input, that a long input scrolls inside the dock, and that the * is still shown.
  • bun run test:e2e -- e2e/regression/session-request-docks.spec.ts --project chromium: 6 passed
  • bun typecheck in packages/app: no errors; bun run check (pre-push): passed
  • Ran it live against a mock MCP server and a plugin that sets message (screenshots below)

Screenshots / recordings

Before:
permission-dock-before

After (MCP call, no description):
dock-greet-light

After (long input with a hook message, scrolls instead of being cut):
dock-long-light
permission-dock-scroll

Visual

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 72" width="240" height="72" fill="currentColor">
  <style>
    .s{animation:s 4s ease-in-out infinite}
    .t{animation:t 4s ease-in-out infinite}
    @keyframes s{0%,20%{transform:translateY(0)}45%,70%{transform:translateY(-17px)}95%,100%{transform:translateY(0)}}
    @keyframes t{0%,20%{transform:translateY(0)}45%,70%{transform:translateY(9px)}95%,100%{transform:translateY(0)}}
  </style>
  <defs>
    <clipPath id="v"><rect x="24" y="28" width="150" height="18"/></clipPath>
  </defs>
  <rect x="6" y="4" width="228" height="64" rx="8" fill="none" stroke="currentColor" stroke-opacity="0.3"/>
  <circle cx="16" cy="13" r="3" opacity="0.6"/>
  <rect x="24" y="11" width="60" height="4" rx="2" opacity="0.7"/>
  <rect x="24" y="20" width="48" height="4" rx="1" opacity="0.9"/>
  <rect x="76" y="20" width="70" height="4" rx="2" opacity="0.45"/>
  <g clip-path="url(#v)">
    <g class="s">
      <rect x="24" y="29" width="54" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="34" width="72" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="39" width="146" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="44" width="146" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="49" width="146" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="54" width="146" height="3" rx="1" opacity="0.5"/>
      <rect x="24" y="59" width="90" height="3" rx="1" opacity="0.5"/>
    </g>
  </g>
  <rect x="177" y="28" width="2" height="18" rx="1" opacity="0.12"/>
  <rect class="t" x="177" y="28" width="2" height="9" rx="1" opacity="0.5"/>
  <g stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.9">
    <line x1="27" y1="53" x2="27" y2="61"/>
    <line x1="23.5" y1="55" x2="30.5" y2="59"/>
    <line x1="23.5" y1="59" x2="30.5" y2="55"/>
  </g>
  <rect x="150" y="54" width="22" height="9" rx="3" fill="none" stroke="currentColor" stroke-opacity="0.35"/>
  <rect x="176" y="54" width="26" height="9" rx="3" opacity="0.25"/>
  <rect x="206" y="54" width="22" height="9" rx="3" opacity="0.8"/>
</svg>

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #60350, #50234

MCP tools ask with resources ["*"] and have no permission description,
so the web and desktop dock showed a bare *. Show the action when there
is no description, hide a lone *, and list metadata.input as key: value
lines cut at 200 chars.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works: rendered with the request from #51519, the dock now shows the tool name (demo_greet) and its arguments, where the base shows only *. Requests that have a description and real resources (e.g. edit) look the same as before. A built-in action without a description (e.g. shell) now shows its name as the hint line.

I could only check the dock component on its own, rendered in a simulated DOM (happy-dom), because Chromium can't launch in my sandbox. The e2e test didn't run there and I couldn't take screenshots. Could you add before/after screenshots of the dock for an MCP call? tsc --noEmit in packages/app is clean.

Public API: unchanged (UI component only).

One concern, inline: the tool's arguments go into the same list as the resources while the * is hidden, which hides what "Allow always" actually covers.

Related: #60350, #50234

<span data-slot="permission-spacer" aria-hidden="true" />
<div data-slot="permission-patterns">
<For each={props.request.resources}>
<For each={[...resources(), ...input()]}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing the input lines into the resource list, and hiding *, changes what the list tells users. For MCP calls, "Allow always" saves save: ["*"], so it approves every future call of this tool with any arguments. But the list now shows only this call's arguments (e.g. issueId: ENG-1), which reads as if "Allow always" covered just this call. Could the input go in its own row (or block), separate from the resources, and could the dock keep showing the scope of "Allow always"? You could also show the * only when there is no input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. With the * hidden, Allow always looked scoped to this call. The input now has its own row. The resources row is back as it was. So the * still shows. The e2e test checks both.

bjck and others added 2 commits October 30, 2026 10:27
Allow always saves the wildcard for MCP tools, so the resources row stays as it was and the call input gets its own row.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The earlier point is fixed: the arguments now have their own row, and the resource list still shows *, so it stays visible that "Allow always" covers every call of the tool. I re-rendered the dock in a simulated DOM (happy-dom): the MCP request shows the tool name, the argument lines, then *, and tools with a description look as before. tsc --noEmit in packages/app is clean.

Public API: unchanged.

No new concerns.

@joby-brentsmith

Copy link
Copy Markdown

Congrats on getting this moving — I'd opened the same fix (#60740, closed as a duplicate of this). Two things from testing the equivalent change in production that this PR doesn't cover yet:

  1. request.message — the permission evaluate hook can attach a server-provided explanation (core/src/permission.ts carries it on the request), and neither the dock nor the TUI renders it. On our deployment that line was what actually told users what was asking. A hint row when request.message is present slots cleanly on top of your dock changes.
  2. The TUI (packages/tui/src/routes/session/permission.tsx) falls back to "Call tool ", so it's less broken than the dock, but it drops request.message too — a one-row add while you're in the area.

On the input rendering: cutting each line at 200 chars truncates longer object args; a max-height + scroll (as in the session-ui patterns row) shows the full arguments without crowding the card. Either way, glad this is moving.

@bjck

bjck commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Congrats on getting this moving — I'd opened the same fix (#60740, closed as a duplicate of this). Two things from testing the equivalent change in production that this PR doesn't cover yet:

  1. request.message — the permission evaluate hook can attach a server-provided explanation (core/src/permission.ts carries it on the request), and neither the dock nor the TUI renders it. On our deployment that line was what actually told users what was asking. A hint row when request.message is present slots cleanly on top of your dock changes.
  2. The TUI (packages/tui/src/routes/session/permission.tsx) falls back to "Call tool ", so it's less broken than the dock, but it drops request.message too — a one-row add while you're in the area.

On the input rendering: cutting each line at 200 chars truncates longer object args; a max-height + scroll (as in the session-ui patterns row) shows the full arguments without crowding the card. Either way, glad this is moving.

Thanks, and sorry yours got closed as the duplicate!
All your points are super useful:

  1. request.message: I've added it. The dock shows it as a hint row under the tool name when the evaluate hook sets one.
  2. Scrolling: It's a very good point, the user is approving these arguments cutting them hides part of what they approve. I added scroll once it is taller than 160px, and the e2e test checks that a long argument stays whole and scrolls inside the dock.
  3. TUI: I think that should live in a seperate PR, since this is related to the desktop app. I'll get to it after this is merged.

@bjck
bjck force-pushed the fix/permission-dock-mcp-tool-name branch from e108d22 to ccba959 Compare October 31, 2026 11:45

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit works, and the * (the scope that "Allow always" saves) is still shown next to the input. I re-rendered the dock in a simulated DOM (happy-dom). An MCP request now shows the tool name, the hook's message, the full arguments in their own row, then *. Tools with a description look as before. tsc --noEmit in packages/app is clean. I couldn't run the e2e test here because Chromium can't launch in my sandbox.

Public API: unchanged (the dock only reads the existing PermissionRequest.message).

No new concerns.

…ion dock

The user approves these arguments, so long ones scroll inside the dock
instead of being cut at 200 chars. A message set by the permission
evaluate hook now shows as a hint row under the tool name.
@bjck
bjck force-pushed the fix/permission-dock-mcp-tool-name branch from ccba959 to dc3fde9 Compare October 31, 2026 11:46

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to add: this push only merges dev. The dock, its CSS and its test are the same as in the version I reviewed last, and tsc --noEmit in packages/app is still clean after the merge.

Public API: unchanged.

No new concerns.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to add: this push only merges dev. The dock, its CSS and its test are the same as in the version I reviewed last, and tsc --noEmit in packages/app is still clean after the merge.

Public API: unchanged.

No new concerns.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to add: this push only merges dev. The PR's dock, CSS and test changes are the same as in the version I reviewed last, and tsc --noEmit in packages/app is still clean after the merges.

Public API: unchanged.

No new concerns.

@usrnk1

usrnk1 commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Pushed a small follow-up so this can land (a6a23fd):

  • Type scale: outside the timeline, v2 uses 13px, not 14px. The dock's title and hint are now 13px with --line-height-base, matching the question dock.
  • Tool name: the fallback now has its own permission-action slot in the code font (13px, 440, -0.04px). Before, it reused permission-hint, which forced the sans font at 14px.
  • Input row: uses --line-height-base, the same as the * resources list below it.
  • Lint: lint:changed flagged the typeof checks in input() and the unexplained i18n key cast. metadata.input is now decoded with Schema, and the cast has a SAFETY: note.

Typecheck is clean, lint:changed reports 0 problems, and session-request-docks.spec.ts passes (6/6).

MCP call, before → after:

Built-in tool (edit), before → after:

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit still works. I re-rendered the dock in a simulated DOM (happy-dom): an MCP request shows the tool name, the hook message, the full arguments, then *, and built-in tools render as before. tsc --noEmit in packages/app is clean. I couldn't run the e2e test here because Chromium can't launch in my sandbox.

Public API: unchanged.

One thing for maintainers: the CSS now makes the permission header title and the description text 13px instead of 14px. That applies to every permission dock, not only MCP calls, so a before/after screenshot of an existing prompt (e.g. edit) would help. Otherwise no new concerns.

@usrnk1
usrnk1 merged commit bd420e0 into anomalyco:dev Nov 4, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v2 web/desktop: MCP permission prompt shows only *, not the tool name

3 participants