Skip to content

Commit 16ba07a

Browse files
committed
Add public properties for website_url and icons, add tests
1 parent b999b4e commit 16ba07a

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,14 @@ def name(self) -> str:
214214
def instructions(self) -> str | None:
215215
return self._mcp_server.instructions
216216

217+
@property
218+
def website_url(self) -> str | None:
219+
return self._mcp_server.website_url
220+
221+
@property
222+
def icons(self) -> list[Icon] | None:
223+
return self._mcp_server.icons
224+
217225
@property
218226
def session_manager(self) -> StreamableHTTPSessionManager:
219227
"""Get the StreamableHTTP session manager.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""Test icon and metadata support (SEP-973)."""
2+
3+
import pytest
4+
5+
from mcp.server.fastmcp import FastMCP
6+
from mcp.types import Icon
7+
8+
pytestmark = pytest.mark.anyio
9+
10+
11+
async def test_icons_and_website_url():
12+
"""Test that icons and websiteUrl are properly returned in API calls."""
13+
14+
# Create test icon
15+
test_icon = Icon(
16+
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
17+
mimeType="image/png",
18+
sizes="1x1"
19+
)
20+
21+
# Create server with website URL and icon
22+
mcp = FastMCP(
23+
"TestServer",
24+
website_url="https://example.com",
25+
icons=[test_icon]
26+
)
27+
28+
# Create tool with icon
29+
@mcp.tool(icons=[test_icon])
30+
def test_tool(message: str) -> str:
31+
"""A test tool with an icon."""
32+
return message
33+
34+
# Create resource with icon
35+
@mcp.resource("test://resource", icons=[test_icon])
36+
def test_resource() -> str:
37+
"""A test resource with an icon."""
38+
return "test content"
39+
40+
# Create prompt with icon
41+
@mcp.prompt("test_prompt", icons=[test_icon])
42+
def test_prompt(text: str) -> str:
43+
"""A test prompt with an icon."""
44+
return text
45+
46+
# Test server metadata includes websiteUrl and icons
47+
assert mcp.name == "TestServer"
48+
assert mcp.website_url == "https://example.com"
49+
assert mcp.icons is not None
50+
assert len(mcp.icons) == 1
51+
assert mcp.icons[0].src == test_icon.src
52+
assert mcp.icons[0].mimeType == test_icon.mimeType
53+
assert mcp.icons[0].sizes == test_icon.sizes
54+
55+
# Test tool includes icon
56+
tools = await mcp.list_tools()
57+
assert len(tools) == 1
58+
tool = tools[0]
59+
assert tool.name == "test_tool"
60+
assert tool.icons is not None
61+
assert len(tool.icons) == 1
62+
assert tool.icons[0].src == test_icon.src
63+
64+
# Test resource includes icon
65+
resources = await mcp.list_resources()
66+
assert len(resources) == 1
67+
resource = resources[0]
68+
assert str(resource.uri) == "test://resource"
69+
assert resource.icons is not None
70+
assert len(resource.icons) == 1
71+
assert resource.icons[0].src == test_icon.src
72+
73+
# Test prompt includes icon
74+
prompts = await mcp.list_prompts()
75+
assert len(prompts) == 1
76+
prompt = prompts[0]
77+
assert prompt.name == "test_prompt"
78+
assert prompt.icons is not None
79+
assert len(prompt.icons) == 1
80+
assert prompt.icons[0].src == test_icon.src
81+
82+
83+
async def test_multiple_icons():
84+
"""Test that multiple icons can be added to tools, resources, and prompts."""
85+
86+
# Create multiple test icons
87+
icon1 = Icon(src="data:image/png;base64,icon1", mimeType="image/png", sizes="16x16")
88+
icon2 = Icon(src="data:image/png;base64,icon2", mimeType="image/png", sizes="32x32")
89+
icon3 = Icon(src="data:image/png;base64,icon3", mimeType="image/png", sizes="64x64")
90+
91+
mcp = FastMCP("MultiIconServer")
92+
93+
# Create tool with multiple icons
94+
@mcp.tool(icons=[icon1, icon2, icon3])
95+
def multi_icon_tool() -> str:
96+
"""A tool with multiple icons."""
97+
return "success"
98+
99+
# Test tool has all icons
100+
tools = await mcp.list_tools()
101+
assert len(tools) == 1
102+
tool = tools[0]
103+
assert tool.icons is not None
104+
assert len(tool.icons) == 3
105+
assert tool.icons[0].sizes == "16x16"
106+
assert tool.icons[1].sizes == "32x32"
107+
assert tool.icons[2].sizes == "64x64"
108+
109+
110+
async def test_no_icons_or_website():
111+
"""Test that server works without icons or websiteUrl."""
112+
113+
mcp = FastMCP("BasicServer")
114+
115+
@mcp.tool()
116+
def basic_tool() -> str:
117+
"""A basic tool without icons."""
118+
return "success"
119+
120+
# Test server metadata has no websiteUrl or icons
121+
assert mcp.name == "BasicServer"
122+
assert mcp.website_url is None
123+
assert mcp.icons is None
124+
125+
# Test tool has no icons
126+
tools = await mcp.list_tools()
127+
assert len(tools) == 1
128+
tool = tools[0]
129+
assert tool.name == "basic_tool"
130+
assert tool.icons is None

0 commit comments

Comments
 (0)