Expose RequestParams._meta in ClientSession.call_tool (#1231)
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
parent
c733eec5d9
commit
40acbc596c
2 changed files with 129 additions and 7 deletions
|
|
@ -328,16 +328,19 @@ class ClientSession(
|
||||||
arguments: dict[str, Any] | None = None,
|
arguments: dict[str, Any] | None = None,
|
||||||
read_timeout_seconds: timedelta | None = None,
|
read_timeout_seconds: timedelta | None = None,
|
||||||
progress_callback: ProgressFnT | None = None,
|
progress_callback: ProgressFnT | None = None,
|
||||||
|
*,
|
||||||
|
meta: dict[str, Any] | None = None,
|
||||||
) -> types.CallToolResult:
|
) -> types.CallToolResult:
|
||||||
"""Send a tools/call request with optional progress callback support."""
|
"""Send a tools/call request with optional progress callback support."""
|
||||||
|
|
||||||
|
_meta: types.RequestParams.Meta | None = None
|
||||||
|
if meta is not None:
|
||||||
|
_meta = types.RequestParams.Meta(**meta)
|
||||||
|
|
||||||
result = await self.send_request(
|
result = await self.send_request(
|
||||||
types.ClientRequest(
|
types.ClientRequest(
|
||||||
types.CallToolRequest(
|
types.CallToolRequest(
|
||||||
params=types.CallToolRequestParams(
|
params=types.CallToolRequestParams(name=name, arguments=arguments, _meta=_meta),
|
||||||
name=name,
|
|
||||||
arguments=arguments,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
types.CallToolResult,
|
types.CallToolResult,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from mcp.shared.session import RequestResponder
|
||||||
from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS
|
from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
LATEST_PROTOCOL_VERSION,
|
LATEST_PROTOCOL_VERSION,
|
||||||
|
CallToolResult,
|
||||||
ClientNotification,
|
ClientNotification,
|
||||||
ClientRequest,
|
ClientRequest,
|
||||||
Implementation,
|
Implementation,
|
||||||
|
|
@ -23,6 +24,7 @@ from mcp.types import (
|
||||||
JSONRPCResponse,
|
JSONRPCResponse,
|
||||||
ServerCapabilities,
|
ServerCapabilities,
|
||||||
ServerResult,
|
ServerResult,
|
||||||
|
TextContent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -492,8 +494,125 @@ async def test_client_capabilities_with_custom_callbacks():
|
||||||
|
|
||||||
# Assert that capabilities are properly set with custom callbacks
|
# Assert that capabilities are properly set with custom callbacks
|
||||||
assert received_capabilities is not None
|
assert received_capabilities is not None
|
||||||
assert received_capabilities.sampling is not None # Custom sampling callback provided
|
# Custom sampling callback provided
|
||||||
|
assert received_capabilities.sampling is not None
|
||||||
assert isinstance(received_capabilities.sampling, types.SamplingCapability)
|
assert isinstance(received_capabilities.sampling, types.SamplingCapability)
|
||||||
assert received_capabilities.roots is not None # Custom list_roots callback provided
|
# Custom list_roots callback provided
|
||||||
|
assert received_capabilities.roots is not None
|
||||||
assert isinstance(received_capabilities.roots, types.RootsCapability)
|
assert isinstance(received_capabilities.roots, types.RootsCapability)
|
||||||
assert received_capabilities.roots.listChanged is True # Should be True for custom callback
|
# Should be True for custom callback
|
||||||
|
assert received_capabilities.roots.listChanged is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.parametrize(argnames="meta", argvalues=[None, {"toolMeta": "value"}])
|
||||||
|
async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
|
||||||
|
"""Test that client tool call requests can include metadata"""
|
||||||
|
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1)
|
||||||
|
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](1)
|
||||||
|
|
||||||
|
mocked_tool = types.Tool(name="sample_tool", inputSchema={})
|
||||||
|
|
||||||
|
async def mock_server():
|
||||||
|
# Receive initialization request from client
|
||||||
|
session_message = await client_to_server_receive.receive()
|
||||||
|
jsonrpc_request = session_message.message
|
||||||
|
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
|
||||||
|
request = ClientRequest.model_validate(
|
||||||
|
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
|
)
|
||||||
|
assert isinstance(request.root, InitializeRequest)
|
||||||
|
|
||||||
|
result = ServerResult(
|
||||||
|
InitializeResult(
|
||||||
|
protocolVersion=LATEST_PROTOCOL_VERSION,
|
||||||
|
capabilities=ServerCapabilities(),
|
||||||
|
serverInfo=Implementation(name="mock-server", version="0.1.0"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Answer initialization request
|
||||||
|
await server_to_client_send.send(
|
||||||
|
SessionMessage(
|
||||||
|
JSONRPCMessage(
|
||||||
|
JSONRPCResponse(
|
||||||
|
jsonrpc="2.0",
|
||||||
|
id=jsonrpc_request.root.id,
|
||||||
|
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Receive initialized notification
|
||||||
|
await client_to_server_receive.receive()
|
||||||
|
|
||||||
|
# Wait for the client to send a 'tools/call' request
|
||||||
|
session_message = await client_to_server_receive.receive()
|
||||||
|
jsonrpc_request = session_message.message
|
||||||
|
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
|
||||||
|
|
||||||
|
assert jsonrpc_request.root.method == "tools/call"
|
||||||
|
|
||||||
|
if meta is not None:
|
||||||
|
assert jsonrpc_request.root.params
|
||||||
|
assert "_meta" in jsonrpc_request.root.params
|
||||||
|
assert jsonrpc_request.root.params["_meta"] == meta
|
||||||
|
|
||||||
|
result = ServerResult(
|
||||||
|
CallToolResult(content=[TextContent(type="text", text="Called successfully")], isError=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send the tools/call result
|
||||||
|
await server_to_client_send.send(
|
||||||
|
SessionMessage(
|
||||||
|
JSONRPCMessage(
|
||||||
|
JSONRPCResponse(
|
||||||
|
jsonrpc="2.0",
|
||||||
|
id=jsonrpc_request.root.id,
|
||||||
|
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for the tools/list request from the client
|
||||||
|
# The client requires this step to validate the tool output schema
|
||||||
|
session_message = await client_to_server_receive.receive()
|
||||||
|
jsonrpc_request = session_message.message
|
||||||
|
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
|
||||||
|
|
||||||
|
assert jsonrpc_request.root.method == "tools/list"
|
||||||
|
|
||||||
|
result = types.ListToolsResult(tools=[mocked_tool])
|
||||||
|
|
||||||
|
await server_to_client_send.send(
|
||||||
|
SessionMessage(
|
||||||
|
JSONRPCMessage(
|
||||||
|
JSONRPCResponse(
|
||||||
|
jsonrpc="2.0",
|
||||||
|
id=jsonrpc_request.root.id,
|
||||||
|
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
server_to_client_send.close()
|
||||||
|
|
||||||
|
async with (
|
||||||
|
ClientSession(
|
||||||
|
server_to_client_receive,
|
||||||
|
client_to_server_send,
|
||||||
|
) as session,
|
||||||
|
anyio.create_task_group() as tg,
|
||||||
|
client_to_server_send,
|
||||||
|
client_to_server_receive,
|
||||||
|
server_to_client_send,
|
||||||
|
server_to_client_receive,
|
||||||
|
):
|
||||||
|
tg.start_soon(mock_server)
|
||||||
|
|
||||||
|
await session.initialize()
|
||||||
|
|
||||||
|
await session.call_tool(name=mocked_tool.name, arguments={"foo": "bar"}, meta=meta)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue