Skip to content

Conversation

@infoxicator
Copy link
Owner

@infoxicator infoxicator commented Oct 27, 2025

Summary by CodeRabbit

  • New Features

    • HTML resources can receive MCP context and host context; new "skybridge" HTML content path and in-page widget runtime enable state persistence, tool calls, and richer host-aware rendering.
  • Documentation

    • Resource rendering guides updated to document the new context props and behavior.
  • Tests

    • Added/updated tests covering skybridge rendering and context forwarding.
  • Chores

    • Workspace/project config adjusted.

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines 55 to +78
// Wait for HTML content from parent
window.addEventListener('message', (event) => {
if (event.source === window.parent) {
if (event.data && event.data.type === 'ui-html-content') {
const payload = event.data.payload || {};
const html = payload.html;
const sandbox = payload.sandbox;
if (typeof sandbox === 'string') {
inner.setAttribute('sandbox', sandbox);
}
if (typeof html === 'string') {
inner.srcdoc = html;
}
} else {
if (inner && inner.contentWindow) {
inner.contentWindow.postMessage(event.data, '*');
if (event.source === window.parent && event.data && event.data.type === 'ui-html-content') {
const payload = event.data.payload || {};
const html = payload.html;

Choose a reason for hiding this comment

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

P1 Badge Restore proxy forwarding for parent messages

In raw HTML proxy mode the message listener now only processes parent messages whose type is ui-html-content and otherwise returns, so any subsequent messages sent by the host (e.g., render data or UI action responses) are never relayed to the inner iframe. Previously the proxy forwarded all parent messages to the content window, which is required for postMessage-based interactions. This regression prevents the embedded widget from receiving follow‑up messages after the initial HTML payload.

Useful? React with 👍 / 👎.

Comment on lines 20 to 23
const openaiAPI = {
toolInput: ${JSON.stringify(toolInput)},
toolOutput: ${JSON.stringify(initialRenderData)},
toolResponseMetadata: ${JSON.stringify(toolResponseMetadata ?? null)},

Choose a reason for hiding this comment

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

P1 Badge Escape injected data in skybridge API script

The apiScript helper embeds JSON.stringify(toolInput) and related values directly inside a <script> element. If any of those JSON strings contain </script> (or similar HTML), the browser will terminate the script early and treat the rest as markup, allowing injection and breaking the initialization of window.openai. The values should be escaped (e.g., replace </script> with <\/script>) before interpolation into the script block.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds MCP and host context propagation to HTML resource rendering, introduces skybridge HTML support, refactors processHTMLResource to accept an options object, adds an OpenAI widget runtime script and bundling, updates components to pass new context, and expands tests and docs to cover these changes.

Changes

Cohort / File(s) Change Summary
Workspace & Git
\.gitignore, pnpm-workspace.yaml
Add .pnpm-home/ to ignore; enable explicit workspace package globs and add onlyBuiltDependencies: [ "esbuild" ].
Docs & READMEs
README.md, docs/src/guide/client/html-resource.md, docs/src/guide/client/resource-renderer.md, sdks/typescript/client/README.md
Document two new optional props mcp and host for HTML/UI resource rendering, describe iframeRenderData behavior and default capabilities for host.
Types
sdks/typescript/client/src/types.ts
Add 'skybridge' to ALL_RESOURCE_CONTENT_TYPES; introduce MCPProps and HostProps types describing tool context and host/display capabilities.
Resource processing
sdks/typescript/client/src/utils/processResource.ts, sdks/typescript/client/src/utils/__tests__/processResource.test.ts
Refactor processHTMLResource(resource, options) to accept ProcessHTMLResourceOptions { proxy?, mcp?, host? }; add support for text/html+skybridge; inject OpenAI runtime script into skybridge HTML; update tests and error messages.
Components
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx, sdks/typescript/client/src/components/UIResourceRenderer.tsx, sdks/typescript/client/src/components/UIResourceRendererWC.tsx
Add mcp?: MCPProps and host?: HostProps props; pass contexts through component hierarchy; update htmlProps omission and treat text/html+skybridge (mapped to skybridge) like raw HTML; add TS-expect-error suppression in WC wrapper.
OpenAI runtime & bundling
sdks/typescript/client/scripts/bundle-open-ai-script.js, sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.ts, sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.bundled.ts, sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts
Add TypeScript runtime implementing a widget API (state persistence, callTool with timeout, follow-ups, display controls, openExternal) and a bundling script that emits a serialized API_RUNTIME_SCRIPT; add helper to produce inline widget script with config.
Tests
sdks/typescript/client/src/components/__tests__/HTMLResourceRenderer.test.tsx, sdks/typescript/client/src/components/__tests__/UIResourceRenderer.test.tsx
Update expectations to accept text/html+skybridge; add tests verifying mcp/host forwarding and that renderData is kept separate from toolOutput; adjust mocks to new options API.
Proxy HTML runtime (dev proxy)
sdks/typescript/client/scripts/proxy/index.html
Remove CSP meta tag and implement in-iframe raw HTML rendering path that writes markup into iframe document; simplify message handling for ui-html-content.

Sequence Diagram(s)

sequenceDiagram
    actor UI as UIResourceRenderer
    participant HTML as HTMLResourceRenderer
    participant Proc as processHTMLResource
    participant Iframe as iframe
    
    UI->>HTML: pass resource, mcp, host
    HTML->>Proc: processHTMLResource(resource, { proxy, mcp, host })
    
    alt content is skybridge
        Proc->>Proc: create OpenAI runtime script via createOpenAiScript
        Proc->>Proc: inject runtime into HTML (head/html/prepend)
        Proc-->>HTML: return enhanced HTML + renderData
    else standard HTML
        Proc-->>HTML: return html or src/srcdoc info
    end
    
    HTML->>Iframe: render with renderData (iframeRenderData + metadata)
    note right of Iframe: toolOutput from mcp is not merged into renderData
Loading
sequenceDiagram
    participant Window as widget window
    participant Runtime as OpenAI widget runtime
    participant Parent as window.parent
    participant Storage as localStorage

    Window->>Runtime: read __MCP_WIDGET_CONFIG__
    Runtime->>Runtime: normalize defaults (theme, locale, displayMode, capabilities)
    Runtime->>Storage: restore widgetState
    Runtime->>Window: attach openai & webplus globals
    Runtime->>Parent: postMessage webplus:set_globals

    Window->>Runtime: openai.callTool(tool, params)
    Runtime->>Parent: postMessage openai:callTool (with requestId)
    Parent-->>Runtime: postMessage response
    Runtime->>Window: resolve callTool promise

    Window->>Runtime: openai.setWidgetState(state)
    Runtime->>Storage: persist state
    Runtime->>Parent: postMessage openai:setWidgetState
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • Areas requiring extra attention:
    • sdks/typescript/client/src/utils/processResource.ts — signature change, skybridge injection logic, proxy/security handling.
    • sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.ts (and bundled output) — initialization, timeout/response handling, messaging structure, and storage interactions.
    • Component updates: HTMLResourceRenderer.tsx and UIResourceRenderer.tsx — verify prop threading, renderData separation, and iframe lifecycle.
    • Tests: ensure updated tests correctly reflect new options shape and skybridge behaviors.

Poem

🐰 A tiny rabbit, script in paw,

whispers mcp and host to every iframe door.
Skybridge stitches contexts, tidy and bright,
runtime bundled snug, ready for flight.
Hop, little widget — message and store!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Feature/skybridge client' is vague and non-descriptive, using generic branch-naming convention terminology that does not convey meaningful information about the core changes. Replace with a specific, descriptive title that explains the primary change, such as 'Add skybridge HTML content type and OpenAI widget runtime support' or 'Implement skybridge content type with OpenAI widget API integration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/skybridge-client

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
sdks/typescript/client/scripts/proxy/index.html (1)

73-86: Restore proxy forwarding for parent messages.

The message listener now only processes parent messages whose type is ui-html-content (line 74) and returns early for all other message types. This means subsequent messages sent by the host (e.g., render data via ui-lifecycle-iframe-render-data, or UI action responses via ui-message-response) are never relayed to the inner iframe. The proxy must forward all non-ui-html-content parent messages to the content window to support postMessage-based interactions.

Apply this diff to forward other parent messages to the inner iframe:

         window.addEventListener('message', (event) => {
           if (event.source === window.parent && event.data && event.data.type === 'ui-html-content') {
             const payload = event.data.payload || {};
             const html = payload.html;
             if (typeof html === 'string') {
               if (!renderHtmlInIframe(html)) {
                 pendingHtml = html;
               }
             }
-          } else if (event.source === inner.contentWindow) {
+          } else if (event.source === window.parent) {
+            // Forward other messages from parent to inner iframe
+            inner.contentWindow.postMessage(event.data, '*');
+          } else if (event.source === inner.contentWindow) {
             // Relay messages from inner to parent
             window.parent.postMessage(event.data, '*');
           }
         });
sdks/typescript/client/src/utils/processResource.ts (1)

23-27: Context wiring and skybridge script injection look solid; verify widgetStateKey scoping

The new ProcessHTMLResourceOptions shape and the extraction of MCP (toolName, toolInput, toolOutput, toolResponseMetadata) and host (theme, locale, userAgent, model, displayMode, maxHeight, safeArea, capabilities) context into the text/html+skybridge branch are coherent, and passing everything through createOpenAiScript keeps the skybridge runtime nicely encapsulated.

Because createOpenAiScript serializes the config and runs it through escapeForInlineScript before embedding it into <script> tags, the earlier P1 concern about unescaped JSON breaking out of the inline script block should be addressed by this flow.

One thing to double-check: widgetStateKey is derived only from toolName:

const widgetStateKey = `openai-widget-state:${toolName ?? ''}:`;

That means all widget instances for the same toolName on a given origin will share a single localStorage entry and overwrite each other’s widgetState. If you expect multiple independent widgets per tool (e.g., multiple HTML resources or conversations using the same tool), you may want to incorporate an instance-specific identifier (resource URI, message ID, etc.) into the key to avoid collisions.

Also applies to: 33-46, 167-191

🧹 Nitpick comments (6)
sdks/typescript/client/src/types.ts (1)

106-133: Context prop shapes look good; consider sharing with widget config types

MCPContextProps and ClientContextProps are flexible and line up with how mcp/host are forwarded into the HTML render pipeline. As a small follow‑up, consider reusing or mirroring these shapes in the widget config/runtime types (e.g. SafeArea, capabilities) to avoid future drift between public types and the runtime script.

sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.ts (1)

123-172: Consider tightening postMessage handling for tool calls

callTool works correctly (per‑call listener, timeout cleanup, safe Promise resolution), but it currently:

  • Listens on window and matches only by type and requestId.
  • Sends messages with targetOrigin: '*'.

If you have access to the parent origin (or another trusted origin) at config time, it would be safer to:

  • Restrict the postMessage calls to that origin.
  • Optionally ignore responses whose event.source isn’t the expected parent window / origin.

Not critical given the random requestId, but worth tightening when possible.

sdks/typescript/client/src/components/UIResourceRendererWC.tsx (1)

62-63: Avoid long‑lived @ts-expect-error by typing normalizeJsonProp generically

The suppression here is understandable, but it hides real drift between the JSON shape and UIResourceRendererProps['remoteDomProps']. You can usually remove the @ts-expect-error by making normalizeJsonProp generic at the call site, e.g.:

-function normalizeJsonProp(prop: unknown): Record<string, unknown> | undefined {
+function normalizeJsonProp<T extends Record<string, unknown>>(prop: unknown): T | undefined {
   if (typeof prop === 'object' && prop !== null) {
-        return prop as Record<string, unknown>;
+        return prop as T;
   }
   if (typeof prop === 'string' && prop.trim() !== '') {
     try {
-        return JSON.parse(prop);
+        return JSON.parse(prop) as T;
     } catch (e) {
       console.error('Failed to parse JSON prop:', { prop, error: e });
       return undefined;
     }
   }
 }

and then:

const remoteDomProps = normalizeJsonProp<UIResourceRendererProps['remoteDomProps']>(rawRemoteDomProps);

This keeps runtime behavior the same while regaining type safety.

sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.bundled.ts (1)

4-4: Tighten postMessage handler checks in callTool

The bundled runtime’s callTool implementation listens to "message" events on window and matches only on data.type and requestId. It doesn’t verify event.source or event.origin, so a script that can obtain a reference to this frame could spoof a openai:callTool:response and incorrectly resolve/reject the Promise.

Since this file is generated, consider updating the source (open-ai-runtime-script.ts) so the handler only processes events from the expected peer, e.g.:

  • Check event.source === window.parent before inspecting event.data.
  • Optionally validate event.origin against an expected/allowed origin.

That would harden the channel without changing the public API.

sdks/typescript/client/src/utils/processResource.ts (2)

111-135: Proxy handling for URL and HTML paths is consistent; consider DRY’ing the same-origin guard

The proxy logic for both text/uri-list and HTML (text/html / text/html+skybridge) correctly:

  • Validates the proxy string with new URL(proxy).
  • Rejects proxies whose host matches window.location.host, avoiding an easy sandbox escape.
  • Logs and falls back gracefully on invalid proxy URLs.

The same-origin guard and error messaging are duplicated across the two branches. If this utility keeps evolving, you might consider extracting a small helper like getSafeProxyUrl(proxy) (encapsulating the window check, host comparison, and logging) to ensure future changes stay in sync between the URL and HTML flows.

Also applies to: 194-219


239-251: Remote DOM base64 decoding is consistent with other branches

The processRemoteDOMResource path now uses the same base64 → Uint8ArrayTextDecoder pattern as the HTML and URI-list handlers, which keeps behavior and error handling aligned across resource types. If this pattern appears elsewhere, a small shared helper (e.g., decodeBase64ToString(blob)) could reduce duplication and centralize future changes (like handling non‑browser environments).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bc3c64 and 6adfb39.

📒 Files selected for processing (19)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • docs/src/guide/client/html-resource.md (2 hunks)
  • docs/src/guide/client/resource-renderer.md (2 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • sdks/typescript/client/README.md (1 hunks)
  • sdks/typescript/client/scripts/bundle-open-ai-script.js (1 hunks)
  • sdks/typescript/client/scripts/proxy/index.html (1 hunks)
  • sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.bundled.ts (1 hunks)
  • sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.ts (1 hunks)
  • sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (1 hunks)
  • sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (8 hunks)
  • sdks/typescript/client/src/components/UIResourceRenderer.tsx (5 hunks)
  • sdks/typescript/client/src/components/UIResourceRendererWC.tsx (1 hunks)
  • sdks/typescript/client/src/components/__tests__/HTMLResourceRenderer.test.tsx (3 hunks)
  • sdks/typescript/client/src/components/__tests__/UIResourceRenderer.test.tsx (4 hunks)
  • sdks/typescript/client/src/types.ts (2 hunks)
  • sdks/typescript/client/src/utils/__tests__/processResource.test.ts (10 hunks)
  • sdks/typescript/client/src/utils/processResource.ts (11 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
sdks/typescript/client/src/types.ts (1)
sdks/typescript/client/src/index.ts (1)
  • ALL_RESOURCE_CONTENT_TYPES (28-28)
sdks/typescript/client/src/components/__tests__/UIResourceRenderer.test.tsx (2)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (1)
  • HTMLResourceRenderer (42-267)
sdks/typescript/client/src/components/UIResourceRenderer.tsx (1)
  • UIResourceRenderer (38-72)
sdks/typescript/client/src/components/__tests__/HTMLResourceRenderer.test.tsx (2)
sdks/typescript/client/src/types.ts (1)
  • UI_METADATA_PREFIX (99-99)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (2)
  • HTMLResourceRenderer (42-267)
  • InternalMessageType (22-36)
sdks/typescript/client/src/components/UIResourceRenderer.tsx (3)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (2)
  • HTMLResourceRendererProps (7-20)
  • HTMLResourceRenderer (42-267)
sdks/typescript/client/src/components/RemoteDOMResourceRenderer.tsx (1)
  • RemoteDOMResourceProps (22-27)
sdks/typescript/client/src/types.ts (2)
  • MCPContextProps (106-111)
  • ClientContextProps (113-133)
sdks/typescript/client/src/utils/__tests__/processResource.test.ts (1)
sdks/typescript/client/src/utils/processResource.ts (1)
  • processHTMLResource (29-231)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (4)
sdks/typescript/client/src/types.ts (2)
  • MCPContextProps (106-111)
  • ClientContextProps (113-133)
sdks/typescript/client/src/utils/metadataUtils.ts (1)
  • getUIResourceMetadata (8-19)
sdks/typescript/client/src/utils/processResource.ts (1)
  • processHTMLResource (29-231)
sdks/typescript/server/src/index.ts (2)
  • ReservedUrlParams (143-145)
  • InternalMessageType (131-141)
sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (2)
sdks/typescript/client/src/types.ts (1)
  • ClientContextProps (113-133)
sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.bundled.ts (1)
  • API_RUNTIME_SCRIPT (4-4)
sdks/typescript/client/src/utils/processResource.ts (2)
sdks/typescript/client/src/types.ts (2)
  • MCPContextProps (106-111)
  • ClientContextProps (113-133)
sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (1)
  • createOpenAiScript (31-59)
🔇 Additional comments (23)
.gitignore (1)

52-52: LGTM!

Adding .pnpm-home/ to the PNPM ignore section is standard practice; this directory contains machine-specific PNPM configuration that shouldn't be committed.

sdks/typescript/client/src/types.ts (1)

6-11: skybridge content type addition looks consistent

Extending ALL_RESOURCE_CONTENT_TYPES with 'skybridge' keeps ResourceContentType in sync and matches the updated HTML tests and docs around text/html+skybridge. No issues from this file’s side.

pnpm-workspace.yaml (1)

2-6: Workspace and onlyBuiltDependencies configuration looks appropriate

Including sdks/typescript/*, examples/*, and docs as workspaces and marking esbuild as an onlyBuiltDependencies entry is aligned with the new TS client tooling and bundling flow. Nothing concerning here.

sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.ts (3)

12-27: Widget config, defaults, and API surface are coherent

The WidgetConfig/OpenAIWidgetAPI shapes, plus the resolved defaults for display mode, sizing, theme/locale, safe area, and capabilities, line up cleanly and give HTML widgets a predictable environment. Defaulting capabilities and merging them with any provided config is a nice touch for host feature detection.

Also applies to: 32-45, 54-67, 95-107


123-137: Widget state persistence and hydration look robust

Persisting widgetState behind a keyed localStorage entry, wrapping both setItem and later getItem/JSON.parse in try/catch, and notifying the parent via openai:setWidgetState gives a solid, failure‑tolerant state channel. The async hydration after initialization is also a good fit to avoid blocking setup.

Also applies to: 252-282


238-251: Global exposure and config cleanup are reasonable

Defining non‑writable, non‑configurable window.openai and window.webplus plus deleting/undefining __MCP_WIDGET_CONFIG__ keeps the public API stable and avoids leaking config. Just be aware this will override any pre‑existing window.openai in the iframe if one exists, which is likely the intended behavior for this runtime.

Also applies to: 284-288

sdks/typescript/client/src/components/__tests__/HTMLResourceRenderer.test.tsx (2)

74-75: Updated error messaging matches new HTML mime support

The test expectations for the invalid mimeType error now mention text/html+skybridge alongside text/html and text/uri-list. This keeps the error copy aligned with the expanded content types and will guard against regressions in the user‑facing message.

Also applies to: 362-363


537-575: Good coverage to keep render data distinct from MCP tool output

The "should keep render data separate from tool output context" test clearly asserts that iframeRenderData + metadata initial render data are the only fields sent in the UI_LIFECYCLE_IFRAME_RENDER_DATA payload, even when mcp.toolOutput is present. This directly guards the new design where tool context flows through widget config rather than being merged into render data.

sdks/typescript/client/src/components/__tests__/UIResourceRenderer.test.tsx (1)

32-33: Tests correctly track HTML renderer props and context forwarding

Switching expectations to expect.objectContaining({ resource }) / { resource, proxy: ... } makes the tests robust to additional props on HTMLResourceRenderer, which is useful now that mcp/host exist. The new "should forward context props to HTMLResourceRenderer" test nicely validates that UIResourceRenderer passes mcp and host through as intended.

Also applies to: 40-41, 78-79, 88-103, 105-118

sdks/typescript/client/README.md (1)

101-103: Client README accurately documents new mcp/host props

The added bullets for mcp and host clearly describe the MCP invocation context and host context (including default { hover: true, touch: false } capabilities) and note that HTML‑specific overrides can be supplied via htmlProps. This aligns with how the props are threaded into HTMLResourceRenderer.

README.md (1)

101-102: Root README clarifies tool output handling for HTML runtimes

The updated mcp/host docs call out that toolOutput is injected into supported runtimes (e.g. Skybridge) via widget config and explicitly not merged into iframeRenderData. That matches the new test coverage and is a useful nuance for integrators.

docs/src/guide/client/html-resource.md (1)

9-18: LGTM!

The documentation clearly describes the new mcp and host props, including their types, purpose, and how they integrate with the HTML resource rendering flow.

docs/src/guide/client/resource-renderer.md (1)

45-54: LGTM!

The documentation accurately describes the new mcp and host props and their integration with htmlProps. The explanation of how these props can be provided either at the top level or within htmlProps is clear.

sdks/typescript/client/src/utils/__tests__/processResource.test.ts (2)

313-353: LGTM!

The test comprehensively verifies that MCP and host context are correctly injected into skybridge HTML, checking for all expected fields including toolInput, toolOutput, toolResponseMetadata, toolName, theme, locale, userAgent, model, displayMode, maxHeight, safeArea, and capabilities.


355-371: LGTM!

The test correctly verifies that the runtime script is injected even when <html> and <head> tags are missing, which is an important edge case for skybridge HTML snippets.

sdks/typescript/client/src/components/UIResourceRenderer.tsx (2)

11-14: LGTM!

The props are correctly typed, with htmlProps properly excluding mcp and host to prevent duplication, allowing these contexts to be provided either at the top level or within htmlProps.


46-59: LGTM!

The skybridge content type is correctly handled alongside rawHtml and externalUrl, with mcp and host props properly forwarded to HTMLResourceRenderer.

sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (2)

22-29: LGTM!

The escaping function correctly sanitizes JSON for inline script embedding by escaping angle brackets, ampersands, and Unicode line terminators (U+2028, U+2029), which prevents script injection and ensures the JSON is safely embedded in HTML context.


31-59: API_RUNTIME_SCRIPT is safe and properly sourced from trusted, controlled code.

Verification confirms the runtime script is securely generated from version-controlled TypeScript source code. The bundling script reads exclusively from the source file with no external or user-controlled inputs. The bundled output is JSON.stringify()'d before being embedded as a constant, and user configuration data is separately protected with escapeForInlineScript() which covers XSS vectors (<, >, &, U+2028, U+2029). No injection vectors were identified.

sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (3)

61-69: LGTM!

The renderData computation correctly merges metadata-derived initial render data with the iframeRenderData prop, with prop values taking precedence for duplicate keys. This maintains backward compatibility while supporting the new context propagation.


71-79: LGTM!

The processHTMLResource call is correctly updated to use the new options-based API, passing proxy, mcp, and host as an options object. The useMemo dependencies are also correctly updated to include the new props.


120-222: LGTM!

The message handling logic correctly uses renderData throughout for all lifecycle events (ui-proxy-iframe-ready, ui-lifecycle-iframe-ready, ui-request-render-data), ensuring consistent data delivery to iframes. The useEffect dependencies are properly updated to include renderData.

sdks/typescript/client/scripts/bundle-open-ai-script.js (1)

1-1: No action needed — esbuild is properly declared.

The review comment's concern about a missing esbuild dependency is incorrect. esbuild is already declared in devDependencies with version ^0.25.5 in sdks/typescript/client/package.json, so the script will execute without dependency-related errors.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
sdks/typescript/client/src/utils/processResource.ts (1)

111-135: <!-- [scratchpad_start]
[task verify_proxy_url_handling]
Status: Analyzing grep results for proxy URL patterns

[observations]

  • Documentation (docs/src/guide/client/using-a-proxy.md) consistently shows absolute URLs: 'https://proxy.mcpui.dev'
  • Line 60 explicitly states: "For security reasons, you MUST NOT host the proxy script on the same origin"
  • Line 66: Requires validation as "http:" or "https:" (absolute URLs)
  • Test cases (processResource.test.ts lines 193-279) all use absolute URLs for passing tests
  • Test line 217 uses 'not-a-valid-url' (relative-like) and verifies fallback behavior
  • Code at lines 113 and 196: new URL(proxy) - constructor requires absolute URL per spec
  • Error handling catches thrown errors from invalid proxy strings
  • All examples in docs use full absolute URLs

[analysis]

  • The review comment's concern is VALID: new URL(proxy) without a base parameter throws on relative URLs
  • Current behavior: silent fallback with console.error when relative URL is passed
  • Documentation shows only absolute URL usage patterns
  • No explicit documentation stating proxy must be absolute
  • Tests confirm relative URLs do fall back (not a new bug, existing behavior)
  • The silent fallback could confuse developers who try to use relative paths

[conclusion]
The original review comment accurately identifies the issue. The code requires absolute URLs due to new URL() constructor requirements. The suggestion to either:

  1. Support relative URLs via resolving against window.location.origin, OR
  2. Document the absolute URL requirement
    ...is a valid refactoring suggestion, not a critical bug.

[done]
Verification complete - original review is sound.
[scratchpad_end]

-->

Consider documenting that proxy must be an absolute URL, or support relative URLs

The current code silently falls back when a relative proxy path (e.g., '/mcp-proxy') is provided, since new URL(proxy) requires an absolute URL. This could confuse developers who expect relative paths to work.

You have two options:

  1. Document the constraint: Add a JSDoc comment or guide entry explicitly stating that proxy must be an absolute URL (e.g., https://proxy.example.com).

  2. Support relative URLs: Resolve relative proxy URLs against window.location.origin when available:

    const base = typeof window !== 'undefined' ? window.location.origin : undefined;
    const proxyUrl = base ? new URL(proxy, base) : new URL(proxy);

Both approaches apply to lines 111–135 (for text/uri-list) and 194–219 (for text/html).

♻️ Duplicate comments (1)
sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (1)

4-18: Inline script escaping correctly fixes the prior injection risk

This refactor cleanly addresses the earlier P1 about embedding JSON into an inline <script>:

  • OpenAiScriptOptions captures exactly the MCP/Host fields you need.
  • createOpenAiScript builds a config object, JSON.stringify’s it, then runs it through escapeForInlineScript, which escapes <, >, &, and the problematic U+2028/U+2029 characters.
  • The escaped JSON is inlined as a JavaScript object literal:
window.__MCP_WIDGET_CONFIG__ = ${escapedConfig};

so any </script> or similar sequences coming from tool input/output cannot break out of the script block.

Given the runtime bundle reads window.__MCP_WIDGET_CONFIG__ and then deletes it, the lifecycle is tight and the surface area small. This looks correct and resolves the previously reported injection concern.

Also applies to: 20-29, 31-59

🧹 Nitpick comments (1)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (1)

120-222: Add autoResizeIframe to the useEffect dependency list

The handleMessage effect reads autoResizeIframe when handling UI_SIZE_CHANGE (lines 180–187), but autoResizeIframe is not included in the dependency array on line 222:

}, [onUIAction, initialRenderData, iframeRenderMode, htmlString, iframeSrcToRender, sandbox]);

If autoResizeIframe changes over the component's lifetime, the effect will continue using the old value. While low-risk in practice (the prop is typically static), this violates the React best practice enforced by the react-hooks/exhaustive-deps rule. Add it to the dependencies:

-  }, [onUIAction, initialRenderData, iframeRenderMode, htmlString, iframeSrcToRender, sandbox]);
+  }, [onUIAction, initialRenderData, iframeRenderMode, htmlString, iframeSrcToRender, sandbox, autoResizeIframe]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6adfb39 and 9e3e93f.

📒 Files selected for processing (7)
  • docs/src/guide/client/html-resource.md (2 hunks)
  • docs/src/guide/client/resource-renderer.md (2 hunks)
  • sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (1 hunks)
  • sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (4 hunks)
  • sdks/typescript/client/src/components/UIResourceRenderer.tsx (5 hunks)
  • sdks/typescript/client/src/types.ts (2 hunks)
  • sdks/typescript/client/src/utils/processResource.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/src/guide/client/html-resource.md
🧰 Additional context used
🧬 Code graph analysis (5)
sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (2)
sdks/typescript/client/src/types.ts (1)
  • HostProps (113-133)
sdks/typescript/client/src/adapters/appssdk/open-ai-runtime-script.bundled.ts (1)
  • API_RUNTIME_SCRIPT (4-4)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (2)
sdks/typescript/client/src/types.ts (2)
  • MCPProps (106-111)
  • HostProps (113-133)
sdks/typescript/client/src/utils/processResource.ts (1)
  • processHTMLResource (29-231)
sdks/typescript/client/src/utils/processResource.ts (2)
sdks/typescript/client/src/types.ts (2)
  • MCPProps (106-111)
  • HostProps (113-133)
sdks/typescript/client/src/adapters/appssdk/open-ai-script.ts (1)
  • createOpenAiScript (31-59)
sdks/typescript/client/src/types.ts (1)
sdks/typescript/client/src/index.ts (1)
  • ALL_RESOURCE_CONTENT_TYPES (28-28)
sdks/typescript/client/src/components/UIResourceRenderer.tsx (3)
sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (2)
  • HTMLResourceRendererProps (7-20)
  • HTMLResourceRenderer (42-267)
sdks/typescript/client/src/components/RemoteDOMResourceRenderer.tsx (1)
  • RemoteDOMResourceProps (22-27)
sdks/typescript/client/src/types.ts (2)
  • MCPProps (106-111)
  • HostProps (113-133)
🔇 Additional comments (11)
sdks/typescript/client/src/utils/processResource.ts (3)

23-46: Options object + MCP/Host extraction looks solid

Switching processHTMLResource to a typed options object (ProcessHTMLResourceOptions) and immediately extracting proxy, mcp, and host into local variables keeps the signature extensible and makes the downstream skybridge integration straightforward. No functional or typing issues stand out here.

Also applies to: 71-79


47-56: Skybridge HTML handling and script injection are correct and guarded

The new text/html+skybridge branch correctly:

  • Reuses the existing HTML decoding path (text or base64 blob).
  • Builds a stable widgetStateKey and passes all MCP/Host fields into createOpenAiScript.
  • Injects the resulting <script> blocks into <head>, <html>, or prepends them if no wrapper tags exist.

Combined with createOpenAiScript’s escaping, this addresses the earlier injection concern around embedding JSON into inline scripts. The regex replacements are conservative (<head[^>]*> / <html[^>]*> with i flag) and should behave well on typical HTML.

Also applies to: 141-193


233-266: Remote DOM base64 decode helper remains correct

The processRemoteDOMResource logic for preferring non-empty text, then base64-decoding blob via Uint8Array.from(atob(...)), and returning structured errors on failure is consistent with the HTML path and looks correct.

sdks/typescript/client/src/components/HTMLResourceRenderer.tsx (1)

3-20: MCP/Host context plumbing into HTML pipeline looks good

Exposing mcp?: MCPProps and host?: HostProps on HTMLResourceRendererProps, then threading them through processHTMLResource(resource, { proxy, mcp, host }) (memoized on [resource, proxy, mcp, host]) is clean and keeps all skybridge/OpenAI wiring confined to the processing layer. No issues with the typing or memoization pattern here.

Also applies to: 42-53, 71-79

sdks/typescript/client/src/types.ts (1)

105-133: MCPProps and HostProps shapes align with downstream usage

The new MCPProps and HostProps definitions match what HTMLResourceRenderer, processHTMLResource, and createOpenAiScript expect (toolInput/output/name/metadata plus theming, locale, userAgent, model, displayMode, maxHeight, safeArea, capabilities).

Reusing HostProps['safeArea'] / ['capabilities'] in OpenAiScriptOptions keeps everything in sync and avoids duplication.

sdks/typescript/client/src/components/UIResourceRenderer.tsx (6)

2-2: LGTM! Imports added correctly.

The addition of HostProps and MCPProps imports aligns with the new context propagation feature and the component props defined below.


11-11: LGTM! Props updated correctly for context propagation.

The htmlProps type correctly omits mcp and host since these are now passed as separate props to the component and forwarded directly to HTMLResourceRenderer. The addition of optional mcp and host props enables proper context propagation.

Also applies to: 13-14


27-29: LGTM! Skybridge mime type support added correctly.

The new branch for text/html+skybridge mime type follows the same pattern as existing mime type checks and correctly returns the skybridge content type.


39-39: LGTM! Props destructured correctly.

The destructuring properly includes the new mcp and host props for use in the component body.


48-48: LGTM! Skybridge case and context propagation implemented correctly.

The skybridge case is appropriately grouped with rawHtml and externalUrl since all three use HTMLResourceRenderer. The explicit passing of mcp and host props to HTMLResourceRenderer enables proper context propagation for skybridge resources.

Also applies to: 50-58


60-68: Verify whether RemoteDOMResourceRenderer should receive context props.

The mcp and host props are passed to HTMLResourceRenderer but not to RemoteDOMResourceRenderer. While this appears intentional (since RemoteDOMResourceProps doesn't include these fields), please confirm whether Remote DOM resources should also receive MCP/Host context for consistency.

If Remote DOM resources need context propagation, you'll need to:

  1. Update RemoteDOMResourceProps type to include mcp? and host?
  2. Pass these props to RemoteDOMResourceRenderer similar to the HTML case

Comment on lines +45 to 55
import type { MCPProps, HostProps } from '@mcp-ui/client';

interface UIResourceRendererProps {
resource: Partial<Resource>;
onUIAction?: (result: UIActionResult) => Promise<unknown>;
supportedContentTypes?: ResourceContentType[];
htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction'>;
htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction' | 'mcp' | 'host'>;
remoteDomProps?: Omit<RemoteDOMResourceProps, 'resource' | 'onUIAction'>;
mcp?: MCPProps;
host?: HostProps;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc/type mismatch for mcp/host vs htmlProps

The type snippet defines:

htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction' | 'mcp' | 'host'>;

so mcp and host cannot be passed via htmlProps. The bullets for mcp and host still say “These can also be provided via htmlProps…”, which is no longer accurate.

Either:

  • keep htmlProps excluding mcp/host and update the text to say these are only top‑level props on UIResourceRenderer, or
  • allow mcp/host through htmlProps again and adjust the Omit<> accordingly.

Also applies to: 82-83

🤖 Prompt for AI Agents
In docs/src/guide/client/resource-renderer.md around lines 45 to 55 (and also
apply the same change at lines 82-83), the prose incorrectly states that mcp and
host can be provided via htmlProps while the type snippet excludes them via
Omit<...,'mcp'|'host'>; either update the documentation bullets to say mcp and
host are top-level props only, or change the type so htmlProps allows mcp and
host (remove them from the Omit), and make the same corresponding change to the
text at lines 82-83 so code and docs remain consistent.

htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction'>;
remoteDomProps?: Omit<RemoteDOMResourceProps, 'resource' | 'onUIAction'>;
htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction' | 'mcp' | 'host'>;
remoteDomProps?: RemoteDOMResourceProps;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential prop override issue with remoteDomProps type.

Changing remoteDomProps from an Omit variant to the full RemoteDOMResourceProps type could cause unintended prop overrides. Since RemoteDOMResourceProps includes resource and onUIAction fields, and at lines 63-66 the spread {...remoteDomProps} occurs after the explicit resource={resource} and onUIAction={onUIAction} props, any values in remoteDomProps would override the component's props.

Consider reverting to the omitted variant:

-  remoteDomProps?: RemoteDOMResourceProps;
+  remoteDomProps?: Omit<RemoteDOMResourceProps, 'resource' | 'onUIAction'>;

Or adjust the spread order in the RemoteDOMResourceRenderer invocation to ensure the explicit props take precedence.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
remoteDomProps?: RemoteDOMResourceProps;
remoteDomProps?: Omit<RemoteDOMResourceProps, 'resource' | 'onUIAction'>;
🤖 Prompt for AI Agents
In sdks/typescript/client/src/components/UIResourceRenderer.tsx around line 12,
the change from an Omit variant to full RemoteDOMResourceProps for
remoteDomProps can allow resource and onUIAction in remoteDomProps to override
the explicit props passed later; either revert remoteDomProps back to
Omit<RemoteDOMResourceProps, 'resource' | 'onUIAction'> to prevent those keys
being present, or keep the full type but move the {...remoteDomProps} spread so
it occurs before the explicit resource={resource} and onUIAction={onUIAction}
props in the RemoteDOMResourceRenderer invocation so the explicit props take
precedence.

Comment on lines +6 to 12
export const ALL_RESOURCE_CONTENT_TYPES = [
'rawHtml',
'externalUrl',
'remoteDom',
'skybridge',
] as const;
export type ResourceContentType = (typeof ALL_RESOURCE_CONTENT_TYPES)[number];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Expose of 'skybridge' in ALL_RESOURCE_CONTENT_TYPES vs docs

Adding 'skybridge' to ALL_RESOURCE_CONTENT_TYPES and thus ResourceContentType makes sense for the new skybridge path.

However, the docs for supportedContentTypes (in docs/src/guide/client/resource-renderer.md, Line 71) still only mention ['rawHtml', 'externalUrl', 'remoteDom']. Consider updating the docs to either:

  • Include 'skybridge' as a valid content type, or
  • Clarify that 'skybridge' is internal/experimental if you don’t want it used directly.
🤖 Prompt for AI Agents
In sdks/typescript/client/src/types.ts around lines 6 to 12, the new 'skybridge'
value is added to ALL_RESOURCE_CONTENT_TYPES (and thus ResourceContentType) but
the docs (docs/src/guide/client/resource-renderer.md, around line 71) still list
only ['rawHtml','externalUrl','remoteDom']; update the documentation to reflect
the code change by either adding 'skybridge' to the supportedContentTypes list
and describing its use/behavior, or explicitly marking 'skybridge' as
internal/experimental with guidance not to use it directly; ensure the docs text
and any examples are consistent with the exported type so users aren’t misled.

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.

2 participants