Skip to content

Rich tool call rendering: readable arguments and responses, post previews, path-identical metadata - #881

Open
crspeller wants to merge 29 commits into
masterfrom
cursor/rich-tool-call-rendering-f0d9
Open

Rich tool call rendering: readable arguments and responses, post previews, path-identical metadata#881
crspeller wants to merge 29 commits into
masterfrom
cursor/rich-tool-call-rendering-f0d9

Conversation

@crspeller

@crspeller crspeller commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the raw-JSON display of LLM tool calls with readable, safe rendering: a generic labeled field list for arguments and responses, permalink-style post previews for read_post and create_post, and the server plumbing that makes a call render identically live and after reload.

Foundation: path-identical tool metadata

  • llm.ToolCall gains Title (resolved MCP display name); the unused Schema field is removed from the wire (providers build tool definitions from llm.Tool.Schema, and requests from id/name/arguments/resultToolCall.Schema never reached a provider and the webapp ignored it).
  • mcp GetTools resolves the effective display title from MCP metadata (title > annotations.title, whitespace-only treated as absent) and Unicode-sanitizes Title/Description at capture (bidi/invisible-char defense).
  • ContentBlock gains Title/Description; both block writers and PostToBlocks/BlocksToPost carry them; SanitizeForDisplay sanitizes them; FilterForNonRequester keeps them visible (tool identity, like Name). redactToolCalls now keeps Title/Description too — fixing a live↔persisted asymmetry where Description was dropped by omission on the live path. buildContentBlocks also gains the previously missing MCPBareName.
  • A reflection-based parity drift-guard (streaming/tool_call_parity_test.go + conversation/tool_use_writer_parity_test.go) maps every llm.ToolCall JSON field to a persistence + redaction policy and fails (per-field subtests) if any writer or redactor diverges.

Card UX (per design, Mattermost styling)

  • Each tool call renders as a bordered card (border/radius/shadow matching QuestionCard) with a chevron + tool-name header; collapsed cards are slim single-row containers.
  • Arguments and responses share the same generic treatment: a two-column grid (bold labels left, values right) for JSON objects — strings as wrapped text, numbers/booleans/primitive arrays as value pills, nested objects as inline JSON — and clamped plain text for non-JSON responses, with Show more to expand. View raw (hosted on the shared card shell) always exposes the exact arguments payload. Accept renders as the filled primary action, Reject as the tinted secondary.
  • Generic values render as plain text via styled-components — never formatText/markdown — so neither LLM-generated arguments nor tool responses can become a link-spoofing surface (the old code-block pipeline for results is gone). arguments == null (redacted) renders nothing; {} keeps the verbatim "No parameters required". The header toggle is keyboard-accessible (role='button', Enter/Space, aria-expanded), and ellipsized titles expose a hover tooltip.

Renderer registry + post preview cards

  • tool_renderers/registry.tsx routes each call by canonical identity (origin kind + bare name); first match wins; no match → generic card. QuestionCard is routed through it too.
  • While awaiting approval, read_post shows a Mattermost permalink-style preview of the referenced post, and create_post shows a preview of the post-to-be — the message rendered exactly as it will appear once posted, authored by the requesting user (whose session the embedded tool executes with). So the user sees precisely what the tool will read or create before approving. The create_post preview is non-interactive (pointer-events: none): the post doesn't exist yet, so its permalink/avatar affordances can't lead anywhere; the read_post preview stays clickable since its post is real. Both fall back to the generic field list when the arguments don't parse, the post can't be fetched, or the call has already executed.

External MCP tools that declare a title now display it (sanitized, ellipsized); built-in/embedded names are unchanged. External-server render overrides are out of scope.

Before / after

