diff --git a/src/mcp/server/fastmcp/resources/resource_manager.py b/src/mcp/server/fastmcp/resources/resource_manager.py index ad559bd5c..b2865def8 100644 --- a/src/mcp/server/fastmcp/resources/resource_manager.py +++ b/src/mcp/server/fastmcp/resources/resource_manager.py @@ -10,6 +10,7 @@ from mcp.server.fastmcp.resources.base import Resource from mcp.server.fastmcp.resources.templates import ResourceTemplate from mcp.server.fastmcp.utilities.logging import get_logger +from mcp.types import Icon if TYPE_CHECKING: from mcp.server.fastmcp.server import Context @@ -61,6 +62,7 @@ def add_template( title: str | None = None, description: str | None = None, mime_type: str | None = None, + icons: list[Icon] | None = None, ) -> ResourceTemplate: """Add a template from a function.""" template = ResourceTemplate.from_function( @@ -70,6 +72,7 @@ def add_template( title=title, description=description, mime_type=mime_type, + icons=icons, ) self._templates[template.uri_template] = template return template diff --git a/src/mcp/server/fastmcp/resources/templates.py b/src/mcp/server/fastmcp/resources/templates.py index 922364cd8..8b5af2574 100644 --- a/src/mcp/server/fastmcp/resources/templates.py +++ b/src/mcp/server/fastmcp/resources/templates.py @@ -12,6 +12,7 @@ from mcp.server.fastmcp.resources.types import FunctionResource, Resource from mcp.server.fastmcp.utilities.context_injection import find_context_parameter, inject_context from mcp.server.fastmcp.utilities.func_metadata import func_metadata +from mcp.types import Icon if TYPE_CHECKING: from mcp.server.fastmcp.server import Context @@ -27,6 +28,7 @@ class ResourceTemplate(BaseModel): title: str | None = Field(description="Human-readable title of the resource", default=None) description: str | None = Field(description="Description of what the resource does") mime_type: str = Field(default="text/plain", description="MIME type of the resource content") + icons: list[Icon] | None = Field(default=None, description="Optional list of icons for the resource template") fn: Callable[..., Any] = Field(exclude=True) parameters: dict[str, Any] = Field(description="JSON schema for function parameters") context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context") @@ -40,6 +42,7 @@ def from_function( title: str | None = None, description: str | None = None, mime_type: str | None = None, + icons: list[Icon] | None = None, context_kwarg: str | None = None, ) -> ResourceTemplate: """Create a template from a function.""" @@ -67,6 +70,7 @@ def from_function( title=title, description=description or fn.__doc__ or "", mime_type=mime_type or "text/plain", + icons=icons, fn=fn, parameters=parameters, context_kwarg=context_kwarg, @@ -103,7 +107,7 @@ async def create_resource( title=self.title, description=self.description, mime_type=self.mime_type, - icons=None, # Resource templates don't support icons + icons=self.icons, fn=lambda: result, # Capture result in closure ) except Exception as e: diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 839632930..c2dbc4436 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -335,6 +335,7 @@ async def list_resource_templates(self) -> list[MCPResourceTemplate]: title=template.title, description=template.description, mimeType=template.mime_type, + icons=template.icons, ) for template in templates ] @@ -559,7 +560,7 @@ def decorator(fn: AnyFunction) -> AnyFunction: title=title, description=description, mime_type=mime_type, - # Note: Resource templates don't support icons + icons=icons, ) else: # Register as regular resource diff --git a/src/mcp/types.py b/src/mcp/types.py index 0cf175584..871322740 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -470,6 +470,8 @@ class ResourceTemplate(BaseMetadata): The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. """ + icons: list[Icon] | None = None + """An optional list of icons for this resource template.""" annotations: Annotations | None = None meta: dict[str, Any] | None = Field(alias="_meta", default=None) """ diff --git a/tests/issues/test_1338_icons_and_metadata.py b/tests/issues/test_1338_icons_and_metadata.py index 3d4f9b868..8a9897fcf 100644 --- a/tests/issues/test_1338_icons_and_metadata.py +++ b/tests/issues/test_1338_icons_and_metadata.py @@ -39,6 +39,12 @@ def test_prompt(text: str) -> str: """A test prompt with an icon.""" return text + # Create resource template with icon + @mcp.resource("test://weather/{city}", icons=[test_icon]) + def test_resource_template(city: str) -> str: + """Get weather for a city.""" + return f"Weather for {city}" + # Test server metadata includes websiteUrl and icons assert mcp.name == "TestServer" assert mcp.website_url == "https://example.com" @@ -75,6 +81,16 @@ def test_prompt(text: str) -> str: assert len(prompt.icons) == 1 assert prompt.icons[0].src == test_icon.src + # Test resource template includes icon + templates = await mcp.list_resource_templates() + assert len(templates) == 1 + template = templates[0] + assert template.name == "test_resource_template" + assert template.uriTemplate == "test://weather/{city}" + assert template.icons is not None + assert len(template.icons) == 1 + assert template.icons[0].src == test_icon.src + async def test_multiple_icons(): """Test that multiple icons can be added to tools, resources, and prompts."""