mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-19 19:16:17 +00:00
89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
"""Capture real HTTP requests through DeerFlow's MCP discovery and tool calls."""
|
|
|
|
import asyncio
|
|
import json
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
from threading import Thread
|
|
|
|
import pytest
|
|
|
|
from deerflow.mcp.tools import get_mcp_tools
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("authenticated", [False, True])
|
|
async def test_parallel_example_user_agent_reaches_tool_requests(tmp_path, monkeypatch, authenticated):
|
|
captured = []
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
def do_POST(self):
|
|
request = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
|
captured.append((self.path, request, dict(self.headers)))
|
|
if "id" not in request:
|
|
self.send_response(202)
|
|
self.end_headers()
|
|
return
|
|
method = request["method"]
|
|
if method == "initialize":
|
|
result = {"protocolVersion": request["params"]["protocolVersion"], "capabilities": {"tools": {}}, "serverInfo": {"name": "capture", "version": "1.0"}}
|
|
elif method == "tools/list":
|
|
result = {"tools": [{"name": name, "description": name, "inputSchema": {"type": "object", "properties": {}}} for name in ("web_search", "web_fetch")]}
|
|
else:
|
|
assert method == "tools/call"
|
|
result = {"content": [{"type": "text", "text": request["params"]["name"]}]}
|
|
body = json.dumps({"jsonrpc": "2.0", "id": request["id"], "result": result}).encode()
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
thread = Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
example = json.loads((Path(__file__).parents[2] / "extensions_config.example.json").read_text())
|
|
parallel = example["mcpServers"]["parallel-search"]
|
|
parallel["enabled"] = True
|
|
parallel["url"] = f"http://127.0.0.1:{server.server_port}/parallel"
|
|
if authenticated:
|
|
monkeypatch.setenv("PARALLEL_AUTHORIZATION", "Bearer test-only")
|
|
parallel.setdefault("headers", {}).update({"Authorization": "$PARALLEL_AUTHORIZATION", "X-Caller": "test-caller"})
|
|
config = {"mcpServers": {"parallel-search": parallel, "other": {"type": "http", "url": f"http://127.0.0.1:{server.server_port}/other", "headers": {"User-Agent": "other-project/1.0", "X-Caller": "other-caller"}}}}
|
|
config_path = tmp_path / "extensions_config.json"
|
|
config_path.write_text(json.dumps(config))
|
|
monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(config_path))
|
|
|
|
tools = {tool.name: tool for tool in await get_mcp_tools()}
|
|
for name in ("parallel-search_web_search", "parallel-search_web_fetch", "parallel-search_web_search", "other_web_search"):
|
|
assert await tools[name].ainvoke({})
|
|
|
|
parallel_requests = [(request, headers) for path, request, headers in captured if path == "/parallel"]
|
|
assert {request["method"] for request, _ in parallel_requests} >= {"initialize", "tools/list", "tools/call"}
|
|
# HTTP tools create a fresh session per call: attribution must survive
|
|
# discovery, reinitialization, and subsequent Search/Fetch requests.
|
|
assert [request["params"]["name"] for request, _ in parallel_requests if request["method"] == "tools/call"] == ["web_search", "web_fetch", "web_search"]
|
|
for _, headers in parallel_requests:
|
|
headers = {name.lower(): value for name, value in headers.items()}
|
|
assert headers["user-agent"] == "deer-flow"
|
|
if authenticated:
|
|
assert headers["authorization"] == "Bearer test-only"
|
|
assert headers["x-caller"] == "test-caller"
|
|
else:
|
|
assert "authorization" not in headers
|
|
other_requests = [headers for path, _, headers in captured if path == "/other"]
|
|
assert other_requests
|
|
for headers in other_requests:
|
|
headers = {name.lower(): value for name, value in headers.items()}
|
|
assert headers["user-agent"] == "other-project/1.0"
|
|
assert headers["x-caller"] == "other-caller"
|
|
assert "authorization" not in headers
|
|
finally:
|
|
await asyncio.to_thread(server.shutdown)
|
|
server.server_close()
|
|
thread.join()
|