Rendered with the real components at the current head of this branch (before = master's ToolCard). The "after" shows both post previews (pending approval), the executed-call generic fallback, plain-text and JSON response rendering, and collapsed cards. (Screenshots are hosted on the cursor/rich-tool-call-rendering-assets-f0d9 branch — not part of the plugin code.)

before after
Before: raw JSON arguments After: read_post and create_post previews, generic field list and responses, collapsed cards

Testing

  • Go: table-driven tests for enrichment, block writers, redaction, sanitization, and MCP title resolution (incl. hostile-Unicode and whitespace-only titles); the parity drift-guard was verified to fail when Title/Description are dropped from redactToolCalls.
  • Webapp: 313 Jest tests across 32 suites — field-list type dispatch, key-order stability, Show more / View raw, response rendering (JSON field list, plain text with no markdown side effects), registry routing (QuestionCard, both preview cards incl. executed/malformed/fetch-failure/external fallbacks, non-interactive create_post preview vs. interactive read_post preview), and ToolApprovalSet decision logic. ESLint + tsc clean; i18n in sync.
  • e2e: tests/tool-config/mock-api/tool-preview-cards.spec.ts (mocked LLM, shared container) covers both cards — read_post shows the seeded post's preview and create_post shows the post-to-be's message before approval, with View raw exposing the exact payloads; assigned to e2e-shard-1. Existing tool-config specs pass unchanged (display names, button labels, status text, and the expand target are unchanged).

Ticket Link

NONE

Release Note

Tool call approval cards were redesigned: arguments and responses now display as readable field lists on bordered cards, read_post and create_post calls show permalink-style previews of the referenced or to-be-created post, and a "View raw" toggle shows the exact JSON payload. External MCP tools that declare a title now display it.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Tool cards now surface MCP-provided tool title and description consistently across live updates and reloaded conversations, including bare-name identity.
    • Added rich, structured cards for supported embedded tools, with improved “View raw” and formatted argument/result rendering.
  • Bug Fixes
    • Hardened display sanitization for hostile bidirectional characters in tool titles, descriptions, and annotation titles.
    • Improved privacy redaction so non-requester views match between live and persisted tool-call rendering.
  • Tests
    • Added parity and round-trip coverage for tool identity fields and redaction behaviour.

cursoragent and others added 3 commits July 13, 2026 09:55
…Call.Schema

Capture MCP title/annotations (sanitized) in mcp GetTools, resolve effective
display title (title > annotations.title). Add Title/Description to ToolCall,
ContentBlock, and both block writers; keep them visible to non-requesters on
both the live (redactToolCalls) and persisted (FilterForNonRequester) paths so
a call renders identically live and after reload. Remove the unused
ToolCall.Schema from the wire (providers build tool defs from Tool.Schema).

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…l_identity

Map server_origin, mcp_bare_name, title, description on persisted rounds
(toolUseBlockToToolCall). Add tool_identity helpers (originKind,
canonicalToolKey, toolDisplayName) and use toolDisplayName in ToolCard,
preferring the MCP-supplied title and ellipsizing long titles; built-in and
embedded display names are unchanged.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…dentity

Add streaming/tool_call_parity_test.go: a per-field policy table over
llm.ToolCall enforced by reflection (exhaustiveness), plus writer-completeness
and live-vs-persisted redaction-parity tests (verified to catch the historical
Description drop-by-omission). Add mcp GetTools title/annotation mapping +
Unicode-sanitization test; conversation SanitizeForDisplay + FilterForNonRequester
title/description coverage; ContentBlock title/description round-trip; TS
tool_identity.test.ts and turn_content_utils new-field mappings.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 LLM Evaluation Results

OpenAI

⚠️ Overall: 21/28 tests passed (75.0%)

Provider Total Passed Failed Pass Rate
⚠️ OPENAI 28 21 7 75.0%

❌ Failed Evaluations

Show 7 failures

OPENAI

1. TestReactEval/[openai]_react_cat_message

  • Score: 0.00
  • Rubric: The word/emoji is a cat emoji or a heart/love emoji
  • Reason: The output is the text string "heart_eyes_cat", not an actual cat emoji (e.g., 😺/🐱) or a heart/love emoji (e.g., ❤️/😍).

2. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: is a list of bugs
  • Reason: The output does not provide a list of bugs; it states inability to access the bug tracker and asks the user to paste bug titles/links, offering a template instead.

3. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: includes a description of each bug
  • Reason: The output asks the user to paste bug titles/links and provides a template, but it does not actually include descriptions of any specific bugs.

4. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes each bug to a user
  • Reason: The output requests that the user provide bug titles/links and includes a template with a “Reported by” field, but it does not actually attribute any specific bugs to any users.

5. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes the bug about trying to save without a color and the save button not doing anything to @maria.nunez
  • Reason: The output does not mention the specific bug about trying to save without a color, does not mention the save button not doing anything, and does not attribute anything to @maria.nunez. It only asks the user to paste bug details.

6. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: the bug about the end user being able to change channel banner is attributed to @maria.nunez
  • Reason: The output does not mention the specific bug about an end user being able to change the channel banner, nor does it attribute that bug to @maria.nunez. It only asks the user to paste bug details and provides a template.

7. TestDirectMessageConversations/[openai]_bot_dm_tool_introspection

  • Score: 0.00
  • Rubric: mentions Github and refers to the documentation
  • Reason: The output refers to documentation (docs.mattermost.com) but does not mention GitHub anywhere. Since the rubric requires both mentioning GitHub and referring to the documentation, it fails.

Anthropic

⚠️ Overall: 21/28 tests passed (75.0%)

Provider Total Passed Failed Pass Rate
⚠️ ANTHROPIC 28 21 7 75.0%

❌ Failed Evaluations

Show 7 failures

ANTHROPIC

1. TestReactEval/[anthropic]_react_cat_message

  • Score: 0.00
  • Rubric: The word/emoji is a cat emoji or a heart/love emoji
  • Reason: The output is the text "heart_eyes_cat", not an actual cat emoji (e.g., 😺) or a heart/love emoji (e.g., ❤️).

2. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: is a list of bugs
  • Reason: The output does not provide a list of bugs. It states it cannot access bug trackers and suggests ways to obtain the list, asking the user to paste bug details.

3. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: includes a description of each bug
  • Reason: The output does not include descriptions of any specific bugs; it only states inability to access trackers and suggests where to look. Therefore it does not include a description of each bug.

4. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes each bug to a user
  • Reason: The output does not list any bugs and does not attribute any bug to a user; it instead states it cannot access bug trackers and suggests ways to find bugs.

5. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes the bug about trying to save without a color and the save button not doing anything to @maria.nunez
  • Reason: The output states it cannot access bug trackers and suggests ways to find bugs, but it does not mention the specific bug about saving without a color or attribute it to @maria.nunez.

6. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: the bug about the end user being able to change channel banner is attributed to @maria.nunez
  • Reason: The output does not mention the specific bug about an end user being able to change the channel banner, and it does not attribute any such bug to @maria.nunez.

7. TestDirectMessageConversations/[anthropic]_bot_dm_tool_introspection

  • Score: 0.50
  • Rubric: mentions Github and refers to the documentation
  • Reason: The output refers to documentation (docs.mattermost.com) but does not mention GitHub anywhere, so it fails the rubric requirement to mention GitHub and refer to the documentation.

This comment was automatically generated by the eval CI pipeline.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tool-call metadata now includes resolved titles, descriptions, annotations, and bare names across MCP resolution, persistence, redaction parity enforcement, and frontend rendering. The webapp adds structured argument views and rich cards for supported embedded tools.

Changes

Tool identity metadata and rich rendering

Layer / File(s) Summary
Metadata contracts and MCP resolution
conversation/content_block.go, llm/tools.go, conversation/content_block_test.go, llm/tools_test.go, mcp/user_clients.go, mcp/user_clients_test.go
Tool and ContentBlock contracts add Title and Description fields; ToolAnnotations type captures MCP hints; user clients sanitize unicode and apply title precedence from annotations; enrichment populates resolved titles.
Tool metadata persistence and conversion
conversation/convert.go, conversation/convert_test.go, conversation/helpers.go, conversation/helpers_test.go
Tool metadata is propagated between ContentBlock and ToolCall through bi-directional conversion and included in assistant-side tool-use blocks.
Streaming, redaction, and parity
streaming/streaming.go, streaming/tool_call_parity_test.go, conversation/tool_use_writer_parity_test.go, toolrunner/toolrunner.go, toolrunner/toolrunner_extended_test.go
Live tool calls are redacted (identity fields kept, payloads cleared) and persisted blocks are filtered in lockstep; comprehensive parity tests enforce field-by-field consistency across both redaction paths and persistence.
Frontend types and identity mapping
webapp/src/components/tool_types.ts, webapp/src/types/conversation.ts, webapp/src/utils/tool_identity.ts, webapp/src/utils/tool_identity.test.ts, webapp/src/components/llmbot_post/turn_content_utils.ts, webapp/src/components/llmbot_post/turn_content_utils.test.ts
Frontend tool types carry title and mcp_bare_name; identity helpers classify origins, derive stable bare names, compose canonical keys, and provide display-name selection with title precedence.
Structured argument rendering
webapp/src/components/tool_arguments.tsx, webapp/src/components/tool_arguments.test.tsx, webapp/src/client.tsx
Tool arguments render as labelled field lists with type-specific formatting, optional clamping, and raw JSON fallback; helper function enables batch user-profile lookup for entity resolution.
Rich card rendering ecosystem
webapp/src/components/tool_renderers/registry.tsx, webapp/src/components/tool_renderers/registry.test.tsx, webapp/src/components/tool_renderers/rich_card_parsers.ts, webapp/src/components/tool_renderers/rich_cards.parsers.test.ts, webapp/src/components/tool_renderers/rich_cards.tsx, webapp/src/components/tool_renderers/rich_card_parts.tsx, webapp/src/components/tool_renderers/entity_chips.tsx, webapp/src/components/tool_renderers/entity_chips.test.tsx, webapp/src/components/tool_renderers/tool_card_shell.tsx
A renderer registry routes tool calls to specialized rich-card components or generic fallback; strict per-tool argument parsers validate payloads; entity chips resolve channels and users; a shared tool-card shell handles approval chrome, status display, result rendering, and raw-view toggles; rich cards render channel/user/message/search tool displays.
Tool approval integration and simplification
webapp/src/components/tool_approval_set.tsx, webapp/src/components/tool_approval_set.test.tsx, webapp/src/components/tool_card.tsx, webapp/src/components/tool_card.test.tsx
Tool approval set delegates rendering to the rich-card registry; ToolCard is refactored to compose ToolCardShell and ToolArguments, removing ~600 lines of inline logic.
Internationalization and end-to-end coverage
webapp/src/i18n/en.json, e2e/scripts/ci-test-groups.mjs, e2e/tests/tool-config/mock-api/rich-create-post-card.spec.ts
Tool-card UI labels and rich-card field descriptions are added; E2E test validates rich create-post card rendering and raw-payload exposure; CI shard configuration is updated.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPServer
  participant UserClients
  participant llmTools
  participant Conversation
  participant Streaming
  participant Webapp
  MCPServer->>UserClients: tool title, description, annotations
  UserClients->>llmTools: sanitized, precedence-resolved metadata
  llmTools->>Conversation: persist title, description
  llmTools->>Streaming: redacted title, description (payload cleared)
  Conversation->>Webapp: rehydrate filtered title, description
  Streaming->>Webapp: stream redacted tool call (title, description intact)
  Webapp->>Webapp: identity mapping: origin, bare name, canonical key
  Webapp->>Webapp: route to rich card or generic fallback
  Webapp->>Webapp: render structured arguments + approval chrome
Loading

Suggested labels: Setup Cloud Test Server

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: richer tool-call rendering plus path-identical metadata handling.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/rich-tool-call-rendering-f0d9

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
conversation/content_block.go (1)

151-184: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Sanitize MCPBareName before display
MCPBareName comes from the server-supplied tool name, and the webapp prettifies it when Title is empty. It should go through the same bidi/spoofing sanitization here as Input, Title, and Description.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conversation/content_block.go` around lines 151 - 184, Update
SanitizeForDisplay to sanitize the MCPBareName field for BlockTypeToolUse blocks
before returning the copied slice, using the same llm.SanitizeNonPrintableChars
helper as Input, Title, and Description. Preserve the existing nil handling and
non-mutating behavior.
conversation/convert.go (1)

83-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve persisted tool titles during rehydration. enrichToolCallFromStore currently overwrites toolCall.Title whenever OverwriteDescription is set, and conversation/convert.go always takes that path for non-redacted tool uses. That can replace a title captured at call time with a newer catalog value if the MCP server changes later. Leave Title untouched when already set, or add a separate title-overwrite flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conversation/convert.go` around lines 83 - 97, Update enrichToolCallFromStore
and its invocation in the toolCall rehydration path so an existing
toolCall.Title is preserved, even when OverwriteDescription is enabled. Only
populate the title from the current tool catalog when the persisted title is
empty; keep description enrichment and redacted-tool behavior unchanged.
🧹 Nitpick comments (3)
llm/tools.go (1)

67-80: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Document the MCP spec's pessimistic default for destructiveHint.

DestructiveHint *bool is documented as "nil pointer means the server did not declare the hint," which is correct for capture, but the MCP spec's stated default when destructiveHint is omitted is true (destructive), not false. Since Annotations isn't consumed yet, this isn't a live bug, but a future consumer naively doing hint.DestructiveHint != nil && *hint.DestructiveHint would silently treat "undeclared" as "safe," inverting the spec's pessimistic default. Worth a one-line comment now so this doesn't get missed when annotations are actually surfaced.

📝 Suggested doc addition
 	// DestructiveHint indicates the tool may perform destructive updates.
-	// A nil pointer means the server did not declare the hint.
+	// A nil pointer means the server did not declare the hint. Per the MCP
+	// spec, an undeclared destructiveHint defaults to true (assume
+	// destructive) — future consumers must not treat nil as "safe."
 	DestructiveHint *bool
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@llm/tools.go` around lines 67 - 80, Update the DestructiveHint field comment
in ToolAnnotations to state that nil means the server omitted the hint and the
MCP specification treats omitted destructiveHint as true (destructive), while
preserving the existing capture semantics.
mcp/user_clients_test.go (1)

137-203: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider table-driven form for this multi-case test.

This test exercises three distinct tool configurations (top-level title + annotations, annotations-only fallback, and no title/annotations at all) inside one monolithic function rather than as table-driven subtests.

As per coding guidelines, **/*_test.go: "Go tests must be table-driven when they contain more than one case."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/user_clients_test.go` around lines 137 - 203, Refactor
TestUserClientsGetToolsMapsAndSanitizesTitleAndAnnotations into table-driven
subtests, with separate cases for top-level title and annotations,
annotations-only fallback, and missing title/annotations. Define each case’s
input and expected values in the table, run them via t.Run, and preserve all
existing assertions for sanitization, annotations, fallback behavior, and empty
title handling.

Source: Coding guidelines

webapp/src/components/tool_card.tsx (1)

59-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Truncated titles have no way to reveal the full text.

Long MCP titles are ellipsized but there's no tooltip/title attribute to let users see the full name on hover.

💡 Suggested fix
-                <ToolName>{displayName}</ToolName>
+                <ToolName title={displayName}>{displayName}</ToolName>

Also applies to: 560-560

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/components/tool_card.tsx` around lines 59 - 71, Update the
ToolName styled span usage to expose the complete tool title through a hover
tooltip, such as a title attribute, while preserving the existing single-line
ellipsis styling. Apply the same change to the corresponding title rendering at
the other affected location.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@conversation/content_block.go`:
- Around line 151-184: Update SanitizeForDisplay to sanitize the MCPBareName
field for BlockTypeToolUse blocks before returning the copied slice, using the
same llm.SanitizeNonPrintableChars helper as Input, Title, and Description.
Preserve the existing nil handling and non-mutating behavior.

In `@conversation/convert.go`:
- Around line 83-97: Update enrichToolCallFromStore and its invocation in the
toolCall rehydration path so an existing toolCall.Title is preserved, even when
OverwriteDescription is enabled. Only populate the title from the current tool
catalog when the persisted title is empty; keep description enrichment and
redacted-tool behavior unchanged.

---

Nitpick comments:
In `@llm/tools.go`:
- Around line 67-80: Update the DestructiveHint field comment in ToolAnnotations
to state that nil means the server omitted the hint and the MCP specification
treats omitted destructiveHint as true (destructive), while preserving the
existing capture semantics.

In `@mcp/user_clients_test.go`:
- Around line 137-203: Refactor
TestUserClientsGetToolsMapsAndSanitizesTitleAndAnnotations into table-driven
subtests, with separate cases for top-level title and annotations,
annotations-only fallback, and missing title/annotations. Define each case’s
input and expected values in the table, run them via t.Run, and preserve all
existing assertions for sanitization, annotations, fallback behavior, and empty
title handling.

In `@webapp/src/components/tool_card.tsx`:
- Around line 59-71: Update the ToolName styled span usage to expose the
complete tool title through a hover tooltip, such as a title attribute, while
preserving the existing single-line ellipsis styling. Apply the same change to
the corresponding title rendering at the other affected location.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: c278c1e7-f3f9-4018-9d10-c44541a15483

📥 Commits

Reviewing files that changed from the base of the PR and between 8add6af and 6e1ecd3.

⛔ Files ignored due to path filters (1)
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • conversation/content_block.go
  • conversation/content_block_test.go
  • conversation/convert.go
  • conversation/convert_test.go
  • conversation/helpers.go
  • conversation/helpers_test.go
  • llm/tools.go
  • llm/tools_test.go
  • mcp/user_clients.go
  • mcp/user_clients_test.go
  • streaming/streaming.go
  • streaming/tool_call_parity_test.go
  • toolrunner/toolrunner.go
  • toolrunner/toolrunner_extended_test.go
  • webapp/src/components/llmbot_post/turn_content_utils.test.ts
  • webapp/src/components/llmbot_post/turn_content_utils.ts
  • webapp/src/components/tool_card.tsx
  • webapp/src/components/tool_types.ts
  • webapp/src/types/conversation.ts
  • webapp/src/utils/tool_identity.test.ts
  • webapp/src/utils/tool_identity.ts

cursoragent and others added 14 commits July 13, 2026 10:41
Split the undefined-origin case out of the test.each table into its own test
so ESLint's no-undefined rule passes.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Replace the pretty-printed JSON blob in ToolCard with a labeled, readable
field list driven purely by the arguments object (insertion key order, so the
layout is identical on the live and persisted paths). Values render by JS type:
strings as plain wrapped text, numbers/booleans/primitive-arrays as compact
value pills, nested objects/arrays as a small inline JSON block. All values
render as plain text via styled-components, never through formatText/markdown.

A single card-level Show more expands all clamped long values, and a required
View raw toggle reveals the exact pretty-printed JSON payload so the approval
surface always lets the user inspect what they are approving. Empty-object args
keep the verbatim No parameters required message; null/redacted args render
nothing. Value pills never carry an icon/avatar (reserved for Phase 3 entity
chips). Header, status, buttons, result rendering, and the result-review
callout are untouched.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Extract the shared approval chrome (header, collapse, decision buttons, result
section, result-review callout) into tool_renderers/tool_card_shell.tsx, and
move the View raw affordance onto the shell so rich cards inherit it. ToolCard
becomes a thin shell + ToolArguments field-list body. ToolArguments keeps the
field list + Show more; its raw view is exposed via ToolArgumentsRaw.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Add tool_renderers/registry.tsx routing each tool call to a rich card or
QuestionCard (migrated as the proving entry), falling back to the generic
ToolCard; matching is on canonical identity (origin kind + bare name) plus a
strict parse. Add entity_chips.tsx (ChannelChip/UserChip: resolve via store or
API, icon/avatar on success, plain-text raw id/username on failure). Add rich
cards for create_post, dm, group_message, search_posts, search_users, read_post,
get_channel_info with strict parse-or-fallback. tool_approval_set routes through
the registry.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…ritance

Extract pure parsers into rich_card_parsers.ts (testable without React deps).
Add rich_card_parsers.parsers.test (valid/malformed/redacted -> null), entity
chip tests (store + API resolution, failure degradation to plain text), and
registry routing tests (QuestionCard, embedded rich cards, external/unknown/
malformed fall back to generic; rich cards expose View raw with the exact
payload). Migrate tool_approval_set.test to mock the registry.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Add tests/tool-config/mock-api/rich-create-post-card.spec.ts asserting the rich
create_post card renders the resolved channel and message body and that View raw
exposes the exact payload; assign it to the lightest e2e shard (shard-1).
Mirrors the existing mock-api tool-config specs.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…ange)

The lockfile was inadvertently rewritten by a local npm install (npm dropped
"peer" markers); no webapp dependency actually changed. Restore master's
lockfile so the CI lockfile-drift check passes.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…ange)

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…ange)

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Completes the drift-guard's writer-completeness check for the conversation-side
auto-run writer (toolUseBlocks is unexported, so streaming/tool_call_parity_test
cannot reach it): assert toolUseBlocks and PostToBlocks emit every persisted
tool_use identity/metadata field for a fully-populated call.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…tool-call-rendering-phase-2-f0d9

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…or/rich-tool-call-rendering-phase-3-f0d9

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
conversation/tool_use_writer_parity_test.go (1)

60-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Loop over require assertions hides all-but-the-first drifted field.

Both tests loop over persistedToolUseFields using require.Containsf/require.NotEmptyf. Since require calls FailNow on the first failure, a regression that drops/empties multiple fields will only ever report the first one per test run, weakening this drift-guard's diagnostic value across CI reruns. Isolating each field in a t.Run subtest (optionally with assert inside) would let every dropped field surface in a single run and better matches the table-driven pattern for tests with more than one case.

♻️ Suggested refactor (illustrative)
 	m := toolUseBlockJSONMap(t, blocks[0])
 	for _, field := range persistedToolUseFields {
-		require.Containsf(t, m, field, "toolUseBlocks dropped persisted tool_use field %q", field)
-		require.NotEmptyf(t, m[field], "toolUseBlocks emitted an empty persisted tool_use field %q", field)
+		field := field
+		t.Run(field, func(t *testing.T) {
+			assert.Containsf(t, m, field, "toolUseBlocks dropped persisted tool_use field %q", field)
+			assert.NotEmptyf(t, m[field], "toolUseBlocks emitted an empty persisted tool_use field %q", field)
+		})
 	}

As per coding guidelines, "Go tests must be table-driven when they contain more than one case."

Also applies to: 76-90

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conversation/tool_use_writer_parity_test.go` around lines 60 - 70, Refactor
the loops in TestToolUseBlocksPersistsPolicyFields and the corresponding test
around the shared persistedToolUseFields assertions into table-driven t.Run
subtests, using each field as the subtest name. Keep the existing field-presence
and non-empty validations, but isolate failures per field so all drifted fields
are reported in one run.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@conversation/tool_use_writer_parity_test.go`:
- Around line 60-70: Refactor the loops in TestToolUseBlocksPersistsPolicyFields
and the corresponding test around the shared persistedToolUseFields assertions
into table-driven t.Run subtests, using each field as the subtest name. Keep the
existing field-presence and non-empty validations, but isolate failures per
field so all drifted fields are reported in one run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7c0065c6-c749-4dff-825c-0b28e0c7c723

📥 Commits

Reviewing files that changed from the base of the PR and between 0b74618 and 765acff.

📒 Files selected for processing (1)
  • conversation/tool_use_writer_parity_test.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 765acff55a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mcp/user_clients.go Outdated
Comment on lines +269 to +270
if llmTool.Title == "" {
llmTool.Title = llmTool.Annotations.Title

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat whitespace-only MCP titles as absent

When an MCP tool declares a top-level or annotation title containing only spaces or allowed whitespace such as tabs, this equality check treats it as a valid effective title. toolDisplayName then returns that truthy title verbatim, so the tool card renders with a blank name instead of falling back to the prettified bare tool name. Trim titles, or test strings.TrimSpace when resolving the effective title.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
webapp/src/components/tool_arguments.tsx (1)

228-297: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse ToolArgumentsRaw instead of re-implementing the raw-JSON block.

The top-level array/primitive fallback (lines 291-297) duplicates the exact markup already defined in ToolArgumentsRaw (lines 231-240). Delegate to the component to keep the raw view in one place.

♻️ Proposed dedup
-    if (entries.length === 0) {
-        return (
-            <Container>
-                <RawJson>{JSON.stringify(args, null, 2)}</RawJson>
-            </Container>
-        );
-    }
+    if (entries.length === 0) {
+        return <ToolArgumentsRaw arguments={args}/>;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/components/tool_arguments.tsx` around lines 228 - 297, Update the
top-level array/primitive fallback in ToolArguments to return ToolArgumentsRaw
with the existing args value instead of duplicating the Container and RawJson
markup. Keep the current entries.length === 0 condition and preserve the
existing raw JSON rendering behavior.
webapp/src/components/tool_renderers/tool_card_shell.tsx (1)

399-401: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid any for the plugin-state selector.

(state: any) => state['plugins-' + manifest.id]?.allowUnsafeLinks ?? false loses type safety. Define a minimal local interface for the plugin state slice instead of casting to any.

As per coding guidelines, "keep TypeScript strictly typed" for webapp .ts/.tsx files.

♻️ Proposed fix
-    const allowUnsafeLinks = useSelector<GlobalState, boolean>((state: any) => state['plugins-' + manifest.id]?.allowUnsafeLinks ?? false);
+    interface PluginState {
+        allowUnsafeLinks?: boolean;
+    }
+    const allowUnsafeLinks = useSelector<GlobalState & Record<string, PluginState | undefined>, boolean>(
+        (state) => state['plugins-' + manifest.id]?.allowUnsafeLinks ?? false,
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/components/tool_renderers/tool_card_shell.tsx` around lines 399 -
401, Replace the any-typed selector callback for allowUnsafeLinks with a minimal
typed interface describing the plugin state slice, and use that interface in the
selector while preserving the dynamic manifest.id lookup and false fallback.
Update the selector near allowUnsafeLinks; leave the siteURL and team selectors
unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webapp/src/components/tool_renderers/rich_card_parsers.ts`:
- Around line 99-106: Replace hardcoded English labels with i18n-backed values
across the affected renderers: in
webapp/src/components/tool_renderers/rich_card_parsers.ts lines 99-106, store
stable message keys in searchFilterKeys and localize them where LabeledPill is
rendered; in lines 137-151, apply the same approach to parseSearchUsers’ “Limit”
label; in webapp/src/components/tool_renderers/rich_cards.tsx lines 245-249 and
288-297, localize ReadPostCard’s “ID” and GetChannelInfoCard’s “Name” and “Team”
labels using the surrounding formatMessage/FormattedMessage pattern.

In `@webapp/src/components/tool_renderers/tool_card_shell.tsx`:
- Around line 550-558: Make the canExpand ToolCallHeader keyboard-accessible by
adding an appropriate interactive role, tabIndex, and keyboard handler that
invokes onToggleCollapse for Enter and Space while preserving the existing click
behavior. Update the ToolCallHeader usage in the tool card shell so
non-expandable cards remain non-interactive.

---

Nitpick comments:
In `@webapp/src/components/tool_arguments.tsx`:
- Around line 228-297: Update the top-level array/primitive fallback in
ToolArguments to return ToolArgumentsRaw with the existing args value instead of
duplicating the Container and RawJson markup. Keep the current entries.length
=== 0 condition and preserve the existing raw JSON rendering behavior.

In `@webapp/src/components/tool_renderers/tool_card_shell.tsx`:
- Around line 399-401: Replace the any-typed selector callback for
allowUnsafeLinks with a minimal typed interface describing the plugin state
slice, and use that interface in the selector while preserving the dynamic
manifest.id lookup and false fallback. Update the selector near
allowUnsafeLinks; leave the siteURL and team selectors unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 9b3a082d-caa5-44ea-a49d-371bb09e43fc

📥 Commits

Reviewing files that changed from the base of the PR and between 765acff and 6279537.

📒 Files selected for processing (19)
  • e2e/scripts/ci-test-groups.mjs
  • e2e/tests/tool-config/mock-api/rich-create-post-card.spec.ts
  • webapp/src/client.tsx
  • webapp/src/components/tool_approval_set.test.tsx
  • webapp/src/components/tool_approval_set.tsx
  • webapp/src/components/tool_arguments.test.tsx
  • webapp/src/components/tool_arguments.tsx
  • webapp/src/components/tool_card.test.tsx
  • webapp/src/components/tool_card.tsx
  • webapp/src/components/tool_renderers/entity_chips.test.tsx
  • webapp/src/components/tool_renderers/entity_chips.tsx
  • webapp/src/components/tool_renderers/registry.test.tsx
  • webapp/src/components/tool_renderers/registry.tsx
  • webapp/src/components/tool_renderers/rich_card_parsers.ts
  • webapp/src/components/tool_renderers/rich_card_parts.tsx
  • webapp/src/components/tool_renderers/rich_cards.parsers.test.ts
  • webapp/src/components/tool_renderers/rich_cards.tsx
  • webapp/src/components/tool_renderers/tool_card_shell.tsx
  • webapp/src/i18n/en.json

Comment thread webapp/src/components/tool_renderers/rich_card_parsers.ts Outdated
Comment thread webapp/src/components/tool_renderers/tool_card_shell.tsx
Comments now target the future reader concisely; no development-phase
narrative.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webapp/src/components/tool_renderers/entity_chips.tsx`:
- Around line 76-77: Update the module-level entity caches used by the renderer
to avoid retaining null failure entries indefinitely: add a bounded retry/TTL
for failed fetches, or invalidate those entries when authentication or server
context changes. Preserve successful entity caching while allowing transient
failures and permission changes to be retried.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: c09ca2d8-b45f-46f2-83df-fec2d77c662e

📥 Commits

Reviewing files that changed from the base of the PR and between 6279537 and 62ca6c1.

📒 Files selected for processing (21)
  • conversation/content_block.go
  • conversation/content_block_test.go
  • conversation/tool_use_writer_parity_test.go
  • e2e/tests/tool-config/mock-api/rich-create-post-card.spec.ts
  • llm/tools.go
  • mcp/user_clients.go
  • streaming/streaming.go
  • streaming/tool_call_parity_test.go
  • webapp/src/components/llmbot_post/turn_content_utils.ts
  • webapp/src/components/tool_approval_set.test.tsx
  • webapp/src/components/tool_approval_set.tsx
  • webapp/src/components/tool_arguments.tsx
  • webapp/src/components/tool_renderers/entity_chips.tsx
  • webapp/src/components/tool_renderers/registry.tsx
  • webapp/src/components/tool_renderers/rich_card_parsers.ts
  • webapp/src/components/tool_renderers/rich_card_parts.tsx
  • webapp/src/components/tool_renderers/rich_cards.tsx
  • webapp/src/components/tool_renderers/tool_card_shell.tsx
  • webapp/src/components/tool_types.ts
  • webapp/src/types/conversation.ts
  • webapp/src/utils/tool_identity.ts
💤 Files with no reviewable changes (1)
  • webapp/src/components/llmbot_post/turn_content_utils.ts
🚧 Files skipped from review as they are similar to previous changes (17)
  • webapp/src/components/tool_approval_set.tsx
  • mcp/user_clients.go
  • conversation/tool_use_writer_parity_test.go
  • webapp/src/utils/tool_identity.ts
  • webapp/src/types/conversation.ts
  • conversation/content_block.go
  • webapp/src/components/tool_types.ts
  • llm/tools.go
  • webapp/src/components/tool_renderers/rich_card_parts.tsx
  • webapp/src/components/tool_arguments.tsx
  • streaming/streaming.go
  • webapp/src/components/tool_renderers/rich_card_parsers.ts
  • webapp/src/components/tool_renderers/rich_cards.tsx
  • e2e/tests/tool-config/mock-api/rich-create-post-card.spec.ts
  • webapp/src/components/tool_renderers/tool_card_shell.tsx
  • conversation/content_block_test.go
  • streaming/tool_call_parity_test.go

Comment thread webapp/src/components/tool_renderers/entity_chips.tsx Outdated
Registry matchers now match on identity only — the cards already fall back to
the generic ToolCard when their strict parse fails, so arguments were parsed
twice per render. Replace the field-by-field toRichProps copy with rest
destructuring. Remove unused canonicalToolKey, the EmbeddedServerOrigin export,
and the speculative channel-icon branch. Use the UserInteractionSelect constant
instead of a string literal. Rename the parser test to match its source file.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
@cursor cursor Bot changed the title Rich tool call rendering (1/3): path-identical tool metadata foundation Rich tool call rendering: readable arguments, per-tool cards, path-identical metadata Jul 20, 2026
- Treat whitespace-only MCP titles as absent so cards never render a blank
  header (Codex); covered in the GetTools test, now table-driven (CodeRabbit).
- Localize the remaining hardcoded pill labels (filter names, ID, Name, Team):
  parsers emit stable keys, labels render via FormattedMessage.
- Make the card header toggle keyboard-accessible (role/tabIndex/Enter/Space,
  aria-expanded) and expose ellipsized titles via a hover tooltip.
- Expire cached entity-chip failures after 30s so transient fetch errors
  recover without a reload; note that omitted destructiveHint means
  destructive per the MCP spec.
- Parity drift-guards report every drifted field via per-field subtests
  instead of stopping at the first failure.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
cursoragent and others added 9 commits July 21, 2026 17:25
Bordered rounded card container with a larger header: chevron, tool name, and
a muted plain-text context (target channel, recipients, or query) supplied by
the rich cards — the name element keeps its exact text. Arguments render as a
two-column grid (bold labels left, values right) in both the generic field
list and the rich cards. Accept is now the filled primary action. All existing
behavior is unchanged: entity chips, value pills, Show more, View raw, result
section, review callout, statuses, and redaction.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
The redesigned header shows the channel context and the chip shows
'name · team'; assert each specifically instead of a substring that now
matches both.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Channel chips use the globe/lock icons like the channel UI (no '#', which is
not a Mattermost convention); the card container matches QuestionCard's border
radius and shadow.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Tool results now use the same generic treatment as arguments: JSON-object
results render as the labeled field list, everything else as clamped plain
text — replacing the markdown code-block pipeline (and its formatText
dependency) in the card shell.

Replace the seven thin per-tool cards with one that adds real value: read_post
renders a permalink-style preview of the referenced post (via PostPreview /
PostMessagePreview), falling back to the generic card when the arguments don't
parse or the post can't be fetched. The registry keeps QuestionCard routing.

Prune code that had no consumer: MCP annotation safety-hint capture, the
header-context slot, entity chips, filter-label i18n strings, and
getProfilesByUsernames. Swap the create_post e2e spec for a read_post preview
spec.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…onse

The read_post card now shows a permalink-style preview, so the referenced
post's text legitimately appears twice on the expanded card. The image-count
and image-request assertions are unchanged and now also cover the preview.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
The permalink-style preview renders the post with Mattermost's normal
markdown, which is redundant once the call has executed (the response shows
the content) and would sit next to the deliberately-unrendered result text.
Executed calls render the generic card, restoring the original guarantees of
the unsafe-post result spec, which is reverted to its strict assertions.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
While a create_post call awaits approval, render its message as a
permalink-style post preview (authored by the requesting user, whose session
the embedded tool executes with) — the same rendering the message gets once
posted — so the user sees exactly what they are approving. Executed or
malformed calls render the generic card. Extend the preview e2e spec to cover
both cards with a shared container.

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
The previewed post does not exist yet, so its permalink/avatar/username
affordances cannot lead anywhere; suppress pointer events on the preview.
The read_post preview stays interactive (its post is real).

Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
@cursor cursor Bot changed the title Rich tool call rendering: readable arguments, per-tool cards, path-identical metadata Rich tool call rendering: readable arguments and responses, post previews, path-identical metadata Jul 22, 2026
@crspeller
crspeller requested a review from asaadmahmood July 23, 2026 12:49
@crspeller
crspeller requested a review from nickmisasi July 23, 2026 12:49
@mm-cloud-bot

Copy link
Copy Markdown

Test server destroyed

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.

3 participants