diff --git a/docs/servers/icons.mdx b/docs/servers/icons.mdx index c9b558094..779c8da98 100644 --- a/docs/servers/icons.mdx +++ b/docs/servers/icons.mdx @@ -12,7 +12,7 @@ Icons provide visual representations for your MCP servers and components, helpin ## Icon Format -Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies a source URL or data URI, and optionally includes MIME type and size information. +Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies a source URL or data URI, and optionally includes MIME type, size, and theme information. ```python from mcp.types import Icon @@ -20,7 +20,8 @@ from mcp.types import Icon icon = Icon( src="https://example.com/icon.png", mimeType="image/png", - sizes=["48x48"] + sizes=["48x48"], + theme="light" ) ``` @@ -29,6 +30,7 @@ The fields serve different purposes: - **src**: URL or data URI pointing to the icon image - **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml") - **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"]) +- **theme** (optional): `"light"` or `"dark"` — indicates whether the icon is designed for a light or dark background ## Server Icons @@ -58,6 +60,36 @@ mcp = FastMCP( Server icons appear in MCP client interfaces to help users identify your server among others they may have installed. +## Theme Variants + +Use the `theme` field to provide separate icon variants for light and dark UI themes. Clients can select the appropriate icon based on their current appearance. The `IconTheme` type from FastMCP provides type safety for this field. + +```python +from fastmcp import FastMCP +from mcp.types import Icon + +mcp = FastMCP( + name="GitHubServer", + icons=[ + Icon( + src="https://github.githubassets.com/favicons/favicon-dark.svg", + mimeType="image/svg+xml", + theme="dark", + ), + Icon( + src="https://github.githubassets.com/favicons/favicon-light.svg", + mimeType="image/svg+xml", + theme="light", + ), + ] +) +``` + + +For type checking, use `from fastmcp import IconTheme` to annotate theme variables: +`theme: IconTheme = "light"` + + ## Component Icons Icons can be added to individual tools, resources, resource templates, and prompts. This helps users visually distinguish between different component types and purposes. diff --git a/docs/v2/servers/icons.mdx b/docs/v2/servers/icons.mdx index a7d038f6a..9ea298b43 100644 --- a/docs/v2/servers/icons.mdx +++ b/docs/v2/servers/icons.mdx @@ -18,6 +18,7 @@ Icons use the standard MCP Icon type from the MCP protocol specification. Each i - **src**: URL or data URI pointing to the icon image - **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml") - **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"]) +- **theme** (optional): `"light"` or `"dark"` — indicates whether the icon is designed for a light or dark background ```python from mcp.types import Icon @@ -25,7 +26,8 @@ from mcp.types import Icon icon = Icon( src="https://example.com/icon.png", mimeType="image/png", - sizes=["48x48"] + sizes=["48x48"], + theme="light" ) ``` @@ -57,6 +59,36 @@ mcp = FastMCP( Server icons appear in MCP client interfaces to help users identify your server among others they may have installed. +## Theme Variants + +Use the `theme` field to provide separate icon variants for light and dark UI themes. Clients can select the appropriate icon based on their current appearance. The `IconTheme` type from FastMCP provides type safety for this field. + +```python +from fastmcp import FastMCP +from mcp.types import Icon + +mcp = FastMCP( + name="GitHubServer", + icons=[ + Icon( + src="https://github.githubassets.com/favicons/favicon-dark.svg", + mimeType="image/svg+xml", + theme="dark", + ), + Icon( + src="https://github.githubassets.com/favicons/favicon-light.svg", + mimeType="image/svg+xml", + theme="light", + ), + ] +) +``` + + +For type checking, use `from fastmcp import IconTheme` to annotate theme variables: +`theme: IconTheme = "light"` + + ## Component Icons Icons can be added to individual tools, resources, resource templates, and prompts: diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py index 14c0abc5b..53ac8afe2 100644 --- a/src/fastmcp/__init__.py +++ b/src/fastmcp/__init__.py @@ -18,6 +18,7 @@ from fastmcp.client import Client from . import client +from .utilities.types import IconTheme __version__ = _version("fastmcp") @@ -31,5 +32,6 @@ "Client", "Context", "FastMCP", + "IconTheme", "settings", ] diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py index 91377df45..770b45d8c 100644 --- a/src/fastmcp/utilities/types.py +++ b/src/fastmcp/utilities/types.py @@ -11,6 +11,7 @@ from typing import ( Annotated, Any, + Literal, Protocol, TypeAlias, Union, @@ -24,6 +25,14 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field, TypeAdapter, UrlConstraints from typing_extensions import TypeVar +# Forward-compatible import: use IconTheme from mcp.types when available (v2+), +# otherwise define locally for MCP SDK v1.x compatibility. +# See https://modelcontextprotocol.io/specification/2025-11-25/schema#icon +try: + from mcp.types import IconTheme # ty: ignore[unresolved-import] +except ImportError: + IconTheme: TypeAlias = Literal["light", "dark"] + T = TypeVar("T", default=Any) # sentinel values for optional arguments diff --git a/tests/utilities/test_inspect.py b/tests/utilities/test_inspect.py index 448e1eb03..538e32ea3 100644 --- a/tests/utilities/test_inspect.py +++ b/tests/utilities/test_inspect.py @@ -849,6 +849,85 @@ def data_uri_tool() -> str: assert info.tools[0].icons[0]["src"] == data_uri assert info.tools[0].icons[0]["mimeType"] == "image/png" + async def test_icon_theme_light_and_dark(self): + """Test that icon theme field is preserved for light/dark variants.""" + from mcp.types import Icon + + mcp = FastMCP( + "ThemeIconServer", + icons=[ + Icon( + src="https://example.com/icon-dark.svg", + mimeType="image/svg+xml", + theme="dark", # ty: ignore[unknown-argument] + ), + Icon( + src="https://example.com/icon-light.svg", + mimeType="image/svg+xml", + theme="light", # ty: ignore[unknown-argument] + ), + ], + ) + + info = await inspect_fastmcp(mcp) + + assert info.icons is not None + assert len(info.icons) == 2 + assert info.icons[0]["theme"] == "dark" + assert info.icons[1]["theme"] == "light" + + async def test_icon_theme_on_tool(self): + """Test that tool icons preserve the theme field.""" + from mcp.types import Icon + + mcp = FastMCP("ToolThemeServer") + + @mcp.tool( + icons=[ + Icon( + src="https://example.com/tool-dark.png", + mimeType="image/png", + theme="dark", # ty: ignore[unknown-argument] + ), + Icon( + src="https://example.com/tool-light.png", + mimeType="image/png", + theme="light", # ty: ignore[unknown-argument] + ), + ] + ) + def themed_tool() -> str: + """A tool with light and dark theme icons.""" + return "themed" + + info = await inspect_fastmcp(mcp) + + assert len(info.tools) == 1 + assert info.tools[0].icons is not None + assert len(info.tools[0].icons) == 2 + assert info.tools[0].icons[0]["theme"] == "dark" + assert info.tools[0].icons[1]["theme"] == "light" + + async def test_icon_theme_type_import(self): + """Test that IconTheme can be imported from fastmcp.""" + from fastmcp import IconTheme + + # Verify that IconTheme accepts valid values + light: IconTheme = "light" + dark: IconTheme = "dark" + assert light == "light" + assert dark == "dark" + + async def test_icon_without_theme(self): + """Test that icons without theme still work (theme defaults to absent).""" + from mcp.types import Icon + + icon = Icon(src="https://example.com/icon.png") + dumped = icon.model_dump() + + # theme should not appear unless explicitly set + assert "theme" not in dumped or dumped.get("theme") is None + async def test_icons_in_fastmcp_v1(self): """Test that icons are extracted from FastMCP 1.x servers.""" from mcp.types import Icon