refactor: split RequestContext between server and client (#1987)
This commit is contained in:
parent
050aeb6456
commit
b1f7eec3cd
4 changed files with 210 additions and 212 deletions
|
|
@ -65,7 +65,7 @@ async def test_stdio_elicitation():
|
|||
create_ask_user_tool(mcp)
|
||||
|
||||
# Create a custom handler for elicitation requests
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
if params.message == "Tool wants to ask: What is your name?":
|
||||
return ElicitResult(action="accept", content={"answer": "Test User"})
|
||||
else: # pragma: no cover
|
||||
|
|
@ -82,7 +82,7 @@ async def test_stdio_elicitation_decline():
|
|||
mcp = MCPServer(name="StdioElicitationDeclineServer")
|
||||
create_ask_user_tool(mcp)
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
await call_tool_and_assert(
|
||||
|
|
@ -121,7 +121,7 @@ async def test_elicitation_schema_validation():
|
|||
|
||||
# Dummy callback (won't be called due to validation failure)
|
||||
async def elicitation_callback(
|
||||
context: RequestContext[ClientSession, None], params: ElicitRequestParams
|
||||
context: RequestContext[ClientSession], params: ElicitRequestParams
|
||||
): # pragma: no cover
|
||||
return ElicitResult(action="accept", content={})
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ async def test_elicitation_with_optional_fields():
|
|||
|
||||
for content, expected in test_cases:
|
||||
|
||||
async def callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="accept", content=content)
|
||||
|
||||
await call_tool_and_assert(mcp, callback, "optional_tool", {}, expected)
|
||||
|
|
@ -196,7 +196,7 @@ async def test_elicitation_with_optional_fields():
|
|||
return f"Validation failed: {str(e)}"
|
||||
|
||||
async def elicitation_callback(
|
||||
context: RequestContext[ClientSession, None], params: ElicitRequestParams
|
||||
context: RequestContext[ClientSession], params: ElicitRequestParams
|
||||
): # pragma: no cover
|
||||
return ElicitResult(action="accept", content={})
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ async def test_elicitation_with_optional_fields():
|
|||
return f"Name: {result.data.name}, Tags: {', '.join(result.data.tags)}"
|
||||
return f"User {result.action}" # pragma: no cover
|
||||
|
||||
async def multiselect_callback(context: RequestContext[ClientSession, Any], params: ElicitRequestParams):
|
||||
async def multiselect_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
if "Please provide tags" in params.message:
|
||||
return ElicitResult(action="accept", content={"name": "Test", "tags": ["tag1", "tag2"]})
|
||||
return ElicitResult(action="decline") # pragma: no cover
|
||||
|
|
@ -240,7 +240,7 @@ async def test_elicitation_with_optional_fields():
|
|||
return f"Name: {result.data.name}, Tags: {tags_str}"
|
||||
return f"User {result.action}" # pragma: no cover
|
||||
|
||||
async def optional_multiselect_callback(context: RequestContext[ClientSession, Any], params: ElicitRequestParams):
|
||||
async def optional_multiselect_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
if "Please provide optional tags" in params.message:
|
||||
return ElicitResult(action="accept", content={"name": "Test", "tags": ["tag1", "tag2"]})
|
||||
return ElicitResult(action="decline") # pragma: no cover
|
||||
|
|
@ -274,7 +274,7 @@ async def test_elicitation_with_default_values():
|
|||
return f"User {result.action}"
|
||||
|
||||
# First verify that defaults are present in the JSON schema sent to clients
|
||||
async def callback_schema_verify(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def callback_schema_verify(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
# Verify the schema includes defaults
|
||||
assert isinstance(params, types.ElicitRequestFormParams), "Expected form mode elicitation"
|
||||
schema = params.requested_schema
|
||||
|
|
@ -296,7 +296,7 @@ async def test_elicitation_with_default_values():
|
|||
)
|
||||
|
||||
# Test overriding defaults
|
||||
async def callback_override(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def callback_override(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(
|
||||
action="accept", content={"email": "john@example.com", "name": "John", "age": 25, "subscribe": False}
|
||||
)
|
||||
|
|
@ -372,7 +372,7 @@ async def test_elicitation_with_enum_titles():
|
|||
return f"User: {result.data.user_name}, Color: {result.data.color}"
|
||||
return f"User {result.action}" # pragma: no cover
|
||||
|
||||
async def enum_callback(context: RequestContext[ClientSession, Any], params: ElicitRequestParams):
|
||||
async def enum_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
if "colors" in params.message and "legacy" not in params.message:
|
||||
return ElicitResult(action="accept", content={"user_name": "Bob", "favorite_colors": ["red", "green"]})
|
||||
elif "color" in params.message:
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ def create_client_for_transport(transport: str, server_url: str):
|
|||
|
||||
# Callback functions for testing
|
||||
async def sampling_callback(
|
||||
context: RequestContext[ClientSession, None], params: CreateMessageRequestParams
|
||||
context: RequestContext[ClientSession], params: CreateMessageRequestParams
|
||||
) -> CreateMessageResult:
|
||||
"""Sampling callback for tests."""
|
||||
return CreateMessageResult(
|
||||
|
|
@ -198,7 +198,7 @@ async def sampling_callback(
|
|||
)
|
||||
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
"""Elicitation callback for tests."""
|
||||
# For restaurant booking test
|
||||
if "No tables available" in params.message:
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ from typing import Any, TypedDict
|
|||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server.context import LifespanContextT, RequestT
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.server.mcpserver.exceptions import ToolError
|
||||
from mcp.server.mcpserver.tools import Tool, ToolManager
|
||||
from mcp.server.mcpserver.utilities.func_metadata import ArgModelBase, FuncMetadata
|
||||
from mcp.server.session import ServerSessionT
|
||||
from mcp.shared.context import LifespanContextT, RequestT
|
||||
from mcp.types import TextContent, ToolAnnotations
|
||||
|
||||
|
||||
|
|
@ -347,9 +347,7 @@ class TestContextHandling:
|
|||
tool = manager.add_tool(tool_without_context)
|
||||
assert tool.context_kwarg is None
|
||||
|
||||
def tool_with_parametrized_context(
|
||||
x: int, ctx: Context[ServerSessionT, LifespanContextT, RequestT]
|
||||
) -> str: # pragma: no cover
|
||||
def tool_with_parametrized_context(x: int, ctx: Context[LifespanContextT, RequestT]) -> str: # pragma: no cover
|
||||
return str(x)
|
||||
|
||||
tool = manager.add_tool(tool_with_parametrized_context)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ async def test_url_elicitation_accept():
|
|||
return f"User {result.action}"
|
||||
|
||||
# Create elicitation callback that accepts URL mode
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
assert params.url == "https://example.com/api_key_setup"
|
||||
assert params.elicitation_id == "test-elicitation-001"
|
||||
|
|
@ -58,7 +58,7 @@ async def test_url_elicitation_decline():
|
|||
# Test only checks decline path
|
||||
return f"User {result.action} authorization"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ async def test_url_elicitation_cancel():
|
|||
# Test only checks cancel path
|
||||
return f"User {result.action} payment"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
return ElicitResult(action="cancel")
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ async def test_url_elicitation_helper_function():
|
|||
# Test only checks accept path - return the type name
|
||||
return type(result).__name__
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="accept")
|
||||
|
||||
async with Client(mcp, elicitation_callback=elicitation_callback) as client:
|
||||
|
|
@ -138,7 +138,7 @@ async def test_url_no_content_in_response():
|
|||
assert result.content is None
|
||||
return f"Action: {result.action}, Content: {result.content}"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
# Verify that this is URL mode
|
||||
assert params.mode == "url"
|
||||
assert isinstance(params, types.ElicitRequestURLParams)
|
||||
|
|
@ -171,7 +171,7 @@ async def test_form_mode_still_works():
|
|||
assert result.data is not None
|
||||
return f"Hello, {result.data.name}!"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
# Verify form mode parameters
|
||||
assert params.mode == "form"
|
||||
assert isinstance(params, types.ElicitRequestFormParams)
|
||||
|
|
@ -207,7 +207,7 @@ async def test_elicit_complete_notification():
|
|||
|
||||
return "Elicitation completed"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="accept") # pragma: no cover
|
||||
|
||||
async with Client(mcp, elicitation_callback=elicitation_callback) as client:
|
||||
|
|
@ -264,7 +264,7 @@ async def test_elicit_url_typed_results():
|
|||
return "Not cancelled" # pragma: no cover
|
||||
|
||||
# Test declined result
|
||||
async def decline_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def decline_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
async with Client(mcp, elicitation_callback=decline_callback) as client:
|
||||
|
|
@ -274,7 +274,7 @@ async def test_elicit_url_typed_results():
|
|||
assert result.content[0].text == "Declined"
|
||||
|
||||
# Test cancelled result
|
||||
async def cancel_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def cancel_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
return ElicitResult(action="cancel")
|
||||
|
||||
async with Client(mcp, elicitation_callback=cancel_callback) as client:
|
||||
|
|
@ -304,7 +304,7 @@ async def test_deprecated_elicit_method():
|
|||
return f"Email: {result.content.get('email', 'none')}"
|
||||
return "No email provided" # pragma: no cover
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
# Verify this is form mode
|
||||
assert params.mode == "form"
|
||||
assert params.requested_schema is not None
|
||||
|
|
@ -332,7 +332,7 @@ async def test_ctx_elicit_url_convenience_method():
|
|||
)
|
||||
return f"Result: {result.action}"
|
||||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
async def elicitation_callback(context: RequestContext[ClientSession], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
assert params.elicitation_id == "ctx-test-001"
|
||||
return ElicitResult(action="accept")
|
||||
|
|
|
|||
Loading…
Reference in a new issue