From b6380bb85b040e291d67c4efa9f02568b3d278ea Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:07:33 +0300 Subject: [PATCH 01/14] show-server-info --- src/mcp/server/fastmcp/server.py | 53 ++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index bf0ce880a..4e2449087 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -85,6 +85,8 @@ class Settings(BaseSettings, Generic[LifespanResultT]): # prompt settings warn_on_duplicate_prompts: bool = True + show_server_info: bool = True + dependencies: list[str] = Field( default_factory=list, description="List of dependencies to install in the server environment", @@ -155,10 +157,57 @@ def run(self, transport: Literal["stdio", "sse"] = "stdio") -> None: if transport not in TRANSPORTS.__args__: # type: ignore raise ValueError(f"Unknown transport: {transport}") + async def run(): await self._run(transport) + anyio.run(run) + + async def _run(self, transport: Literal["stdio", "sse"]): + if self.settings.show_server_info: + server_info = await self.get_server_info() + logger.debug(f"Server name: {server_info.name}") + logger.debug(f"Server: {server_info.host}:{server_info.port}") + for asset_type, asset_list in server_info.assets.items(): + if not asset_list: + continue + logger.debug(f"{asset_type}:") + for asset in asset_list: + logger.debug(f" - {asset.name} - {asset.description}") + logger.debug("Server running...") if transport == "stdio": - anyio.run(self.run_stdio_async) + await self.run_stdio_async() else: # transport == "sse" - anyio.run(self.run_sse_async) + await self.run_sse_async() + + async def get_server_info(self): + """ + Asynchronously retrieves and returns server information. + + This method gathers details about the server, including its name, host, port, + instructions, and various assets such as tools, resources, prompts, and resource templates. + + Returns: + dict: A dictionary containing the server's information: + - name (str): The name of the server. + - host (str): The host address of the server. + - port (int): The port number the server is running on. + - instructions (str): Instructions or guidelines related to the server. + - assets (dict): A dictionary of server assets: + - tools (list): A list of tools available on the server. + - resources (list): A list of resources available on the server. + - prompts (list): A list of prompts available on the server. + - resource_templates (list): A list of resource templates available on the server. + """ + return { + 'name': self.name, + 'host': self.settings.host, + 'port': self.settings.port, + 'instructions': self.instructions, + 'assets': { + 'tools': await self.list_tools(), + 'resources': await self.list_resources(), + 'prompts': await self.list_prompts(), + 'resource_templates': await self.list_resource_templates() + } + } def _setup_handlers(self) -> None: """Set up core MCP protocol handlers.""" From 48a2ae428eb591493631f399c8b6cabc3e31ca8f Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:17:39 +0300 Subject: [PATCH 02/14] show-server-info --- src/mcp/server/fastmcp/server.py | 27 ++++++++++++++------------- src/mcp/types.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 4e2449087..25045a89f 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -49,6 +49,7 @@ from mcp.types import Resource as MCPResource from mcp.types import ResourceTemplate as MCPResourceTemplate from mcp.types import Tool as MCPTool +from mcp.types import ServerInfo logger = get_logger(__name__) @@ -177,7 +178,7 @@ async def _run(self, transport: Literal["stdio", "sse"]): else: # transport == "sse" await self.run_sse_async() - async def get_server_info(self): + async def get_server_info(self) -> ServerInfo: """ Asynchronously retrieves and returns server information. @@ -196,18 +197,18 @@ async def get_server_info(self): - prompts (list): A list of prompts available on the server. - resource_templates (list): A list of resource templates available on the server. """ - return { - 'name': self.name, - 'host': self.settings.host, - 'port': self.settings.port, - 'instructions': self.instructions, - 'assets': { - 'tools': await self.list_tools(), - 'resources': await self.list_resources(), - 'prompts': await self.list_prompts(), - 'resource_templates': await self.list_resource_templates() - } - } + return ServerInfo( + name=self.name, + host=self.settings.host, + port=self.settings.port, + instructions=self.instructions, + assets=ServerInfo.ServerInfoAssets( + tools=await self.list_tools(), + resources=await self.list_resources(), + prompts=await self.list_prompts(), + resource_templates=await self.list_resource_templates() + ) + ) def _setup_handlers(self) -> None: """Set up core MCP protocol handlers.""" diff --git a/src/mcp/types.py b/src/mcp/types.py index bd71d51f0..afb419907 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1128,3 +1128,19 @@ class ServerResult( ] ): pass + +class ServerInfo(BaseModel): + class ServerInfoAssets(BaseModel): + tools: list[Tool] + prompts: list[Prompt] + resources: list[Resource] + resource_templates: list[ResourceTemplate] + + name: str + host: str + port: int + instructions: str | None + assets: ServerInfoAssets + + + \ No newline at end of file From 303bbf1035f480897cbe471d64e76afa2a9e2bdb Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:18:01 +0300 Subject: [PATCH 03/14] show-server-info --- src/mcp/server/fastmcp/server.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 25045a89f..a70928cfd 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -185,17 +185,7 @@ async def get_server_info(self) -> ServerInfo: This method gathers details about the server, including its name, host, port, instructions, and various assets such as tools, resources, prompts, and resource templates. - Returns: - dict: A dictionary containing the server's information: - - name (str): The name of the server. - - host (str): The host address of the server. - - port (int): The port number the server is running on. - - instructions (str): Instructions or guidelines related to the server. - - assets (dict): A dictionary of server assets: - - tools (list): A list of tools available on the server. - - resources (list): A list of resources available on the server. - - prompts (list): A list of prompts available on the server. - - resource_templates (list): A list of resource templates available on the server. + Returns: ServerInfo """ return ServerInfo( name=self.name, From 55e596e278846c662ba0456f9fa5d34160c15c8b Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:25:19 +0300 Subject: [PATCH 04/14] show-server-info --- src/mcp/server/fastmcp/server.py | 11 +++++------ src/mcp/types.py | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index a70928cfd..a81da148f 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -166,6 +166,7 @@ async def _run(self, transport: Literal["stdio", "sse"]): server_info = await self.get_server_info() logger.debug(f"Server name: {server_info.name}") logger.debug(f"Server: {server_info.host}:{server_info.port}") + logger.debug(f"Instructions: {server_info.instructions}") for asset_type, asset_list in server_info.assets.items(): if not asset_list: continue @@ -192,12 +193,10 @@ async def get_server_info(self) -> ServerInfo: host=self.settings.host, port=self.settings.port, instructions=self.instructions, - assets=ServerInfo.ServerInfoAssets( - tools=await self.list_tools(), - resources=await self.list_resources(), - prompts=await self.list_prompts(), - resource_templates=await self.list_resource_templates() - ) + tools=await self.list_tools() or [], + resources=await self.list_resources() or [], + prompts=await self.list_prompts() or [], + resource_templates=await self.list_resource_templates() or [] ) def _setup_handlers(self) -> None: diff --git a/src/mcp/types.py b/src/mcp/types.py index afb419907..b1e578d91 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1129,18 +1129,25 @@ class ServerResult( ): pass -class ServerInfo(BaseModel): - class ServerInfoAssets(BaseModel): - tools: list[Tool] - prompts: list[Prompt] - resources: list[Resource] - resource_templates: list[ResourceTemplate] - +class ServerInfo(BaseModel): name: str host: str port: int instructions: str | None - assets: ServerInfoAssets + tools: list[Tool] + prompts: list[Prompt] + resources: list[Resource] + resource_templates: list[ResourceTemplate] + + @property + def assets(self) -> dict[str, list[Tool] | list[Prompt] | list[Resource] | list[ResourceTemplate]]: + assets_dict = { + "tools": self.tools, + "prompts": self.prompts, + "resources": self.resources, + "resource_templates": self.resource_templates + } + return assets_dict \ No newline at end of file From bfd77d762e3595f48d0f9e87a766017b07d9a97b Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:27:20 +0300 Subject: [PATCH 05/14] fix format --- src/mcp/server/fastmcp/server.py | 3 ++- src/mcp/types.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index a81da148f..af8fa3787 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -184,7 +184,8 @@ async def get_server_info(self) -> ServerInfo: Asynchronously retrieves and returns server information. This method gathers details about the server, including its name, host, port, - instructions, and various assets such as tools, resources, prompts, and resource templates. + instructions, and various assets such as tools, resources, + prompts, and resource templates. Returns: ServerInfo """ diff --git a/src/mcp/types.py b/src/mcp/types.py index b1e578d91..56a44c638 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1140,7 +1140,9 @@ class ServerInfo(BaseModel): resource_templates: list[ResourceTemplate] @property - def assets(self) -> dict[str, list[Tool] | list[Prompt] | list[Resource] | list[ResourceTemplate]]: + def assets(self) -> dict[ + str, list[Tool] | list[Prompt] | list[Resource] | list[ResourceTemplate] + ]: assets_dict = { "tools": self.tools, "prompts": self.prompts, From ef14b985fa75440c4b04493099499ecb069662ab Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Fri, 28 Mar 2025 14:29:22 +0300 Subject: [PATCH 06/14] fix format --- src/mcp/server/fastmcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index af8fa3787..03525f4ff 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -42,6 +42,7 @@ EmbeddedResource, GetPromptResult, ImageContent, + ServerInfo, TextContent, ) from mcp.types import Prompt as MCPPrompt @@ -49,7 +50,6 @@ from mcp.types import Resource as MCPResource from mcp.types import ResourceTemplate as MCPResourceTemplate from mcp.types import Tool as MCPTool -from mcp.types import ServerInfo logger = get_logger(__name__) From 84fff937bf72f15cd75b855234d825b358cb026b Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 11:51:59 +0300 Subject: [PATCH 07/14] show-server-info --- src/mcp/server/fastmcp/server.py | 30 ++++++++++++++++-------------- src/mcp/types.py | 26 ++++++++++++++++---------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 997cf5142..ee88bda8e 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -85,7 +85,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]): # prompt settings warn_on_duplicate_prompts: bool = True - show_server_info: bool = True + show_server_info: bool = False dependencies: list[str] = Field( default_factory=list, @@ -157,27 +157,29 @@ def run(self, transport: Literal["stdio", "sse"] = "stdio") -> None: if transport not in TRANSPORTS.__args__: # type: ignore raise ValueError(f"Unknown transport: {transport}") - async def run(): await self._run(transport) - anyio.run(run) + anyio.run(self._run, transport) async def _run(self, transport: Literal["stdio", "sse"]): if self.settings.show_server_info: - server_info = await self.get_server_info() - logger.debug(f"Server name: {server_info.name}") - logger.debug(f"Server: {server_info.host}:{server_info.port}") - logger.debug(f"Instructions: {server_info.instructions}") - for asset_type, asset_list in server_info.assets.items(): - if not asset_list: - continue - logger.debug(f"{asset_type}:") - for asset in asset_list: - logger.debug(f" - {asset.name} - {asset.description}") - logger.debug("Server running...") + await self._log_server_info() if transport == "stdio": await self.run_stdio_async() else: # transport == "sse" await self.run_sse_async() + async def _log_server_info(self): + server_info = await self.get_server_info() + logger.info(f"Server name: {server_info.name}") + logger.info(f"Server: {server_info.host}:{server_info.port}") + logger.info(f"Instructions: {server_info.instructions}") + for asset_type, asset_list in server_info.assets.items(): + if not asset_list: + continue + logger.info(f"{asset_type}:") + for asset in asset_list: + logger.info(f" - {asset.name} - {asset.description}") + logger.info("Server running...") + async def get_server_info(self) -> ServerInfo: """ Asynchronously retrieves and returns server information. diff --git a/src/mcp/types.py b/src/mcp/types.py index 56a44c638..a0755a228 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -6,6 +6,7 @@ Literal, TypeAlias, TypeVar, + TypedDict, ) from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel @@ -1129,6 +1130,14 @@ class ServerResult( ): pass + +class ServerInfoAssets(TypedDict): + tools: list[Tool] + prompts: list[Prompt] + resources: list[Resource] + resource_templates: list[ResourceTemplate] + + class ServerInfo(BaseModel): name: str host: str @@ -1140,16 +1149,13 @@ class ServerInfo(BaseModel): resource_templates: list[ResourceTemplate] @property - def assets(self) -> dict[ - str, list[Tool] | list[Prompt] | list[Resource] | list[ResourceTemplate] - ]: - assets_dict = { - "tools": self.tools, - "prompts": self.prompts, - "resources": self.resources, - "resource_templates": self.resource_templates - } - return assets_dict + def assets(self) -> ServerInfoAssets: + return ServerInfoAssets( + tools=self.tools, + prompts=self.prompts, + resources=self.resources, + resource_templates=self.resource_templates, + ) \ No newline at end of file From fa9467df76f1b28d36ee2cb849296fb4d4d5823a Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 15:18:47 +0300 Subject: [PATCH 08/14] Update server.py --- src/mcp/server/fastmcp/server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index e839fd365..c808b5f0a 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -234,7 +234,11 @@ def run( anyio.run(self._run, transport) - async def _run(self, transport: Literal["stdio", "sse", "streamable-http"]): + async def _run( + self, + transport: Literal["stdio", "sse", "streamable-http"], + mount_path: str | None = None, + ) -> None: if self.settings.show_server_info: await self._log_server_info() match transport: From 465bdc8c5c94591b0221a3a90767c39c8c061883 Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 15:20:00 +0300 Subject: [PATCH 09/14] Update types.py --- src/mcp/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/types.py b/src/mcp/types.py index 4cf812a6c..d727bb740 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -5,8 +5,8 @@ Generic, Literal, TypeAlias, - TypeVar, TypedDict, + TypeVar, ) from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel @@ -1218,4 +1218,4 @@ def assets(self) -> ServerInfoAssets: ) - \ No newline at end of file + From 82f6d9aa3ceca89d20a0bc5312dbcfa76af53ade Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 15:20:31 +0300 Subject: [PATCH 10/14] Update types.py --- src/mcp/types.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mcp/types.py b/src/mcp/types.py index d727bb740..40934dbd3 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1216,6 +1216,3 @@ def assets(self) -> ServerInfoAssets: resources=self.resources, resource_templates=self.resource_templates, ) - - - From 1b794cbddfe1933d4caa20d31e0c60b877a88541 Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 16:03:17 +0300 Subject: [PATCH 11/14] show-server-info --- src/mcp/server/fastmcp/server.py | 78 +++++++--------------- src/mcp/types.py | 109 +++++++++++-------------------- 2 files changed, 62 insertions(+), 125 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index de5b56d17..cc90aef9e 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -42,6 +42,7 @@ GetPromptResult, ImageContent, ServerInfo, + ServerInfoAsset, TextContent, ) from mcp.types import Prompt as MCPPrompt @@ -92,9 +93,9 @@ class Settings(BaseSettings, Generic[LifespanResultT]): description="List of dependencies to install in the server environment", ) - lifespan: ( - Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None - ) = Field(None, description="Lifespan context manager") + lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None = Field( + None, description="Lifespan context manager" + ) def lifespan_wrapper( @@ -110,27 +111,17 @@ async def wrap(s: MCPServer[LifespanResultT]) -> AsyncIterator[object]: class FastMCP: - def __init__( - self, name: str | None = None, instructions: str | None = None, **settings: Any - ): + def __init__(self, name: str | None = None, instructions: str | None = None, **settings: Any): self.settings = Settings(**settings) self._mcp_server = MCPServer( name=name or "FastMCP", instructions=instructions, - lifespan=lifespan_wrapper(self, self.settings.lifespan) - if self.settings.lifespan - else default_lifespan, - ) - self._tool_manager = ToolManager( - warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools - ) - self._resource_manager = ResourceManager( - warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources - ) - self._prompt_manager = PromptManager( - warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts + lifespan=lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan, ) + self._tool_manager = ToolManager(warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools) + self._resource_manager = ResourceManager(warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources) + self._prompt_manager = PromptManager(warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts) self.dependencies = self.settings.dependencies # Set up MCP protocol handlers @@ -158,7 +149,7 @@ def run(self, transport: Literal["stdio", "sse"] = "stdio") -> None: raise ValueError(f"Unknown transport: {transport}") anyio.run(self._run, transport) - + async def _run(self, transport: Literal["stdio", "sse"]): if self.settings.show_server_info: await self._log_server_info() @@ -172,7 +163,8 @@ async def _log_server_info(self): logger.info(f"Server name: {server_info.name}") logger.info(f"Server: {server_info.host}:{server_info.port}") logger.info(f"Instructions: {server_info.instructions}") - for asset_type, asset_list in server_info.assets.items(): + for asset_type, asset_list in server_info.assets.model_dump().items(): + asset_list: list[ServerInfoAsset] if not asset_list: continue logger.info(f"{asset_type}:") @@ -185,7 +177,7 @@ async def get_server_info(self) -> ServerInfo: Asynchronously retrieves and returns server information. This method gathers details about the server, including its name, host, port, - instructions, and various assets such as tools, resources, + instructions, and various assets such as tools, resources, prompts, and resource templates. Returns: ServerInfo @@ -198,7 +190,7 @@ async def get_server_info(self) -> ServerInfo: tools=await self.list_tools() or [], resources=await self.list_resources() or [], prompts=await self.list_prompts() or [], - resource_templates=await self.list_resource_templates() or [] + resource_templates=await self.list_resource_templates() or [], ) def _setup_handlers(self) -> None: @@ -300,9 +292,7 @@ def add_tool( """ self._tool_manager.add_tool(fn, name=name, description=description) - def tool( - self, name: str | None = None, description: str | None = None - ) -> Callable[[AnyFunction], AnyFunction]: + def tool(self, name: str | None = None, description: str | None = None) -> Callable[[AnyFunction], AnyFunction]: """Decorator to register a tool. Tools can optionally request a Context object by adding a parameter with the @@ -331,8 +321,7 @@ async def async_tool(x: int, context: Context) -> str: # Check if user passed function directly instead of calling decorator if callable(name): raise TypeError( - "The @tool decorator was used incorrectly. " - "Did you forget to call it? Use @tool() instead of @tool" + "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool" ) def decorator(fn: AnyFunction) -> AnyFunction: @@ -412,8 +401,7 @@ def decorator(fn: AnyFunction) -> AnyFunction: if uri_params != func_params: raise ValueError( - f"Mismatch between URI parameters {uri_params} " - f"and function parameters {func_params}" + f"Mismatch between URI parameters {uri_params} and function parameters {func_params}" ) # Register as template @@ -446,9 +434,7 @@ def add_prompt(self, prompt: Prompt) -> None: """ self._prompt_manager.add_prompt(prompt) - def prompt( - self, name: str | None = None, description: str | None = None - ) -> Callable[[AnyFunction], AnyFunction]: + def prompt(self, name: str | None = None, description: str | None = None) -> Callable[[AnyFunction], AnyFunction]: """Decorator to register a prompt. Args: @@ -563,9 +549,7 @@ async def list_prompts(self) -> list[MCPPrompt]: for prompt in prompts ] - async def get_prompt( - self, name: str, arguments: dict[str, Any] | None = None - ) -> GetPromptResult: + async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult: """Get a prompt by name with arguments.""" try: messages = await self._prompt_manager.render_prompt(name, arguments) @@ -663,9 +647,7 @@ def request_context(self) -> RequestContext[ServerSessionT, LifespanContextT]: raise ValueError("Context is not available outside of a request") return self._request_context - async def report_progress( - self, progress: float, total: float | None = None - ) -> None: + async def report_progress(self, progress: float, total: float | None = None) -> None: """Report progress for the current operation. Args: @@ -673,11 +655,7 @@ async def report_progress( total: Optional total value e.g. 100 """ - progress_token = ( - self.request_context.meta.progressToken - if self.request_context.meta - else None - ) + progress_token = self.request_context.meta.progressToken if self.request_context.meta else None if progress_token is None: return @@ -695,9 +673,7 @@ async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContent Returns: The resource content as either text or bytes """ - assert ( - self._fastmcp is not None - ), "Context is not available outside of a request" + assert self._fastmcp is not None, "Context is not available outside of a request" return await self._fastmcp.read_resource(uri) async def log( @@ -715,18 +691,12 @@ async def log( logger_name: Optional logger name **extra: Additional structured data to include """ - await self.request_context.session.send_log_message( - level=level, data=message, logger=logger_name - ) + await self.request_context.session.send_log_message(level=level, data=message, logger=logger_name) @property def client_id(self) -> str | None: """Get the client ID if available.""" - return ( - getattr(self.request_context.meta, "client_id", None) - if self.request_context.meta - else None - ) + return getattr(self.request_context.meta, "client_id", None) if self.request_context.meta else None @property def request_id(self) -> str: diff --git a/src/mcp/types.py b/src/mcp/types.py index a0755a228..1b1d1184f 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -6,7 +6,6 @@ Literal, TypeAlias, TypeVar, - TypedDict, ) from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel @@ -66,9 +65,7 @@ class Meta(BaseModel): RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams | dict[str, Any] | None) -NotificationParamsT = TypeVar( - "NotificationParamsT", bound=NotificationParams | dict[str, Any] | None -) +NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams | dict[str, Any] | None) MethodT = TypeVar("MethodT", bound=str) @@ -179,9 +176,7 @@ class JSONRPCError(BaseModel): model_config = ConfigDict(extra="allow") -class JSONRPCMessage( - RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError] -): +class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError]): pass @@ -302,9 +297,7 @@ class InitializeResult(Result): """Instructions describing how to use the server and its features.""" -class InitializedNotification( - Notification[NotificationParams | None, Literal["notifications/initialized"]] -): +class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): """ This notification is sent from the client to the server after initialization has finished. @@ -342,9 +335,7 @@ class ProgressNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class ProgressNotification( - Notification[ProgressNotificationParams, Literal["notifications/progress"]] -): +class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]): """ An out-of-band notification used to inform the receiver of a progress update for a long-running request. @@ -354,9 +345,7 @@ class ProgressNotification( params: ProgressNotificationParams -class ListResourcesRequest( - PaginatedRequest[RequestParams | None, Literal["resources/list"]] -): +class ListResourcesRequest(PaginatedRequest[RequestParams | None, Literal["resources/list"]]): """Sent from the client to request a list of resources the server has.""" method: Literal["resources/list"] @@ -418,9 +407,7 @@ class ListResourcesResult(PaginatedResult): resources: list[Resource] -class ListResourceTemplatesRequest( - PaginatedRequest[RequestParams | None, Literal["resources/templates/list"]] -): +class ListResourceTemplatesRequest(PaginatedRequest[RequestParams | None, Literal["resources/templates/list"]]): """Sent from the client to request a list of resource templates the server has.""" method: Literal["resources/templates/list"] @@ -444,9 +431,7 @@ class ReadResourceRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class ReadResourceRequest( - Request[ReadResourceRequestParams, Literal["resources/read"]] -): +class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/read"]]): """Sent from the client to the server, to read a specific resource URI.""" method: Literal["resources/read"] @@ -487,9 +472,7 @@ class ReadResourceResult(Result): class ResourceListChangedNotification( - Notification[ - NotificationParams | None, Literal["notifications/resources/list_changed"] - ] + Notification[NotificationParams | None, Literal["notifications/resources/list_changed"]] ): """ An optional notification from the server to the client, informing it that the list @@ -529,9 +512,7 @@ class UnsubscribeRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class UnsubscribeRequest( - Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]] -): +class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): """ Sent from the client to request cancellation of resources/updated notifications from the server. @@ -553,9 +534,7 @@ class ResourceUpdatedNotificationParams(NotificationParams): class ResourceUpdatedNotification( - Notification[ - ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"] - ] + Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] ): """ A notification from the server to the client, informing it that a resource has @@ -566,9 +545,7 @@ class ResourceUpdatedNotification( params: ResourceUpdatedNotificationParams -class ListPromptsRequest( - PaginatedRequest[RequestParams | None, Literal["prompts/list"]] -): +class ListPromptsRequest(PaginatedRequest[RequestParams | None, Literal["prompts/list"]]): """Sent from the client to request a list of prompts and prompt templates.""" method: Literal["prompts/list"] @@ -686,9 +663,7 @@ class GetPromptResult(Result): class PromptListChangedNotification( - Notification[ - NotificationParams | None, Literal["notifications/prompts/list_changed"] - ] + Notification[NotificationParams | None, Literal["notifications/prompts/list_changed"]] ): """ An optional notification from the server to the client, informing it that the list @@ -746,9 +721,7 @@ class CallToolResult(Result): isError: bool = False -class ToolListChangedNotification( - Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]] -): +class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): """ An optional notification from the server to the client, informing it that the list of tools it offers has changed. @@ -758,9 +731,7 @@ class ToolListChangedNotification( params: NotificationParams | None = None -LoggingLevel = Literal[ - "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency" -] +LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"] class SetLevelRequestParams(RequestParams): @@ -793,9 +764,7 @@ class LoggingMessageNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class LoggingMessageNotification( - Notification[LoggingMessageNotificationParams, Literal["notifications/message"]] -): +class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): """Notification of a log message passed from server to client.""" method: Literal["notifications/message"] @@ -890,9 +859,7 @@ class CreateMessageRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class CreateMessageRequest( - Request[CreateMessageRequestParams, Literal["sampling/createMessage"]] -): +class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): """A request from the server to sample an LLM via the client.""" method: Literal["sampling/createMessage"] @@ -1049,9 +1016,7 @@ class CancelledNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class CancelledNotification( - Notification[CancelledNotificationParams, Literal["notifications/cancelled"]] -): +class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): """ This notification can be sent by either side to indicate that it is canceling a previously-issued request. @@ -1082,12 +1047,7 @@ class ClientRequest( class ClientNotification( - RootModel[ - CancelledNotification - | ProgressNotification - | InitializedNotification - | RootsListChangedNotification - ] + RootModel[CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification] ): pass @@ -1131,14 +1091,19 @@ class ServerResult( pass -class ServerInfoAssets(TypedDict): - tools: list[Tool] - prompts: list[Prompt] - resources: list[Resource] - resource_templates: list[ResourceTemplate] +class ServerInfoAsset(BaseModel): + name: str + description: str | None + +class ServerInfoAssets(BaseModel): + tools: list[ServerInfoAsset] + prompts: list[ServerInfoAsset] + resources: list[ServerInfoAsset] + resource_templates: list[ServerInfoAsset] -class ServerInfo(BaseModel): + +class ServerInfo(BaseModel): name: str host: str port: int @@ -1151,11 +1116,13 @@ class ServerInfo(BaseModel): @property def assets(self) -> ServerInfoAssets: return ServerInfoAssets( - tools=self.tools, - prompts=self.prompts, - resources=self.resources, - resource_templates=self.resource_templates, + tools=[ServerInfoAsset(name=tool.name, description=tool.description) for tool in self.tools], + prompts=[ServerInfoAsset(name=prompt.name, description=prompt.description) for prompt in self.prompts], + resources=[ + ServerInfoAsset(name=resource.name, description=resource.description) for resource in self.resources + ], + resource_templates=[ + ServerInfoAsset(name=resource_template.name, description=resource_template.description) + for resource_template in self.resource_templates + ], ) - - - \ No newline at end of file From 90d46a00f7ac8d2dd6a443c86ba68ace79fd104d Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 16:08:15 +0300 Subject: [PATCH 12/14] show-server-info - fixes --- src/mcp/server/fastmcp/server.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 5767caa9e..77d0078c6 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -118,9 +118,9 @@ class Settings(BaseSettings, Generic[LifespanResultT]): description="List of dependencies to install in the server environment", ) - lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None = Field( - None, description="Lifespan context manager" - ) + lifespan: ( + Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None + ) = Field(None, description="Lifespan context manager") auth: AuthSettings | None = None @@ -424,7 +424,8 @@ async def async_tool(x: int, context: Context) -> str: # Check if user passed function directly instead of calling decorator if callable(name): raise TypeError( - "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool" + "The @tool decorator was used incorrectly. " + "Did you forget to call it? Use @tool() instead of @tool" ) def decorator(fn: AnyFunction) -> AnyFunction: @@ -506,7 +507,8 @@ def decorator(fn: AnyFunction) -> AnyFunction: if uri_params != func_params: raise ValueError( - f"Mismatch between URI parameters {uri_params} and function parameters {func_params}" + f"Mismatch between URI parameters {uri_params} " + f"and function parameters {func_params}" ) # Register as template @@ -539,7 +541,9 @@ def add_prompt(self, prompt: Prompt) -> None: """ self._prompt_manager.add_prompt(prompt) - def prompt(self, name: str | None = None, description: str | None = None) -> Callable[[AnyFunction], AnyFunction]: + def prompt( + self, name: str | None = None, description: str | None = None + ) -> Callable[[AnyFunction], AnyFunction]: """Decorator to register a prompt. Args: @@ -902,7 +906,9 @@ async def list_prompts(self) -> list[MCPPrompt]: for prompt in prompts ] - async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult: + async def get_prompt( + self, name: str, arguments: dict[str, Any] | None = None + ) -> GetPromptResult: """Get a prompt by name with arguments.""" try: messages = await self._prompt_manager.render_prompt(name, arguments) @@ -1032,7 +1038,9 @@ async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContent Returns: The resource content as either text or bytes """ - assert self._fastmcp is not None, "Context is not available outside of a request" + assert ( + self._fastmcp is not None + ), "Context is not available outside of a request" return await self._fastmcp.read_resource(uri) async def log( @@ -1060,7 +1068,11 @@ async def log( @property def client_id(self) -> str | None: """Get the client ID if available.""" - return getattr(self.request_context.meta, "client_id", None) if self.request_context.meta else None + return ( + getattr(self.request_context.meta, "client_id", None) + if self.request_context.meta + else None + ) @property def request_id(self) -> str: From 18a279e0796db428b905c8d2a6c32a561284524e Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 16:09:37 +0300 Subject: [PATCH 13/14] show-server-info - fixes --- src/mcp/types.py | 64 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/mcp/types.py b/src/mcp/types.py index d2eddaa84..6e21fe006 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -5,7 +5,6 @@ Generic, Literal, TypeAlias, - TypedDict, TypeVar, ) @@ -74,7 +73,9 @@ class Meta(BaseModel): RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams | dict[str, Any] | None) -NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams | dict[str, Any] | None) +NotificationParamsT = TypeVar( + "NotificationParamsT", bound=NotificationParams | dict[str, Any] | None +) MethodT = TypeVar("MethodT", bound=str) @@ -190,7 +191,9 @@ class JSONRPCError(BaseModel): model_config = ConfigDict(extra="allow") -class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError]): +class JSONRPCMessage( + RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError] +): pass @@ -311,7 +314,9 @@ class InitializeResult(Result): """Instructions describing how to use the server and its features.""" -class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): +class InitializedNotification( + Notification[NotificationParams | None, Literal["notifications/initialized"]] +): """ This notification is sent from the client to the server after initialization has finished. @@ -354,7 +359,9 @@ class ProgressNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]): +class ProgressNotification( + Notification[ProgressNotificationParams, Literal["notifications/progress"]] +): """ An out-of-band notification used to inform the receiver of a progress update for a long-running request. @@ -450,7 +457,9 @@ class ReadResourceRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/read"]]): +class ReadResourceRequest( + Request[ReadResourceRequestParams, Literal["resources/read"]] +): """Sent from the client to the server, to read a specific resource URI.""" method: Literal["resources/read"] @@ -491,7 +500,9 @@ class ReadResourceResult(Result): class ResourceListChangedNotification( - Notification[NotificationParams | None, Literal["notifications/resources/list_changed"]] + Notification[ + NotificationParams | None, Literal["notifications/resources/list_changed"] + ] ): """ An optional notification from the server to the client, informing it that the list @@ -531,7 +542,9 @@ class UnsubscribeRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): +class UnsubscribeRequest( + Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]] +): """ Sent from the client to request cancellation of resources/updated notifications from the server. @@ -553,7 +566,9 @@ class ResourceUpdatedNotificationParams(NotificationParams): class ResourceUpdatedNotification( - Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] + Notification[ + ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"] + ] ): """ A notification from the server to the client, informing it that a resource has @@ -681,7 +696,9 @@ class GetPromptResult(Result): class PromptListChangedNotification( - Notification[NotificationParams | None, Literal["notifications/prompts/list_changed"]] + Notification[ + NotificationParams | None, Literal["notifications/prompts/list_changed"] + ] ): """ An optional notification from the server to the client, informing it that the list @@ -788,7 +805,9 @@ class CallToolResult(Result): isError: bool = False -class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): +class ToolListChangedNotification( + Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]] +): """ An optional notification from the server to the client, informing it that the list of tools it offers has changed. @@ -798,7 +817,9 @@ class ToolListChangedNotification(Notification[NotificationParams | None, Litera params: NotificationParams | None = None -LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"] +LoggingLevel = Literal[ + "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency" +] class SetLevelRequestParams(RequestParams): @@ -831,7 +852,9 @@ class LoggingMessageNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): +class LoggingMessageNotification( + Notification[LoggingMessageNotificationParams, Literal["notifications/message"]] +): """Notification of a log message passed from server to client.""" method: Literal["notifications/message"] @@ -926,7 +949,9 @@ class CreateMessageRequestParams(RequestParams): model_config = ConfigDict(extra="allow") -class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): +class CreateMessageRequest( + Request[CreateMessageRequestParams, Literal["sampling/createMessage"]] +): """A request from the server to sample an LLM via the client.""" method: Literal["sampling/createMessage"] @@ -1083,7 +1108,9 @@ class CancelledNotificationParams(NotificationParams): model_config = ConfigDict(extra="allow") -class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): +class CancelledNotification( + Notification[CancelledNotificationParams, Literal["notifications/cancelled"]] +): """ This notification can be sent by either side to indicate that it is canceling a previously-issued request. @@ -1114,7 +1141,12 @@ class ClientRequest( class ClientNotification( - RootModel[CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification] + RootModel[ + CancelledNotification + | ProgressNotification + | InitializedNotification + | RootsListChangedNotification + ] ): pass From ce7e82b1dbbbe034b81e85d7af0de26beaba6417 Mon Sep 17 00:00:00 2001 From: Bar Nuri Date: Wed, 28 May 2025 16:13:50 +0300 Subject: [PATCH 14/14] Update types.py --- src/mcp/types.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/mcp/types.py b/src/mcp/types.py index 6e21fe006..c7a32bfbd 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1215,13 +1215,32 @@ class ServerInfo(BaseModel): @property def assets(self) -> ServerInfoAssets: return ServerInfoAssets( - tools=[ServerInfoAsset(name=tool.name, description=tool.description) for tool in self.tools], - prompts=[ServerInfoAsset(name=prompt.name, description=prompt.description) for prompt in self.prompts], + tools=[ + ServerInfoAsset( + name=tool.name, + description=tool.description + ) + for tool in self.tools + ], + prompts=[ + ServerInfoAsset( + name=prompt.name, + description=prompt.description + ) + for prompt in self.prompts + ], resources=[ - ServerInfoAsset(name=resource.name, description=resource.description) for resource in self.resources + ServerInfoAsset( + name=resource.name, + description=resource.description + ) + for resource in self.resources ], resource_templates=[ - ServerInfoAsset(name=resource_template.name, description=resource_template.description) + ServerInfoAsset( + name=resource_template.name, + description=resource_template.description + ) for resource_template in self.resource_templates ], )