This commit is contained in:
parent
3a6f2996cd
commit
837ef904f8
1 changed files with 1757 additions and 567 deletions
|
|
@ -16,6 +16,12 @@ from mcp import Client, ClientSessionGroup, MCPError
|
|||
pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")]
|
||||
|
||||
|
||||
def _server_info(client: Client) -> Implementation:
|
||||
"""Narrow `client.server_info` for `connect_with_session`: these servers all identify themselves."""
|
||||
assert client.server_info is not None
|
||||
return client.server_info
|
||||
|
||||
|
||||
async def test_both_servers_call_their_tool_search() -> None:
|
||||
"""tutorial001 + tutorial002: two unrelated servers, one colliding tool name."""
|
||||
async with Client(tutorial001.mcp) as library, Client(tutorial002.mcp) as web:
|
||||
|
|
@ -29,7 +35,7 @@ async def test_a_connected_server_is_aggregated_into_the_group() -> None:
|
|||
"""tutorial003: the group exposes every component of every connected server as a dict."""
|
||||
async with Client(tutorial001.mcp) as library:
|
||||
group = ClientSessionGroup()
|
||||
await group.connect_with_session(library.server_info, library.session)
|
||||
await group.connect_with_session(_server_info(library), library.session)
|
||||
assert sorted(group.tools) == ["search"]
|
||||
assert sorted(group.resources) == ["hours"]
|
||||
assert group.prompts == {}
|
||||
|
|
@ -40,9 +46,9 @@ async def test_colliding_names_are_rejected() -> None:
|
|||
"""tutorial003: without a hook the second `search` raises, and nothing from `Web` is kept."""
|
||||
async with Client(tutorial001.mcp) as library, Client(tutorial002.mcp) as web:
|
||||
group = ClientSessionGroup()
|
||||
await group.connect_with_session(library.server_info, library.session)
|
||||
await group.connect_with_session(_server_info(library), library.session)
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await group.connect_with_session(web.server_info, web.session)
|
||||
await group.connect_with_session(_server_info(web), web.session)
|
||||
assert str(exc_info.value) == "{'search'} already exist in group tools."
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
assert sorted(group.tools) == ["search"]
|
||||
|
|
@ -56,8 +62,8 @@ async def test_component_name_hook_prefixes_every_name() -> None:
|
|||
"""tutorial004: the hook rewrites every registered name, so both servers coexist."""
|
||||
async with Client(tutorial001.mcp) as library, Client(tutorial002.mcp) as web:
|
||||
group = ClientSessionGroup(component_name_hook=tutorial004.by_server)
|
||||
await group.connect_with_session(library.server_info, library.session)
|
||||
await group.connect_with_session(web.server_info, web.session)
|
||||
await group.connect_with_session(_server_info(library), library.session)
|
||||
await group.connect_with_session(_server_info(web), web.session)
|
||||
assert sorted(group.tools) == ["Library.search", "Web.search"]
|
||||
assert sorted(group.resources) == ["Library.hours"]
|
||||
|
||||
|
|
@ -71,7 +77,7 @@ async def test_the_key_is_prefixed_but_the_wire_name_is_not() -> None:
|
|||
"""tutorial004: the dict key is yours; the `Tool` inside keeps the name the server declared."""
|
||||
async with Client(tutorial002.mcp) as web:
|
||||
group = ClientSessionGroup(component_name_hook=tutorial004.by_server)
|
||||
await group.connect_with_session(web.server_info, web.session)
|
||||
await group.connect_with_session(_server_info(web), web.session)
|
||||
assert group.tools["Web.search"].name == "search"
|
||||
|
||||
|
||||
|
|
@ -79,8 +85,8 @@ async def test_call_tool_routes_to_the_owning_server() -> None:
|
|||
"""tutorial004: `group.call_tool` resolves the prefixed name to the session that owns it."""
|
||||
async with Client(tutorial001.mcp) as library, Client(tutorial002.mcp) as web:
|
||||
group = ClientSessionGroup(component_name_hook=tutorial004.by_server)
|
||||
await group.connect_with_session(library.server_info, library.session)
|
||||
await group.connect_with_session(web.server_info, web.session)
|
||||
await group.connect_with_session(_server_info(library), library.session)
|
||||
await group.connect_with_session(_server_info(web), web.session)
|
||||
web_result = await group.call_tool("Web.search", {"query": "model context protocol"})
|
||||
assert web_result.structured_content == {"result": "12 pages match 'model context protocol'."}
|
||||
library_result = await group.call_tool("Library.search", {"query": "dune"})
|
||||
|
|
@ -91,8 +97,8 @@ async def test_disconnect_removes_every_component_of_that_server() -> None:
|
|||
"""tutorial004: `disconnect_from_server` takes the session back out of all three dicts."""
|
||||
async with Client(tutorial001.mcp) as library, Client(tutorial002.mcp) as web:
|
||||
group = ClientSessionGroup(component_name_hook=tutorial004.by_server)
|
||||
await group.connect_with_session(library.server_info, library.session)
|
||||
web_session = await group.connect_with_session(web.server_info, web.session)
|
||||
await group.connect_with_session(_server_info(library), library.session)
|
||||
web_session = await group.connect_with_session(_server_info(web), web.session)
|
||||
await group.disconnect_from_server(web_session)
|
||||
assert sorted(group.tools) == ["Library.search"]
|
||||
assert sorted(group.resources) == ["Library.hours"]
|
||||
|
|
|
|||
Loading…
Reference in a new issue