mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
"""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):
|
|
# Rust calculator (meval) returns inf for 1/0 rather than an error
|
|
result = client.call_tool("calculator", {"expression": "1/0"})
|
|
assert result["isError"] is False
|
|
assert "inf" 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
|