mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
b45d9663ec
* fix(python): expose client capabilities to MCP servers Add list_roots_callback support to Python connectors to properly advertise the roots capability during MCP initialization handshake. Previously, the Python library was not exposing any client capabilities because the list_roots_callback was not being passed to ClientSession. This fix ensures the roots capability is always advertised, bringing the Python library in line with the TypeScript implementation. Changes: - Add roots and list_roots_callback params to BaseConnector - Add get_roots() and set_roots() methods for managing roots - Pass list_roots_callback to ClientSession in all connectors - Export Root type from connectors module - Update unit tests to expect list_roots_callback parameter Closes MCP-1063 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(python): add roots and list_roots_callback params to connectors - Add roots and list_roots_callback parameters to StdioConnector, HttpConnector, and SandboxConnector constructors - Add comprehensive unit tests for client capabilities exposure - Tests verify roots capability, get_roots(), set_roots(), and that all callbacks are correctly passed to ClientSession 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: fix import sorting and update API documentation - Fix import sorting in test_client_capabilities.py (ruff) - Regenerate API documentation for updated connector classes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(python): add list_roots() method to server Context Allow MCP servers to request the list of roots from connected clients via ctx.list_roots(). This follows the same pattern as ctx.sample() and ctx.elicit() for server-to-client requests. * feat(python): add roots params to MCPClient and config - Add roots and list_roots_callback parameters to MCPClient.__init__, from_dict(), and from_config_file() methods - Update create_connector_from_config() to pass roots params to all connector types (Stdio, HTTP, Sandbox) - Allows clients to configure roots at the MCPClient level and have them propagate to all sessions * test(python): add get_client_roots tool to primitive server Add a tool that demonstrates the roots capability by requesting and returning the list of roots from the connected client. Used for integration testing of the roots feature. * test(python): add integration tests for roots capability Add comprehensive integration tests for the roots feature: - test_roots_capability: Verify server can request/receive roots - test_roots_empty_by_default: Verify empty roots when not provided - test_roots_with_custom_callback: Verify custom callback precedence - test_set_roots_updates_server_view: Verify dynamic root updates * test(python): update unit tests for roots parameters Update mock assertions in test_client.py to expect the new roots and list_roots_callback parameters in create_connector_from_config calls. * ci(python): add roots to primitive tests and README badge - Add roots to CI workflow primitive test matrix - Add roots to PR comment report primitives list - Add Roots badge to README Primitives row * feat(docs): enhance MCP client and server documentation for roots feature - Added `roots` and `list_roots_callback` parameters to MCP client initialization documentation. - Updated `create_connector_from_config` documentation to include new parameters. - Introduced `list_roots` method documentation in server context, detailing its usage and return values. - Improved overall clarity and completeness of API reference for better developer guidance. * feat(python): add get_client_roots tool to context example - Introduced a new tool, `get_client_roots`, to the context example, demonstrating how to retrieve and display the list of roots exposed by the client. - Updated documentation to include the new tool and its functionality, enhancing the example's coverage of context features. * feat(docs): add roots documentation for client and server - Introduced new documentation for the `roots` feature, detailing how clients can expose file system roots to MCP servers and how servers can access these roots. - Added examples for configuring roots in the MCP client and using the `list_roots()` method in the server context. - Enhanced overall clarity and usability of the documentation to support developers in implementing the roots functionality. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: pietrozullo <pietro.zullo@gmail.com>
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
"""Integration tests for the roots capability.
|
|
|
|
These tests verify that the client properly exposes roots to MCP servers
|
|
and that servers can request the list of roots from the client.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
|
|
import pytest
|
|
from mcp.types import ListRootsResult, Root
|
|
|
|
from mcp_use.client import MCPClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_roots_capability(primitive_server):
|
|
"""Test that a server can request and receive roots from the client."""
|
|
# Define roots to expose to the server
|
|
roots = [
|
|
Root(uri="file:///home/user/project", name="My Project"),
|
|
Root(uri="file:///home/user/data"),
|
|
]
|
|
|
|
config = {"mcpServers": {"PrimitiveServer": {"url": f"{primitive_server}/mcp"}}}
|
|
client = MCPClient(config, roots=roots)
|
|
|
|
try:
|
|
await client.create_all_sessions()
|
|
session = client.get_session("PrimitiveServer")
|
|
|
|
# Call the get_client_roots tool which requests roots from the client
|
|
result = await session.call_tool(name="get_client_roots", arguments={})
|
|
content = result.content[0]
|
|
logger.info(f"Result: {content}")
|
|
|
|
# Parse the JSON response
|
|
data = json.loads(content.text)
|
|
|
|
# Verify the roots were received correctly
|
|
assert data["count"] == 2
|
|
assert len(data["roots"]) == 2
|
|
|
|
# Check first root (with name)
|
|
root1 = data["roots"][0]
|
|
assert root1["uri"] == "file:///home/user/project"
|
|
assert root1["name"] == "My Project"
|
|
|
|
# Check second root (without name)
|
|
root2 = data["roots"][1]
|
|
assert root2["uri"] == "file:///home/user/data"
|
|
assert root2["name"] is None
|
|
|
|
finally:
|
|
await client.close_all_sessions()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_roots_empty_by_default(primitive_server):
|
|
"""Test that roots are empty when not provided."""
|
|
config = {"mcpServers": {"PrimitiveServer": {"url": f"{primitive_server}/mcp"}}}
|
|
client = MCPClient(config)
|
|
|
|
try:
|
|
await client.create_all_sessions()
|
|
session = client.get_session("PrimitiveServer")
|
|
|
|
# Call the get_client_roots tool
|
|
result = await session.call_tool(name="get_client_roots", arguments={})
|
|
content = result.content[0]
|
|
logger.info(f"Result: {content}")
|
|
|
|
# Parse the JSON response
|
|
data = json.loads(content.text)
|
|
|
|
# Verify no roots were returned
|
|
assert data["count"] == 0
|
|
assert len(data["roots"]) == 0
|
|
|
|
finally:
|
|
await client.close_all_sessions()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_roots_with_custom_callback(primitive_server):
|
|
"""Test that a custom list_roots_callback can provide dynamic roots."""
|
|
# Custom callback that returns different roots
|
|
custom_roots = [
|
|
Root(uri="file:///custom/workspace", name="Custom Workspace"),
|
|
]
|
|
|
|
async def custom_list_roots_callback(context):
|
|
return ListRootsResult(roots=custom_roots)
|
|
|
|
config = {"mcpServers": {"PrimitiveServer": {"url": f"{primitive_server}/mcp"}}}
|
|
client = MCPClient(
|
|
config,
|
|
roots=[Root(uri="file:///default/path")], # Default roots (should be ignored)
|
|
list_roots_callback=custom_list_roots_callback,
|
|
)
|
|
|
|
try:
|
|
await client.create_all_sessions()
|
|
session = client.get_session("PrimitiveServer")
|
|
|
|
# Call the get_client_roots tool
|
|
result = await session.call_tool(name="get_client_roots", arguments={})
|
|
content = result.content[0]
|
|
logger.info(f"Result: {content}")
|
|
|
|
# Parse the JSON response
|
|
data = json.loads(content.text)
|
|
|
|
# Verify custom callback's roots were returned, not default
|
|
assert data["count"] == 1
|
|
assert data["roots"][0]["uri"] == "file:///custom/workspace"
|
|
assert data["roots"][0]["name"] == "Custom Workspace"
|
|
|
|
finally:
|
|
await client.close_all_sessions()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_roots_updates_server_view(primitive_server):
|
|
"""Test that set_roots() updates the roots visible to the server."""
|
|
# Start with initial roots
|
|
initial_roots = [
|
|
Root(uri="file:///initial/path", name="Initial"),
|
|
]
|
|
|
|
config = {"mcpServers": {"PrimitiveServer": {"url": f"{primitive_server}/mcp"}}}
|
|
client = MCPClient(config, roots=initial_roots)
|
|
|
|
try:
|
|
await client.create_all_sessions()
|
|
session = client.get_session("PrimitiveServer")
|
|
|
|
# Verify initial roots
|
|
result = await session.call_tool(name="get_client_roots", arguments={})
|
|
data = json.loads(result.content[0].text)
|
|
assert data["count"] == 1
|
|
assert data["roots"][0]["uri"] == "file:///initial/path"
|
|
|
|
# Update roots via connector
|
|
new_roots = [
|
|
Root(uri="file:///updated/path1", name="Updated 1"),
|
|
Root(uri="file:///updated/path2", name="Updated 2"),
|
|
]
|
|
await session.connector.set_roots(new_roots)
|
|
|
|
# Verify updated roots
|
|
result = await session.call_tool(name="get_client_roots", arguments={})
|
|
data = json.loads(result.content[0].text)
|
|
assert data["count"] == 2
|
|
assert data["roots"][0]["uri"] == "file:///updated/path1"
|
|
assert data["roots"][1]["uri"] == "file:///updated/path2"
|
|
|
|
finally:
|
|
await client.close_all_sessions()
|