Skip to content

Commit 3e798bf

Browse files
authored
Add icons support for ResourceTemplate (#1412)
1 parent f676f6c commit 3e798bf

File tree

5 files changed

+28
-2
lines changed

5 files changed

+28
-2
lines changed

src/mcp/server/fastmcp/resources/resource_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from mcp.server.fastmcp.resources.base import Resource
1111
from mcp.server.fastmcp.resources.templates import ResourceTemplate
1212
from mcp.server.fastmcp.utilities.logging import get_logger
13+
from mcp.types import Icon
1314

1415
if TYPE_CHECKING:
1516
from mcp.server.fastmcp.server import Context
@@ -61,6 +62,7 @@ def add_template(
6162
title: str | None = None,
6263
description: str | None = None,
6364
mime_type: str | None = None,
65+
icons: list[Icon] | None = None,
6466
) -> ResourceTemplate:
6567
"""Add a template from a function."""
6668
template = ResourceTemplate.from_function(
@@ -70,6 +72,7 @@ def add_template(
7072
title=title,
7173
description=description,
7274
mime_type=mime_type,
75+
icons=icons,
7376
)
7477
self._templates[template.uri_template] = template
7578
return template

src/mcp/server/fastmcp/resources/templates.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from mcp.server.fastmcp.resources.types import FunctionResource, Resource
1313
from mcp.server.fastmcp.utilities.context_injection import find_context_parameter, inject_context
1414
from mcp.server.fastmcp.utilities.func_metadata import func_metadata
15+
from mcp.types import Icon
1516

1617
if TYPE_CHECKING:
1718
from mcp.server.fastmcp.server import Context
@@ -27,6 +28,7 @@ class ResourceTemplate(BaseModel):
2728
title: str | None = Field(description="Human-readable title of the resource", default=None)
2829
description: str | None = Field(description="Description of what the resource does")
2930
mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
31+
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for the resource template")
3032
fn: Callable[..., Any] = Field(exclude=True)
3133
parameters: dict[str, Any] = Field(description="JSON schema for function parameters")
3234
context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
@@ -40,6 +42,7 @@ def from_function(
4042
title: str | None = None,
4143
description: str | None = None,
4244
mime_type: str | None = None,
45+
icons: list[Icon] | None = None,
4346
context_kwarg: str | None = None,
4447
) -> ResourceTemplate:
4548
"""Create a template from a function."""
@@ -67,6 +70,7 @@ def from_function(
6770
title=title,
6871
description=description or fn.__doc__ or "",
6972
mime_type=mime_type or "text/plain",
73+
icons=icons,
7074
fn=fn,
7175
parameters=parameters,
7276
context_kwarg=context_kwarg,
@@ -103,7 +107,7 @@ async def create_resource(
103107
title=self.title,
104108
description=self.description,
105109
mime_type=self.mime_type,
106-
icons=None, # Resource templates don't support icons
110+
icons=self.icons,
107111
fn=lambda: result, # Capture result in closure
108112
)
109113
except Exception as e:

src/mcp/server/fastmcp/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ async def list_resource_templates(self) -> list[MCPResourceTemplate]:
335335
title=template.title,
336336
description=template.description,
337337
mimeType=template.mime_type,
338+
icons=template.icons,
338339
)
339340
for template in templates
340341
]
@@ -559,7 +560,7 @@ def decorator(fn: AnyFunction) -> AnyFunction:
559560
title=title,
560561
description=description,
561562
mime_type=mime_type,
562-
# Note: Resource templates don't support icons
563+
icons=icons,
563564
)
564565
else:
565566
# Register as regular resource

src/mcp/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ class ResourceTemplate(BaseMetadata):
470470
The MIME type for all resources that match this template. This should only be
471471
included if all resources matching this template have the same type.
472472
"""
473+
icons: list[Icon] | None = None
474+
"""An optional list of icons for this resource template."""
473475
annotations: Annotations | None = None
474476
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
475477
"""

tests/issues/test_1338_icons_and_metadata.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ def test_prompt(text: str) -> str:
3939
"""A test prompt with an icon."""
4040
return text
4141

42+
# Create resource template with icon
43+
@mcp.resource("test://weather/{city}", icons=[test_icon])
44+
def test_resource_template(city: str) -> str:
45+
"""Get weather for a city."""
46+
return f"Weather for {city}"
47+
4248
# Test server metadata includes websiteUrl and icons
4349
assert mcp.name == "TestServer"
4450
assert mcp.website_url == "https://example.com"
@@ -75,6 +81,16 @@ def test_prompt(text: str) -> str:
7581
assert len(prompt.icons) == 1
7682
assert prompt.icons[0].src == test_icon.src
7783

84+
# Test resource template includes icon
85+
templates = await mcp.list_resource_templates()
86+
assert len(templates) == 1
87+
template = templates[0]
88+
assert template.name == "test_resource_template"
89+
assert template.uriTemplate == "test://weather/{city}"
90+
assert template.icons is not None
91+
assert len(template.icons) == 1
92+
assert template.icons[0].src == test_icon.src
93+
7894

7995
async def test_multiple_icons():
8096
"""Test that multiple icons can be added to tools, resources, and prompts."""

0 commit comments

Comments
 (0)