Files
prefecthq--fastmcp/tests/resources/test_resource_template_meta.py
wehub-resource-sync 60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

116 lines
4.3 KiB
Python

from mcp_types import Annotations, Icon
from fastmcp import FastMCP
from fastmcp.resources import ResourceTemplate
from fastmcp.server.providers.fastmcp_provider import (
FastMCPProvider,
FastMCPProviderResourceTemplate,
)
class TestResourceTemplateMeta:
"""Test ResourceTemplate meta functionality."""
def test_template_meta_parameter(self):
"""Test that meta parameter is properly handled."""
def template_func(param: str) -> str:
return f"Result: {param}"
meta_data = {"version": "2.0", "template": "test"}
template = ResourceTemplate.from_function(
fn=template_func,
uri_template="test://{param}",
name="test_template",
meta=meta_data,
)
assert template.meta == meta_data
mcp_template = template.to_mcp_template()
# MCP template includes fastmcp meta, so check that our meta is included
assert mcp_template.meta is not None
assert meta_data.items() <= mcp_template.meta.items()
class TestResourceTemplateFieldPreservation:
"""Regression for #4061: annotations/meta/title/icons must survive
materialization of a Resource from a ResourceTemplate."""
def _template(self) -> ResourceTemplate:
def fn(param: str) -> str:
return f"value-{param}"
return ResourceTemplate.from_function(
fn=fn,
uri_template="data://{param}",
name="t",
title="Human Title",
meta={"owner": "team-a"},
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png")],
annotations=Annotations(priority=0.5, audience=["user"]),
)
async def test_function_template_create_resource_preserves_all_fields(self):
template = self._template()
resource = await template.create_resource("data://x", {"param": "x"})
assert resource.title == "Human Title"
assert resource.meta == {"owner": "team-a"}
assert resource.annotations is not None
assert resource.annotations.priority == 0.5
assert resource.annotations.audience == ["user"]
assert resource.icons is not None
assert len(resource.icons) == 1
assert str(resource.icons[0].src) == "https://example.com/icon.png"
async def test_created_resource_meta_is_not_aliased_to_template(self):
"""Mutating the materialized resource's meta must not bleed back into
the template (each materialization must be independent)."""
template = self._template()
resource = await template.create_resource("data://x", {"param": "x"})
assert resource.meta is not None
resource.meta["mutated"] = True
assert "mutated" not in (template.meta or {})
async def test_fastmcp_provider_template_preserves_fields_without_double_wrap(
self,
):
"""The FastMCPProvider path wraps template.get_meta() (already
namespaced). Re-materializing must not nest fastmcp under fastmcp."""
sub = FastMCP("sub")
@sub.resource(
"data://{param}",
title="Sub Title",
meta={"owner": "team-b"},
icons=[Icon(src="https://example.com/s.png", mime_type="image/png")],
annotations=Annotations(priority=0.9),
tags={"alpha"},
)
def fn(param: str) -> str:
return f"sub-{param}"
provider = FastMCPProvider(sub)
templates = await provider._list_resource_templates()
assert len(templates) == 1
wrapped = templates[0]
assert isinstance(wrapped, FastMCPProviderResourceTemplate)
resource = await wrapped.create_resource("data://x", {"param": "x"})
assert resource.title == "Sub Title"
assert resource.annotations is not None
assert resource.annotations.priority == 0.9
assert resource.icons is not None and len(resource.icons) == 1
mcp_resource = resource.to_mcp_resource()
assert mcp_resource.meta is not None
# User meta survives end-to-end.
assert mcp_resource.meta.get("owner") == "team-b"
# Exactly one fastmcp namespace, and it is NOT double-wrapped.
fastmcp_ns = mcp_resource.meta.get("fastmcp")
assert isinstance(fastmcp_ns, dict)
assert "fastmcp" not in fastmcp_ns
assert "alpha" in fastmcp_ns.get("tags", [])