Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
fa216f2
1
Parent(s): fe6c0c1
Consolidate prefix logic
Browse files- src/fastmcp/server/server.py +59 -77
- test_revert_check.py +50 -0
- tests/deprecated/test_deprecated.py +0 -80
- tests/deprecated/test_mount_separators.py +23 -21
- tests/server/test_server.py +12 -6
src/fastmcp/server/server.py
CHANGED
|
@@ -12,6 +12,7 @@ from contextlib import (
|
|
| 12 |
AsyncExitStack,
|
| 13 |
asynccontextmanager,
|
| 14 |
)
|
|
|
|
| 15 |
from functools import partial
|
| 16 |
from pathlib import Path
|
| 17 |
from typing import TYPE_CHECKING, Any, Generic, Literal, overload
|
|
@@ -322,10 +323,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 322 |
"""Get all registered tools, indexed by registered key."""
|
| 323 |
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
| 324 |
tools: dict[str, Tool] = {}
|
| 325 |
-
for prefix,
|
| 326 |
try:
|
| 327 |
-
server_tools = await server.get_tools()
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
except Exception as e:
|
| 330 |
logger.warning(
|
| 331 |
f"Failed to get tools from mounted server '{prefix}': {e}"
|
|
@@ -345,10 +350,17 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 345 |
"""Get all registered resources, indexed by registered key."""
|
| 346 |
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
| 347 |
resources: dict[str, Resource] = {}
|
| 348 |
-
for prefix,
|
| 349 |
try:
|
| 350 |
-
server_resources = await server.get_resources()
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
except Exception as e:
|
| 353 |
logger.warning(
|
| 354 |
f"Failed to get resources from mounted server '{prefix}': {e}"
|
|
@@ -370,10 +382,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 370 |
templates := self._cache.get("resource_templates")
|
| 371 |
) is self._cache.NOT_FOUND:
|
| 372 |
templates: dict[str, ResourceTemplate] = {}
|
| 373 |
-
for prefix,
|
| 374 |
try:
|
| 375 |
-
server_templates =
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
except Exception as e:
|
| 378 |
logger.warning(
|
| 379 |
"Failed to get resource templates from mounted server "
|
|
@@ -396,10 +417,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 396 |
"""
|
| 397 |
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
| 398 |
prompts: dict[str, Prompt] = {}
|
| 399 |
-
for prefix,
|
| 400 |
try:
|
| 401 |
-
server_prompts = await server.get_prompts()
|
| 402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
except Exception as e:
|
| 404 |
logger.warning(
|
| 405 |
f"Failed to get prompts from mounted server '{prefix}': {e}"
|
|
@@ -562,10 +588,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 562 |
return await self._tool_manager.call_tool(key, arguments)
|
| 563 |
|
| 564 |
# Check mounted servers to see if they have the tool
|
| 565 |
-
for
|
| 566 |
-
if
|
| 567 |
-
tool_key =
|
| 568 |
-
return await
|
| 569 |
|
| 570 |
raise NotFoundError(f"Unknown tool: {key!r}")
|
| 571 |
|
|
@@ -604,10 +630,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 604 |
)
|
| 605 |
]
|
| 606 |
else:
|
| 607 |
-
for
|
| 608 |
-
if
|
| 609 |
-
|
| 610 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
else:
|
| 612 |
raise NotFoundError(f"Unknown resource: {uri}")
|
| 613 |
|
|
@@ -653,10 +683,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 653 |
return await self._prompt_manager.render_prompt(name, arguments)
|
| 654 |
|
| 655 |
# Check mounted servers to see if they have the prompt
|
| 656 |
-
for
|
| 657 |
-
if
|
| 658 |
-
prompt_name =
|
| 659 |
-
return await
|
|
|
|
|
|
|
| 660 |
|
| 661 |
raise NotFoundError(f"Unknown prompt: {name}")
|
| 662 |
|
|
@@ -1731,60 +1763,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1731 |
return True
|
| 1732 |
|
| 1733 |
|
|
|
|
| 1734 |
class MountedServer:
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
prefix: str,
|
| 1738 |
-
server: FastMCP[LifespanResultT],
|
| 1739 |
-
):
|
| 1740 |
-
self.server = server
|
| 1741 |
-
self.prefix = prefix
|
| 1742 |
-
|
| 1743 |
-
async def get_tools(self) -> dict[str, Tool]:
|
| 1744 |
-
tools = await self.server.get_tools()
|
| 1745 |
-
return {f"{self.prefix}_{key}": tool for key, tool in tools.items()}
|
| 1746 |
-
|
| 1747 |
-
async def get_resources(self) -> dict[str, Resource]:
|
| 1748 |
-
resources = await self.server.get_resources()
|
| 1749 |
-
return {
|
| 1750 |
-
add_resource_prefix(
|
| 1751 |
-
key, self.prefix, self.server.resource_prefix_format
|
| 1752 |
-
): resource
|
| 1753 |
-
for key, resource in resources.items()
|
| 1754 |
-
}
|
| 1755 |
-
|
| 1756 |
-
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
| 1757 |
-
templates = await self.server.get_resource_templates()
|
| 1758 |
-
return {
|
| 1759 |
-
add_resource_prefix(
|
| 1760 |
-
key, self.prefix, self.server.resource_prefix_format
|
| 1761 |
-
): template
|
| 1762 |
-
for key, template in templates.items()
|
| 1763 |
-
}
|
| 1764 |
-
|
| 1765 |
-
async def get_prompts(self) -> dict[str, Prompt]:
|
| 1766 |
-
prompts = await self.server.get_prompts()
|
| 1767 |
-
return {f"{self.prefix}_{key}": prompt for key, prompt in prompts.items()}
|
| 1768 |
-
|
| 1769 |
-
def match_tool(self, key: str) -> bool:
|
| 1770 |
-
return key.startswith(f"{self.prefix}_")
|
| 1771 |
-
|
| 1772 |
-
def strip_tool_prefix(self, key: str) -> str:
|
| 1773 |
-
return key.removeprefix(f"{self.prefix}_")
|
| 1774 |
-
|
| 1775 |
-
def match_resource(self, key: str) -> bool:
|
| 1776 |
-
return has_resource_prefix(key, self.prefix, self.server.resource_prefix_format)
|
| 1777 |
-
|
| 1778 |
-
def strip_resource_prefix(self, key: str) -> str:
|
| 1779 |
-
return remove_resource_prefix(
|
| 1780 |
-
key, self.prefix, self.server.resource_prefix_format
|
| 1781 |
-
)
|
| 1782 |
-
|
| 1783 |
-
def match_prompt(self, key: str) -> bool:
|
| 1784 |
-
return key.startswith(f"{self.prefix}_")
|
| 1785 |
-
|
| 1786 |
-
def strip_prompt_prefix(self, key: str) -> str:
|
| 1787 |
-
return key.removeprefix(f"{self.prefix}_")
|
| 1788 |
|
| 1789 |
|
| 1790 |
def add_resource_prefix(
|
|
|
|
| 12 |
AsyncExitStack,
|
| 13 |
asynccontextmanager,
|
| 14 |
)
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
from functools import partial
|
| 17 |
from pathlib import Path
|
| 18 |
from typing import TYPE_CHECKING, Any, Generic, Literal, overload
|
|
|
|
| 323 |
"""Get all registered tools, indexed by registered key."""
|
| 324 |
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
| 325 |
tools: dict[str, Tool] = {}
|
| 326 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 327 |
try:
|
| 328 |
+
server_tools = await mounted_server.server.get_tools()
|
| 329 |
+
# Apply prefix to each tool key
|
| 330 |
+
prefixed_tools = {
|
| 331 |
+
f"{prefix}_{key}": tool for key, tool in server_tools.items()
|
| 332 |
+
}
|
| 333 |
+
tools.update(prefixed_tools)
|
| 334 |
except Exception as e:
|
| 335 |
logger.warning(
|
| 336 |
f"Failed to get tools from mounted server '{prefix}': {e}"
|
|
|
|
| 350 |
"""Get all registered resources, indexed by registered key."""
|
| 351 |
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
| 352 |
resources: dict[str, Resource] = {}
|
| 353 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 354 |
try:
|
| 355 |
+
server_resources = await mounted_server.server.get_resources()
|
| 356 |
+
# Apply prefix to each resource key
|
| 357 |
+
prefixed_resources = {
|
| 358 |
+
add_resource_prefix(
|
| 359 |
+
key, prefix, mounted_server.server.resource_prefix_format
|
| 360 |
+
): resource
|
| 361 |
+
for key, resource in server_resources.items()
|
| 362 |
+
}
|
| 363 |
+
resources.update(prefixed_resources)
|
| 364 |
except Exception as e:
|
| 365 |
logger.warning(
|
| 366 |
f"Failed to get resources from mounted server '{prefix}': {e}"
|
|
|
|
| 382 |
templates := self._cache.get("resource_templates")
|
| 383 |
) is self._cache.NOT_FOUND:
|
| 384 |
templates: dict[str, ResourceTemplate] = {}
|
| 385 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 386 |
try:
|
| 387 |
+
server_templates = (
|
| 388 |
+
await mounted_server.server.get_resource_templates()
|
| 389 |
+
)
|
| 390 |
+
# Apply prefix to each template key
|
| 391 |
+
prefixed_templates = {
|
| 392 |
+
add_resource_prefix(
|
| 393 |
+
key, prefix, mounted_server.server.resource_prefix_format
|
| 394 |
+
): template
|
| 395 |
+
for key, template in server_templates.items()
|
| 396 |
+
}
|
| 397 |
+
templates.update(prefixed_templates)
|
| 398 |
except Exception as e:
|
| 399 |
logger.warning(
|
| 400 |
"Failed to get resource templates from mounted server "
|
|
|
|
| 417 |
"""
|
| 418 |
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
| 419 |
prompts: dict[str, Prompt] = {}
|
| 420 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 421 |
try:
|
| 422 |
+
server_prompts = await mounted_server.server.get_prompts()
|
| 423 |
+
# Apply prefix to each prompt key
|
| 424 |
+
prefixed_prompts = {
|
| 425 |
+
f"{prefix}_{key}": prompt
|
| 426 |
+
for key, prompt in server_prompts.items()
|
| 427 |
+
}
|
| 428 |
+
prompts.update(prefixed_prompts)
|
| 429 |
except Exception as e:
|
| 430 |
logger.warning(
|
| 431 |
f"Failed to get prompts from mounted server '{prefix}': {e}"
|
|
|
|
| 588 |
return await self._tool_manager.call_tool(key, arguments)
|
| 589 |
|
| 590 |
# Check mounted servers to see if they have the tool
|
| 591 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 592 |
+
if key.startswith(f"{prefix}_"):
|
| 593 |
+
tool_key = key.removeprefix(f"{prefix}_")
|
| 594 |
+
return await mounted_server.server._call_tool(tool_key, arguments)
|
| 595 |
|
| 596 |
raise NotFoundError(f"Unknown tool: {key!r}")
|
| 597 |
|
|
|
|
| 630 |
)
|
| 631 |
]
|
| 632 |
else:
|
| 633 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 634 |
+
if has_resource_prefix(
|
| 635 |
+
str(uri), prefix, mounted_server.server.resource_prefix_format
|
| 636 |
+
):
|
| 637 |
+
new_uri = remove_resource_prefix(
|
| 638 |
+
str(uri), prefix, mounted_server.server.resource_prefix_format
|
| 639 |
+
)
|
| 640 |
+
return await mounted_server.server._mcp_read_resource(new_uri)
|
| 641 |
else:
|
| 642 |
raise NotFoundError(f"Unknown resource: {uri}")
|
| 643 |
|
|
|
|
| 683 |
return await self._prompt_manager.render_prompt(name, arguments)
|
| 684 |
|
| 685 |
# Check mounted servers to see if they have the prompt
|
| 686 |
+
for prefix, mounted_server in self._mounted_servers.items():
|
| 687 |
+
if name.startswith(f"{prefix}_"):
|
| 688 |
+
prompt_name = name.removeprefix(f"{prefix}_")
|
| 689 |
+
return await mounted_server.server._mcp_get_prompt(
|
| 690 |
+
prompt_name, arguments
|
| 691 |
+
)
|
| 692 |
|
| 693 |
raise NotFoundError(f"Unknown prompt: {name}")
|
| 694 |
|
|
|
|
| 1763 |
return True
|
| 1764 |
|
| 1765 |
|
| 1766 |
+
@dataclass
|
| 1767 |
class MountedServer:
|
| 1768 |
+
prefix: str
|
| 1769 |
+
server: FastMCP[Any]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1770 |
|
| 1771 |
|
| 1772 |
def add_resource_prefix(
|
test_revert_check.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Quick test to verify the revert worked correctly."""
|
| 3 |
+
|
| 4 |
+
import asyncio
|
| 5 |
+
|
| 6 |
+
from fastmcp import FastMCP
|
| 7 |
+
from fastmcp.client import Client
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
async def test_empty_prefix_behavior():
|
| 11 |
+
"""Test that empty prefix correctly adds underscore."""
|
| 12 |
+
|
| 13 |
+
main_app = FastMCP("MainApp")
|
| 14 |
+
sub_app = FastMCP("SubApp")
|
| 15 |
+
|
| 16 |
+
@sub_app.tool
|
| 17 |
+
def sub_tool() -> str:
|
| 18 |
+
return "This is from the sub app"
|
| 19 |
+
|
| 20 |
+
@sub_app.resource("data://test")
|
| 21 |
+
def sub_resource():
|
| 22 |
+
return "Resource data"
|
| 23 |
+
|
| 24 |
+
# Mount with empty prefix
|
| 25 |
+
main_app.mount("", sub_app)
|
| 26 |
+
|
| 27 |
+
# Check that tools have underscore prefix
|
| 28 |
+
tools = await main_app.get_tools()
|
| 29 |
+
print(f"Tools: {list(tools.keys())}")
|
| 30 |
+
assert "_sub_tool" in tools, f"Expected '_sub_tool' in {list(tools.keys())}"
|
| 31 |
+
|
| 32 |
+
# Check that resources work correctly
|
| 33 |
+
resources = await main_app.get_resources()
|
| 34 |
+
print(f"Resources: {list(resources.keys())}")
|
| 35 |
+
# Empty prefix for resources should result in no prefix change
|
| 36 |
+
assert "data://test" in resources, (
|
| 37 |
+
f"Expected 'data://test' in {list(resources.keys())}"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# Test calling the tool
|
| 41 |
+
async with Client(main_app) as client:
|
| 42 |
+
result = await client.call_tool("_sub_tool", {})
|
| 43 |
+
print(f"Tool result: {result[0].text}")
|
| 44 |
+
assert "This is from the sub app" in result[0].text
|
| 45 |
+
|
| 46 |
+
print("✅ Empty prefix correctly adds underscore for tools!")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
asyncio.run(test_empty_prefix_behavior())
|
tests/deprecated/test_deprecated.py
CHANGED
|
@@ -109,83 +109,3 @@ def test_from_client_deprecation_warning():
|
|
| 109 |
server = FastMCP("TestServer")
|
| 110 |
with pytest.warns(DeprecationWarning, match="from_client"):
|
| 111 |
FastMCP.from_client(Client(server))
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
def test_mount_tool_separator_deprecation_warning():
|
| 115 |
-
"""Test that using tool_separator in mount() raises a deprecation warning."""
|
| 116 |
-
main_app = FastMCP("MainApp")
|
| 117 |
-
sub_app = FastMCP("SubApp")
|
| 118 |
-
|
| 119 |
-
with pytest.warns(
|
| 120 |
-
DeprecationWarning,
|
| 121 |
-
match="The tool_separator parameter is deprecated and will be removed in a future version",
|
| 122 |
-
):
|
| 123 |
-
main_app.mount("sub", sub_app, tool_separator="-")
|
| 124 |
-
|
| 125 |
-
# Verify the separator is ignored and the default is used
|
| 126 |
-
@sub_app.tool
|
| 127 |
-
def test_tool():
|
| 128 |
-
return "test"
|
| 129 |
-
|
| 130 |
-
mounted_server = main_app._mounted_servers["sub"]
|
| 131 |
-
assert mounted_server.match_tool("sub_test_tool")
|
| 132 |
-
assert not mounted_server.match_tool("sub-test_tool")
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
def test_mount_resource_separator_deprecation_warning():
|
| 136 |
-
"""Test that using resource_separator in mount() raises a deprecation warning."""
|
| 137 |
-
main_app = FastMCP("MainApp")
|
| 138 |
-
sub_app = FastMCP("SubApp")
|
| 139 |
-
|
| 140 |
-
with pytest.warns(
|
| 141 |
-
DeprecationWarning,
|
| 142 |
-
match="The resource_separator parameter is deprecated and ignored",
|
| 143 |
-
):
|
| 144 |
-
main_app.mount("sub", sub_app, resource_separator="+")
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
def test_mount_prompt_separator_deprecation_warning():
|
| 148 |
-
"""Test that using prompt_separator in mount() raises a deprecation warning."""
|
| 149 |
-
main_app = FastMCP("MainApp")
|
| 150 |
-
sub_app = FastMCP("SubApp")
|
| 151 |
-
|
| 152 |
-
with pytest.warns(
|
| 153 |
-
DeprecationWarning,
|
| 154 |
-
match="The prompt_separator parameter is deprecated and will be removed in a future version",
|
| 155 |
-
):
|
| 156 |
-
main_app.mount("sub", sub_app, prompt_separator="-")
|
| 157 |
-
|
| 158 |
-
# Verify the separator is ignored and the default is used
|
| 159 |
-
@sub_app.prompt
|
| 160 |
-
def test_prompt():
|
| 161 |
-
return "test"
|
| 162 |
-
|
| 163 |
-
mounted_server = main_app._mounted_servers["sub"]
|
| 164 |
-
assert mounted_server.match_prompt("sub_test_prompt")
|
| 165 |
-
assert not mounted_server.match_prompt("sub-test_prompt")
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
async def test_import_server_separator_deprecation_warnings():
|
| 169 |
-
"""Test that using separators in import_server() raises deprecation warnings."""
|
| 170 |
-
main_app = FastMCP("MainApp")
|
| 171 |
-
sub_app = FastMCP("SubApp")
|
| 172 |
-
|
| 173 |
-
with pytest.warns(
|
| 174 |
-
DeprecationWarning,
|
| 175 |
-
match="The tool_separator parameter is deprecated and will be removed in a future version",
|
| 176 |
-
):
|
| 177 |
-
await main_app.import_server("sub", sub_app, tool_separator="-")
|
| 178 |
-
|
| 179 |
-
main_app = FastMCP("MainApp")
|
| 180 |
-
with pytest.warns(
|
| 181 |
-
DeprecationWarning,
|
| 182 |
-
match="The resource_separator parameter is deprecated and ignored",
|
| 183 |
-
):
|
| 184 |
-
await main_app.import_server("sub", sub_app, resource_separator="+")
|
| 185 |
-
|
| 186 |
-
main_app = FastMCP("MainApp")
|
| 187 |
-
with pytest.warns(
|
| 188 |
-
DeprecationWarning,
|
| 189 |
-
match="The prompt_separator parameter is deprecated and will be removed in a future version",
|
| 190 |
-
):
|
| 191 |
-
await main_app.import_server("sub", sub_app, prompt_separator="-")
|
|
|
|
| 109 |
server = FastMCP("TestServer")
|
| 110 |
with pytest.warns(DeprecationWarning, match="from_client"):
|
| 111 |
FastMCP.from_client(Client(server))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/deprecated/test_mount_separators.py
CHANGED
|
@@ -1,14 +1,27 @@
|
|
| 1 |
"""Tests for the deprecated separator parameters in mount() and import_server() methods."""
|
| 2 |
|
| 3 |
import pytest
|
|
|
|
| 4 |
|
| 5 |
-
from fastmcp import FastMCP
|
| 6 |
|
| 7 |
# reset deprecation warnings for this module
|
| 8 |
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
|
| 9 |
|
| 10 |
|
| 11 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"""Test that using tool_separator in mount() raises a deprecation warning."""
|
| 13 |
main_app = FastMCP("MainApp")
|
| 14 |
sub_app = FastMCP("SubApp")
|
|
@@ -24,24 +37,12 @@ def test_mount_tool_separator_deprecation_warning():
|
|
| 24 |
def test_tool():
|
| 25 |
return "test"
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def test_mount_resource_separator_deprecation_warning():
|
| 33 |
-
"""Test that using resource_separator in mount() raises a deprecation warning."""
|
| 34 |
-
main_app = FastMCP("MainApp")
|
| 35 |
-
sub_app = FastMCP("SubApp")
|
| 36 |
-
|
| 37 |
-
with pytest.warns(
|
| 38 |
-
DeprecationWarning,
|
| 39 |
-
match="The resource_separator parameter is deprecated and ignored",
|
| 40 |
-
):
|
| 41 |
-
main_app.mount("sub", sub_app, resource_separator="+")
|
| 42 |
|
| 43 |
|
| 44 |
-
def test_mount_prompt_separator_deprecation_warning():
|
| 45 |
"""Test that using prompt_separator in mount() raises a deprecation warning."""
|
| 46 |
main_app = FastMCP("MainApp")
|
| 47 |
sub_app = FastMCP("SubApp")
|
|
@@ -57,9 +58,10 @@ def test_mount_prompt_separator_deprecation_warning():
|
|
| 57 |
def test_prompt():
|
| 58 |
return "test"
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
async def test_import_server_separator_deprecation_warnings():
|
|
|
|
| 1 |
"""Tests for the deprecated separator parameters in mount() and import_server() methods."""
|
| 2 |
|
| 3 |
import pytest
|
| 4 |
+
from mcp import McpError
|
| 5 |
|
| 6 |
+
from fastmcp import Client, FastMCP
|
| 7 |
|
| 8 |
# reset deprecation warnings for this module
|
| 9 |
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
|
| 10 |
|
| 11 |
|
| 12 |
+
def test_mount_resource_separator_deprecation_warning():
|
| 13 |
+
"""Test that using resource_separator in mount() raises a deprecation warning."""
|
| 14 |
+
main_app = FastMCP("MainApp")
|
| 15 |
+
sub_app = FastMCP("SubApp")
|
| 16 |
+
|
| 17 |
+
with pytest.warns(
|
| 18 |
+
DeprecationWarning,
|
| 19 |
+
match="The resource_separator parameter is deprecated and ignored",
|
| 20 |
+
):
|
| 21 |
+
main_app.mount("sub", sub_app, resource_separator="+")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
async def test_mount_tool_separator_deprecation_warning():
|
| 25 |
"""Test that using tool_separator in mount() raises a deprecation warning."""
|
| 26 |
main_app = FastMCP("MainApp")
|
| 27 |
sub_app = FastMCP("SubApp")
|
|
|
|
| 37 |
def test_tool():
|
| 38 |
return "test"
|
| 39 |
|
| 40 |
+
async with Client(main_app) as client:
|
| 41 |
+
assert "sub_test_tool" in {t.name for t in await client.list_tools()}
|
| 42 |
+
assert "sub-test_tool" not in {t.name for t in await client.list_tools()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
|
| 45 |
+
async def test_mount_prompt_separator_deprecation_warning():
|
| 46 |
"""Test that using prompt_separator in mount() raises a deprecation warning."""
|
| 47 |
main_app = FastMCP("MainApp")
|
| 48 |
sub_app = FastMCP("SubApp")
|
|
|
|
| 58 |
def test_prompt():
|
| 59 |
return "test"
|
| 60 |
|
| 61 |
+
async with Client(main_app) as client:
|
| 62 |
+
assert await client.get_prompt("sub_test_prompt")
|
| 63 |
+
with pytest.raises(McpError, match="Unknown prompt"):
|
| 64 |
+
await client.get_prompt("sub-test_prompt")
|
| 65 |
|
| 66 |
|
| 67 |
async def test_import_server_separator_deprecation_warnings():
|
tests/server/test_server.py
CHANGED
|
@@ -9,7 +9,6 @@ from fastmcp.exceptions import NotFoundError
|
|
| 9 |
from fastmcp.prompts.prompt import FunctionPrompt, Prompt
|
| 10 |
from fastmcp.resources import Resource, ResourceTemplate
|
| 11 |
from fastmcp.server.server import (
|
| 12 |
-
MountedServer,
|
| 13 |
add_resource_prefix,
|
| 14 |
has_resource_prefix,
|
| 15 |
remove_resource_prefix,
|
|
@@ -1183,16 +1182,23 @@ class TestResourcePrefixMounting:
|
|
| 1183 |
async def test_mounted_server_matching_and_stripping(
|
| 1184 |
self, uri, prefix, expected_match, expected_strip
|
| 1185 |
):
|
| 1186 |
-
"""Test that
|
| 1187 |
-
|
|
|
|
|
|
|
| 1188 |
server = FastMCP()
|
| 1189 |
-
mounted = MountedServer(prefix=prefix, server=server)
|
| 1190 |
|
| 1191 |
# Test matching
|
| 1192 |
-
assert
|
|
|
|
|
|
|
|
|
|
| 1193 |
|
| 1194 |
# Test stripping
|
| 1195 |
-
assert
|
|
|
|
|
|
|
|
|
|
| 1196 |
|
| 1197 |
async def test_import_server_with_new_prefix_format(self):
|
| 1198 |
"""Test that import_server correctly uses the new prefix format."""
|
|
|
|
| 9 |
from fastmcp.prompts.prompt import FunctionPrompt, Prompt
|
| 10 |
from fastmcp.resources import Resource, ResourceTemplate
|
| 11 |
from fastmcp.server.server import (
|
|
|
|
| 12 |
add_resource_prefix,
|
| 13 |
has_resource_prefix,
|
| 14 |
remove_resource_prefix,
|
|
|
|
| 1182 |
async def test_mounted_server_matching_and_stripping(
|
| 1183 |
self, uri, prefix, expected_match, expected_strip
|
| 1184 |
):
|
| 1185 |
+
"""Test that resource prefix utility functions correctly match and strip resource prefixes."""
|
| 1186 |
+
from fastmcp.server.server import has_resource_prefix, remove_resource_prefix
|
| 1187 |
+
|
| 1188 |
+
# Create a basic server to get the default resource prefix format
|
| 1189 |
server = FastMCP()
|
|
|
|
| 1190 |
|
| 1191 |
# Test matching
|
| 1192 |
+
assert (
|
| 1193 |
+
has_resource_prefix(uri, prefix, server.resource_prefix_format)
|
| 1194 |
+
== expected_match
|
| 1195 |
+
)
|
| 1196 |
|
| 1197 |
# Test stripping
|
| 1198 |
+
assert (
|
| 1199 |
+
remove_resource_prefix(uri, prefix, server.resource_prefix_format)
|
| 1200 |
+
== expected_strip
|
| 1201 |
+
)
|
| 1202 |
|
| 1203 |
async def test_import_server_with_new_prefix_format(self):
|
| 1204 |
"""Test that import_server correctly uses the new prefix format."""
|