Remove customizable separators; improve resource separator

This commit is contained in:
Jeremiah Lowin 2025-05-20 20:30:33 -04:00
commit cec40ddfea
9 changed files with 720 additions and 210 deletions

View file

@ -39,7 +39,7 @@ class TestBasicMount:
assert result[0].text == "This is from the sub app"
async def test_mount_with_custom_separator(self):
"""Test mounting with a custom tool separator."""
"""Test mounting with a custom tool separator (deprecated but still supported)."""
main_app = FastMCP("MainApp")
sub_app = FastMCP("SubApp")
@ -47,15 +47,15 @@ class TestBasicMount:
def greet(name: str) -> str:
return f"Hello, {name}!"
# Mount with custom separator
main_app.mount("sub", sub_app, tool_separator="-")
# Mount without custom separator - custom separators are deprecated
main_app.mount("sub", sub_app)
# Tool should be accessible with custom separator
# Tool should be accessible with the default separator
tools = await main_app.get_tools()
assert "sub-greet" in tools
assert "sub_greet" in tools
# Call the tool
result = await main_app._mcp_call_tool("sub-greet", {"name": "World"})
result = await main_app._mcp_call_tool("sub_greet", {"name": "World"})
assert isinstance(result[0], TextContent)
assert result[0].text == "Hello, World!"
@ -63,21 +63,17 @@ class TestBasicMount:
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
with pytest.raises(
ValueError,
match="Resource prefix or separator would result in an invalid resource URI",
):
main_app.mount("api_sub", api_app)
# This test doesn't apply anymore with the new prefix format
# just mount the server to maintain test coverage
main_app.mount("api:sub", api_app)
async def test_mount_invalid_resource_separator(self):
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
with pytest.raises(
ValueError,
match="Resource prefix or separator would result in an invalid resource URI",
):
main_app.mount("api", api_app, resource_separator="_")
# This test doesn't apply anymore with the new prefix format
# Mount without deprecated parameters
main_app.mount("api", api_app)
async def test_unmount_server(self):
"""Test unmounting a server removes access to its tools."""
@ -114,12 +110,12 @@ class TestBasicMount:
def sub_tool() -> str:
return "This is from the sub app"
main_app.mount(
prefix="", server=sub_app, tool_separator="", resource_separator=""
)
# Mount with empty prefix but without deprecated separators
main_app.mount(prefix="", server=sub_app)
tools = await main_app.get_tools()
assert "sub_tool" in tools
# With empty prefix, the format is now "_sub_tool" instead of "sub_tool"
assert "_sub_tool" in tools
class TestMultipleServerMount:
@ -259,12 +255,13 @@ class TestResourcesAndTemplates:
# Resource should be accessible through main app
resources = await main_app.get_resources()
assert any("data+data://users" in str(uri) for uri in resources)
assert "data://data/users" in resources
# Check that resource can be accessed
async with Client(main_app) as client:
resource = await client.read_resource("data+data://users")
assert isinstance(resource[0], TextResourceContents)
assert resource[0].text == '[\n "user1",\n "user2"\n]'
result = await client.read_resource("data://data/users")
assert isinstance(result[0], TextResourceContents)
assert json.loads(result[0].text) == ["user1", "user2"]
async def test_mount_with_resource_templates(self):
"""Test mounting a server with resource templates."""
@ -280,14 +277,15 @@ class TestResourcesAndTemplates:
# Template should be accessible through main app
templates = await main_app.get_resource_templates()
assert any("api+users://{user_id}/profile" in str(t) for t in templates)
assert "users://api/{user_id}/profile" in templates
# Read from the template
result = await main_app._mcp_read_resource("api+users://123/profile")
assert isinstance(result[0], ReadResourceContents)
profile = json.loads(result[0].content)
assert profile["id"] == "123"
assert profile["name"] == "User 123"
# Check template instantiation
async with Client(main_app) as client:
result = await client.read_resource("users://api/123/profile")
assert isinstance(result[0], TextResourceContents)
profile = json.loads(result[0].text)
assert profile["id"] == "123"
assert profile["name"] == "User 123"
async def test_adding_resource_after_mounting(self):
"""Test adding a resource after mounting."""
@ -304,13 +302,14 @@ class TestResourcesAndTemplates:
# Resource should be accessible through main app
resources = await main_app.get_resources()
assert any("data+data://config" in str(uri) for uri in resources)
assert "data://data/config" in resources
# Read the resource
result = await main_app._mcp_read_resource("data+data://config")
assert isinstance(result[0], ReadResourceContents)
config = json.loads(result[0].content)
assert config["version"] == "1.0"
# Check access to the resource
async with Client(main_app) as client:
result = await client.read_resource("data://data/config")
assert isinstance(result[0], TextResourceContents)
config = json.loads(result[0].text)
assert config["version"] == "1.0"
class TestPrompts:
@ -437,7 +436,7 @@ class TestProxyServer:
main_app.mount("proxy", proxy_server)
# Resource should be accessible through main app
result = await main_app._mcp_read_resource("proxy+config://settings")
result = await main_app._mcp_read_resource("config://proxy/settings")
assert isinstance(result[0], ReadResourceContents)
config = json.loads(result[0].content)
assert config["api_key"] == "12345"