Skip to content

feat(mobile): render MCP Apps tool interfaces in the chat transcript - #8472

Merged
iscekic merged 11 commits into
mainfrom
drain/mcp-apps-transcript-views
Oct 22, 2026
Merged

iscekic merged 11 commits into
mainfrom
drain/mcp-apps-transcript-views

Conversation

@iscekic

@iscekic iscekic commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

A remote MCP tool whose server attached an MCP Apps view now draws that view in the mobile chat transcript, under the row the call already had. Tools without a view, and calls that have not finished, keep today's rendering.

harness-sdk (plugins/remote-mcp)

  • The offered tool carries serverToolName (the name a view calls the tool by) and uiResourceUri (the view the server attached, when it attached one). Neither is in definition, so nothing new enters the cached prefix.
  • call answers the whole result: the text the model reads, plus the server's content and structuredContent. deps.onCallResult reports each answer, because a view renders what the text drops.
  • remoteMcpApp(server, deps, { tools, context, openLink }) is the host half of one view, over the official AppBridge (ext-apps 1.7.5, already the package the plugin reads a tool's view uri with). It answers the handshake with the host's identity, capabilities and context, sends the call's input and result, reports size changes, opens links, and forwards a view's tools/call through the same client a model's call uses — refusing any name its own server was not discovered to offer. Sampling, model-context updates, messages and downloads are not advertised. The surface supplies one transport that writes text into its sandbox and hands the text back through receive; it names no method.

apps/mobile

  • Discovery keeps each tool's view and the server's own tool names; the transcript looks them up without touching the device stores (remote-mcp-surface.ts).
  • McpAppView mounts a WebView that loads one page of the app's own: a proxy that owns a single srcdoc iframe with sandbox="allow-scripts" — an opaque origin, no file access, no way to navigate anything but itself — under a content policy built only from the origins the resource declared, default-deny when it declared none, with anything that would widen it (wildcards, 'unsafe-eval', non-http schemes) dropped. Only JSON text for the bridge crosses into it or out of it, and only messages from that iframe are relayed.
  • The row keeps one height from the moment it is drawn — the view's own reported height inside the transcript's clamp — so a view that loads a moment after its call does not move the list. Loading, an empty document and a failure each have their own state; a failure offers Retry.
  • Copy: modelChat.mcp.app.label, .failed and .empty added to all 87 catalogs; everything else reuses existing keys (common.loading, common.retry).

End-to-end evidence (Android, emulator on this VM)

Exercised on a dev build of this branch against a local dev stack, with the official examples/basic-server-vanillajs from @modelcontextprotocol/ext-apps 1.7.5 served over Streamable HTTP, and a throwaway OpenAI-compatible backend that answers the chat with a tools/call for whichever mcp_<server>_get-time tool the app discovered. Both endpoints reached the emulator over adb reverse, on an owned fake account.

  1. The model's call to mcp_apps-example_get-time runs on the device, the tool row renders, and the view draws the App with the server's time from the live result's structured content: Server Time: 2026-10-21T15:23:02.665Z.
  2. Tapping the App's own Get Server Time button calls back through the bridge: the request goes out through the host to the same server and the answer comes back, and the view re-renders with 2026-10-21T15:23:51.601Z.

Screenshots are attached in the comment below.

Static checks on this branch: packages/harness-sdk check:ci (typecheck, lint, 58 suites, build, platform and package checks), mobile typecheck, the whole mobile vitest suite (1205 files, 18059 tests), and check:i18n / check:plurals.

Also

todo-list-model-picker.mounted.test.tsx reached a hardware back-button subscription through a react-native mock that answered every other native export it reads. That shard is red on main for the same reason; the mock now answers this one too, so the suite mounts the sheet wherever the shard lands.

Limits

  • A transcript reopened from storage has the text the model read and no structured result, so its view draws from the call's input alone. Live calls carry the structured result.
  • Model-issued remote MCP calls have no permission prompt in the mobile chat today, so a view's callback uses exactly the same path; this change does not alter that policy.
  • Resource permissions (camera, microphone, geolocation, clipboard) are not granted to the sandbox; the document gets the declared CSP only.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Host layer: Where should the MCP Apps host bridge (AppBridge wiring, tools/call relay, resource read) live?
    Answer: harness-sdk remote-mcp plugin (Recommended)
    Confidence: 0.70 · Precedent: Engineering conventions: 'Worker direct, extract any common code to an internal library' (session 5d8113f51c48). The plugin already owns remote MCP and pins ext-apps.
  2. Question: Result fidelity: The harness flattens a remote tool result to text today, so a view cannot render the structured result it was written for. How far should the change go?
    Answer: Relay structuredContent for live calls (Recommended)
    Confidence: 0.50 · Precedent: Speed and least work: 'do the least amount of work possible, with no drop in quality' (session 7f3470e0ba6f).
  3. Question: Render states: Which transcript states should show the inline view?
    Answer: Completed parts only, inline (Recommended)
    Confidence: 0.75 · Precedent: UX and design quality: 'CLS is a massive enemy' (session 00eb2a1d3e4b). A fullscreen sheet is beyond the brief.
  4. Question: Localization: The view needs a failure+retry line and an empty-state line. How should the copy be handled?
    Answer: Two new keys in all 87 catalogs (Recommended)
    Confidence: 0.85 · Precedent: UX and design quality: 'the mobile app always must respect i18n' (session dcb3f9170164).
  5. Question: E2E driver: How should the Android end-to-end run produce the remote MCP tool call?
    Answer: Scripted chat backend drives the tool call (Recommended)
    Confidence: 0.75 · Precedent: Proof and verification: prove behavior with a real request through real clients (session 4807b1d78fc7); fixtures rule: use real backend state when a fixture cannot mimic real usage (session b1be07191a1d)
  6. Question: Bridge calls: What may a rendered view call back through the bridge?
    Answer: Same server's discovered tools only (Recommended)
    Confidence: 0.80 · Precedent: Autonomy: make executive decisions with "is this reasonable?" as the test (session 7f3470e0ba6f). Exploit janitor lanes treat harm to users as a real concern (session 2f71af3a787e). The brief limits calls to that server's tools, with the same prompts as model calls.

A remote tool's `_meta` no longer stops at discovery, and a call's answer is
no longer flattened before a surface can render it.

The offered tool carries `serverToolName`, the name a view calls the tool by,
and `uiResourceUri`, the view the server attached, when it attached one.
Neither is in `definition`, so the model is told nothing new. `call` answers
the whole result — the text the model reads, plus the server's content and
structured content — and `deps.onCallResult` reports each answer, because a
view renders what the text drops.

`remoteMcpApp(server, deps, { tools, context, openLink })` is the host half of
one view, over the official `AppBridge`: it answers the handshake with the
host's identity, capabilities and context, sends the call's input and result,
reports size changes, opens links, and forwards a view's `tools/call` through
the same client a model's call uses, refusing any name its own server was not
discovered to offer. Sampling, model-context updates, messages and downloads
are not advertised. The surface supplies one transport that writes text into
its sandbox and hands the text back through `receive`; it names no method.

Proof: the remote-mcp suites drive a real protocol server through the bridge —
the handshake, the context, initialization, size changes, the call's input and
result, a callback to the same server, a refused callback, a failed call, and
an opened link — plus the offered tool's view and the answer reported to the
surface.
A remote MCP tool whose server attached a `ui://` view now draws that view in
the transcript, under the row the call already had. Tools without one, and
calls that have not finished, keep today's rendering.

The view runs in a WebView that loads one page of the app's own: a proxy that
owns a single `srcdoc` iframe with `sandbox="allow-scripts"` — an opaque
origin, no file access, no way to navigate anything but itself — under a
content policy built only from the origins the resource declared, and
default-deny when it declared none. Only JSON text for the bridge crosses into
it or out of it, and only messages from that iframe are relayed.

The call's answer is what the view receives: the live call's content and
structured result are kept for the row that draws them (`mcpCallResultOf`), so
an interface that renders the server's structured data shows it. A transcript
reopened from storage has the text the model read and no structured result; the
view then draws from the call's input alone.

The row keeps one height from the moment it is drawn — the view's own reported
height inside the transcript's clamp — so a view that loads a moment after its
call does not move the list. Loading, an empty document and a failure each have
their own state, and a failure offers Retry.

Copy: `modelChat.mcp.app.label`, `.failed` and `.empty` are added to every
catalog.

Proof: the chat suites cover the view lookup, the document read, the policy,
the height clamp, the arguments handed to a view, and the router that picks the
card; the Android run renders the official example App with the server's time
and calls its tool back through the bridge.
@iscekic iscekic added drain Work item from the findings drain janitor:library-suggester Library-opportunity finding from the janitor library suggester labels Oct 21, 2026
@iscekic

iscekic commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Android end-to-end run of this branch against a local dev stack, with the official examples/basic-server-vanillajs from @modelcontextprotocol/ext-apps 1.7.5 served over Streamable HTTP and a throwaway chat backend that answers with a tools/call for the discovered mcp_apps-example_get-time tool. Both endpoints reached the emulator over adb reverse; the account is an owned fake one.

  • mcp-app-view.png: the tool row and the App's own interface drawn inline in the transcript, showing the server time from the live call's structured result.
  • mcp-app-bridge-call.png: after tapping the App's Get Server Time button — the view called its server back through the host bridge and re-rendered with the fresh answer (the time moved from …15:23:02.665Z to …15:23:51.601Z).

Transcript row with the MCP App interface and its live server time

Same view after its own button called the server back through the bridge

…policy

The resource's `_meta` is the server's to write, and metadata that is not the
shape the specification defines is not a reason to refuse to draw the view: it
is a resource that named no origins, so it may load none.

Proof: a policy request whose `connectDomains` is not a list resolves to the
default-deny policy rather than an error.
The card is what the transcript routes to; the view inside it has no other
caller, so it is not exported and the barrel re-exports only the card.
The sheet subscribes to the hardware back button, and the suite's react-native
mock answered every other native export it reads. The mock now answers this one
too, so the sheet mounts in the shard it lands in.
Comment thread apps/mobile/src/lib/chat/remote-mcp.ts Resolved
Comment thread apps/mobile/src/lib/chat/remote-mcp.ts Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the last review is a type-only annotation (useRef<WebView<unknown>> and <WebView<unknown>>) that restores the full WebView props type under WebView 14's undefined default generic; it erases at compile time and cannot change the rendered output or bridge behavior.

Assumptions: [INFERENCE] Dependencies are not installed in this sandbox, so pnpm typecheck/lint and vitest were not reproduced. Both declaration shapes (react-native-webview 13.16.1 in the branch lockfile, 14.0.1 in the CI merge tree) accept a generic on WebView, and unknown intersects to the base WebViewProps rather than weakening it to any; the author reports typecheck and lint passing on both trees. markdown-mermaid.tsx was checked as the other WebView caller and holds no ref, so it needs no matching change. The earlier inline findings (doctype placement, retry remount, bounded resource read, abort not publishing failure, bounded answer retention) all carry bot: Fixed replies and were verified in the previous review; nothing on those paths changed here.

Files Reviewed (2 files)
  • apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx (incremental) - no issues (type-argument only; no runtime or layout change)
  • apps/mobile/src/components/agents/markdown-mermaid.tsx (targeted check for the same WebView typing pattern) - no issues (no ref, so no generic needed)
Previous Review Summaries (5 snapshots, latest commit 2c77f76)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 2c77f76)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only PR-authored change since the last review is the Effect 4 adaptation in app.ts, and it preserves the previous behavior: a failed view tools/call still returns a failed CallToolResult with the same explanation, and remote-mcp.ts now uses catchCause, matching effect 4.0.5.

Assumptions: [INFERENCE] Dependencies are not installed in this sandbox, so typecheck and vitest were not reproduced; the two changed call sites were verified against the Effect 4 migration in bf36e6174 (catchAll* -> catch*, Effect.match retained) and the REST of git diff c6fdad2..HEAD is main's history carried by merge commit 9cc3fdc07, not this PR's edits. The earlier review's three fixes and their inline replies still hold; no active finding remains.

Files Reviewed (5 files)
  • packages/harness-sdk/src/plugins/remote-mcp/app.ts
  • apps/mobile/src/lib/chat/remote-mcp.ts
  • packages/harness-sdk/src/plugins/remote-mcp/remote-mcp-fixture.ts
  • packages/harness-sdk/.oxlintrc.json (main-origin, checked for PR interaction)
  • apps/mobile/src/components/agents/tool-part-renderer.test.ts (main-origin, checked for PR interaction)

Previous review (commit c6fdad2)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The three fixes at c6fdad2a7 hold: a cancelled view read no longer publishes a server failure, the loaded flag resets only when a retry remounts the page, and an oversized answer is dropped rather than retained.

Assumptions: [INFERENCE] Dependencies are not installed in this sandbox, so the reported typecheck and vitest runs were not reproduced; the three fixes were verified by reading the changed code against client.ts, deps.ts, and remote-mcp-views.ts. Removing onCallFailure also means a resource read that is genuinely refused (for example an OAuth 401 between discovery and read) is reported only by the row's Retry, not as a server sign-in failure; that matches the stated intent, and the tool-call path still publishes it.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (5 files)
  • apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx - no issues (retry now resets pageLoaded in its own effect, keyed on attempt, so a re-discovery re-read for the same mounted page no longer leaves the row blank)
  • apps/mobile/src/lib/chat/remote-mcp.ts - no issues (the read builds its own deps with the 15 s bound and no onCallFailure; resource reads timeoutMs directly, so the abort no longer reaches lostConnection)
  • apps/mobile/src/lib/chat/remote-mcp-call-results.ts - no issues (an answer over MAX_CHARS is now dropped before it is stored, so the documented bound holds)
  • apps/mobile/src/lib/chat/remote-mcp-call-results.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-views.test.ts - no issues

Fix these issues in Kilo Cloud

Previous review (commit 8d3562e)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The fixes at HEAD close all four earlier findings, but the new abort signal turns a normal row unmount into a published server failure that drops the server's tools and views from the live chat.

Assumptions: [INFERENCE] Dependencies are not installed in this sandbox, so the reported typecheck/vitest runs were not reproduced; the abort → unreachable → onCallFailure → publish() chain was read from remote-mcp.ts, client.ts, failure.ts, and connection.ts but not executed. Whether remoteServerView values change identity depends on a re-discovery happening while a chat is mounted (the server Retry in use-remote-mcp-servers.ts does this). The WebView key={attempt} fix and the 15 s read deadline are taken as satisfying the earlier retry and deadline findings.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/chat/remote-mcp.ts 387 The abortable view read keeps onCallFailure, so scrolling a row away aborts the request, is classified unreachable, and publishes the server as failed, removing its tools and views
apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx 84 setPageLoaded(false) also runs when only view identity changes (a re-discovery republishes views); the WebView is not remounted, so no onLoadEnd re-arms the bridge and the row goes permanently blank

SUGGESTION

File Line Issue
apps/mobile/src/lib/chat/remote-mcp-call-results.ts 48 The eviction loop never drops the answer just added, so one answer over MAX_CHARS is retained and the documented size bound does not hold
Files Reviewed (7 files)
  • apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx - 1 issue
  • apps/mobile/src/lib/chat/remote-mcp.ts - 1 issue
  • apps/mobile/src/lib/chat/remote-mcp-call-results.ts - 1 issue
  • apps/mobile/src/lib/chat/remote-mcp-call-results.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-app.ts - no issues (previous doctype finding fixed and covered by a test)
  • apps/mobile/src/lib/chat/remote-mcp-app.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-views.test.ts - no issues

