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
343 lines
11 KiB
Python
343 lines
11 KiB
Python
"""Tests for icon support across all MCP object types."""
|
|
|
|
from mcp_types import Icon
|
|
|
|
from fastmcp import Client, FastMCP
|
|
from fastmcp.prompts import Message, Prompt
|
|
from fastmcp.resources import Resource
|
|
from fastmcp.resources.template import ResourceTemplate
|
|
from fastmcp.tools import Tool
|
|
|
|
|
|
class TestServerIcons:
|
|
"""Test icon support at the server/implementation level."""
|
|
|
|
async def test_server_with_icons_and_website_url(self):
|
|
"""Test that server accepts icons and websiteUrl in constructor."""
|
|
icons = [
|
|
Icon(
|
|
src="https://example.com/icon.png",
|
|
mime_type="image/png",
|
|
sizes=["48x48"],
|
|
),
|
|
Icon(
|
|
src="data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=",
|
|
mime_type="image/svg+xml",
|
|
sizes=["any"],
|
|
),
|
|
]
|
|
|
|
mcp = FastMCP(
|
|
name="TestServer",
|
|
version="1.0.0",
|
|
website_url="https://example.com",
|
|
icons=icons,
|
|
)
|
|
|
|
# Verify that icons and website_url are passed to the underlying server
|
|
async with Client(mcp) as client:
|
|
server_info = client.initialize_result.server_info
|
|
assert server_info.website_url == "https://example.com"
|
|
assert server_info.icons == icons
|
|
|
|
async def test_server_without_icons_and_website_url(self):
|
|
"""Test that server works without icons and websiteUrl."""
|
|
mcp = FastMCP(name="TestServer")
|
|
|
|
async with Client(mcp) as client:
|
|
server_info = client.initialize_result.server_info
|
|
assert server_info.website_url is None
|
|
assert server_info.icons is None
|
|
|
|
|
|
class TestToolIcons:
|
|
"""Test icon support for tools."""
|
|
|
|
async def test_tool_with_icons(self):
|
|
"""Test that tools can have icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
icons = [
|
|
Icon(src="https://example.com/tool-icon.png", mime_type="image/png"),
|
|
]
|
|
|
|
@mcp.tool(icons=icons)
|
|
def my_tool(name: str) -> str:
|
|
"""A tool with an icon."""
|
|
return f"Hello, {name}!"
|
|
|
|
async with Client(mcp) as client:
|
|
tools = await client.list_tools()
|
|
assert len(tools) == 1
|
|
tool = tools[0]
|
|
assert tool.icons == icons
|
|
|
|
async def test_tool_from_function_with_icons(self):
|
|
"""Test creating a tool from a function with icons."""
|
|
icons = [Icon(src="https://example.com/icon.png")]
|
|
|
|
def my_function(x: int) -> int:
|
|
"""A function."""
|
|
return x * 2
|
|
|
|
tool = Tool.from_function(my_function, icons=icons)
|
|
assert tool.icons == icons
|
|
|
|
# Verify it converts to MCP tool correctly
|
|
mcp_tool = tool.to_mcp_tool()
|
|
assert mcp_tool.icons == icons
|
|
|
|
async def test_tool_without_icons(self):
|
|
"""Test that tools work without icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
@mcp.tool
|
|
def my_tool(name: str) -> str:
|
|
"""A tool without an icon."""
|
|
return f"Hello, {name}!"
|
|
|
|
async with Client(mcp) as client:
|
|
tools = await client.list_tools()
|
|
assert len(tools) == 1
|
|
tool = tools[0]
|
|
assert tool.icons is None
|
|
|
|
|
|
class TestResourceIcons:
|
|
"""Test icon support for resources."""
|
|
|
|
async def test_resource_with_icons(self):
|
|
"""Test that resources can have icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
icons = [Icon(src="https://example.com/resource-icon.png")]
|
|
|
|
@mcp.resource("test://resource", icons=icons)
|
|
def my_resource() -> str:
|
|
"""A resource with an icon."""
|
|
return "Resource content"
|
|
|
|
async with Client(mcp) as client:
|
|
resources = await client.list_resources()
|
|
assert len(resources) == 1
|
|
resource = resources[0]
|
|
assert resource.icons == icons
|
|
|
|
async def test_resource_from_function_with_icons(self):
|
|
"""Test creating a resource from a function with icons."""
|
|
icons = [Icon(src="https://example.com/icon.png")]
|
|
|
|
def my_function() -> str:
|
|
"""A function."""
|
|
return "content"
|
|
|
|
resource = Resource.from_function(
|
|
my_function,
|
|
uri="test://resource",
|
|
icons=icons,
|
|
)
|
|
assert resource.icons == icons
|
|
|
|
# Verify it converts to MCP resource correctly
|
|
mcp_resource = resource.to_mcp_resource()
|
|
assert mcp_resource.icons == icons
|
|
|
|
async def test_resource_without_icons(self):
|
|
"""Test that resources work without icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
@mcp.resource("test://resource")
|
|
def my_resource() -> str:
|
|
"""A resource without an icon."""
|
|
return "Resource content"
|
|
|
|
async with Client(mcp) as client:
|
|
resources = await client.list_resources()
|
|
assert len(resources) == 1
|
|
resource = resources[0]
|
|
assert resource.icons is None
|
|
|
|
|
|
class TestResourceTemplateIcons:
|
|
"""Test icon support for resource templates."""
|
|
|
|
async def test_resource_template_with_icons(self):
|
|
"""Test that resource templates can have icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
icons = [Icon(src="https://example.com/template-icon.png")]
|
|
|
|
@mcp.resource("test://resource/{id}", icons=icons)
|
|
def my_template(id: str) -> str:
|
|
"""A resource template with an icon."""
|
|
return f"Resource {id}"
|
|
|
|
async with Client(mcp) as client:
|
|
templates = await client.list_resource_templates()
|
|
assert len(templates) == 1
|
|
template = templates[0]
|
|
assert template.icons == icons
|
|
|
|
async def test_resource_template_from_function_with_icons(self):
|
|
"""Test creating a resource template from a function with icons."""
|
|
icons = [Icon(src="https://example.com/icon.png")]
|
|
|
|
def my_function(id: str) -> str:
|
|
"""A function."""
|
|
return f"content-{id}"
|
|
|
|
template = ResourceTemplate.from_function(
|
|
my_function,
|
|
uri_template="test://resource/{id}",
|
|
icons=icons,
|
|
)
|
|
assert template.icons == icons
|
|
|
|
# Verify it converts to MCP template correctly
|
|
mcp_template = template.to_mcp_template()
|
|
assert mcp_template.icons == icons
|
|
|
|
async def test_resource_template_without_icons(self):
|
|
"""Test that resource templates work without icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
@mcp.resource("test://resource/{id}")
|
|
def my_template(id: str) -> str:
|
|
"""A resource template without an icon."""
|
|
return f"Resource {id}"
|
|
|
|
async with Client(mcp) as client:
|
|
templates = await client.list_resource_templates()
|
|
assert len(templates) == 1
|
|
template = templates[0]
|
|
assert template.icons is None
|
|
|
|
|
|
class TestPromptIcons:
|
|
"""Test icon support for prompts."""
|
|
|
|
async def test_prompt_with_icons(self):
|
|
"""Test that prompts can have icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
icons = [Icon(src="https://example.com/prompt-icon.png")]
|
|
|
|
@mcp.prompt(icons=icons)
|
|
def my_prompt(name: str):
|
|
"""A prompt with an icon."""
|
|
return Message(f"Hello, {name}!")
|
|
|
|
async with Client(mcp) as client:
|
|
prompts = await client.list_prompts()
|
|
assert len(prompts) == 1
|
|
prompt = prompts[0]
|
|
assert prompt.icons == icons
|
|
|
|
async def test_prompt_from_function_with_icons(self):
|
|
"""Test creating a prompt from a function with icons."""
|
|
icons = [Icon(src="https://example.com/icon.png")]
|
|
|
|
def my_function(topic: str):
|
|
"""A function."""
|
|
return Message(f"Tell me about {topic}")
|
|
|
|
prompt = Prompt.from_function(my_function, icons=icons)
|
|
assert prompt.icons == icons
|
|
|
|
# Verify it converts to MCP prompt correctly
|
|
mcp_prompt = prompt.to_mcp_prompt()
|
|
assert mcp_prompt.icons == icons
|
|
|
|
async def test_prompt_without_icons(self):
|
|
"""Test that prompts work without icons."""
|
|
mcp = FastMCP("TestServer")
|
|
|
|
@mcp.prompt
|
|
def my_prompt(name: str):
|
|
"""A prompt without an icon."""
|
|
return Message(f"Hello, {name}!")
|
|
|
|
async with Client(mcp) as client:
|
|
prompts = await client.list_prompts()
|
|
assert len(prompts) == 1
|
|
prompt = prompts[0]
|
|
assert prompt.icons is None
|
|
|
|
|
|
class TestIconTypes:
|
|
"""Test different types of icon data."""
|
|
|
|
async def test_multiple_icon_sizes(self):
|
|
"""Test that multiple icon sizes can be specified."""
|
|
icons = [
|
|
Icon(
|
|
src="https://example.com/icon-48.png",
|
|
mime_type="image/png",
|
|
sizes=["48x48"],
|
|
),
|
|
Icon(
|
|
src="https://example.com/icon-96.png",
|
|
mime_type="image/png",
|
|
sizes=["96x96"],
|
|
),
|
|
Icon(
|
|
src="https://example.com/icon.svg",
|
|
mime_type="image/svg+xml",
|
|
sizes=["any"],
|
|
),
|
|
]
|
|
|
|
mcp = FastMCP("TestServer", icons=icons)
|
|
|
|
async with Client(mcp) as client:
|
|
server_info = client.initialize_result.server_info
|
|
assert len(server_info.icons) == 3
|
|
assert server_info.icons == icons
|
|
|
|
async def test_data_uri_icon(self):
|
|
"""Test using data URIs for icons."""
|
|
# Simple SVG data URI
|
|
data_uri = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6Ii8+PC9zdmc+"
|
|
|
|
icons = [Icon(src=data_uri, mime_type="image/svg+xml")]
|
|
|
|
mcp = FastMCP("TestServer")
|
|
|
|
@mcp.tool(icons=icons)
|
|
def my_tool() -> str:
|
|
"""A tool with a data URI icon."""
|
|
return "result"
|
|
|
|
async with Client(mcp) as client:
|
|
tools = await client.list_tools()
|
|
assert tools[0].icons[0].src == data_uri
|
|
|
|
async def test_icon_without_optional_fields(self):
|
|
"""Test that icons work with only the src field."""
|
|
icons = [Icon(src="https://example.com/icon.png")]
|
|
|
|
mcp = FastMCP("TestServer", icons=icons)
|
|
|
|
async with Client(mcp) as client:
|
|
server_info = client.initialize_result.server_info
|
|
assert server_info.icons[0].src == "https://example.com/icon.png"
|
|
assert server_info.icons[0].mime_type is None
|
|
assert server_info.icons[0].sizes is None
|
|
|
|
|
|
class TestIconImport:
|
|
"""Test that Icon must be imported from mcp_types."""
|
|
|
|
def test_icon_import(self):
|
|
"""Test that Icon must be imported from mcp.types, not fastmcp."""
|
|
# Icon should NOT be available from fastmcp
|
|
import fastmcp
|
|
|
|
assert not hasattr(fastmcp, "Icon")
|
|
|
|
# Icon should be imported from mcp.types
|
|
from mcp_types import Icon as MCPIcon
|
|
|
|
icon = MCPIcon(src="https://example.com/icon.png")
|
|
assert icon.src == "https://example.com/icon.png"
|