Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mcp/server/fastmcp/resources/resource_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/mcp/server/fastmcp/resources/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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."""
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
"""
Expand Down
16 changes: 16 additions & 0 deletions tests/issues/test_1338_icons_and_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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."""
Expand Down
Loading