Fix these issues in Kilo Cloud

Previous review (commit b140c5d)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The incremental change since b6ab7a6 only adds a BackHandler stub to an existing mounted test (todo-list-model-picker.mounted.test.tsx:38-40) and introduces no new defect, but all four previously reported issues remain present at HEAD: the policy meta can still precede the doctype, the view row's Retry still cannot reload a failed page, the view's resource read still takes the 60 s call deadline, and retained call answers are still capped by count rather than size.

Assumptions: [INFERENCE] WebView originWhitelist/onShouldStartLoadWithRequest behaviour is proven by the author's Android run only; iOS was not exercised. @modelcontextprotocol/ext-apps internals could not be read in this sandbox, so the bridge analysis relies on the new tests and the package's public contract. Dependencies are not installed here, so the reported typecheck/test runs were not reproduced. The new BackHandler mock replaces the whole react-native module, so the real consumer that needs it lives in an installed package; that could not be confirmed here, but the test-only stub has no product impact. The new two-line comment on the mock is explanatory, not style noise. Deliberate limits stated in the PR body (no permission prompt for view callbacks, input-only rendering for reopened transcripts) were treated as accepted policy, not findings.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/chat/remote-mcp-app.ts 120 Policy meta is prepended before the doctype when the resource has no <head>, which puts the view in quirks mode

