mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Permit empty prefixes
This commit is contained in:
parent
feb7ddff4d
commit
728aeec293
7 changed files with 68 additions and 46 deletions
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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}'")
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue