Merge verified Odysseus fixes

This commit is contained in:
pewdiepie-archdaemon
2026-07-23 14:49:02 +00:00
parent 4c9a8ca115
commit d8a2059df8
117 changed files with 15693 additions and 3191 deletions
+61 -15
View File
@@ -87,6 +87,7 @@ _BUILTIN_NPX_SERVERS = {
# Global flag to disable MCP if there are compatibility issues
MCP_DISABLED = os.environ.get("ODYSSEUS_DISABLE_MCP", "").lower() in ("1", "true", "yes")
BROWSER_MCP_REQUIRE_CACHE = os.environ.get("ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE", "").lower() in ("1", "true", "yes")
# Strong references to the fire-and-forget startup tasks scheduled below.
@@ -103,6 +104,46 @@ def _spawn_bg(coro) -> asyncio.Task:
task.add_done_callback(_BG_TASKS.discard)
return task
def _find_browser_executable() -> str:
"""Find a browser binary for the built-in Playwright MCP server.
Docker images ship Debian's `chromium`; desktop installs may already have
Chrome/Chromium in a conventional location. If nothing is found, return an
empty string and let Playwright MCP use its own default browser/channel.
"""
configured = os.environ.get("ODYSSEUS_BROWSER_EXECUTABLE", "").strip()
if configured:
return configured
for name in ("google-chrome", "chromium", "chromium-browser"):
path = shutil.which(name)
if path:
return path
for candidate in (
"/opt/google/chrome/chrome",
"/usr/bin/google-chrome",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
):
if os.path.isfile(candidate):
return candidate
return ""
def _browser_mcp_args(args: list[str]) -> list[str]:
"""Return Playwright MCP args with a concrete browser executable when found."""
out = list(args or [])
if "--executable-path" not in out:
browser = _find_browser_executable()
if browser:
out.extend(["--executable-path", browser])
if os.environ.get("ODYSSEUS_BROWSER_ISOLATED", "1").lower() not in ("0", "false", "no"):
if "--isolated" not in out and "--user-data-dir" not in out:
out.append("--isolated")
if os.environ.get("ODYSSEUS_BROWSER_NO_SANDBOX", "1").lower() not in ("0", "false", "no"):
if "--no-sandbox" not in out and "--sandbox" not in out:
out.append("--no-sandbox")
return out
def builtin_python_env(base_dir: str) -> dict[str, str]:
"""Environment for built-in Python MCP subprocesses.
@@ -162,39 +203,44 @@ async def register_builtin_servers(mcp_manager):
async def _start_npx_servers():
await asyncio.sleep(3) # let Python servers finish first
for server_id, cfg in _BUILTIN_NPX_SERVERS.items():
# Skip the server if its npx package isn't cached. Without this
# check, npx would try to download/install the package on first
# use, which can take minutes (or hang) on fresh installs without
# Playwright system deps. Wrapping that in asyncio.wait_for to
# bound the wait sounds reasonable, but mcp.client.stdio uses an
# internal anyio task group that can't survive the resulting
# cross-task cancellation: it raises "Attempted to exit cancel
# scope in a different task than it was entered in" in a sibling
# task, which cascades cancellations into the rest of the event
# loop and downs the app. Detecting installed-state up-front lets
# us bail with a useful warning before we ever touch stdio_client.
args = cfg["args"]
# Browser automation is a shipped built-in, so the default path
# lets `npx -y` install @playwright/mcp on first start. Locked-down
# installs can opt back into the old no-network startup behavior
# with ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE=1.
args = _browser_mcp_args(cfg["args"]) if server_id == "builtin_browser" else list(cfg["args"])
pkg_spec = _npx_package_from_args(args)
if pkg_spec and not await _is_npx_package_cached(npx_path, pkg_spec):
if BROWSER_MCP_REQUIRE_CACHE and pkg_spec and not await _is_npx_package_cached(npx_path, pkg_spec):
logger.warning(
f"{cfg['name']} is not available.\n"
f" Reason: npm package {pkg_spec!r} is not installed in the npx cache.\n"
f" Impact: tools provided by this MCP server will be unavailable.\n"
f" Fix: {os.path.basename(npx_path)} -y {pkg_spec} --version\n"
f" (run once, then restart Odysseus)\n"
f" Notes: this server is optional; see README.md "
f"'Built-in MCP servers' for details."
f" Notes: ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE=1 is set, "
f"so Odysseus will not install browser automation on startup."
)
continue
logger.info(f"Starting NPX server: {cfg['name']} ({npx_path} {' '.join(args)})")
try:
env = None
if server_id == "builtin_browser":
cache_home = os.environ.get(
"ODYSSEUS_BROWSER_MCP_CACHE",
os.path.join(base_dir, "data", "local", "playwright-mcp-cache"),
)
os.makedirs(cache_home, exist_ok=True)
env = {
"XDG_CACHE_HOME": cache_home,
"PLAYWRIGHT_BROWSERS_PATH": os.path.join(cache_home, "browsers"),
}
ok = await mcp_manager.connect_server(
server_id=server_id,
name=cfg["name"],
transport="stdio",
command=npx_path,
args=args,
env=env,
)
if ok:
logger.info(f"Built-in NPX server registered: {cfg['name']}")