Server initialize response update to last spec (add title, description) (#1634)
This commit is contained in:
parent
f397783c39
commit
3863f203e9
7 changed files with 73 additions and 1 deletions
|
|
@ -147,9 +147,12 @@ class FastMCP(Generic[LifespanResultT]):
|
||||||
def __init__( # noqa: PLR0913
|
def __init__( # noqa: PLR0913
|
||||||
self,
|
self,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
instructions: str | None = None,
|
instructions: str | None = None,
|
||||||
website_url: str | None = None,
|
website_url: str | None = None,
|
||||||
icons: list[Icon] | None = None,
|
icons: list[Icon] | None = None,
|
||||||
|
version: str | None = None,
|
||||||
auth_server_provider: (OAuthAuthorizationServerProvider[Any, Any, Any] | None) = None,
|
auth_server_provider: (OAuthAuthorizationServerProvider[Any, Any, Any] | None) = None,
|
||||||
token_verifier: TokenVerifier | None = None,
|
token_verifier: TokenVerifier | None = None,
|
||||||
event_store: EventStore | None = None,
|
event_store: EventStore | None = None,
|
||||||
|
|
@ -204,9 +207,12 @@ class FastMCP(Generic[LifespanResultT]):
|
||||||
|
|
||||||
self._mcp_server = MCPServer(
|
self._mcp_server = MCPServer(
|
||||||
name=name or "FastMCP",
|
name=name or "FastMCP",
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
instructions=instructions,
|
instructions=instructions,
|
||||||
website_url=website_url,
|
website_url=website_url,
|
||||||
icons=icons,
|
icons=icons,
|
||||||
|
version=version,
|
||||||
# TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an FastMCP and Server.
|
# 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.
|
# 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
|
lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan), # type: ignore
|
||||||
|
|
@ -245,6 +251,14 @@ class FastMCP(Generic[LifespanResultT]):
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self._mcp_server.name
|
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
|
@property
|
||||||
def instructions(self) -> str | None:
|
def instructions(self) -> str | None:
|
||||||
return self._mcp_server.instructions
|
return self._mcp_server.instructions
|
||||||
|
|
@ -257,6 +271,10 @@ class FastMCP(Generic[LifespanResultT]):
|
||||||
def icons(self) -> list[Icon] | None:
|
def icons(self) -> list[Icon] | None:
|
||||||
return self._mcp_server.icons
|
return self._mcp_server.icons
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> str | None:
|
||||||
|
return self._mcp_server.version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session_manager(self) -> StreamableHTTPSessionManager:
|
def session_manager(self) -> StreamableHTTPSessionManager:
|
||||||
"""Get the StreamableHTTP session manager.
|
"""Get the StreamableHTTP session manager.
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,8 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
version: str | None = None,
|
version: str | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
instructions: str | None = None,
|
instructions: str | None = None,
|
||||||
website_url: str | None = None,
|
website_url: str | None = None,
|
||||||
icons: list[types.Icon] | None = None,
|
icons: list[types.Icon] | None = None,
|
||||||
|
|
@ -149,6 +151,8 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
self.instructions = instructions
|
self.instructions = instructions
|
||||||
self.website_url = website_url
|
self.website_url = website_url
|
||||||
self.icons = icons
|
self.icons = icons
|
||||||
|
|
@ -181,6 +185,8 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
return InitializationOptions(
|
return InitializationOptions(
|
||||||
server_name=self.name,
|
server_name=self.name,
|
||||||
server_version=self.version if self.version else pkg_version("mcp"),
|
server_version=self.version if self.version else pkg_version("mcp"),
|
||||||
|
title=self.title,
|
||||||
|
description=self.description,
|
||||||
capabilities=self.get_capabilities(
|
capabilities=self.get_capabilities(
|
||||||
notification_options or NotificationOptions(),
|
notification_options or NotificationOptions(),
|
||||||
experimental_capabilities or {},
|
experimental_capabilities or {},
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from mcp.types import (
|
||||||
class InitializationOptions(BaseModel):
|
class InitializationOptions(BaseModel):
|
||||||
server_name: str
|
server_name: str
|
||||||
server_version: str
|
server_version: str
|
||||||
|
title: str | None = None
|
||||||
|
description: str | None = None
|
||||||
capabilities: ServerCapabilities
|
capabilities: ServerCapabilities
|
||||||
instructions: str | None = None
|
instructions: str | None = None
|
||||||
website_url: str | None = None
|
website_url: str | None = None
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,8 @@ class ServerSession(
|
||||||
capabilities=self._init_options.capabilities,
|
capabilities=self._init_options.capabilities,
|
||||||
serverInfo=types.Implementation(
|
serverInfo=types.Implementation(
|
||||||
name=self._init_options.server_name,
|
name=self._init_options.server_name,
|
||||||
|
title=self._init_options.title,
|
||||||
|
description=self._init_options.description,
|
||||||
version=self._init_options.server_version,
|
version=self._init_options.server_version,
|
||||||
websiteUrl=self._init_options.website_url,
|
websiteUrl=self._init_options.website_url,
|
||||||
icons=self._init_options.icons,
|
icons=self._init_options.icons,
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,12 @@ class Implementation(BaseMetadata):
|
||||||
|
|
||||||
version: str
|
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
|
websiteUrl: str | None = None
|
||||||
"""An optional URL of the website for this implementation."""
|
"""An optional URL of the website for this implementation."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from mcp.types import (
|
||||||
BlobResourceContents,
|
BlobResourceContents,
|
||||||
ContentBlock,
|
ContentBlock,
|
||||||
EmbeddedResource,
|
EmbeddedResource,
|
||||||
|
Icon,
|
||||||
ImageContent,
|
ImageContent,
|
||||||
TextContent,
|
TextContent,
|
||||||
TextResourceContents,
|
TextResourceContents,
|
||||||
|
|
@ -29,9 +30,23 @@ from mcp.types import (
|
||||||
class TestServer:
|
class TestServer:
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_create_server(self):
|
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.name == "FastMCP"
|
||||||
|
assert mcp.title == "FastMCP Server"
|
||||||
|
assert mcp.description == "Server description"
|
||||||
assert mcp.instructions == "Server instructions"
|
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
|
@pytest.mark.anyio
|
||||||
async def test_normalize_path(self):
|
async def test_normalize_path(self):
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,29 @@ from mcp.shared.metadata_utils import get_display_name
|
||||||
from mcp.types import Prompt, Resource, ResourceTemplate, Tool, ToolAnnotations
|
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
|
@pytest.mark.anyio
|
||||||
async def test_tool_title_precedence():
|
async def test_tool_title_precedence():
|
||||||
"""Test that tool title precedence works correctly: title > annotations.title > name."""
|
"""Test that tool title precedence works correctly: title > annotations.title > name."""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue