Files
MoviePilot/tests/test_agent_mcp.py
2026-07-08 08:44:33 +08:00

144 lines
4.6 KiB
Python

import sys
import textwrap
import pytest
from app.agent.mcp import AgentMcpManager, AgentMcpToolSpec
from app.agent.tools.impl.mcp import McpExternalTool
from app.schemas.agent import AgentMcpServerConfig
def _write_stdio_mcp_server(tmp_path):
"""写入一个用于测试的最小 stdio MCP 服务。"""
server_path = tmp_path / "stdio_mcp_server.py"
server_path.write_text(
textwrap.dedent(
"""
import json
import sys
TOOLS = [
{
"name": "echo",
"description": "Echo input text.",
"inputSchema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text to echo"}
},
"required": ["text"],
},
}
]
for line in sys.stdin:
request = json.loads(line)
request_id = request.get("id")
method = request.get("method")
if request_id is None:
continue
if method == "initialize":
result = {
"protocolVersion": request["params"]["protocolVersion"],
"capabilities": {"tools": {}},
"serverInfo": {"name": "Fake MCP", "version": "1.0.0"},
}
elif method == "tools/list":
result = {"tools": TOOLS}
elif method == "tools/call":
args = request.get("params", {}).get("arguments", {})
result = {"content": [{"type": "text", "text": args.get("text", "")}]}
else:
result = {}
print(json.dumps({"jsonrpc": "2.0", "id": request_id, "result": result}), flush=True)
"""
),
encoding="utf-8",
)
return server_path
@pytest.mark.anyio
async def test_stdio_mcp_server_lists_tools(tmp_path):
"""stdio MCP 服务器应能被初始化并读取工具列表。"""
server_path = _write_stdio_mcp_server(tmp_path)
manager = AgentMcpManager()
server = AgentMcpServerConfig(
id="fake",
name="Fake MCP",
transport="stdio",
command=sys.executable,
args=[str(server_path)],
timeout=5,
)
tools = await manager.list_server_tools(server)
assert len(tools) == 1
assert tools[0].name == "echo"
assert tools[0].agent_tool_name == "mcp_fake_mcp_echo"
assert tools[0].input_schema["properties"]["text"]["type"] == "string"
@pytest.mark.anyio
async def test_stdio_mcp_server_calls_tool(tmp_path):
"""stdio MCP 工具应能通过 tools/call 返回内容。"""
server_path = _write_stdio_mcp_server(tmp_path)
manager = AgentMcpManager()
server = AgentMcpServerConfig(
id="fake",
name="Fake MCP",
transport="stdio",
command=sys.executable,
args=[str(server_path)],
timeout=5,
)
result = await manager.call_server_tool(server, "echo", {"text": "hello"})
assert result == {"content": [{"type": "text", "text": "hello"}]}
def test_normalize_server_generates_runtime_defaults():
"""MCP 配置规范化应补齐默认值并清理空字段。"""
manager = AgentMcpManager()
server = manager.normalize_server(
{
"id": "demo",
"name": "Demo",
"transport": "http",
"url": " https://example.com/mcp ",
"headers": {" Authorization ": "Bearer token", "": "ignored"},
"timeout": "bad",
}
)
assert server.id == "demo"
assert server.url == "https://example.com/mcp"
assert server.headers == {"Authorization": "Bearer token"}
assert server.timeout == 30
assert server.require_admin is True
def test_mcp_external_tool_uses_discovered_schema():
"""外部 MCP 工具应保留发现到的 JSON Schema。"""
server = AgentMcpServerConfig(id="fake", name="Fake MCP")
spec = AgentMcpToolSpec(
server=server,
name="echo",
agent_tool_name="mcp_fake_echo",
description="Echo input text.",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
)
tool = McpExternalTool(spec=spec, session_id="session-1", user_id="10001")
assert tool.name == "mcp_fake_echo"
assert tool.args_schema["properties"]["text"]["type"] == "string"
assert tool.require_admin is True