SUGGESTION

File Line Issue
apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx 216 Retry after a WebView page failure re-reads the document but never reloads the page
apps/mobile/src/lib/chat/remote-mcp.ts 391 The view's resource read uses the 60 s call deadline and is not cancelled on unmount
apps/mobile/src/lib/chat/remote-mcp.ts 140 Retained call answers are capped by count, not by size
Files Reviewed (5 files)
  • apps/mobile/src/components/todos/todo-list-model-picker.mounted.test.tsx (incremental) - no issues
  • apps/mobile/src/lib/chat/remote-mcp-app.ts - 1 issue (carried, re-verified at HEAD)
  • apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx - 1 issue (carried, re-verified at HEAD)
  • apps/mobile/src/lib/chat/remote-mcp.ts - 2 issues (carried, re-verified at HEAD)

Fix these issues in Kilo Cloud

Previous review (commit b6ab7a6)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The mobile/harness split is sound and the sandbox around server-authored HTML is deliberate, but the policy injection can force a resource without an explicit <head> into quirks mode, and the view row's Retry does not reload the page it claims to retry.

Assumptions: [INFERENCE] WebView originWhitelist/onShouldStartLoadWithRequest behaviour is proven by the author's Android run only; iOS was not exercised. @modelcontextprotocol/ext-apps internals (size-change validation, AppBridge.connect semantics) could not be read in this sandbox, so the analysis of the bridge relies on the new tests and the package's public contract. Dependencies are not installed here, so the reported typecheck/test runs were not reproduced. Deliberate limits stated in the PR body (no permission prompt for view callbacks, input-only rendering for reopened transcripts) were treated as accepted policy, not findings.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/chat/remote-mcp-app.ts 120 Policy meta is prepended before the doctype when the resource has no <head>, which puts the view in quirks mode

SUGGESTION

File Line Issue
apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx 216 Retry after a WebView page failure re-reads the document but never reloads the page
apps/mobile/src/lib/chat/remote-mcp.ts 391 The view's resource read uses the 60 s call deadline and is not cancelled on unmount
apps/mobile/src/lib/chat/remote-mcp.ts 140 Retained call answers are capped by count, not by size
Files Reviewed (99 files)
  • packages/harness-sdk/src/plugins/remote-mcp/app.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/app-contract.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/client.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/deps.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/index.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/tools.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/remote-mcp-app.test.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/remote-mcp-offered.test.ts - no issues
  • packages/harness-sdk/src/plugins/remote-mcp/remote-mcp-fixture.ts - no issues
  • packages/harness-sdk/.oxlintrc.json - no issues
  • packages/harness-sdk/README.md - no issues
  • apps/mobile/src/components/agents/tool-cards/mcp-app-card.tsx - 1 issue
  • apps/mobile/src/components/agents/tool-cards/index.ts - no issues
  • apps/mobile/src/components/agents/tool-part-renderer.tsx - no issues
  • apps/mobile/src/components/agents/tool-part-renderer.test.ts - no issues
  • apps/mobile/src/components/todos/todo-list-model-picker.mounted.test.tsx - no issues
  • apps/mobile/src/lib/chat/remote-mcp-app.ts - 1 issue
  • apps/mobile/src/lib/chat/remote-mcp-app.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp.ts - 2 issues
  • apps/mobile/src/lib/chat/remote-mcp-state.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-state.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-surface.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-views.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp-views.test.ts - no issues
  • apps/mobile/src/lib/chat/remote-mcp.test-helpers.ts - no issues
  • apps/mobile/src/i18n/locales/*.json (87 catalogs) - no issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 81.5K · Output: 4.5K · Cached: 393.2K

Review guidance: REVIEW.md from base branch main

Four things the view's own review found:

- A document without a head gets its policy after the doctype. Putting it in
  front of the doctype is a parse error, and the parser draws the whole
  document in quirks mode from there.
- A retry gets a page of its own. A page that failed to load has no host object
  left to hand a document to, so re-reading the resource into the same webview
  could never recover.
- The document read is bounded like a discovery and ends with the row that
  asked: 15 seconds, and the request is aborted when the row goes away.
- The answers kept for the rows are bounded by characters as well as by count.
  This is the only place the app holds a server's payload after a call ends.

Proof: the policy tests cover a document that starts with a doctype; the
retained-answer tests cover eviction by count and by size.
Comment thread apps/mobile/src/lib/chat/remote-mcp.ts Resolved
@iscekic

iscekic commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-ran the Android proof on the head that includes the review fixes (retry remount, bounded read, bounded retention, doctype placement). Same fixtures as the first run: the official 1.7.5 basic-server-vanillajs example over Streamable HTTP, a scripted chat backend that answers with the discovered mcp_apps-example_get-time call, both over adb reverse, on an owned fake account.

The view drew the live result (Server Time: …16:56:32.457Z), and tapping the App's own Get Server Time button called back through the bridge and re-rendered with …16:57:02.633Z — the capture attached here is that state.

MCP App view after its own button called the server back through the bridge, on the final head

…ript-views

# Conflicts:
#	apps/mobile/src/components/todos/todo-list-model-picker.mounted.test.tsx
…for the page

Three things the second review pass found:

- The read no longer carries `onCallFailure`. A row that unmounts aborts its
  read, and that abort was classified as the server being unreachable, which
  published the server as failed and dropped its tools. A read that does not
  answer is reported by the row's own Retry.
- The page's loaded flag resets on a retry, not on a re-read. A re-read for the
  same mounted page (a re-discovery republishes the view) would otherwise wait
  for a load that never comes again and leave the row blank.
- An answer larger than the retention bound on its own is not kept, so no
  single server payload can exceed the cap the module documents.

Proof: the retained-answer tests cover eviction by count, by accumulated size
and by one oversized answer; the read's test asserts it stays bounded and
unreported.
@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 21, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 22, 2026
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Resolved the main-branch conflict and checked the merged integration.

  • Arabic, Gujarati, Kannada, Lithuanian, and Latvian retain both this PR's MCP App strings and main's plural tool-count entries.
  • Adapted the host bridge's result handling to Effect 4. Successful calls still return the full structured result; a server-refused call still returns an error result to the view.
  • pnpm install --frozen-lockfile passed.
  • Harness SDK check:ci passed: typechecks, lint, 58 test files / 366 tests, build, platform and package checks.
  • Mobile typecheck, lint, unused-code check, catalog check, and plural check passed. Eight affected mobile test files passed, covering 831 tests.
  • A throwaway runtime smoke used the built SDK against a loopback-only HTTP MCP server. It observed discovery, resource read, AppBridge initialization, a structured tool callback, and server-refused-call recovery on Effect 4.
  • A separate i18next runtime smoke rendered all three App-state strings and six plural counts in each conflicted locale.

This follow-up changes no visual layout. The Android screenshots already attached to this PR remain the visual evidence; Android was not rerun for this merge recovery.

@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Fixed the CI typecheck/lint failure introduced by the newer base's WebView 14 declarations. Its default generic is now undefined; the transcript view's ref therefore made its props resolve to never. The ref and JSX now explicitly use unknown for the no-extra-props generic, preserving the complete WebView props type without a cast or suppression.

Verification:

  • Checked out the exact synthetic merge tree used by the failing CI run in an isolated worktree, installed its frozen lockfile, built SDK/tRPC prerequisites, and passed mobile typecheck and lint with WebView 14.0.1 and Expo SDK 58.
  • The same typecheck and lint passed on the branch's WebView 13 dependency tree.
  • Three transcript/view regression files passed, covering 755 tests.
  • Comparing the component's transformed output before and after the two type annotations produced byte-identical JavaScript. No runtime or layout change was introduced.

@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 22, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 3ab9012 Oct 22, 2026
176 checks passed
@iscekic
iscekic deleted the drain/mcp-apps-transcript-views branch October 22, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain janitor:library-suggester Library-opportunity finding from the janitor library suggester merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants