Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 22, 2025

Problem

The Python SDK was missing support for the uiMetadata field when creating UI resources, causing metadata like preferred-frame-size to be ignored and resulting in meta: null in the output. This prevented users from specifying important UI hints such as preferred dimensions or initial render data.

# This was being ignored, resulting in meta: null
resource = create_ui_resource({
    "uri": "ui://chart",
    "uiMetadata": {
        "preferred-frame-size": [800, 600],
    },
    "content": {"type": "rawHtml", "htmlString": "<div>...</div>"},
    "encoding": "text"
})

Solution

Implemented full metadata support in the Python SDK to match the TypeScript implementation:

  • Added uiMetadata and metadata optional fields to CreateUIResourceOptions
  • UI-specific metadata is automatically prefixed with mcpui.dev/ui- for client recognition
  • Custom metadata can be provided alongside UI metadata without prefixing
  • Metadata is properly included in the resource's _meta field for MCP protocol compliance

Usage

from mcp_ui_server import create_ui_resource

# Specify preferred frame size
resource = create_ui_resource({
    "uri": "ui://visualization",
    "content": {
        "type": "externalUrl",
        "iframeUrl": "https://charts.example.com"
    },
    "encoding": "text",
    "uiMetadata": {
        "preferred-frame-size": [800, 600]
    }
})

# Provide initial render data
resource = create_ui_resource({
    "uri": "ui://dashboard",
    "content": {
        "type": "remoteDom",
        "script": "function Dashboard({ theme }) { ... }",
        "framework": "react"
    },
    "encoding": "text",
    "uiMetadata": {
        "initial-render-data": {
            "theme": "dark",
            "userId": "123"
        }
    }
})

# Combine UI and custom metadata
resource = create_ui_resource({
    "uri": "ui://widget",
    "content": {"type": "rawHtml", "htmlString": "<div>Widget</div>"},
    "encoding": "text",
    "uiMetadata": {
        "preferred-frame-size": [640, 480]
    },
    "metadata": {
        "version": "1.0.0",
        "customKey": "value"
    }
})

Changes

  • types.py: Added uiMetadata and metadata fields, metadata constants
  • core.py: Implemented _get_additional_resource_props() helper for metadata processing
  • test_metadata.py: Added 11 comprehensive test cases covering all metadata scenarios
  • README.md: Added extensive documentation with examples
  • python_server_demo.py: Updated example to demonstrate metadata usage

Testing

  • All 32 tests pass (21 existing + 11 new metadata tests)
  • Covers all content types (rawHtml, externalUrl, remoteDom)
  • Covers both encodings (text, blob)
  • Tests metadata prefixing, override behavior, and serialization
  • Linting (Ruff) and type checking (Pyright) clean
  • Security scan (CodeQL) clean - 0 vulnerabilities

Compatibility

  • Matches TypeScript SDK implementation
  • No breaking changes - purely additive
  • Works with all existing code without modification
  • Compatible with MCP protocol specifications

Fixes #[issue_number]

Original prompt

This section details on the original issue you should resolve

<issue_title>python SDK and metadata</issue_title>
<issue_description>I tried passing in the preferred-frame-size as metadata from python sdk's create_ui_resource as shown below

@mcp.tool
def some_ui_resource():
    """plot a dataframe"""
    df = fetch_a_dataframe()
    axes = df.plot()
    #convert matplotlib plot to base64 string
    import io
    import base64
    buf = io.BytesIO()
    fig = axes.get_figure()
    fig.savefig(buf, format='png')
    buf.seek(0)
    plot_data = base64.b64encode(buf.read()).decode('utf-8')
    buf.close()
    return create_ui_resource({
        "uri": f"ui://some/ui/path",
        "uiMetadata": {
            "preferred-frame-size": [800, 600],
        },
        "content": {
            "type": "rawHtml",
            "htmlString": f"""
                <img src="data:image/jpeg;base64,{plot_data}" alt="plot"/>
            """
        },
        "encoding": "text"
    })

I tried a model_dump of the created resource object and seems like the metadata is null.

{
    "type": "resource",
    "resource": {
        "uri": "ui://some/uipath",
        "mimeType": "text/html",
        "meta": null,
        "text": "\n                <img src=\"data:image/jpeg;base64,some_data_here\" alt=\"plot\"/>\n            "
    },
    "annotations": null,
    "meta": null
}

is this a missing feature in python sdk?</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #133


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 22, 2025

Deploying mcp-ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: b3d50a1
Status: ✅  Deploy successful!
Preview URL: https://b9ab52d7.mcp-ui.pages.dev
Branch Preview URL: https://copilot-fix-preferred-frame.mcp-ui.pages.dev

View logs

Copilot AI changed the title [WIP] Fix preferred frame size in python SDK Add metadata support to Python SDK for UI resources Oct 22, 2025
Copilot AI requested a review from idosal October 22, 2025 21:34
@idosal idosal marked this pull request as ready for review October 25, 2025 19:54
@idosal idosal requested a review from Copilot October 25, 2025 19:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds comprehensive metadata support to the Python SDK, enabling UI resources to include metadata like preferred-frame-size and initial-render-data. The implementation matches the TypeScript SDK's approach by automatically prefixing UI-specific metadata with mcpui.dev/ui- while allowing custom metadata to be provided alongside without prefixing.

Key changes:

  • Added uiMetadata and metadata optional fields to CreateUIResourceOptions
  • Implemented metadata processing with automatic UI prefix application
  • Added 11 comprehensive test cases covering all metadata scenarios

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
types.py Added UI metadata constants, keys, and optional metadata fields to CreateUIResourceOptions
core.py Implemented _get_additional_resource_props() helper for metadata processing and integration
test_metadata.py Added comprehensive test suite with 11 test cases for all metadata scenarios
__init__.py Exported UIMetadataKey class for external use
README.md Added extensive documentation section with metadata usage examples
python_server_demo.py Updated example to demonstrate metadata usage with UIMetadataKey
python-server-demo/README.md Added metadata documentation and usage examples

"encoding": "text"
"encoding": "text",
"uiMetadata": {
UIMetadataKey.PREFERRED_FRAME_SIZE: ["800px", "600px"] # CSS dimension strings (can be px, %, vh, etc.)
Copy link

Copilot AI Oct 25, 2025

Choose a reason for hiding this comment

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

Using UIMetadataKey.PREFERRED_FRAME_SIZE as a dictionary key follows Python best practices, but the comment suggests this can accept CSS unit strings while the constant is defined as a plain string 'preferred-frame-size'. Consider documenting the expected value types (e.g., list of strings or list of numbers) in the UIMetadataKey class docstring to clarify the API contract.

Copilot uses AI. Check for mistakes.


class UIMetadataKey:
"""Keys for UI metadata."""
Copy link

Copilot AI Oct 25, 2025

Choose a reason for hiding this comment

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

The UIMetadataKey class lacks documentation about the expected value types for each metadata key. For example, PREFERRED_FRAME_SIZE should clarify whether it expects a list of integers (pixels) or strings (CSS units), and INITIAL_RENDER_DATA should specify that it expects a dictionary. This would improve API usability and prevent type confusion.

Suggested change
"""Keys for UI metadata."""
"""
Keys for UI metadata.
- PREFERRED_FRAME_SIZE: expects a list of two integers [width, height] in pixels, e.g. [800, 600].
- INITIAL_RENDER_DATA: expects a dictionary containing initial render data for the UI component.
"""

Copilot uses AI. Check for mistakes.
},
"encoding": "text",
"uiMetadata": {
"preferred-frame-size": [800, 600] # width, height in pixels or css units
Copy link

Copilot AI Oct 25, 2025

Choose a reason for hiding this comment

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

The comment indicates 'pixels or css units' but the example shows numeric values without units. This creates ambiguity about whether clients should pass [800, 600] (integers) or ['800px', '600px'] (strings). Consider providing examples for both cases or clarifying which format is preferred and how they differ in behavior.

Copilot uses AI. Check for mistakes.
@idosal idosal changed the title Add metadata support to Python SDK for UI resources feat: support metadata in Python SDK Oct 25, 2025
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 7 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="examples/python-server-demo/README.md">

<violation number="1" location="examples/python-server-demo/README.md:76">
The example sets `preferred-frame-size` to string numbers (&quot;1200&quot;, &quot;800&quot;), but UI clients expect either numeric pixels or CSS values with units. Update the example to use integers or unit-suffixed strings so the metadata actually applies.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

},
"encoding": "text",
"uiMetadata": {
"preferred-frame-size": ["1200", "800"],
Copy link

@cubic-dev-ai cubic-dev-ai bot Oct 25, 2025

Choose a reason for hiding this comment

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

The example sets preferred-frame-size to string numbers ("1200", "800"), but UI clients expect either numeric pixels or CSS values with units. Update the example to use integers or unit-suffixed strings so the metadata actually applies.

Prompt for AI agents
Address the following comment on examples/python-server-demo/README.md at line 76:

<comment>The example sets `preferred-frame-size` to string numbers (&quot;1200&quot;, &quot;800&quot;), but UI clients expect either numeric pixels or CSS values with units. Update the example to use integers or unit-suffixed strings so the metadata actually applies.</comment>

<file context>
@@ -50,6 +52,38 @@ Each tool returns an MCP resource that can be rendered by MCP UI clients:
+    },
+    &quot;encoding&quot;: &quot;text&quot;,
+    &quot;uiMetadata&quot;: {
+        &quot;preferred-frame-size&quot;: [&quot;1200&quot;, &quot;800&quot;],
+    },
+    # Optional: custom metadata (not prefixed)
</file context>
Suggested change
"preferred-frame-size": ["1200", "800"],
"preferred-frame-size": [1200, 800],
Fix with Cubic

@idosal idosal merged commit 9bc3c64 into main Oct 25, 2025
14 checks passed
github-actions bot pushed a commit that referenced this pull request Nov 4, 2025
# 1.0.0 (2025-11-04)

### Bug Fixes

* add a bridge to pass messages in and out of the proxy ([#38](#38)) ([30ccac0](30ccac0))
* bump client version ([75c9236](75c9236))
* **client:** specify iframe ([fd0b70a](fd0b70a))
* **client:** styling ([6ff9b68](6ff9b68))
* dependencies ([887f61f](887f61f))
* Enable bidirectional message relay in rawhtml proxy mode ([#138](#138)) ([f0bdefb](f0bdefb))
* ensure Apps SDK adapter is bundled properly and initialized wth config ([#137](#137)) ([4f7c25c](4f7c25c))
* export RemoteDomResource ([2b86f2d](2b86f2d))
* export ResourceRenderer and HtmlResource ([2b841a5](2b841a5))
* exports ([3a93a16](3a93a16))
* fix file extension reference in package.json ([927989c](927989c))
* iframe handle ([#15](#15)) ([66bd4fd](66bd4fd))
* lint ([4487820](4487820))
* lint ([d0a91f9](d0a91f9))
* minor typo ([a0bee9c](a0bee9c))
* move react dependencies to be peer dependencies ([#91](#91)) ([f672f3e](f672f3e)), closes [#90](#90)
* package config ([8dc1e53](8dc1e53))
* packaging ([9e6babd](9e6babd))
* pass ref explicitly using iframeProps ([#33](#33)) ([d01b5d1](d01b5d1))
* publish ([0943e7a](0943e7a))
* ref passing to UIResourceRenderer ([#32](#32)) ([d28c23f](d28c23f))
* remove shared dependency ([e66e8f4](e66e8f4))
* rename components and methods to fit new scope ([#22](#22)) ([6bab1fe](6bab1fe))
* rename delivery -> encoding and flavor -> framework ([#36](#36)) ([9a509ed](9a509ed))
* Ruby comment ([b22dc2e](b22dc2e))
* support react-router ([21ffb95](21ffb95))
* text and blob support in RemoteDOM resources ([ec68eb9](ec68eb9))
* trigger release ([aaca831](aaca831))
* typescript ci publish ([e7c0ebf](e7c0ebf))
* typescript types to be compatible with MCP SDK ([#10](#10)) ([74365d7](74365d7))
* update deps ([4091ef4](4091ef4))
* update isUIResource to use EmbeddedResource type ([#122](#122)) ([5a65a0b](5a65a0b)), closes [#117](#117)
* use targetOrigin in the proxy message relay ([#40](#40)) ([b3fb54e](b3fb54e))
* validate URL ([b7c994d](b7c994d))
* wc dist overwrite ([#63](#63)) ([9e46c56](9e46c56))

### Documentation

* bump ([#4](#4)) ([ad4d163](ad4d163))

### Features

* add convenience function isUIResource to client SDK ([#86](#86)) ([607c6ad](607c6ad))
* add embeddedResourceProps for annotations ([#99](#99)) ([b96ec44](b96ec44))
* add proxy option to externalUrl ([#37](#37)) ([7b95cd0](7b95cd0))
* add remote-dom content type ([#18](#18)) ([5dacf37](5dacf37))
* add Ruby server SDK ([#31](#31)) ([5ffcde4](5ffcde4))
* add sandbox permissions instead of an override ([#83](#83)) ([b1068e9](b1068e9))
* add ui-request-render-data message type ([#111](#111)) ([26135ce](26135ce))
* add UIResourceRenderer Web Component ([#58](#58)) ([ec8f299](ec8f299))
* auto resize with the autoResizeIframe prop ([#56](#56)) ([76c867a](76c867a))
* change onGenericMcpAction to optional onUiAction ([1913b59](1913b59))
* **client:** allow setting supportedContentTypes for HtmlResource ([#17](#17)) ([e009ef1](e009ef1))
* consolidate ui:// and ui-app:// ([#8](#8)) ([2e08035](2e08035))
* pass iframe props down ([#14](#14)) ([112539d](112539d))
* refactor UTFtoB64 (bump server version) ([#95](#95)) ([2d5e16b](2d5e16b))
* send render data to the iframe ([#51](#51)) ([d38cfc7](d38cfc7))
* separate html and remote-dom props ([#24](#24)) ([a7f0529](a7f0529))
* support adapters ([#127](#127)) ([d4bd152](d4bd152))
* support generic messages response ([#35](#35)) ([10b407b](10b407b))
* support metadata in Python SDK ([#134](#134)) ([9bc3c64](9bc3c64))
* support passing resource metadata ([#87](#87)) ([f1c1c9b](f1c1c9b))
* support proxy for rawHtml ([#132](#132)) ([1bbeb09](1bbeb09))
* support ui action result types ([#6](#6)) ([899d152](899d152))
* switch to ResourceRenderer ([#21](#21)) ([6fe3166](6fe3166))

### BREAKING CHANGES

* The existing naming is ambiguous. Renaming delivery to encoding and flavor to framework should clarify the intent.
* exported names have changed
* removed deprecated client API
* (previous one didn't take due to semantic-release misalignment)
github-actions bot pushed a commit that referenced this pull request Nov 29, 2025
…t/v5.15.0) (2025-11-29)

### Bug Fixes

* ensure Apps SDK adapter is bundled properly and initialized wth config ([#137](#137)) ([4f7c25c](4f7c25c))

### Features

* support metadata in Python SDK ([#134](#134)) ([9bc3c64](9bc3c64))
github-actions bot pushed a commit that referenced this pull request Nov 29, 2025
…r/v5.14.0) (2025-11-29)

### Features

* support metadata in Python SDK ([#134](#134)) ([9bc3c64](9bc3c64))
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.

python SDK and metadata

2 participants