Skip to content
Open
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
36 changes: 34 additions & 2 deletions docs/servers/icons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ 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

icon = Icon(
src="https://example.com/icon.png",
mimeType="image/png",
sizes=["48x48"]
sizes=["48x48"],
theme="light"
)
```

Expand All @@ -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

Expand Down Expand Up @@ -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",
),
]
)
```

<Tip>
For type checking, use `from fastmcp import IconTheme` to annotate theme variables:
`theme: IconTheme = "light"`
</Tip>

## 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.
Expand Down
34 changes: 33 additions & 1 deletion docs/v2/servers/icons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ 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

icon = Icon(
src="https://example.com/icon.png",
mimeType="image/png",
sizes=["48x48"]
sizes=["48x48"],
theme="light"
)
```

Expand Down Expand Up @@ -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",
),
]
)
```

<Tip>
For type checking, use `from fastmcp import IconTheme` to annotate theme variables:
`theme: IconTheme = "light"`
</Tip>

## Component Icons

Icons can be added to individual tools, resources, resource templates, and prompts:
Expand Down
2 changes: 2 additions & 0 deletions src/fastmcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from fastmcp.client import Client
from . import client
from .utilities.types import IconTheme

__version__ = _version("fastmcp")

Expand All @@ -31,5 +32,6 @@
"Client",
"Context",
"FastMCP",
"IconTheme",
"settings",
]
9 changes: 9 additions & 0 deletions src/fastmcp/utilities/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import (
Annotated,
Any,
Literal,
Protocol,
TypeAlias,
Union,
Expand All @@ -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
Expand Down
79 changes: 79 additions & 0 deletions tests/utilities/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down