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
18 changes: 18 additions & 0 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,12 @@ class FastMCP(Generic[LifespanResultT]):
def __init__( # noqa: PLR0913
self,
name: str | None = None,
title: str | None = None,
description: str | None = None,
instructions: str | None = None,
website_url: str | None = None,
icons: list[Icon] | None = None,
version: str | None = None,
auth_server_provider: (OAuthAuthorizationServerProvider[Any, Any, Any] | None) = None,
token_verifier: TokenVerifier | None = None,
event_store: EventStore | None = None,
Expand Down Expand Up @@ -191,9 +194,12 @@ def __init__( # noqa: PLR0913

self._mcp_server = MCPServer(
name=name or "FastMCP",
title=title,
description=description,
instructions=instructions,
website_url=website_url,
icons=icons,
version=version,
# TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an FastMCP and Server.
# We need to create a Lifespan type that is a generic on the server type, like Starlette does.
lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan), # type: ignore
Expand Down Expand Up @@ -231,6 +237,14 @@ def __init__( # noqa: PLR0913
def name(self) -> str:
return self._mcp_server.name

@property
def title(self) -> str | None:
return self._mcp_server.title

@property
def description(self) -> str | None:
return self._mcp_server.description

@property
def instructions(self) -> str | None:
return self._mcp_server.instructions
Expand All @@ -243,6 +257,10 @@ def website_url(self) -> str | None:
def icons(self) -> list[Icon] | None:
return self._mcp_server.icons

@property
def version(self) -> str | None:
return self._mcp_server.version

@property
def session_manager(self) -> StreamableHTTPSessionManager:
"""Get the StreamableHTTP session manager.
Expand Down
6 changes: 6 additions & 0 deletions src/mcp/server/lowlevel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ def __init__(
self,
name: str,
version: str | None = None,
title: str | None = None,
description: str | None = None,
instructions: str | None = None,
website_url: str | None = None,
icons: list[types.Icon] | None = None,
Expand All @@ -145,6 +147,8 @@ def __init__(
):
self.name = name
self.version = version
self.title = title
self.description = description
self.instructions = instructions
self.website_url = website_url
self.icons = icons
Expand Down Expand Up @@ -176,6 +180,8 @@ def pkg_version(package: str) -> str:
return InitializationOptions(
server_name=self.name,
server_version=self.version if self.version else pkg_version("mcp"),
title=self.title,
description=self.description,
capabilities=self.get_capabilities(
notification_options or NotificationOptions(),
experimental_capabilities or {},
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
class InitializationOptions(BaseModel):
server_name: str
server_version: str
title: str | None = None
description: str | None = None
capabilities: ServerCapabilities
instructions: str | None = None
website_url: str | None = None
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/server/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ async def _received_request(self, responder: RequestResponder[types.ClientReques
capabilities=self._init_options.capabilities,
serverInfo=types.Implementation(
name=self._init_options.server_name,
title=self._init_options.title,
description=self._init_options.description,
version=self._init_options.server_version,
websiteUrl=self._init_options.website_url,
icons=self._init_options.icons,
Expand Down
6 changes: 6 additions & 0 deletions src/mcp/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ class Implementation(BaseMetadata):

version: str

title: str | None = None
"""An optional human-readable title for this implementation."""

description: str | None = None
"""An optional human-readable description of what this implementation does."""

websiteUrl: str | None = None
"""An optional URL of the website for this implementation."""

Expand Down
17 changes: 16 additions & 1 deletion tests/server/fastmcp/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
BlobResourceContents,
ContentBlock,
EmbeddedResource,
Icon,
ImageContent,
TextContent,
TextResourceContents,
Expand All @@ -33,9 +34,23 @@
class TestServer:
@pytest.mark.anyio
async def test_create_server(self):
mcp = FastMCP(instructions="Server instructions")
mcp = FastMCP(
title="FastMCP Server",
description="Server description",
instructions="Server instructions",
website_url="https://example.com/mcp_server",
version="1.0",
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png", sizes=["48x48", "96x96"])],
)
assert mcp.name == "FastMCP"
assert mcp.title == "FastMCP Server"
assert mcp.description == "Server description"
assert mcp.instructions == "Server instructions"
assert mcp.website_url == "https://example.com/mcp_server"
assert mcp.version == "1.0"
assert isinstance(mcp.icons, list)
assert len(mcp.icons) == 1
assert mcp.icons[0].src == "https://example.com/icon.png"

@pytest.mark.anyio
async def test_normalize_path(self):
Expand Down
23 changes: 23 additions & 0 deletions tests/server/fastmcp/test_title.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@
from mcp.types import Prompt, Resource, ResourceTemplate, Tool, ToolAnnotations


@pytest.mark.anyio
async def test_server_name_title_description_version():
"""Test that server title and description are set and retrievable correctly."""
mcp = FastMCP(
name="TestServer",
title="Test Server Title",
description="This is a test server description.",
version="1.0",
)

assert mcp.title == "Test Server Title"
assert mcp.description == "This is a test server description."
assert mcp.version == "1.0"

# Start server and connect client
async with create_connected_server_and_client_session(mcp._mcp_server) as client:
init_result = await client.initialize()
assert init_result.serverInfo.name == "TestServer"
assert init_result.serverInfo.title == "Test Server Title"
assert init_result.serverInfo.description == "This is a test server description."
assert init_result.serverInfo.version == "1.0"


@pytest.mark.anyio
async def test_tool_title_precedence():
"""Test that tool title precedence works correctly: title > annotations.title > name."""
Expand Down
Loading