mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-25 05:56:18 +00:00
* fix(mcp): scope sessions and task access by thread incarnation * fix(mcp): preserve thread incarnation in delegated subagents * fix(mcp): preserve incarnation in durable batches * fix(mcp): bind standalone graph lifecycle context * fix(studio): preserve implicit thread creation metadata --------- Co-authored-by: CorgiBoyG <CorgiBoyG@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
"""Configured stdio working directories must reach discovery and pooled calls."""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from deerflow.config.paths import Paths
|
|
from deerflow.mcp import tools as mcp_tools
|
|
from deerflow.mcp.session_pool import MCPSessionPool
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("thread_incarnation", [None, "incarnation-1"], ids=["legacy", "versioned"])
|
|
@pytest.mark.parametrize("relative_script", [True, False], ids=["relative-entrypoint", "relative-tool-input"])
|
|
async def test_stdio_cwd_from_config_reaches_discovery_and_tool_calls(tmp_path, monkeypatch, relative_script, thread_incarnation):
|
|
server_dir = tmp_path / "mcp server"
|
|
server_dir.mkdir()
|
|
(server_dir / "marker.txt").write_text("configured-directory", encoding="utf-8")
|
|
server_path = server_dir / "server.py"
|
|
server_path.write_text(
|
|
"""
|
|
from pathlib import Path
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
mcp = FastMCP("cwd-test")
|
|
|
|
@mcp.tool()
|
|
def read_marker() -> str:
|
|
return Path("marker.txt").read_text(encoding="utf-8")
|
|
|
|
mcp.run(transport="stdio")
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
config_path = tmp_path / "extensions_config.json"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"mcpServers": {
|
|
"local": {
|
|
"command": sys.executable,
|
|
"args": ["server.py" if relative_script else str(server_path)],
|
|
"cwd": "$TEST_MCP_CWD",
|
|
}
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(config_path))
|
|
monkeypatch.setenv("TEST_MCP_CWD", str(server_dir))
|
|
monkeypatch.setattr(mcp_tools, "get_paths", lambda: Paths(tmp_path / "deerflow"))
|
|
pool = MCPSessionPool()
|
|
monkeypatch.setattr(mcp_tools, "get_session_pool", lambda: pool)
|
|
runtime = SimpleNamespace(context={"thread_id": "thread", "user_id": "user", "thread_incarnation": thread_incarnation}, config={})
|
|
|
|
try:
|
|
tools = await mcp_tools.get_mcp_tools()
|
|
assert [tool.name for tool in tools] == ["local_read_marker"]
|
|
content, _artifact = await tools[0].coroutine(runtime=runtime)
|
|
assert content[0]["text"] == "configured-directory"
|
|
finally:
|
|
await pool.close_all()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("thread_incarnation", [None, "incarnation-1"], ids=["legacy", "versioned"])
|
|
@pytest.mark.parametrize(
|
|
"cwd_config",
|
|
[{}, {"cwd": None}, {"cwd": ""}, {"cwd": "$TEST_UNSET_MCP_CWD"}],
|
|
ids=["omitted", "null", "empty", "unset-env"],
|
|
)
|
|
async def test_empty_stdio_cwd_preserves_default_working_directories(tmp_path, monkeypatch, cwd_config, thread_incarnation):
|
|
launch_dir = tmp_path / "gateway"
|
|
launch_dir.mkdir()
|
|
monkeypatch.chdir(launch_dir)
|
|
monkeypatch.delenv("TEST_UNSET_MCP_CWD", raising=False)
|
|
server_path = tmp_path / "cwd_server.py"
|
|
server_path.write_text(
|
|
"""
|
|
from pathlib import Path
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
mcp = FastMCP("cwd-defaults-test")
|
|
|
|
@mcp.tool(description=str(Path.cwd()))
|
|
def read_cwd() -> str:
|
|
return str(Path.cwd())
|
|
|
|
mcp.run(transport="stdio")
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
config_path = tmp_path / "extensions_config.json"
|
|
config_path.write_text(
|
|
json.dumps({"mcpServers": {"local": {"command": sys.executable, "args": [str(server_path)], **cwd_config}}}),
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(config_path))
|
|
paths = Paths(tmp_path / "deerflow")
|
|
monkeypatch.setattr(mcp_tools, "get_paths", lambda: paths)
|
|
pool = MCPSessionPool()
|
|
monkeypatch.setattr(mcp_tools, "get_session_pool", lambda: pool)
|
|
runtime = SimpleNamespace(context={"thread_id": "thread", "user_id": "user", "thread_incarnation": thread_incarnation}, config={})
|
|
|
|
try:
|
|
tools = await mcp_tools.get_mcp_tools()
|
|
assert [tool.name for tool in tools] == ["local_read_cwd"]
|
|
assert Path(tools[0].description) == launch_dir.resolve()
|
|
content, _artifact = await tools[0].coroutine(runtime=runtime)
|
|
assert Path(content[0]["text"]) == paths.sandbox_work_dir("thread", user_id="user").resolve()
|
|
finally:
|
|
await pool.close_all()
|