2026-10-30 实测:Codex bash 里有 SEARXNG_URL + proxy, MCP 链却只有 duckduckgo、DNS 瞬间失败——Codex 的 MCP 子进程不继承 Codex 进程 env;给租户 config 加 [mcp_servers.websearch.env] 后同一任务立刻返回真实 SearXNG 结果。修法:worker 在 _docker_task_context 算出网关后、容器启动前,用 ensure_websearch_mcp_env 把 proxy + 搜索 env 写进租户 config(网关此时才已知, 播种时写不死;写失败只记日志不阻塞)。 测试:新增 test_websearch_mcp_env 4 项,相关共 33 项全过。
72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
"""ensure_websearch_mcp_env: MCP env lives in config.toml, because
|
|
Codex's MCP subprocess does not inherit the Codex process env
|
|
(2026-10-30: Codex bash had SEARXNG_URL + proxy, MCP chain was
|
|
still ["duckduckgo"] only; adding the config env section made
|
|
the same task return real SearXNG results)."""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
from cloud.websearch import ensure_websearch_mcp_env # noqa: E402
|
|
|
|
|
|
BASE = ("model = \"gpt-6.1-sol\"\n\n"
|
|
"[mcp_servers.websearch]\n"
|
|
"command = \"python3\"\n"
|
|
"args = [\"/var/lib/codex-app-server/codex-home/"
|
|
"websearch_mcp.py\"]\n")
|
|
|
|
|
|
class TestMcpEnv(unittest.TestCase):
|
|
def _home(self, text):
|
|
tmp = tempfile.mkdtemp()
|
|
with open(os.path.join(tmp, "config.toml"), "w") as fh:
|
|
fh.write(text)
|
|
return tmp
|
|
|
|
def _read(self, home):
|
|
with open(os.path.join(home, "config.toml")) as fh:
|
|
return fh.read()
|
|
|
|
def test_insert_env_after_websearch_section(self):
|
|
home = self._home(BASE)
|
|
self.assertTrue(ensure_websearch_mcp_env(
|
|
home, {"SEARXNG_URL": "http://searx.test",
|
|
"http_proxy": "http://10.0.0.1:8888"}))
|
|
text = self._read(home)
|
|
self.assertIn("[mcp_servers.websearch.env]", text)
|
|
self.assertIn('SEARXNG_URL = "http://searx.test"', text)
|
|
self.assertIn('http_proxy = "http://10.0.0.1:8888"', text)
|
|
self.assertIn('command = "python3"', text)
|
|
|
|
def test_replace_existing_env_preserves_other_sections(self):
|
|
home = self._home(BASE + "[mcp_servers.websearch.env]\n"
|
|
"SEARXNG_URL = \"http://old.test\"\n\n"
|
|
"[other]\nfoo = 1\n")
|
|
self.assertTrue(ensure_websearch_mcp_env(
|
|
home, {"SEARXNG_URL": "http://new.test"}))
|
|
text = self._read(home)
|
|
self.assertIn('SEARXNG_URL = "http://new.test"', text)
|
|
self.assertNotIn("old.test", text)
|
|
self.assertIn("[other]\nfoo = 1", text)
|
|
|
|
def test_empty_env_writes_nothing(self):
|
|
home = self._home(BASE)
|
|
self.assertFalse(ensure_websearch_mcp_env(home, {}))
|
|
self.assertEqual(self._read(home), BASE)
|
|
|
|
def test_idempotent_second_write(self):
|
|
home = self._home(BASE)
|
|
env = {"SEARXNG_URL": "http://searx.test"}
|
|
self.assertTrue(ensure_websearch_mcp_env(home, env))
|
|
self.assertFalse(ensure_websearch_mcp_env(home, env))
|
|
self.assertEqual(self._read(home).count(
|
|
"[mcp_servers.websearch.env]"), 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|