Repository navigation
[Bug]: Jira MCP Apps render blank inline cards despite successful tool results #16992
Description
Problem
In the 2026-10-07 desktop nightly, Jira MCP Apps embeds appear as empty rounded white rectangles in a thread. In light mode the rectangles blend into the background but still reserve space. Collapsing the reasoning trace leaves four app embeds: two working Confluence document cards and two blank Jira cards.
This is separate from the Confluence dark-mode text contrast report (#16991).
Read-only findings
Initial read-only inspection of persisted turn items and captured HTML establishes:
- Exactly four tool outputs carry
t3McpApp: twogetConfluencePagecalls and twogetJiraIssuecalls from the Atlassian MCP integration. - All four tool items completed successfully. Neither Jira result has
isError: true; both contain structured issue data (issuesandcontext, with one issue node). - All four captured HTML attachments exist. The two Jira documents are identical, approximately 2.7 KB HTML shells containing an empty app root and an external module script plus stylesheet. The two Confluence documents are identical, approximately 753 KB captured documents with their JavaScript inline.
- A nearby
preview_evaluatecall failed withPreview automation evaluate timed out after 15000ms.Its stored output is a text error and contains not3McpAppreference. One Jira capture precedes that timeout; the other follows it immediately, explaining why a blank app appears beneath the error. - A read-only HTTP request to the Jira shell's public module asset currently returns 200, JavaScript content, and
Access-Control-Allow-Origin: *. The declared resource CSP permits its origin. This does not prove the asset loaded or executed successfully in the affected client.
The blank rows therefore belong to successfully captured Jira MCP resources, rather than an embed created by the evaluate failure. The initial resource read/capture succeeded. Subsequent synthetic browser verification isolates an uncaught cookie-access exception during Jira app rendering (details below). Temporal proximity alone does not establish a connection to browser automation.
Source inspection
Latest main at inspection: 3143335fc3a568cbbb5174764961889272135cb9.
session-logic.tscreates standalone MCP app rows from completed tool items carrying a matching reference. These survive collapsing the work trace.McpAppSnapshot.tscaptures HTML and injects CSP; it does not snapshot external JS/CSS dependencies or establish that the app renders.McpAppFrame.tsxretains an app-height container (initially 320 px) and loads a sandboxed opaque-origin iframe. Its visible failure state covers asset URL acquisition; it does not cover module execution failure, missing app initialization, or an empty rendered root.host.tswaits forui/notifications/initializedbefore replaying the tool result, with no initialization-failure state exposed to the frame. An app that never initializes can remain empty indefinitely.
These explain why a client-side app failure can leave reserved blank space. The synthetic reproduction below narrows the failure to Jira cookie access in an opaque-origin sandbox. The actual nightly client was subsequently inspected in Zen; see the live confirmation below.
Authorized synthetic browser verification
Used a separate T3 collaborative browser tab, without accessing the private thread or manipulating the user's browser. The test recreated the Jira HTML shell using its public module/CSS assets, sandbox="allow-scripts allow-forms", a permitting resource CSP, a minimal MCP initialize response matching the inspected host, and entirely synthetic issue data (DEMO-1). It was an isolated app reproduction, not a full T3 client integration test.
Observed sequence:
- Both the JavaScript module and stylesheet loaded successfully (HTTP 200).
- The app sent
ui/initializeand completedui/notifications/initialized. - After receiving synthetic
ui/notifications/tool-result, an uncaughtSecurityErroroccurred:
Failed to read the 'cookie' property from 'Document':
The document is sandboxed and lacks the 'allow-same-origin' flag.
The public bundle stack passes through cookie get, getAnonymousIdFromStorage, getAnonymousIdFromCookieAndUpdateLocalStorage, and getAnonymousId, then analytics construction and a React effect. The first throwing location is bundle line 123, column 65004. The app root subsequently contains zero child elements and no text.
- As a diagnostic control, an empty cookie getter/setter was substituted only inside the synthetic frame before the bundle ran. Without changing the sandbox, module, synthetic result, or host response, the card rendered
Synthetic example issue,Synthetic project,DEMO-1, andOpen. The app reported a height of 76 px. - Removing that diagnostic substitution and repeating the result replay restored the cookie exception and empty root.
An IndexedDB access-denied promise rejection also occurs, including in the control where the card renders. It is therefore not the blocking exception isolated by this test. localStorage setup warnings similarly do not prevent initialization.
This establishes a reproducible incompatibility between this Jira bundle's unguarded analytics cookie access and the host's intentional opaque-origin sandbox. It strongly explains the two blank Jira embeds, independently of the nearby preview automation timeout. The subsequent Zen inspection confirms the same cookie-access failure in the actual nightly client.
The diagnostic cookie substitution is not a proposed production fix. Broadening the iframe's origin privileges is not necessary to demonstrate the cause. Any eventual fix should preserve sandbox isolation and ensure widget telemetry/storage failures cannot erase the result; the host should also expose app runtime failures rather than leaving blank space.
Confirmed in the actual Zen nightly client
After explicit authorization, inspected the affected thread in the logged-in hosted nightly (0.0.46-nightly.20261007.2787) using Zen's native UI and Developer Tools. No scripts or diagnostic substitutions were applied to the live thread.
- Both Jira iframe accessibility trees have no child content; both Confluence frames have document-card content.
- The Jira bundle logs receiving
ui/notifications/tool-inputandui/notifications/tool-result. Result replay therefore reached the app after initialization. - The console contains two uncaught exceptions from the Jira bundle:
DOMException: Document.cookie getter: Forbidden in a sandboxed
document without the 'allow-same-origin' flag.
The source-mapped stack in both cases is:
js-cookie/dist/js.cookie.mjs:80
analytics-web-client/.../cookie/cookieWrapper.js:30
getAnonymousIdFromStorage (integration/user.js:41)
getAnonymousIdFromCookieAndUpdateLocalStorage (integration/user.js:94)
getAnonymousId (integration/user.js:32)
analyticsWebClient.js:585
web/src/analytics/analytics-client.tsx:58
web/src/analytics/index.tsx:26
web/src/jira/jira-widget.tsx:117
React
This matches the synthetic reproduction and confirms the failure is after successful initialization/result delivery, rather than the nearby preview_evaluate timeout or failure to fetch the app's module. An initialization-only deadline would not catch this case. No private screenshots, URLs, identifiers, or tool data are included here. Zen was returned to the thread view with Developer Tools closed.
Expected behavior
Successful Jira tool results should render their app cards. If an app cannot load, initialize, or render, the thread should expose an understandable failure and access to the tool result instead of indefinite blank space.
Scope and related issues
Observed in the desktop report and confirmed in hosted web through Zen. Mobile behavior was not verified. #7212 concerns the floating collaborative browser panel, not inline MCP app cards. #16991 concerns Confluence theme contrast. Searches for MCP blank/empty/embed/render and Jira reports found no matching issue.
No private screenshots, thread IDs, customer identifiers, private URLs, or raw tool payloads are included. Live state was accessed read-only. After explicit authorization, a separate synthetic preview tab and read-only inspection of the actual nightly thread in Zen were used. No private screenshots were taken or published, and no code changes were made.
Activity
juliusmarminge commentedon Oct 7, 2026
Note
Grok responding on behalf of Julius.
Thanks for the detailed write-up. I checked this against main at
3143335fc3, which includes the MCP Apps host from #16236. I haven't reproduced it in a running app, and I can't see the Jira shell, so everything about its HTML comes from your report.Confirmed in code: a card that never initializes stays blank, with no failure state and no way to see the tool result
mcp-approw instead of the normal tool row (session-logic.ts#L733-L750). The app card is therefore the only view of that result.MCP_APP_DEFAULT_HEIGHT = 320(mcpApp.ts#L21, McpAppFrame.tsx#L94, #L496).tool-input/tool-resultonly afterui/notifications/initialized(host.ts#L202-L211, #L389-L396). The spec requires that ordering (apps.mdx#L485). But the host has no timer except the 2 s teardown one (host.ts#L478-L481), and it doesn't tell the frame whether the app ever initialized.So if the app's script fails to load or throws before
ui/initialize, the result is exactly what you describe: a 320 px empty box that stays that way, with the successful tool result unreachable.What would block the Jira shell (candidates, none confirmed)
The Confluence documents have their JS inline and render. Per your report, the Jira shell is an empty root plus an external module script and stylesheet. Under the host's policy, that difference matters:
<meta>into the document (McpAppSnapshot.ts#L90-L96). Itsscript-src/style-srcare only'unsafe-inline'plus the declaredresourceDomains, andconnect-srcdefaults to'none'(mcpApp.ts#L178-L195). Declared domains are filtered to bare origins: scheme + host (optional leading*.) + optional port (mcpApp.ts#L63-L76). A declared entry with a path or a trailing/would be silently dropped. Your note says the declared CSP permits the module's origin, but that's not the same as the injected one. Could you check the<meta http-equiv="Content-Security-Policy">at the top of the captured Jira attachment and confirm the module/stylesheet origin appears inscript-src/style-src? Please also check whether any API origin the app fetches appears inconnect-src.sandbox="allow-scripts allow-forms"and neverallow-same-origin(McpAppFrame.tsx#L535-L536). The asset route also sendsContent-Security-Policy: sandbox allow-scripts allow-forms allow-popups(http.ts#L56-L61). Module scripts are fetched with CORS (MDN), so a request from an opaque origin needs a permissiveAccess-Control-Allow-Origin, which you saw (*). More likely to matter: in an opaque origin,localStoragethrowsSecurityError(MDN). An app that touches storage during startup would die beforeui/initialize. The spec's web-host design runs the view inside a sandbox proxy withallow-scripts+allow-same-originon a separate origin (apps.mdx#L474-L475). T3 omitsallow-same-originon purpose.'unsafe-eval'inscript-src(also true of the spec's sample policy). It only matters if the bundle evals.Which of these hits the Jira app needs the iframe console, so it's not pinned yet.
Possible fixes (for discussion)
initialized/stalledsignal. If the app hasn't sentui/notifications/initializedwithin N seconds, the frame swaps the blank box for "The {server} app didn't load" with a toggle that shows the plain tool call/result (it already has that data viauseTurnItemDetail, McpAppFrame.tsx#L150-L162). This fixes the blank space whatever the root cause.Not verified: the actual runtime failure in the Jira iframe, the injected CSP in your captured document, and mobile/web behavior (mobile uses a separate
McpAppWebView). No open PR currently touchesMcpAppFrame.tsx,host.ts,mcpApp.tsorMcpAppSnapshot.ts. Related but separate: #16991 (Confluence dark-mode contrast).kaovilai commentedon Oct 17, 2026
Note
Responses generated with Codex (model: rits/zai-org/glm-5-3)
I can reproduce a related GitHub MCP Apps case in T3 Code on 2026-10-16. This is not the same Jira render failure, but it confirms the broader class where a tool result depends on an MCP Apps UI that the thread does not expose.
Repro:
mcp__github__update_pull_requestagainstopenshift-eng/ship-help-bot#1028.awaiting_user_submission, but no MCP Apps form/card is visible in the thread UI.update_pull_requestintentionally defers to an interactive form when the client advertisesio.modelcontextprotocol/ui; the tool metadata points toui://github-mcp-server/pr-edit(pkg/github/ui_capability.go,pkg/github/pullrequests.go).gh pr edit ... --body-file ...updated the PR successfully. The GitHub MCP server also documentsmcp_apps_disable_form_deferral/X-MCP-Features: mcp_apps_disable_form_deferralto disable form deferral on that connection.For us, direct
gh pr editandgh apiattempts were blocked by local hook policy, and an unauthenticated T3 preview got GitHub's 404 page, so those paths did not test the GitHub form itself. The completed click-to-runghcommand did update PR #1028.This reinforces the fallback requirement: when an MCP App is absent, fails to initialize/render, or intentionally waits for user submission, the thread should still show the deferred status and either the form or a usable plain-tool-result escape hatch.