Skip to content

[Bug]: Jira MCP Apps render blank inline cards despite successful tool results #16992

Description

@PixPMusic

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: two getConfluencePage calls and two getJiraIssue calls from the Atlassian MCP integration.
  • All four tool items completed successfully. Neither Jira result has isError: true; both contain structured issue data (issues and context, 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_evaluate call failed with Preview automation evaluate timed out after 15000ms. Its stored output is a text error and contains no t3McpApp reference. 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.ts creates standalone MCP app rows from completed tool items carrying a matching reference. These survive collapsing the work trace.
  • McpAppSnapshot.ts captures HTML and injects CSP; it does not snapshot external JS/CSS dependencies or establish that the app renders.
  • McpAppFrame.tsx retains 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.ts waits for ui/notifications/initialized before 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:

  1. Both the JavaScript module and stylesheet loaded successfully (HTTP 200).
  2. The app sent ui/initialize and completed ui/notifications/initialized.
  3. After receiving synthetic ui/notifications/tool-result, an uncaught SecurityError occurred:
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.

  1. 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, and Open. The app reported a height of 76 px.
  2. 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-input and ui/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

  1. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    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

    • When a completed tool item carries an app reference, it becomes an mcp-app row instead of the normal tool row (session-logic.ts#L733-L750). The app card is therefore the only view of that result.
    • The frame reserves its height up front: MCP_APP_DEFAULT_HEIGHT = 320 (mcpApp.ts#L21, McpAppFrame.tsx#L94, #L496).
    • The only error text covers failing to get the asset URL: "Unable to load the … app" (McpAppFrame.tsx#L542-L546). The code itself notes that "a frame cannot report a failed load" (#L126-L129).
    • The host sends tool-input/tool-result only after ui/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:

    1. Injected CSP. At capture time the server writes a CSP <meta> into the document (McpAppSnapshot.ts#L90-L96). Its script-src/style-src are only 'unsafe-inline' plus the declared resourceDomains, and connect-src defaults 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 in script-src/style-src? Please also check whether any API origin the app fetches appears in connect-src.
    2. Opaque origin. The frame is sandbox="allow-scripts allow-forms" and never allow-same-origin (McpAppFrame.tsx#L535-L536). The asset route also sends Content-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 permissive Access-Control-Allow-Origin, which you saw (*). More likely to matter: in an opaque origin, localStorage throws SecurityError (MDN). An app that touches storage during startup would die before ui/initialize. The spec's web-host design runs the view inside a sandbox proxy with allow-scripts + allow-same-origin on a separate origin (apps.mdx#L474-L475). T3 omits allow-same-origin on purpose.
    3. No 'unsafe-eval' in script-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)

    • Add an init deadline to the host, e.g. expose an initialized/stalled signal. If the app hasn't sent ui/notifications/initialized within 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 via useTurnItemDetail, McpAppFrame.tsx#L150-L162). This fixes the blank space whatever the root cause.
    • Optionally surface CSP violations from the frame, so a blocked origin is visible instead of silent. That depends on the app document reporting them, so it needs a design pass.
    • Whether to loosen the opaque-origin sandbox, e.g. a dedicated per-app origin like the spec's sandbox proxy, is a separate security/design decision. It shouldn't be bundled with the fallback.

    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 touches McpAppFrame.tsx, host.ts, mcpApp.ts or McpAppSnapshot.ts. Related but separate: #16991 (Confluence dark-mode contrast).

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions