Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
6da0e6b
1
Parent(s): 92533b3
Permit empty prefixes
Browse files
src/fastmcp/prompts/prompt_manager.py
CHANGED
|
@@ -11,20 +11,23 @@ class PromptManager(BasePromptManager):
|
|
| 11 |
Adds ability to import prompts from other managers with prefixed names.
|
| 12 |
"""
|
| 13 |
|
| 14 |
-
def import_prompts(
|
|
|
|
|
|
|
| 15 |
"""
|
| 16 |
Import all prompts from another PromptManager with prefixed names.
|
| 17 |
|
| 18 |
Args:
|
| 19 |
manager: Another PromptManager instance to import prompts from
|
| 20 |
prefix: Prefix to add to prompt names. The resulting prompt name will
|
| 21 |
-
be in the format "{prefix}
|
| 22 |
-
|
|
|
|
| 23 |
the imported prompt would be available as "weather/forecast_prompt"
|
| 24 |
"""
|
| 25 |
for name, prompt in manager._prompts.items():
|
| 26 |
# Create prefixed name - we keep the original name in the Prompt object
|
| 27 |
-
prefixed_name = f"{prefix}
|
| 28 |
|
| 29 |
# Log the import
|
| 30 |
logger.debug(f"Importing prompt with name {name} as {prefixed_name}")
|
|
|
|
| 11 |
Adds ability to import prompts from other managers with prefixed names.
|
| 12 |
"""
|
| 13 |
|
| 14 |
+
def import_prompts(
|
| 15 |
+
self, manager: "PromptManager", prefix: str | None = None
|
| 16 |
+
) -> None:
|
| 17 |
"""
|
| 18 |
Import all prompts from another PromptManager with prefixed names.
|
| 19 |
|
| 20 |
Args:
|
| 21 |
manager: Another PromptManager instance to import prompts from
|
| 22 |
prefix: Prefix to add to prompt names. The resulting prompt name will
|
| 23 |
+
be in the format "{prefix}{original_name}" if prefix is provided,
|
| 24 |
+
otherwise the original name is used.
|
| 25 |
+
For example, with prefix "weather/" and prompt "forecast_prompt",
|
| 26 |
the imported prompt would be available as "weather/forecast_prompt"
|
| 27 |
"""
|
| 28 |
for name, prompt in manager._prompts.items():
|
| 29 |
# Create prefixed name - we keep the original name in the Prompt object
|
| 30 |
+
prefixed_name = f"{prefix}{name}" if prefix else name
|
| 31 |
|
| 32 |
# Log the import
|
| 33 |
logger.debug(f"Importing prompt with name {name} as {prefixed_name}")
|
src/fastmcp/resources/resource_manager.py
CHANGED
|
@@ -10,20 +10,25 @@ logger = logging.getLogger(__name__)
|
|
| 10 |
class ResourceManager(BaseResourceManager):
|
| 11 |
"""ResourceManager that adds methods to import resources from other managers."""
|
| 12 |
|
| 13 |
-
def import_resources(
|
|
|
|
|
|
|
| 14 |
"""Import resources from another resource manager.
|
| 15 |
|
| 16 |
-
Resources are imported with a prefixed URI
|
| 17 |
-
URI "data://users" and you import it with prefix "app", the
|
| 18 |
-
will have URI "app+data://users".
|
|
|
|
| 19 |
|
| 20 |
Args:
|
| 21 |
manager: The ResourceManager to import from
|
| 22 |
-
prefix: A prefix to apply to the resource URIs
|
|
|
|
|
|
|
| 23 |
"""
|
| 24 |
for uri, resource in manager._resources.items():
|
| 25 |
# Create prefixed URI and copy the resource with the new URI
|
| 26 |
-
prefixed_uri = f"{prefix}
|
| 27 |
|
| 28 |
# Log the import
|
| 29 |
logger.debug(f"Importing resource with URI {uri} as {prefixed_uri}")
|
|
@@ -31,20 +36,27 @@ class ResourceManager(BaseResourceManager):
|
|
| 31 |
# Store directly in resources dictionary
|
| 32 |
self._resources[prefixed_uri] = resource
|
| 33 |
|
| 34 |
-
def import_templates(
|
|
|
|
|
|
|
| 35 |
"""Import resource templates from another resource manager.
|
| 36 |
|
| 37 |
-
Templates are imported with a prefixed URI template
|
| 38 |
-
URI template "data://users/{id}" and you import
|
| 39 |
-
imported template will have URI template
|
|
|
|
| 40 |
|
| 41 |
Args:
|
| 42 |
manager: The ResourceManager to import templates from
|
| 43 |
-
prefix: A prefix to apply to the template URIs
|
|
|
|
|
|
|
| 44 |
"""
|
| 45 |
for uri_template, template in manager._templates.items():
|
| 46 |
# Create prefixed URI template and copy the template with the new URI template
|
| 47 |
-
prefixed_uri_template =
|
|
|
|
|
|
|
| 48 |
|
| 49 |
# Log the import
|
| 50 |
logger.debug(
|
|
|
|
| 10 |
class ResourceManager(BaseResourceManager):
|
| 11 |
"""ResourceManager that adds methods to import resources from other managers."""
|
| 12 |
|
| 13 |
+
def import_resources(
|
| 14 |
+
self, manager: "ResourceManager", prefix: str | None = None
|
| 15 |
+
) -> None:
|
| 16 |
"""Import resources from another resource manager.
|
| 17 |
|
| 18 |
+
Resources are imported with a prefixed URI if a prefix is provided. For example,
|
| 19 |
+
if a resource has URI "data://users" and you import it with prefix "app+", the
|
| 20 |
+
imported resource will have URI "app+data://users". If no prefix is provided,
|
| 21 |
+
the original URI is used.
|
| 22 |
|
| 23 |
Args:
|
| 24 |
manager: The ResourceManager to import from
|
| 25 |
+
prefix: A prefix to apply to the resource URIs, including the delimiter.
|
| 26 |
+
For example, "app+" would result in URIs like "app+data://users".
|
| 27 |
+
If None, the original URI is used.
|
| 28 |
"""
|
| 29 |
for uri, resource in manager._resources.items():
|
| 30 |
# Create prefixed URI and copy the resource with the new URI
|
| 31 |
+
prefixed_uri = f"{prefix}{uri}" if prefix else uri
|
| 32 |
|
| 33 |
# Log the import
|
| 34 |
logger.debug(f"Importing resource with URI {uri} as {prefixed_uri}")
|
|
|
|
| 36 |
# Store directly in resources dictionary
|
| 37 |
self._resources[prefixed_uri] = resource
|
| 38 |
|
| 39 |
+
def import_templates(
|
| 40 |
+
self, manager: "ResourceManager", prefix: str | None = None
|
| 41 |
+
) -> None:
|
| 42 |
"""Import resource templates from another resource manager.
|
| 43 |
|
| 44 |
+
Templates are imported with a prefixed URI template if a prefix is provided.
|
| 45 |
+
For example, if a template has URI template "data://users/{id}" and you import
|
| 46 |
+
it with prefix "app+", the imported template will have URI template
|
| 47 |
+
"app+data://users/{id}". If no prefix is provided, the original URI template is used.
|
| 48 |
|
| 49 |
Args:
|
| 50 |
manager: The ResourceManager to import templates from
|
| 51 |
+
prefix: A prefix to apply to the template URIs, including the delimiter.
|
| 52 |
+
For example, "app+" would result in URI templates like "app+data://users/{id}".
|
| 53 |
+
If None, the original URI template is used.
|
| 54 |
"""
|
| 55 |
for uri_template, template in manager._templates.items():
|
| 56 |
# Create prefixed URI template and copy the template with the new URI template
|
| 57 |
+
prefixed_uri_template = (
|
| 58 |
+
f"{prefix}{uri_template}" if prefix else uri_template
|
| 59 |
+
)
|
| 60 |
|
| 61 |
# Log the import
|
| 62 |
logger.debug(
|
src/fastmcp/server/server.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from typing import Any, Dict
|
| 2 |
|
| 3 |
import mcp.server.fastmcp
|
| 4 |
import mcp.types
|
|
@@ -9,6 +9,9 @@ from fastmcp.server.context import Context
|
|
| 9 |
from fastmcp.tools.tool_manager import ToolManager
|
| 10 |
from fastmcp.utilities.logging import get_logger
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
logger = get_logger(__name__)
|
| 13 |
|
| 14 |
|
|
@@ -62,20 +65,21 @@ class FastMCP(mcp.server.fastmcp.FastMCP):
|
|
| 62 |
# Mount the app in the list of mounted apps
|
| 63 |
self._mounted_apps[prefix] = app
|
| 64 |
|
| 65 |
-
# Import tools from the mounted app
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
# Import resources from the mounted app
|
| 69 |
-
self._resource_manager.import_resources(app._resource_manager, prefix)
|
| 70 |
|
| 71 |
-
# Import
|
| 72 |
-
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
# Import prompts
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
logger.info(f"Mounted app with prefix '{prefix}'")
|
| 78 |
-
logger.debug(f"Imported tools with prefix '{
|
| 79 |
-
logger.debug(f"Imported resources with prefix '{
|
| 80 |
-
logger.debug(f"Imported templates with prefix '{
|
| 81 |
-
logger.debug(f"Imported prompts with prefix '{
|
|
|
|
| 1 |
+
from typing import TYPE_CHECKING, Any, Dict
|
| 2 |
|
| 3 |
import mcp.server.fastmcp
|
| 4 |
import mcp.types
|
|
|
|
| 9 |
from fastmcp.tools.tool_manager import ToolManager
|
| 10 |
from fastmcp.utilities.logging import get_logger
|
| 11 |
|
| 12 |
+
if TYPE_CHECKING:
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
logger = get_logger(__name__)
|
| 16 |
|
| 17 |
|
|
|
|
| 65 |
# Mount the app in the list of mounted apps
|
| 66 |
self._mounted_apps[prefix] = app
|
| 67 |
|
| 68 |
+
# Import tools from the mounted app with / delimiter
|
| 69 |
+
tool_prefix = f"{prefix}/"
|
| 70 |
+
self._tool_manager.import_tools(app._tool_manager, tool_prefix)
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
# Import resources and templates from the mounted app with + delimiter
|
| 73 |
+
resource_prefix = f"{prefix}+"
|
| 74 |
+
self._resource_manager.import_resources(app._resource_manager, resource_prefix)
|
| 75 |
+
self._resource_manager.import_templates(app._resource_manager, resource_prefix)
|
| 76 |
|
| 77 |
+
# Import prompts with / delimiter
|
| 78 |
+
prompt_prefix = f"{prefix}/"
|
| 79 |
+
self._prompt_manager.import_prompts(app._prompt_manager, prompt_prefix)
|
| 80 |
|
| 81 |
logger.info(f"Mounted app with prefix '{prefix}'")
|
| 82 |
+
logger.debug(f"Imported tools with prefix '{tool_prefix}'")
|
| 83 |
+
logger.debug(f"Imported resources with prefix '{resource_prefix}'")
|
| 84 |
+
logger.debug(f"Imported templates with prefix '{resource_prefix}'")
|
| 85 |
+
logger.debug(f"Imported prompts with prefix '{prompt_prefix}'")
|
src/fastmcp/tools/tool_manager.py
CHANGED
|
@@ -12,19 +12,22 @@ class ToolManager(mcp.server.fastmcp.tools.ToolManager):
|
|
| 12 |
Adds ability to import tools from other managers with prefixed names.
|
| 13 |
"""
|
| 14 |
|
| 15 |
-
def import_tools(
|
|
|
|
|
|
|
| 16 |
"""
|
| 17 |
Import all tools from another ToolManager with prefixed names.
|
| 18 |
|
| 19 |
Args:
|
| 20 |
tool_manager: Another ToolManager instance to import tools from
|
| 21 |
-
prefix: Prefix to add to tool names
|
| 22 |
-
be in the format "{prefix}
|
| 23 |
-
|
|
|
|
| 24 |
the imported tool would be available as "weather/forecast"
|
| 25 |
"""
|
| 26 |
for name, tool in tool_manager._tools.items():
|
| 27 |
-
prefixed_name = f"{prefix}
|
| 28 |
|
| 29 |
# Create a shallow copy of the tool with the prefixed name
|
| 30 |
copied_tool = Tool.from_function(
|
|
|
|
| 12 |
Adds ability to import tools from other managers with prefixed names.
|
| 13 |
"""
|
| 14 |
|
| 15 |
+
def import_tools(
|
| 16 |
+
self, tool_manager: "ToolManager", prefix: str | None = None
|
| 17 |
+
) -> None:
|
| 18 |
"""
|
| 19 |
Import all tools from another ToolManager with prefixed names.
|
| 20 |
|
| 21 |
Args:
|
| 22 |
tool_manager: Another ToolManager instance to import tools from
|
| 23 |
+
prefix: Prefix to add to tool names, including the delimiter.
|
| 24 |
+
The resulting tool name will be in the format "{prefix}{original_name}"
|
| 25 |
+
if prefix is provided, otherwise the original name is used.
|
| 26 |
+
For example, with prefix "weather/" and tool "forecast",
|
| 27 |
the imported tool would be available as "weather/forecast"
|
| 28 |
"""
|
| 29 |
for name, tool in tool_manager._tools.items():
|
| 30 |
+
prefixed_name = f"{prefix}{name}" if prefix else name
|
| 31 |
|
| 32 |
# Create a shallow copy of the tool with the prefixed name
|
| 33 |
copied_tool = Tool.from_function(
|
tests/prompts/test_prompt_manager.py
CHANGED
|
@@ -44,7 +44,7 @@ def test_import_prompts():
|
|
| 44 |
target_manager = PromptManager()
|
| 45 |
|
| 46 |
# Import prompts from source to target
|
| 47 |
-
prefix = "nlp"
|
| 48 |
target_manager.import_prompts(source_manager, prefix)
|
| 49 |
|
| 50 |
# Verify prompts were imported with prefixes
|
|
@@ -109,7 +109,7 @@ def test_import_prompts_with_duplicates():
|
|
| 109 |
target_manager._prompts["common"] = target_prompt
|
| 110 |
|
| 111 |
# Import prompts with prefix
|
| 112 |
-
prefix = "external"
|
| 113 |
target_manager.import_prompts(source_manager, prefix)
|
| 114 |
|
| 115 |
# Verify both prompts exist in target manager
|
|
@@ -146,10 +146,10 @@ def test_import_prompts_with_nested_prefixes():
|
|
| 146 |
first_manager._prompts["analyze"] = original_prompt
|
| 147 |
|
| 148 |
# Import to second manager with prefix
|
| 149 |
-
second_manager.import_prompts(first_manager, "text")
|
| 150 |
|
| 151 |
# Import from second to third with another prefix
|
| 152 |
-
third_manager.import_prompts(second_manager, "ai")
|
| 153 |
|
| 154 |
# Verify the nested prefixing
|
| 155 |
assert "text/analyze" in second_manager._prompts
|
|
|
|
| 44 |
target_manager = PromptManager()
|
| 45 |
|
| 46 |
# Import prompts from source to target
|
| 47 |
+
prefix = "nlp/"
|
| 48 |
target_manager.import_prompts(source_manager, prefix)
|
| 49 |
|
| 50 |
# Verify prompts were imported with prefixes
|
|
|
|
| 109 |
target_manager._prompts["common"] = target_prompt
|
| 110 |
|
| 111 |
# Import prompts with prefix
|
| 112 |
+
prefix = "external/"
|
| 113 |
target_manager.import_prompts(source_manager, prefix)
|
| 114 |
|
| 115 |
# Verify both prompts exist in target manager
|
|
|
|
| 146 |
first_manager._prompts["analyze"] = original_prompt
|
| 147 |
|
| 148 |
# Import to second manager with prefix
|
| 149 |
+
second_manager.import_prompts(first_manager, "text/")
|
| 150 |
|
| 151 |
# Import from second to third with another prefix
|
| 152 |
+
third_manager.import_prompts(second_manager, "ai/")
|
| 153 |
|
| 154 |
# Verify the nested prefixing
|
| 155 |
assert "text/analyze" in second_manager._prompts
|
tests/resources/test_resource_manager.py
CHANGED
|
@@ -39,7 +39,7 @@ def test_import_resources():
|
|
| 39 |
target_manager = ResourceManager()
|
| 40 |
|
| 41 |
# Import resources from source to target
|
| 42 |
-
prefix = "data"
|
| 43 |
target_manager.import_resources(source_manager, prefix)
|
| 44 |
|
| 45 |
# Verify resources were imported with prefixes
|
|
@@ -126,7 +126,7 @@ def test_import_templates():
|
|
| 126 |
target_manager = ResourceManager()
|
| 127 |
|
| 128 |
# Import templates from source to target
|
| 129 |
-
prefix = "shop"
|
| 130 |
target_manager.import_templates(source_manager, prefix)
|
| 131 |
|
| 132 |
# Verify templates were imported with prefixes
|
|
@@ -212,7 +212,7 @@ def test_import_multiple_resource_types():
|
|
| 212 |
target_manager = ResourceManager()
|
| 213 |
|
| 214 |
# Import both resources and templates
|
| 215 |
-
prefix = "test"
|
| 216 |
target_manager.import_resources(source_manager, prefix)
|
| 217 |
target_manager.import_templates(source_manager, prefix)
|
| 218 |
|
|
|
|
| 39 |
target_manager = ResourceManager()
|
| 40 |
|
| 41 |
# Import resources from source to target
|
| 42 |
+
prefix = "data+"
|
| 43 |
target_manager.import_resources(source_manager, prefix)
|
| 44 |
|
| 45 |
# Verify resources were imported with prefixes
|
|
|
|
| 126 |
target_manager = ResourceManager()
|
| 127 |
|
| 128 |
# Import templates from source to target
|
| 129 |
+
prefix = "shop+"
|
| 130 |
target_manager.import_templates(source_manager, prefix)
|
| 131 |
|
| 132 |
# Verify templates were imported with prefixes
|
|
|
|
| 212 |
target_manager = ResourceManager()
|
| 213 |
|
| 214 |
# Import both resources and templates
|
| 215 |
+
prefix = "test+"
|
| 216 |
target_manager.import_resources(source_manager, prefix)
|
| 217 |
target_manager.import_templates(source_manager, prefix)
|
| 218 |
|
tests/tools/test_tool_manager.py
CHANGED
|
@@ -23,7 +23,7 @@ def test_import_tools():
|
|
| 23 |
target_manager = ToolManager()
|
| 24 |
|
| 25 |
# Import tools from source to target
|
| 26 |
-
prefix = "source"
|
| 27 |
target_manager.import_tools(source_manager, prefix)
|
| 28 |
|
| 29 |
# Verify tools were imported with prefixes
|
|
@@ -65,7 +65,7 @@ def test_tool_duplicate_behavior():
|
|
| 65 |
) # Pre-create with the prefixed name
|
| 66 |
|
| 67 |
# Import tools from source to target
|
| 68 |
-
target_manager.import_tools(source_manager, "source")
|
| 69 |
|
| 70 |
# The original tool in the target manager is replaced by the imported one
|
| 71 |
assert target_manager._tools["source/common_tool"].fn.__name__ == source_fn.__name__
|
|
@@ -89,8 +89,8 @@ def test_import_tools_with_multiple_prefixes():
|
|
| 89 |
|
| 90 |
# Create target manager and import from both sources
|
| 91 |
main_manager = ToolManager()
|
| 92 |
-
main_manager.import_tools(weather_manager, "weather")
|
| 93 |
-
main_manager.import_tools(news_manager, "news")
|
| 94 |
|
| 95 |
# Verify tools were imported with correct prefixes
|
| 96 |
assert "weather/forecast" in main_manager._tools
|
|
|
|
| 23 |
target_manager = ToolManager()
|
| 24 |
|
| 25 |
# Import tools from source to target
|
| 26 |
+
prefix = "source/"
|
| 27 |
target_manager.import_tools(source_manager, prefix)
|
| 28 |
|
| 29 |
# Verify tools were imported with prefixes
|
|
|
|
| 65 |
) # Pre-create with the prefixed name
|
| 66 |
|
| 67 |
# Import tools from source to target
|
| 68 |
+
target_manager.import_tools(source_manager, "source/")
|
| 69 |
|
| 70 |
# The original tool in the target manager is replaced by the imported one
|
| 71 |
assert target_manager._tools["source/common_tool"].fn.__name__ == source_fn.__name__
|
|
|
|
| 89 |
|
| 90 |
# Create target manager and import from both sources
|
| 91 |
main_manager = ToolManager()
|
| 92 |
+
main_manager.import_tools(weather_manager, "weather/")
|
| 93 |
+
main_manager.import_tools(news_manager, "news/")
|
| 94 |
|
| 95 |
# Verify tools were imported with correct prefixes
|
| 96 |
assert "weather/forecast" in main_manager._tools
|