feat(qr-server): use Python SDK meta parameter for resource CSP metadata (#303)

* update qr-server w/ python sdk fix

* feat(qr-server): use Python SDK meta parameter for resource CSP metadata

- Update qr-server to use the new meta parameter on @mcp.resource decorator
  instead of manually overriding the read_resource handler
- Add ui.resourceUri to tool meta alongside legacy ui/resourceUri for forward compatibility
- Simplify server.py by removing the custom _read_resource_with_meta handler
- Add qr-server to grid screenshot generation tests
- Update grid-cell.png screenshot

This leverages the Python SDK's native support for resource metadata,
eliminating the need for low-level handler manipulation.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Olivier Chafik 2026-01-19 19:11:42 +00:00 • committed by GitHub
commit de8048cb6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 15 additions and 46 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

@ -2,7 +2,7 @@
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "mcp>=1.9.0",
# "mcp @ git+https://github.com/modelcontextprotocol/python-sdk@main",
# "qrcode[pil]>=8.0",
# "uvicorn>=0.34.0",
# "starlette>=0.46.0",
@ -30,7 +30,10 @@ PORT = int(os.environ.get("PORT", "3108"))
mcp = FastMCP("QR Code Server", port=PORT, stateless_http=True)
@mcp.tool(meta={"ui/resourceUri": WIDGET_URI})
@mcp.tool(meta={
"ui":{"resourceUri": WIDGET_URI},
"ui/resourceUri": WIDGET_URI, # legacy support
})
def generate_qr(
text: str = "https://modelcontextprotocol.io",
box_size: int = 10,
@ -72,53 +75,17 @@ def generate_qr(
return [types.ImageContent(type="image", data=b64, mimeType="image/png")]
# Register widget resource using FastMCP decorator (returns HTML string)
@mcp.resource(WIDGET_URI, mime_type="text/html;profile=mcp-app")
# IMPORTANT: all the external domains used by app must be listed
# in the meta.ui.csp.resourceDomains - otherwise they will be blocked by CSP policy
@mcp.resource(
WIDGET_URI,
mime_type="text/html;profile=mcp-app",
meta={"ui": {"csp": {"resourceDomains": ["https://unpkg.com"]}}},
)
def widget() -> str:
"""Widget HTML resource with CSP metadata for external dependencies."""
return Path(__file__).parent.joinpath("widget.html").read_text()
# Override the read_resource handler to inject _meta into the response
# This is needed because FastMCP doesn't support custom _meta on resources
_low_level_server = mcp._mcp_server
async def _read_resource_with_meta(req: types.ReadResourceRequest):
"""Custom handler that injects CSP metadata for the widget resource."""
uri = str(req.params.uri)
html = Path(__file__).parent.joinpath("widget.html").read_text()
if uri == WIDGET_URI:
# NOTE: Must use model_validate with '_meta' key (not 'meta') due to Pydantic alias behavior
content = types.TextResourceContents.model_validate({
"uri": WIDGET_URI,
"mimeType": "text/html;profile=mcp-app",
"text": html,
# IMPORTANT: all the external domains used by app must be listed
# in the _meta.ui.csp.resourceDomains - otherwise they will be blocked by CSP policy
"_meta": {"ui": {"csp": {"resourceDomains": ["https://unpkg.com"]}}}
})
return types.ServerResult(
types.ReadResourceResult(contents=[content])
)
# Fallback for other resources (shouldn't happen for this server)
return types.ServerResult(
types.ReadResourceResult(
contents=[
types.TextResourceContents(
uri=uri,
mimeType="text/plain",
text="Resource not found"
)
]
)
)
# Replace the handler after FastMCP has registered its own
_low_level_server.request_handlers[types.ReadResourceRequest] = _read_resource_with_meta
if __name__ == "__main__":
if "--stdio" in sys.argv:
# Claude Desktop mode

View file

@ -54,6 +54,8 @@ const SERVERS = [
},
{ key: "map-server", name: "Map Server", dir: "map-server" },
{ key: "pdf-server", name: "PDF Server", dir: "pdf-server" },
{ key: "qr-server", name: "QR Code Server", dir: "qr-server" },
{ key: "say-server", name: "Say Demo", dir: "say-server" },
{
key: "scenario-modeler",
name: "SaaS Scenario Modeler",