"""Tests for completion handler with context functionality.""" import pytest from mcp_types import ( CompleteRequestParams, CompleteResult, Completion, PromptReference, ResourceTemplateReference, ) from mcp import Client from mcp.server import Server, ServerRequestContext @pytest.mark.anyio async def test_completion_handler_receives_context(): """Test that the completion handler receives context correctly.""" # Track what the handler receives received_params: CompleteRequestParams | None = None async def handle_completion(ctx: ServerRequestContext, params: CompleteRequestParams) -> CompleteResult: nonlocal received_params received_params = params return CompleteResult(completion=Completion(values=["test-completion"], total=1, has_more=False)) server = Server("test-server", on_completion=handle_completion) async with Client(server) as client: # Test with context result = await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="test://resource/{param}"), argument={"name": "param", "value": "test"}, context_arguments={"previous": "value"}, ) # Verify handler received the context assert received_params is not None assert received_params.context is not None assert received_params.context.arguments == {"previous": "value"} assert result.completion.values == ["test-completion"] @pytest.mark.anyio async def test_completion_backward_compatibility(): """Test that completion works without context (backward compatibility).""" context_was_none = False async def handle_completion(ctx: ServerRequestContext, params: CompleteRequestParams) -> CompleteResult: nonlocal context_was_none context_was_none = params.context is None return CompleteResult(completion=Completion(values=["no-context-completion"], total=1, has_more=False)) server = Server("test-server", on_completion=handle_completion) async with Client(server) as client: # Test without context result = await client.complete( ref=PromptReference(type="ref/prompt", name="test-prompt"), argument={"name": "arg", "value": "val"} ) # Verify context was None assert context_was_none assert result.completion.values == ["no-context-completion"] @pytest.mark.anyio async def test_dependent_completion_scenario(): """Test a real-world scenario with dependent completions.""" async def handle_completion(ctx: ServerRequestContext, params: CompleteRequestParams) -> CompleteResult: # Simulate database/table completion scenario assert isinstance(params.ref, ResourceTemplateReference) assert params.ref.uri == "db://{database}/{table}" if params.argument.name == "database": return CompleteResult( completion=Completion(values=["users_db", "products_db", "analytics_db"], total=3, has_more=False) ) assert params.argument.name == "table" assert params.context and params.context.arguments db = params.context.arguments.get("database") if db == "users_db": return CompleteResult( completion=Completion(values=["users", "sessions", "permissions"], total=3, has_more=False) ) else: assert db == "products_db" return CompleteResult( completion=Completion(values=["products", "categories", "inventory"], total=3, has_more=False) ) server = Server("test-server", on_completion=handle_completion) async with Client(server) as client: # First, complete database db_result = await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="db://{database}/{table}"), argument={"name": "database", "value": ""}, ) assert "users_db" in db_result.completion.values assert "products_db" in db_result.completion.values # Then complete table with database context table_result = await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="db://{database}/{table}"), argument={"name": "table", "value": ""}, context_arguments={"database": "users_db"}, ) assert table_result.completion.values == ["users", "sessions", "permissions"] # Different database gives different tables table_result2 = await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="db://{database}/{table}"), argument={"name": "table", "value": ""}, context_arguments={"database": "products_db"}, ) assert table_result2.completion.values == ["products", "categories", "inventory"] @pytest.mark.anyio async def test_completion_error_on_missing_context(): """Test that server can raise error when required context is missing.""" async def handle_completion(ctx: ServerRequestContext, params: CompleteRequestParams) -> CompleteResult: assert isinstance(params.ref, ResourceTemplateReference) assert params.ref.uri == "db://{database}/{table}" assert params.argument.name == "table" if not params.context or not params.context.arguments or "database" not in params.context.arguments: raise ValueError("Please select a database first to see available tables") db = params.context.arguments.get("database") assert db == "test_db" return CompleteResult(completion=Completion(values=["users", "orders", "products"], total=3, has_more=False)) server = Server("test-server", on_completion=handle_completion) async with Client(server, mode="legacy") as client: # Try to complete table without database context - should raise error with pytest.raises(Exception) as exc_info: await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="db://{database}/{table}"), argument={"name": "table", "value": ""}, ) # Verify error message assert "Please select a database first" in str(exc_info.value) # Now complete with proper context - should work normally result_with_context = await client.complete( ref=ResourceTemplateReference(type="ref/resource", uri="db://{database}/{table}"), argument={"name": "table", "value": ""}, context_arguments={"database": "test_db"}, ) # Should get normal completions assert result_with_context.completion.values == ["users", "orders", "products"]