diff --git a/src/fastmcp/prompts/prompt_manager.py b/src/fastmcp/prompts/prompt_manager.py index bab2d663e..99317f259 100644 --- a/src/fastmcp/prompts/prompt_manager.py +++ b/src/fastmcp/prompts/prompt_manager.py @@ -11,20 +11,23 @@ class PromptManager(BasePromptManager): Adds ability to import prompts from other managers with prefixed names. """ - def import_prompts(self, manager: "PromptManager", prefix: str) -> None: + def import_prompts( + self, manager: "PromptManager", prefix: str | None = None + ) -> None: """ Import all prompts from another PromptManager with prefixed names. Args: manager: Another PromptManager instance to import prompts from prefix: Prefix to add to prompt names. The resulting prompt name will - be in the format "{prefix}/{original_name}" - For example, with prefix "weather" and prompt "forecast_prompt", + be in the format "{prefix}{original_name}" if prefix is provided, + otherwise the original name is used. + For example, with prefix "weather/" and prompt "forecast_prompt", the imported prompt would be available as "weather/forecast_prompt" """ for name, prompt in manager._prompts.items(): # Create prefixed name - we keep the original name in the Prompt object - prefixed_name = f"{prefix}/{name}" + prefixed_name = f"{prefix}{name}" if prefix else name # Log the import logger.debug(f"Importing prompt with name {name} as {prefixed_name}") diff --git a/src/fastmcp/resources/resource_manager.py b/src/fastmcp/resources/resource_manager.py index 64ae4f83b..6fd51b7ea 100644 --- a/src/fastmcp/resources/resource_manager.py +++ b/src/fastmcp/resources/resource_manager.py @@ -10,20 +10,25 @@ logger = logging.getLogger(__name__) class ResourceManager(BaseResourceManager): """ResourceManager that adds methods to import resources from other managers.""" - def import_resources(self, manager: "ResourceManager", prefix: str) -> None: + def import_resources( + self, manager: "ResourceManager", prefix: str | None = None + ) -> None: """Import resources from another resource manager. - Resources are imported with a prefixed URI. For example, if a resource has - URI "data://users" and you import it with prefix "app", the imported resource - will have URI "app+data://users". + Resources are imported with a prefixed URI if a prefix is provided. For example, + if a resource has URI "data://users" and you import it with prefix "app+", the + imported resource will have URI "app+data://users". If no prefix is provided, + the original URI is used. Args: manager: The ResourceManager to import from - prefix: A prefix to apply to the resource URIs + prefix: A prefix to apply to the resource URIs, including the delimiter. + For example, "app+" would result in URIs like "app+data://users". + If None, the original URI is used. """ for uri, resource in manager._resources.items(): # Create prefixed URI and copy the resource with the new URI - prefixed_uri = f"{prefix}+{uri}" + prefixed_uri = f"{prefix}{uri}" if prefix else uri # Log the import logger.debug(f"Importing resource with URI {uri} as {prefixed_uri}") @@ -31,20 +36,27 @@ class ResourceManager(BaseResourceManager): # Store directly in resources dictionary self._resources[prefixed_uri] = resource - def import_templates(self, manager: "ResourceManager", prefix: str) -> None: + def import_templates( + self, manager: "ResourceManager", prefix: str | None = None + ) -> None: """Import resource templates from another resource manager. - Templates are imported with a prefixed URI template. For example, if a template has - URI template "data://users/{id}" and you import it with prefix "app", the - imported template will have URI template "app+data://users/{id}". + Templates are imported with a prefixed URI template if a prefix is provided. + For example, if a template has URI template "data://users/{id}" and you import + it with prefix "app+", the imported template will have URI template + "app+data://users/{id}". If no prefix is provided, the original URI template is used. Args: manager: The ResourceManager to import templates from - prefix: A prefix to apply to the template URIs + prefix: A prefix to apply to the template URIs, including the delimiter. + For example, "app+" would result in URI templates like "app+data://users/{id}". + If None, the original URI template is used. """ for uri_template, template in manager._templates.items(): # Create prefixed URI template and copy the template with the new URI template - prefixed_uri_template = f"{prefix}+{uri_template}" + prefixed_uri_template = ( + f"{prefix}{uri_template}" if prefix else uri_template + ) # Log the import logger.debug( diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index d114e610d..61289f5fc 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict import mcp.server.fastmcp import mcp.types @@ -9,6 +9,9 @@ from fastmcp.server.context import Context from fastmcp.tools.tool_manager import ToolManager from fastmcp.utilities.logging import get_logger +if TYPE_CHECKING: + pass + logger = get_logger(__name__) @@ -62,20 +65,21 @@ class FastMCP(mcp.server.fastmcp.FastMCP): # Mount the app in the list of mounted apps self._mounted_apps[prefix] = app - # Import tools from the mounted app - self._tool_manager.import_tools(app._tool_manager, prefix) + # Import tools from the mounted app with / delimiter + tool_prefix = f"{prefix}/" + self._tool_manager.import_tools(app._tool_manager, tool_prefix) - # Import resources from the mounted app - self._resource_manager.import_resources(app._resource_manager, prefix) + # Import resources and templates from the mounted app with + delimiter + resource_prefix = f"{prefix}+" + self._resource_manager.import_resources(app._resource_manager, resource_prefix) + self._resource_manager.import_templates(app._resource_manager, resource_prefix) - # Import resource templates - self._resource_manager.import_templates(app._resource_manager, prefix) - - # Import prompts - self._prompt_manager.import_prompts(app._prompt_manager, prefix) + # Import prompts with / delimiter + prompt_prefix = f"{prefix}/" + self._prompt_manager.import_prompts(app._prompt_manager, prompt_prefix) logger.info(f"Mounted app with prefix '{prefix}'") - logger.debug(f"Imported tools with prefix '{prefix}/'") - logger.debug(f"Imported resources with prefix '{prefix}+'") - logger.debug(f"Imported templates with prefix '{prefix}+'") - logger.debug(f"Imported prompts with prefix '{prefix}/'") + logger.debug(f"Imported tools with prefix '{tool_prefix}'") + logger.debug(f"Imported resources with prefix '{resource_prefix}'") + logger.debug(f"Imported templates with prefix '{resource_prefix}'") + logger.debug(f"Imported prompts with prefix '{prompt_prefix}'") diff --git a/src/fastmcp/tools/tool_manager.py b/src/fastmcp/tools/tool_manager.py index 133c93c32..1b3b95533 100644 --- a/src/fastmcp/tools/tool_manager.py +++ b/src/fastmcp/tools/tool_manager.py @@ -12,19 +12,22 @@ class ToolManager(mcp.server.fastmcp.tools.ToolManager): Adds ability to import tools from other managers with prefixed names. """ - def import_tools(self, tool_manager: "ToolManager", prefix: str) -> None: + def import_tools( + self, tool_manager: "ToolManager", prefix: str | None = None + ) -> None: """ Import all tools from another ToolManager with prefixed names. Args: tool_manager: Another ToolManager instance to import tools from - prefix: Prefix to add to tool names. The resulting tool name will - be in the format "{prefix}/{original_name}" - For example, with prefix "weather" and tool "forecast", + prefix: Prefix to add to tool names, including the delimiter. + The resulting tool name will be in the format "{prefix}{original_name}" + if prefix is provided, otherwise the original name is used. + For example, with prefix "weather/" and tool "forecast", the imported tool would be available as "weather/forecast" """ for name, tool in tool_manager._tools.items(): - prefixed_name = f"{prefix}/{name}" + prefixed_name = f"{prefix}{name}" if prefix else name # Create a shallow copy of the tool with the prefixed name copied_tool = Tool.from_function( diff --git a/tests/prompts/test_prompt_manager.py b/tests/prompts/test_prompt_manager.py index 1e8dac16c..3a5accec0 100644 --- a/tests/prompts/test_prompt_manager.py +++ b/tests/prompts/test_prompt_manager.py @@ -44,7 +44,7 @@ def test_import_prompts(): target_manager = PromptManager() # Import prompts from source to target - prefix = "nlp" + prefix = "nlp/" target_manager.import_prompts(source_manager, prefix) # Verify prompts were imported with prefixes @@ -109,7 +109,7 @@ def test_import_prompts_with_duplicates(): target_manager._prompts["common"] = target_prompt # Import prompts with prefix - prefix = "external" + prefix = "external/" target_manager.import_prompts(source_manager, prefix) # Verify both prompts exist in target manager @@ -146,10 +146,10 @@ def test_import_prompts_with_nested_prefixes(): first_manager._prompts["analyze"] = original_prompt # Import to second manager with prefix - second_manager.import_prompts(first_manager, "text") + second_manager.import_prompts(first_manager, "text/") # Import from second to third with another prefix - third_manager.import_prompts(second_manager, "ai") + third_manager.import_prompts(second_manager, "ai/") # Verify the nested prefixing assert "text/analyze" in second_manager._prompts diff --git a/tests/resources/test_resource_manager.py b/tests/resources/test_resource_manager.py index 9ae89d47a..6484415e2 100644 --- a/tests/resources/test_resource_manager.py +++ b/tests/resources/test_resource_manager.py @@ -39,7 +39,7 @@ def test_import_resources(): target_manager = ResourceManager() # Import resources from source to target - prefix = "data" + prefix = "data+" target_manager.import_resources(source_manager, prefix) # Verify resources were imported with prefixes @@ -126,7 +126,7 @@ def test_import_templates(): target_manager = ResourceManager() # Import templates from source to target - prefix = "shop" + prefix = "shop+" target_manager.import_templates(source_manager, prefix) # Verify templates were imported with prefixes @@ -212,7 +212,7 @@ def test_import_multiple_resource_types(): target_manager = ResourceManager() # Import both resources and templates - prefix = "test" + prefix = "test+" target_manager.import_resources(source_manager, prefix) target_manager.import_templates(source_manager, prefix) diff --git a/tests/tools/test_tool_manager.py b/tests/tools/test_tool_manager.py index 8a84fff72..3accb8178 100644 --- a/tests/tools/test_tool_manager.py +++ b/tests/tools/test_tool_manager.py @@ -23,7 +23,7 @@ def test_import_tools(): target_manager = ToolManager() # Import tools from source to target - prefix = "source" + prefix = "source/" target_manager.import_tools(source_manager, prefix) # Verify tools were imported with prefixes @@ -65,7 +65,7 @@ def test_tool_duplicate_behavior(): ) # Pre-create with the prefixed name # Import tools from source to target - target_manager.import_tools(source_manager, "source") + target_manager.import_tools(source_manager, "source/") # The original tool in the target manager is replaced by the imported one assert target_manager._tools["source/common_tool"].fn.__name__ == source_fn.__name__ @@ -89,8 +89,8 @@ def test_import_tools_with_multiple_prefixes(): # Create target manager and import from both sources main_manager = ToolManager() - main_manager.import_tools(weather_manager, "weather") - main_manager.import_tools(news_manager, "news") + main_manager.import_tools(weather_manager, "weather/") + main_manager.import_tools(news_manager, "news/") # Verify tools were imported with correct prefixes assert "weather/forecast" in main_manager._tools