"""Tests for the MCP client.""" from __future__ import annotations import pytest from openjarvis.mcp.client import MCPClient from openjarvis.mcp.protocol import MCPError from openjarvis.mcp.server import MCPServer from openjarvis.mcp.transport import InProcessTransport from openjarvis.tools._stubs import ToolSpec from openjarvis.tools.calculator import CalculatorTool from openjarvis.tools.think import ThinkTool @pytest.fixture def client(): """MCP client connected via in-process transport.""" server = MCPServer([CalculatorTool(), ThinkTool()]) transport = InProcessTransport(server) return MCPClient(transport) class TestMCPClient: def test_initialize_handshake(self, client): result = client.initialize() assert "protocolVersion" in result assert "serverInfo" in result assert result["serverInfo"]["name"] == "openjarvis" assert client._initialized is True def test_initialize_sets_capabilities(self, client): client.initialize() assert "tools" in client._capabilities def test_list_tools(self, client): tools = client.list_tools() assert len(tools) == 2 assert all(isinstance(t, ToolSpec) for t in tools) names = {t.name for t in tools} assert "calculator" in names assert "think" in names def test_list_tools_have_descriptions(self, client): tools = client.list_tools() for t in tools: assert t.description # non-empty def test_list_tools_have_parameters(self, client): tools = client.list_tools() for t in tools: assert "properties" in t.parameters def test_call_tool_calculator(self, client): result = client.call_tool("calculator", {"expression": "10 + 5"}) assert result["isError"] is False assert "15" in result["content"][0]["text"] def test_call_tool_think(self, client): result = client.call_tool("think", {"thought": "Reasoning step."}) assert result["isError"] is False assert "Reasoning step." in result["content"][0]["text"] def test_call_tool_error(self, client): result = client.call_tool("calculator", {"expression": "1/0"}) assert result["isError"] is True assert "division by zero" in result["content"][0]["text"] def test_call_unknown_tool_raises(self, client): with pytest.raises(MCPError) as exc_info: client.call_tool("nonexistent", {}) assert "Unknown tool" in str(exc_info.value) def test_client_server_roundtrip(self, client): """Full lifecycle: initialize -> list -> call -> close.""" info = client.initialize() assert "serverInfo" in info tools = client.list_tools() assert len(tools) >= 1 result = client.call_tool("calculator", {"expression": "7 * 8"}) assert "56" in result["content"][0]["text"] client.close() def test_close(self, client): client.close() # Close should not raise even if called multiple times client.close() def test_incremental_ids(self, client): """Each request should get a unique ID.""" id1 = client._next_id() id2 = client._next_id() assert id2 > id1 def test_call_tool_with_no_arguments(self, client): """Calling a tool with no arguments passes empty dict.""" result = client.call_tool("think") # Think tool echoes empty thought assert result["isError"] is False