Skip to content

MCP Apps: structured CSP/permissions types, resource meta propagation fix, QR example#3031

Merged
jlowin merged 6 commits intomainfrom
feat/mcp-apps-structured-metadata
Jan 30, 2026
Merged

MCP Apps: structured CSP/permissions types, resource meta propagation fix, QR example#3031
jlowin merged 6 commits intomainfrom
feat/mcp-apps-structured-metadata

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 30, 2026

Phase 1 shipped ToolUI and ResourceUI with placeholder types for CSP (str) and permissions (list[str]). These didn't match the actual MCP Apps wire format — hosts expect structured objects like {"resourceDomains": ["https://unpkg.com"]}, not a raw string. This fixes the type models and, more critically, fixes a bug where resource metadata (including CSP declarations) wasn't propagated to resources/read response content items — meaning hosts never saw CSP directives and blocked all external scripts.

from fastmcp.server.apps import ResourceCSP, ResourcePermissions, ResourceUI, ToolUI

@mcp.resource(
    "ui://my-app/view.html",
    ui=ResourceUI(
        csp=ResourceCSP(resource_domains=["https://unpkg.com"]),
        permissions=ResourcePermissions(microphone={}, clipboard_write={}),
    ),
)
def app_html() -> str:
    return Path("dist/index.html").read_text()

The CSP bug was found by comparing wire-level output against the upstream MCP SDK: hosts read _meta.ui.csp from content items in resources/read, not from the resource listing. Resource.convert_result() now propagates the resource's component-level meta (including ui) to each content item, and preserves the resource's MIME type for ui:// resources.

Also includes a QR code example (examples/apps/qr_server/) ported from the ext-apps repo — verified working end-to-end in Goose.

@jlowin jlowin added the enhancement Improvement to existing functionality. For issues and smaller PR improvements. label Jan 30, 2026
@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. server Related to FastMCP server implementation or server-side functionality. and removed enhancement Improvement to existing functionality. For issues and smaller PR improvements. labels Jan 30, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 30, 2026

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 13 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

This pull request enhances the MCP Apps UI system by introducing two new structured data models—ResourceCSP and ResourcePermissions—that replace primitive field types in ToolUI and ResourceUI components. The changes include wire-format updates to support domain lists and permission fields, a complete example QR code generator application demonstrating the new UI capabilities, fixes to resource path resolution in the install CLI, and improvements to resource MIME type handling to ensure non-UI resources preserve their correct MIME types instead of defaulting to text/plain.

Possibly related PRs

  • #3009: Extends the same MCP Apps UI models (ToolUI and ResourceUI) in src/fastmcp/server/apps.py with structured metadata for security and permissions configuration.
  • #2663: Modifies the Resource.convert_result method to wrap plain strings/bytes with the resource's MIME type and metadata, directly overlapping with the resource handling changes in this PR.
  • #2598: Updates resource content handling in src/fastmcp/resources/resource.py to preserve and propagate MIME type information through resource conversion flows.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description comprehensively covers the changes, includes code examples, and explains the bug fix. However, it does not follow the provided template structure with required checklist items. Add the Contributors Checklist and Review Checklist sections from the template with appropriate checkboxes marked.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: structured CSP/permissions types, resource meta propagation fix, and QR example.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mcp-apps-structured-metadata

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/fastmcp/server/apps.py (1)

94-118: ⚠️ Potential issue | 🟠 Major

Apply UI metadata type upgrades consistently across all decorator styles and MCP object types.

The structured UI types (ToolUI/ResourceUI with ResourceCSP, ResourcePermissions) were added to server-level decorators (@server.tool(), @server.resource()), but the following are missing the corresponding ui parameter or field:

  • @resource() standalone decorator — no ui: ResourceUI | dict parameter
  • @prompt() standalone decorator — no ui parameter
  • ResourceMeta — no ui field (only meta: dict[str, Any])
  • PromptMeta — no ui field (only meta: dict[str, Any])
  • ResourceTemplate.from_function() — no ui parameter (matches comment at line 290: "doesn't have metadata support yet")
  • Prompt.from_function() — no ui parameter

Per the requirement that changes affecting MCP objects must be adopted across Tools, Resources, Resource Templates, and Prompts: add ui parameter/field to the above, wire format serialization via ui_to_meta_dict(), and add tests for ResourceTemplate and Prompt UI metadata round-tripping.

🧹 Nitpick comments (2)
docs/development/v3-notes/mcp-apps-notes.mdx (2)

178-184: Convert the build workflow to a Steps component.

This is a sequential procedure; please render it with the Steps component for consistency and scanability.

As per coding guidelines Use Steps component for procedures, tutorials, setup guides, and sequential instructions in MDX documentation.


23-57: Provide at least one complete, runnable example for the core pattern.

These snippets are illustrative but not end-to-end runnable. Consider adding a full, copy‑pasteable example (with filename) that includes a minimal server + UI resource flow.

As per coding guidelines Always include complete, runnable code examples that users can copy and execute in MDX documentation.

Comment thread docs/development/v3-notes/mcp-apps-notes.mdx Outdated
Comment thread examples/apps/qr_server.py Outdated
Comment on lines +32 to +36
VIEW_URI = "ui://qr-server/view.html"

mcp = FastMCP("QR Code Server", stateless_http=True)

EMBEDDED_VIEW_HTML = """\
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add module-level type annotations for constants and server instance.

This keeps the example aligned with the repo’s typing standard.

🧾 Suggested update
-VIEW_URI = "ui://qr-server/view.html"
+VIEW_URI: str = "ui://qr-server/view.html"

-mcp = FastMCP("QR Code Server", stateless_http=True)
+mcp: FastMCP = FastMCP("QR Code Server", stateless_http=True)

-EMBEDDED_VIEW_HTML = """\
+EMBEDDED_VIEW_HTML: str = """\
As per coding guidelines Use Python ≥3.10 with full type annotations for all code.

Comment thread examples/apps/qr_server.py Outdated
Comment on lines +107 to +148
@mcp.tool(ui=ToolUI(resource_uri=VIEW_URI))
def generate_qr(
text: str = "https://gofastmcp.com",
box_size: int = 10,
border: int = 4,
error_correction: str = "M",
fill_color: str = "black",
back_color: str = "white",
) -> list[types.ImageContent]:
"""Generate a QR code from text.

Args:
text: The text/URL to encode
box_size: Size of each box in pixels (default: 10)
border: Border size in boxes (default: 4)
error_correction: Error correction level - L(7%), M(15%), Q(25%), H(30%)
fill_color: Foreground color (hex like #FF0000 or name like red)
back_color: Background color (hex like #FFFFFF or name like white)
"""
error_levels = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}

qr = qrcode.QRCode(
version=1,
error_correction=error_levels.get(
error_correction.upper(), qrcode.constants.ERROR_CORRECT_M
),
box_size=box_size,
border=border,
)
qr.add_data(text)
qr.make(fit=True)

img = qr.make_image(fill_color=fill_color, back_color=back_color)
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
return [types.ImageContent(type="image", data=b64, mimeType="image/png")]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validate QR parameters to avoid silent fallbacks and invalid values.

Right now invalid error_correction falls back silently and negative sizes can slip through.

✅ Suggested validation
-    qr = qrcode.QRCode(
-        version=1,
-        error_correction=error_levels.get(
-            error_correction.upper(), qrcode.constants.ERROR_CORRECT_M
-        ),
-        box_size=box_size,
-        border=border,
-    )
+    if box_size <= 0:
+        raise ValueError("box_size must be > 0")
+    if border < 0:
+        raise ValueError("border must be >= 0")
+
+    error_key = error_correction.upper()
+    if error_key not in error_levels:
+        raise ValueError("error_correction must be one of: L, M, Q, H")
+
+    qr = qrcode.QRCode(
+        version=1,
+        error_correction=error_levels[error_key],
+        box_size=box_size,
+        border=border,
+    )
📝 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
@mcp.tool(ui=ToolUI(resource_uri=VIEW_URI))
def generate_qr(
text: str = "https://gofastmcp.com",
box_size: int = 10,
border: int = 4,
error_correction: str = "M",
fill_color: str = "black",
back_color: str = "white",
) -> list[types.ImageContent]:
"""Generate a QR code from text.
Args:
text: The text/URL to encode
box_size: Size of each box in pixels (default: 10)
border: Border size in boxes (default: 4)
error_correction: Error correction level - L(7%), M(15%), Q(25%), H(30%)
fill_color: Foreground color (hex like #FF0000 or name like red)
back_color: Background color (hex like #FFFFFF or name like white)
"""
error_levels = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}
qr = qrcode.QRCode(
version=1,
error_correction=error_levels.get(
error_correction.upper(), qrcode.constants.ERROR_CORRECT_M
),
box_size=box_size,
border=border,
)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color=fill_color, back_color=back_color)
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
return [types.ImageContent(type="image", data=b64, mimeType="image/png")]
`@mcp.tool`(ui=ToolUI(resource_uri=VIEW_URI))
def generate_qr(
text: str = "https://gofastmcp.com",
box_size: int = 10,
border: int = 4,
error_correction: str = "M",
fill_color: str = "black",
back_color: str = "white",
) -> list[types.ImageContent]:
"""Generate a QR code from text.
Args:
text: The text/URL to encode
box_size: Size of each box in pixels (default: 10)
border: Border size in boxes (default: 4)
error_correction: Error correction level - L(7%), M(15%), Q(25%), H(30%)
fill_color: Foreground color (hex like `#FF0000` or name like red)
back_color: Background color (hex like `#FFFFFF` or name like white)
"""
error_levels = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}
if box_size <= 0:
raise ValueError("box_size must be > 0")
if border < 0:
raise ValueError("border must be >= 0")
error_key = error_correction.upper()
if error_key not in error_levels:
raise ValueError("error_correction must be one of: L, M, Q, H")
qr = qrcode.QRCode(
version=1,
error_correction=error_levels[error_key],
box_size=box_size,
border=border,
)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color=fill_color, back_color=back_color)
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
return [types.ImageContent(type="image", data=b64, mimeType="image/png")]

Comment thread examples/apps/qr_server.py Outdated
Comment on lines +160 to +164
if __name__ == "__main__":
if "--stdio" in sys.argv:
mcp.run(transport="stdio")
else:
mcp.run(transport="streamable-http", host="0.0.0.0", port=3001)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid binding to all interfaces by default in the example.

0.0.0.0 exposes the server to the LAN; safer to default to localhost and allow override.

🔒 Suggested safer default
+import os
@@
-        mcp.run(transport="streamable-http", host="0.0.0.0", port=3001)
+        host = os.getenv("FASTMCP_EXAMPLE_HOST", "127.0.0.1")
+        mcp.run(transport="streamable-http", host=host, port=3001)
🧰 Tools
🪛 Ruff (0.14.14)

[error] 164-164: Possible binding to all interfaces

(S104)

@jlowin jlowin added the mcp apps Related to MCP Apps - user-facing applications with frontend bundles served by MCP servers. label Jan 30, 2026
Hosts read CSP/permissions metadata from content items in the
resources/read response, not from the resource listing. Our
convert_result() was dropping the resource's meta when wrapping
plain str/bytes returns, so the host never saw the CSP declarations
and blocked external scripts.

Also fixes: resource MIME type preservation for ui:// resources,
install CLI path resolution for JSON configs, and switches QR example
to explicit ToolResult to avoid unneeded structuredContent.
@jlowin jlowin force-pushed the feat/mcp-apps-structured-metadata branch from 852c7da to 8d3c115 Compare January 30, 2026 18:48
@jlowin jlowin changed the title MCP Apps: structured CSP/permissions types, QR example, pattern notes MCP Apps: structured CSP/permissions types, resource meta propagation fix, QR example Jan 30, 2026
@jlowin jlowin merged commit 12cb58f into main Jan 30, 2026
13 checks passed
@jlowin jlowin deleted the feat/mcp-apps-structured-metadata branch January 30, 2026 19:05
gfortaine pushed a commit to gfortaine/fastmcp that referenced this pull request Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. mcp apps Related to MCP Apps - user-facing applications with frontend bundles served by MCP servers. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant