Skip to content

feat(conversations): render MCP Apps widgets inline in chat (#7202) - #7205

Open
oxoxDev wants to merge 43 commits into
tinyhumansai:mainfrom
oxoxDev:feat/7202-mcp-apps-in-chat
Open

oxoxDev wants to merge 43 commits into
tinyhumansai:mainfrom
oxoxDev:feat/7202-mcp-apps-in-chat

Conversation

@oxoxDev

@oxoxDev oxoxDev commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Tool-provided UI (MCP Apps ui:// widgets and the OpenAI Apps SDK openai/outputTemplate convention) now renders inline in the chat on desktop.
  • Core resolves each tool's declared UI, reads widget documents, proxies widget tool calls, advertises the UI capability and gives the agent a show_ui tool plus prompt guidance.
  • The app shell serves a sandbox proxy for widget documents; the frontend hosts the MCP Apps bridge and places widgets in the assistant turn.
  • With no widget, tool-result links fall back to a link action with a phone QR; mobile gets this fallback only.

Problem

  • MCP servers such as Swiggy ship ui:// widgets. The chat dropped them and showed only the text of the result, so flows that need a widget (payment method, payment status, checkout) could not be completed.

Solution

  • core: the mcp/ui glue over tinymcp::ui; RPCs mcp_ui_resource_read and mcp_ui_tool_call; the UI capability is advertised to servers; a show_ui tool; prompt guidance on where tool UI appears and to repeat payment links.
  • app shell: the ohwidget:// sandbox proxy scheme with a two-level iframe. The outer frame has its own origin; the inner widget has an opaque origin and only allow-scripts and allow-forms. A frame-src CSP entry admits the proxy. The proxy follows the host theme with a transparent canvas.
  • frontend: the MCP Apps bridge (ui/initialize, tools/call with a per-view remembered Allow/Deny, open-link, message prefill, size-changed) and the Apps SDK shim. Widgets are shown after the activity group and above the answer, de-duplicated per template, and updated with newer data without remounting. Host scroll arrows stand down where the widget has its own navigation. Frames have a transparent canvas and fit their content width. Links and a QR are shown only when there is no widget and never for image URLs; app-scheme links (upi, phonepe) are shown as a QR.
  • Security: the widget runs in an opaque-origin sandbox with scripts and forms only, so it has no access to host storage, cookies or DOM. It reaches only what the host bridge exposes and the CSP the proxy declares for the origins the server listed. Tool calls from a widget go through the host; anything that is not read-only asks for confirmation, and the answer is remembered only for that view.
  • Pins: vendor/tinymcp stays at the pin on main (v0.9.0), which already includes feat: MCP Apps host support — tool _meta, resources, rich results and tinymcp::ui (#50) tinymcp#51 (released in v0.7.0); this PR changes no gitlink. crates/openhuman-app depends on tinymcp (ui feature) directly for the widget origin rules. Rebased onto current main, with three follow-on commits: fix(mcp): follow tinymcp's remote tool display trait (display_description moved to McpRemoteToolExt), fix(mcp): log configured server UI from the tools the module lists (configured servers now list through the module, so the UI log reads the listed tools' _meta), chore(deps): lock the regex dependency tinymcp's ui feature adds, and refactor(mcp): advertise the UI capability without gating host.rs on the mcp feature (keeps the gated-test allowlist unchanged).
  • Known gaps: widgets do not survive a cold reload (the structured payload is not persisted; that needs tinyagents), mobile gets the fallback only, and a structuredContent over 64 KB is dropped.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — Rust: 203 mcp:: tests plus tool outcome capture (3), core::all (77), orchestrator prompt (42) and the app widget scheme and external navigation tests pass. Vitest: 152 files / 1973 tests pass (1 skipped) over conversations, assistant-ui, utils and i18n.
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/ci-fast.yml. Measured locally with diff-cover <lcov files> --compare-branch=upstream/main (the scripts/ci/self-hosted/diff-cover.sh invocation): 88% overall, 989 of 1,112 changed lines covered. Rust core (scoped cargo llvm-cov --lib over the mcp::, tool_outcome_capture, orchestrator and tools:: tests, core product features): mcp/ui 90-100% per file (schemas.rs 90%, cache.rs 98%, decorate.rs 99%, ops.rs 96%, tools.rs 99%, port.rs 53%, discovery.rs 68%), tool_outcome_capture.rs 100%, tools/ops.rs 100%, mcp/host.rs 100%, mcp/mod.rs 89%. App crate (lcov from the CI Fast Tauri coverage lane on the previous head): widget_scheme.rs 83%, external_navigation.rs 100%. Frontend (Vitest coverage over features/conversations and components/assistant-ui, 111 files, 1,410 tests): 86% of 379 changed lines. The CI coverage gate cannot report on this PR because the Rust core lane fails on main itself (modules/wallet_service_tests.rs does not compile with the product features; openhuman-embed isolation_mcp and session_store fail identically on a clean main checkout).
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change). Row 4.2.14 added.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related. Matrix feature ID 4.2.14.
  • No new external network dependencies introduced (mock backend used per Testing Strategy) — tests use no live servers.
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md). Added the tool UI widgets check.
  • Linked issue closed via Closes #7202 in the ## Related section

Gates run on this branch: cargo fmt --all --check and the app-crate fmt, cargo check -p openhuman, cargo check in crates/openhuman-app, cargo clippy -p openhuman --lib --no-deps -- -D warnings, tsc --noEmit, eslint and prettier on the changed files, pnpm i18n:check. pnpm rust:layout reports four over-limit files this PR does not touch (security_gate.rs, security_gate_tests.rs, wallet_host.rs, policy_paths_and_risk_tests.rs), which main already carries.

CI on this head: the failing CI Fast lanes are failing on current main as well, in files and tests this PR does not touch: rust-layout (four over-limit files), agent-runtime-boundary, saas-ambient, ignored-tests-ratchet, gated-test-allowlist (each reproduced on a clean checkout of main), clippy-product and embed-clippy (hosting, voice and embed test files), the gate-contract and coverage test builds (modules/*_tests.rs, in_process tests), and openhuman-embed isolation_mcp (fails identically on a clean main checkout). Frontend lanes (tsc, prettier, eslint, i18n, vitest) and the Tauri clippy and coverage lanes pass.

Manual: verified in a desktop dev build against the live Swiggy Instamart and Food MCPs. Search, cart, payment-method and payment-status widgets render in chat.

Impact

  • Desktop only for the widget; mobile gets the link / QR fallback.
  • Security: new sandboxed iframe surface, covered under Solution.
  • Compatibility: adds a ui feature dependency on tinymcp, whose regex dependency is now in both lockfiles.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/7202-mcp-apps-in-chat
  • Commit SHA: see head of branch

Validation Run

  • pnpm --filter openhuman-app format:check (prettier on changed files)
  • pnpm typecheck
  • Focused tests: Vitest conversations / assistant-ui / utils / i18n; Rust mcp::, tool outcome capture, core::all, prompt
  • Rust fmt/check (if changed)
  • Tauri fmt/check (if changed)

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: tool-declared widgets render inline in chat.
  • User-visible effect: payment and checkout widgets from MCP servers are usable in the conversation.

Parity Contract

  • Legacy behavior preserved: chats without widget-bearing tools render as before.
  • Guard/fallback/dispatch parity checks: link / QR fallback when no widget.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution (closed/superseded/updated): N/A

🤖 Generated with Claude Code

https://claude.ai/code/session_01CF6HjmewhxDhceA782GnTL

Summary by CodeRabbit

  • New Features
    • Tool-provided interactive views can appear in chat alongside assistant responses.
    • Views can request confirmation before running actions, prefill the message composer, and adapt their layout to the content.
    • Link results offer browser access or a QR code for opening on a phone. Unsafe links are blocked, and app handoff links are presented for phone use.
  • Localization
    • Added translated labels and prompts for view controls and action confirmations across supported languages.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

This PR adds MCP UI support across core MCP handling, the desktop shell, and chat. It resolves tool-provided presentations, serves sandboxed widget documents, renders widgets inline, handles widget actions and link fallbacks, and adds translations and tests.

Changes

MCP UI widgets in chat

Layer / File(s) Summary
Core MCP UI integration
crates/openhuman-core/src/mcp/..., crates/openhuman-core/src/agent/..., crates/openhuman-core/src/tools/..., app/src/features/conversations/tools/toolSpecs.ts, app/src/features/conversations/tools/toolPhrases.ts
Core enables MCP UI capabilities, registers controllers and tools, decorates MCP tool results with presentation metadata, logs UI-related server discovery, and updates tool labels and prompt guidance.
MCP UI data and operations
crates/openhuman-core/src/mcp/ui/*, crates/openhuman-core/src/tools/impl/network/mcp_server_tools.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture*
Core adds presentation types, caches and resolution, server resource access, resource_read and tool_call operations, controller schemas, and the show_ui tool.
Desktop widget proxy
crates/openhuman-app/assets/widget-proxy.html, crates/openhuman-app/src/widget_scheme.rs, crates/openhuman-app/src/external_navigation.rs, crates/openhuman-app/src/lib.rs, crates/openhuman-app/tauri.conf.json
The desktop shell serves a widget proxy with a generated CSP, registers its URI scheme, and allows widget proxy origins in the webview.
Frontend widget host and chat rendering
app/src/features/conversations/tools/mcpUi/*, app/src/components/assistant-ui/thread.tsx, app/src/components/assistant-ui/thread.mcpWidgets.test.tsx, app/src/features/conversations/components/AssistantUiToolCall.tsx
The frontend adds a JSON-RPC bridge, Apps SDK shim, sandbox frame, sizing and scroll scripts, composer prefilling, widget collection, and inline rendering alongside assistant activity groups.
Link classification and fallback actions
app/src/features/conversations/utils/format.ts, app/src/components/assistant-ui/markdown-text.tsx, app/src/features/conversations/components/AgentMessageBubble.tsx, app/src/features/conversations/tools/mcpUi/LinkActions.tsx, app/src/lib/i18n/*, docs/*
Chat classifies URLs and renders handoff links through QR actions. MCP link fallbacks, translations, smoke-check guidance, and coverage documentation are added.

Priority: ➖ Normal

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

Change: Feature

Suggested reviewers: senamakel


Merge Risk | 🟡 Moderate · up to 32805

Merge Risk: 🟡 Moderate · up to 32805

A widget can run later actions with different arguments without asking again. Bind approval to the approved arguments or confirm each action before merging. The test-isolation defects and misleading Russian labels also remain.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 32805

Interactive widgets can now act through connected services. A single approval permits later actions with different inputs without clearly disclosing that continuing permission. Document isolation across reconnection and workspace changes also remains uncertain. Sandboxing and service-policy checks substantially limit the exposure.

Retained concerns

  • High · security · observed: An action-shaped Allow prompt grants continuing, argument-independent authority for that tool in the mounted widget. Later or concurrent calls can execute different or repeated mutations without another approval. This is not a bypass of core policy, but it permits a malicious or compromised widget to use a broader grant than the dialog explains.
  • Medium · security · inferred: Cached document ownership does not include the workspace/agent host or connection generation. Documents survive disconnect until expiry or eviction, and inline lookup does not require current server availability. If a server identifier is reused across authority contexts, stale documents could be served under a different context. Identifier reuse and resulting security exposure remain unverified; current tool calls independently revalidate availability and policy.
Security review details

Security Blast Radius

  • inferred — A malicious widget document or compromised script from a declared resource origin can exercise the bridge. Its established action scope is the frame's bound server and currently app-visible tools, using the connection's existing authority and subject to policy. It also receives presentation data and may read other ui:// resources on that server. Wider tenant exposure depends on unresolved cache-identity reuse, not a demonstrated sandbox escape.

Security Findings and Attack Paths

  • inferred — After obtaining one Allow decision, a widget can repeat the same mutating tool with different arguments and have those calls marked confirmed without another dialog. Same-name concurrent calls share approval. The path requires a connected service, an offered app-visible tool, and writable policy with available budget; it does not establish unrestricted host access.

Trust Boundaries and Controls

  • observed — The host bridge accepts messages only from its proxy frame, and the proxy forwards only between its parent and inner widget window. Server selection remains host-owned. Separate origins, the inner sandbox, and restrictive CSP provide substantial containment; no browser-level escape was verified.

Resilience and Maintainability Implications

  • observed — Deny is remembered for the tool and prevents confirmed execution. Frame keys include server and document identity, limiting approval reuse across differently keyed widgets, while updates to the same template preserve the mounted frame and its decisions. Core guards run again for each execution, but approvals are not transaction-bound.

Hardening Proposals

  • proposed — Make the consent model explicit: use action-specific approvals bound to meaningful inputs, or clearly disclose a continuing tool grant with visible scope and revocation. Concurrent and repeated mutations should follow the chosen consent model rather than silently inheriting a singular-action approval.
  • proposed — Establish document ownership using workspace/agent and server-generation scope, or prove equivalent globally unique identity guarantees. Define disconnect and reconfiguration invalidation, including rejection of late fetch completions, and clarify whether inline identifiers intentionally authorize retrieval independently of the owning server.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 98 functions across 52 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: rendering MCP Apps widgets inline in conversations. It matches the pull request objectives and changeset.
Linked Issues check Passed Issue [#7202] requires desktop inline rendering, widget actions, confirmation for non-read-only calls, link and QR fallback, and at least 80% changed-line coverage. The reviewed changes implement MCP …
Out of Scope Changes check Passed The changes stay within [#7202]. Core UI resolution, MCP RPCs, tool decoration, capability advertisement, and prompt guidance deliver widget data. The app-shell proxy and sandbox policy host widget do…

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit watched the widgets appear,
Beside the chat, both bright and clear.
A QR code twinkled in the light,
Safe frames held each view just right.
“Hop,” said the rabbit, “links are near!”
Then nibbled lettuce, pleased and cheer.

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

@oxoxDev
oxoxDev force-pushed the feat/7202-mcp-apps-in-chat branch from e2cf19c to 24e945e Compare October 12, 2026 07:12
Adds the mcp::ui domain: resolves MCP Apps (_meta.ui.resourceUri), legacy
ui/resourceUri and Apps SDK openai/outputTemplate widgets plus safe links
from a tool result into a bounded mcp_ui presentation, the mcp_ui RPC
namespace (resource_read, tool_call with confirmation for non-read-only
tools), the show_ui host tool, and advertises the MCP Apps extension in
the client initialize capabilities.
Logs, under [mcp-ui], each tool's _meta keys and widget template URI and
each listed resource, for installed servers on connect, reconnect and boot,
and for configured servers after the tool cache refresh.
Installed per-tool, configured per-tool, mcp_call_tool and
mcp_registry_tool_call results now carry an mcp_ui presentation in place of
the raw mcp_result envelope, and the outcome capture drops any envelope
that reaches it so widget HTML never enters events or transcripts.
Tool widgets run on their own origin: the proxy page renders the widget in
an inner frame sandboxed without allow-same-origin, relays JSON-RPC to the
app, and carries a CSP that opens only the https origins the widget
declared and keeps the frame from navigating to a remote page.
The proxy frame was sandboxed without allow-same-origin, so its origin was
opaque. Widgets pin the origin of the first host message and post back to
it; postMessage(msg, "null") throws, so ui/notifications/initialized never
reached the host and the widget never got its tool result. The proxy now
keeps its ohwidget origin; the widget's inner frame stays without
allow-same-origin.
…he view

A widget that polls a tool (Swiggy's payment status check) asked the
user again on every poll, after Allow and after Deny alike. Keep one
decision per tool name for the life of the frame; concurrent calls
share the pending prompt instead of replacing it.
…ng it

The frame keeps one bridge per widget document. A newer presentation of
the same widget reaches it as fresh tool-input and tool-result
notifications once the widget has initialized.
…in the card

A widget mounted inside the tool-call disclosure disappeared when the
turn finished and the group collapsed. Each assistant message now shows
the latest presentation of every widget template right after the
activity group that produced it, before the answer text, keyed by server
and resource so the frame survives the turn completing. The tool card
keeps only the link fallback for results without a sandboxable widget.
…rs in widgets

A small host script finds horizontally scrolling regions in a widget
document, overlays fixed-position Scroll left and Scroll right buttons
that follow each one and hide at its ends, and maps vertical wheel onto
a horizontal-only scroller until it reaches an end.
Widget template resolution, presentation caps, widget document checks,
link classification (including the image filter) and the widget
tool-call visibility and read-only rules now live in tinymcp's ui
feature, with their tests. This host keeps the mcp_ui RPCs, the document
cache, the UiAwareTool decorator, show_ui, discovery logs and the prompt
guidance. The presentation wire shape is unchanged.
A widget with native carousel buttons showed two right arrows. The
injected buttons now stand down for a row while a clickable control sits
at its edge, or a navigation-named one sits next to it, re-checked as the
widget renders; wheel mapping follows the same rule. Only the outermost
scrolling row of a region gets buttons, and none narrower than 240 px.
@oxoxDev
oxoxDev force-pushed the feat/7202-mcp-apps-in-chat branch from dff7a3c to 1557cae Compare October 12, 2026 08:00
@oxoxDev
oxoxDev marked this pull request as ready for review October 12, 2026 08:28
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2


  • 🪄 Fix CodeRabbit comments on this PR
🤖 Prompt to fix review comments
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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:
Review comments at @app/src/features/conversations/tools/mcpUi/McpAppFrame.tsx:
- Around line 93-101: Update the decision lookup and storage in the
`decisionsRef` flow so remembered confirmations are scoped to both the tool name
and serialized arguments; alternatively, avoid reusing remembered decisions and
request confirmation for every call. Ensure calls to the same tool with
different arguments cannot inherit a prior approval.

Review comments at @app/src/lib/i18n/ru.ts:
- Around line 1890-1893: Update the Russian translations for
conversations.mcpUi.widgetTitle, conversations.mcpUi.widgetLoading, and
conversations.mcpUi.widgetUnavailable to consistently use “виджет” for the
inline chat widget instead of “окно”.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dd14bd28-9286-40da-aed9-6a42cbc9c1bd
📥 Commits

Reviewing files that changed from the base of the PR and between bf3cf35 and 1557cae.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • crates/openhuman-app/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (89)
  • app/src/components/assistant-ui/markdown-text.tsx
  • app/src/components/assistant-ui/thread.mcpWidgets.test.tsx
  • app/src/components/assistant-ui/thread.tsx
  • app/src/features/conversations/components/AgentMessageBubble.tsx
  • app/src/features/conversations/components/AssistantUiToolCall.tsx
  • app/src/features/conversations/tools/__fixtures__/coreToolNames.json
  • app/src/features/conversations/tools/mcpUi/LinkActions.test.tsx
  • app/src/features/conversations/tools/mcpUi/LinkActions.tsx
  • app/src/features/conversations/tools/mcpUi/McpAppFrame.test.tsx
  • app/src/features/conversations/tools/mcpUi/McpAppFrame.tsx
  • app/src/features/conversations/tools/mcpUi/McpUiBody.test.tsx
  • app/src/features/conversations/tools/mcpUi/McpUiBody.tsx
  • app/src/features/conversations/tools/mcpUi/McpWidgetStrip.tsx
  • app/src/features/conversations/tools/mcpUi/appsSdkShim.test.ts
  • app/src/features/conversations/tools/mcpUi/appsSdkShim.ts
  • app/src/features/conversations/tools/mcpUi/composerPrefill.ts
  • app/src/features/conversations/tools/mcpUi/frameFit.test.ts
  • app/src/features/conversations/tools/mcpUi/frameFit.ts
  • app/src/features/conversations/tools/mcpUi/mcpAppBridge.test.ts
  • app/src/features/conversations/tools/mcpUi/mcpAppBridge.ts
  • app/src/features/conversations/tools/mcpUi/messageWidgets.ts
  • app/src/features/conversations/tools/mcpUi/scrollAffordance.test.ts
  • app/src/features/conversations/tools/mcpUi/scrollAffordance.ts
  • app/src/features/conversations/tools/mcpUi/types.test.ts
  • app/src/features/conversations/tools/mcpUi/types.ts
  • app/src/features/conversations/tools/mcpUi/widgetUrl.test.ts
  • app/src/features/conversations/tools/mcpUi/widgetUrl.ts
  • app/src/features/conversations/tools/toolPhrases.ts
  • app/src/features/conversations/tools/toolSpecs.ts
  • app/src/features/conversations/utils/format.test.ts
  • app/src/features/conversations/utils/format.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ja.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/tr.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/services/api/mcpUiApi.ts
  • crates/openhuman-app/Cargo.toml
  • crates/openhuman-app/assets/widget-proxy.html
  • crates/openhuman-app/src/external_navigation.rs
  • crates/openhuman-app/src/external_navigation_tests.rs
  • crates/openhuman-app/src/lib.rs
  • crates/openhuman-app/src/widget_scheme.rs
  • crates/openhuman-app/src/widget_scheme_tests.rs
  • crates/openhuman-app/tauri.conf.json
  • crates/openhuman-core/Cargo.toml
  • crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.rs
  • crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs
  • crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture.rs
  • crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture_tests.rs
  • crates/openhuman-core/src/core/all.rs
  • crates/openhuman-core/src/mcp/host.rs
  • crates/openhuman-core/src/mcp/mod.rs
  • crates/openhuman-core/src/mcp/registry/action_tool.rs
  • crates/openhuman-core/src/mcp/registry/bus.rs
  • crates/openhuman-core/src/mcp/registry/mod.rs
  • crates/openhuman-core/src/mcp/registry/tools.rs
  • crates/openhuman-core/src/mcp/ui/cache.rs
  • crates/openhuman-core/src/mcp/ui/decorate.rs
  • crates/openhuman-core/src/mcp/ui/decorate_tests.rs
  • crates/openhuman-core/src/mcp/ui/discovery.rs
  • crates/openhuman-core/src/mcp/ui/discovery_tests.rs
  • crates/openhuman-core/src/mcp/ui/mod.rs
  • crates/openhuman-core/src/mcp/ui/ops.rs
  • crates/openhuman-core/src/mcp/ui/ops_tests.rs
  • crates/openhuman-core/src/mcp/ui/port.rs
  • crates/openhuman-core/src/mcp/ui/resolve.rs
  • crates/openhuman-core/src/mcp/ui/resolve_tests.rs
  • crates/openhuman-core/src/mcp/ui/schemas.rs
  • crates/openhuman-core/src/mcp/ui/schemas_tests.rs
  • crates/openhuman-core/src/mcp/ui/tools.rs
  • crates/openhuman-core/src/mcp/ui/tools_tests.rs
  • crates/openhuman-core/src/mcp/ui/types.rs
  • crates/openhuman-core/src/tools/impl/network/mcp_server_tools.rs
  • crates/openhuman-core/src/tools/ops.rs
  • crates/openhuman-core/src/tools/toolpacks/guides/mcp.md
  • docs/RELEASE-MANUAL-SMOKE.md
  • docs/TEST-COVERAGE-MATRIX.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +93 to +101
let decision = decisionsRef.current.get(name);
if (!decision) {
decision = new Promise<void>((resolve, reject) =>
setPending({ name, args, resolve, reject })
);
decision.catch(() => undefined);
decisionsRef.current.set(name, decision);
}
await decision;

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.

🔒 Security & Privacy | 🛡️ Detected with Advanced Tier | 🟠 Major | 🏗️ Heavy lift

Authorization Bypass

Reachability: External
Exploitability: Moderate
CWE: CWE-863 — Incorrect Authorization

View Security blast radius

Bind remembered confirmations to the approved tool arguments.

decisionsRef is keyed only by name, so a later call to the same non-read-only tool with different args reuses the earlier approval. The confirmation dialog shows only pending.name, not the arguments that the user approves. Key the remembered decision by the tool name and serialized arguments, or confirm every call instead.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @app/src/features/conversations/tools/mcpUi/McpAppFrame.tsx
around lines 93 - 101:
Update the decision lookup and storage in the `decisionsRef` flow so remembered
confirmations are scoped to both the tool name and serialized arguments;
alternatively, avoid reusing remembered decisions and request confirmation for
every call. Ensure calls to the same tool with different arguments cannot
inherit a prior approval.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

Comment thread app/src/lib/i18n/ru.ts
Comment on lines +1890 to +1893
'conversations.mcpUi.widgetTitle': 'Окно инструмента',
'conversations.mcpUi.widgetLoading': 'Загрузка окна…',
'conversations.mcpUi.widgetUnavailable':
'Это окно больше недоступно. Переподключите сервер, чтобы увидеть его снова.',

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use one term for the embedded widget.

These strings call the inline widget an “окно” (“window”), which can suggest a separate application window. Use “виджет” consistently in the title, loading, and unavailable messages.

The PR objective describes widgets rendered inline in chat.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @app/src/lib/i18n/ru.ts around lines 1890 - 1893:
Update the Russian translations for conversations.mcpUi.widgetTitle,
conversations.mcpUi.widgetLoading, and conversations.mcpUi.widgetUnavailable to
consistently use “виджет” for the inline chat widget instead of “окно”.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

@coderabbitai coderabbitai 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.

Actionable comments posted: 2


  • 🪄 Fix CodeRabbit comments on this PR
🤖 Prompt to fix review comments
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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:
Review comments at @crates/openhuman-core/src/mcp/ui/cache_tests.rs:
- Line 41: Update the cache test setup around Instant::now to derive distinct
entry timestamps from one base instant, ensuring key 0 is uniquely oldest when
the test exercises evict.

Review comments at @crates/openhuman-core/src/mcp/ui/schemas_tests.rs:
- Line 33: Update with_workspace to set OPENHUMAN_WORKSPACE through
EnvVarGuard::workspace_unlocked and remove the explicit post-await cleanup, so
the guard clears the variable on normal return and during unwinding without
restoring a previous value.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0ff76726-8b98-44de-aebb-9fb31908a348
📥 Commits

Reviewing files that changed from the base of the PR and between 1557cae and 32805e9.

📒 Files selected for processing (7)
  • crates/openhuman-core/src/mcp/ui/cache.rs
  • crates/openhuman-core/src/mcp/ui/cache_tests.rs
  • crates/openhuman-core/src/mcp/ui/decorate_tests.rs
  • crates/openhuman-core/src/mcp/ui/discovery_tests.rs
  • crates/openhuman-core/src/mcp/ui/port.rs
  • crates/openhuman-core/src/mcp/ui/port_tests.rs
  • crates/openhuman-core/src/mcp/ui/schemas_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

map.insert(
key,
Timed {
at: Instant::now(),

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Give eviction entries distinct timestamps.

If successive Instant::now() calls return the same value, key 0 is not uniquely oldest. evict can then remove another key, and this test fails even when eviction works. Derive distinct timestamps from one base instant.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @crates/openhuman-core/src/mcp/ui/cache_tests.rs at line 41:
Update the cache test setup around Instant::now to derive distinct entry
timestamps from one base instant, ensuring key 0 is uniquely oldest when the
test exercises evict.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

}
let output = body.await;
unsafe {
std::env::remove_var("OPENHUMAN_WORKSPACE");

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '23,76p' crates/openhuman-core/src/mcp/ui/schemas_tests.rs
rg -n 'test_env_lock|OPENHUMAN_WORKSPACE' crates/openhuman-core/src/mcp/ui/*tests.rs | head -60

Repository: tinyhumansai/openhuman

Length of output: 1910


🏁 Script executed:

set -eu
rg -n -F --glob '*.rs' -- 'TEST_ENV_LOCK' crates/openhuman-core/src
rg -n -F --glob '*.rs' -- 'OPENHUMAN_WORKSPACE' crates/openhuman-core/src
printf '%s\n' '--- config source ---'
rg -l -F --glob '*.rs' -- 'TEST_ENV_LOCK' crates/openhuman-core/src/config | while IFS= read -r f; do
  echo "FILE: $f"
  nl -ba "$f" | sed -n '1,180p'
done
printf '%s\n' '--- helper and callers ---'
nl -ba crates/openhuman-core/src/mcp/ui/schemas_tests.rs | sed -n '1,95p'

Repository: tinyhumansai/openhuman

Length of output: 42764


🏁 Script executed:

set -eu
printf '%s\n' '--- shared environment guard contract ---'
nl -ba crates/openhuman-core/src/config/test_env.rs | sed -n '1,190p'
printf '%s\n' '--- relevant workspace cleanup conventions ---'
nl -ba crates/openhuman-core/src/integrations/composio/action_tool_tests.rs | sed -n '280,390p'
nl -ba crates/openhuman-core/src/config/schema/load_active_user_and_dirs_tests.rs | sed -n '90,175p'
nl -ba crates/openhuman-core/src/security/secret_storage/encrypted_file_backend_tests.rs | sed -n '330,370p'
printf '%s\n' '--- config lock declaration ---'
nl -ba crates/openhuman-core/src/config/mod.rs | sed -n '68,88p'

Repository: tinyhumansai/openhuman

Length of output: 21328


Make with_workspace use the panic-safe environment guard.

The explicit cleanup runs only after body.await. If the body panics, the temporary directory and lock guard are dropped, but OPENHUMAN_WORKSPACE remains set to the deleted path. A later test can then read this stale workspace.

Use EnvVarGuard::workspace_unlocked. Its Drop implementation performs cleanup during unwinding. Do not restore a previous workspace value; the shared guard intentionally clears prior workspace values to avoid resurrecting leaked test paths.

Suggested fix
--- "a/crates/openhuman-core/src/mcp/ui/schemas_tests.rs"
+++ "b/crates/openhuman-core/src/mcp/ui/schemas_tests.rs"
@@ -23,16 +23,12 @@
 }
 
 async fn with_workspace<F: std::future::Future>(body: F) -> F::Output {
     let _env_lock = crate::config::TEST_ENV_LOCK.lock().await;
     let workspace = tempfile::tempdir().expect("workspace");
-    unsafe {
-        std::env::set_var("OPENHUMAN_WORKSPACE", workspace.path());
-    }
+    let _workspace_env =
+        crate::config::test_env::EnvVarGuard::workspace_unlocked(workspace.path());
     let output = body.await;
-    unsafe {
-        std::env::remove_var("OPENHUMAN_WORKSPACE");
-    }
     output
 }
 
 fn params(value: Value) -> Map<String, Value> {
🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @crates/openhuman-core/src/mcp/ui/schemas_tests.rs at line 33:
Update with_workspace to set OPENHUMAN_WORKSPACE through
EnvVarGuard::workspace_unlocked and remove the explicit post-await cleanup, so
the guard clears the variable on normal return and during unwinding without
restoring a previous value.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

This branch has not been deployed

No deployments
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.

Render MCP Apps widgets (tool-provided UI) inline in chat

1 participant