feat: add MCP tool integration via MCPO (#11)
This commit is contained in:
@@ -14,6 +14,7 @@ from steward.config import Settings
|
||||
from steward.llm.client import LLMClient
|
||||
from steward.tools.client import (
|
||||
ToolClient,
|
||||
_discover_mcpo_server_names,
|
||||
_operation_id,
|
||||
_resolve_operation,
|
||||
_schema_to_json_schema,
|
||||
@@ -25,6 +26,22 @@ from tests.conftest import make_settings
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
MCPO_ROOT_SPEC: dict[str, Any] = {
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "MCP OpenAPI Proxy",
|
||||
"version": "1.0",
|
||||
"description": (
|
||||
"Automatically generated API from MCP Tool Schemas\n\n"
|
||||
"- **available tools**:\n"
|
||||
" - [github](/github/docs)\n"
|
||||
" - [memory](/memory/docs)\n"
|
||||
),
|
||||
},
|
||||
"paths": {},
|
||||
}
|
||||
|
||||
|
||||
SIMPLE_SPEC: dict[str, Any] = {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Test", "version": "1.0.0"},
|
||||
@@ -248,6 +265,10 @@ def test_resolve_operation_not_found():
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_discover_mcpo_server_names():
|
||||
assert _discover_mcpo_server_names(MCPO_ROOT_SPEC) == ["github", "memory"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_client_get_spec():
|
||||
"""get_spec() fetches from /openapi.json and caches the result."""
|
||||
@@ -277,6 +298,48 @@ async def test_tool_client_get_tools():
|
||||
assert tools2 is tools
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_client_get_tools_from_mcpo_subservers():
|
||||
with respx.mock:
|
||||
respx.get("http://tools.local/openapi.json").mock(
|
||||
return_value=Response(200, json=MCPO_ROOT_SPEC)
|
||||
)
|
||||
respx.get("http://tools.local/github/openapi.json").mock(
|
||||
return_value=Response(200, json=SIMPLE_SPEC)
|
||||
)
|
||||
respx.get("http://tools.local/memory/openapi.json").mock(
|
||||
return_value=Response(200, json=SIMPLE_SPEC)
|
||||
)
|
||||
client = ToolClient("http://tools.local")
|
||||
tools = await client.get_tools()
|
||||
|
||||
tool_names = {tool["function"]["name"] for tool in tools}
|
||||
assert "github__list_items" in tool_names
|
||||
assert "memory__list_items" in tool_names
|
||||
assert len(tools) == 6
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_client_call_mcpo_namespaced_tool():
|
||||
with respx.mock:
|
||||
respx.get("http://tools.local/openapi.json").mock(
|
||||
return_value=Response(200, json=MCPO_ROOT_SPEC)
|
||||
)
|
||||
respx.get("http://tools.local/github/openapi.json").mock(
|
||||
return_value=Response(200, json=SIMPLE_SPEC)
|
||||
)
|
||||
respx.get("http://tools.local/memory/openapi.json").mock(
|
||||
return_value=Response(200, json=SIMPLE_SPEC)
|
||||
)
|
||||
respx.get("http://tools.local/github/items").mock(
|
||||
return_value=Response(200, json=[{"id": 1}])
|
||||
)
|
||||
client = ToolClient("http://tools.local")
|
||||
result = await client.call("github__list_items", {"limit": 10})
|
||||
|
||||
assert "id" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_client_call_get_with_query():
|
||||
with respx.mock:
|
||||
|
||||
Reference in New Issue
Block a user