From 68d31bf58519ccece68ec225b628b8473af669c4 Mon Sep 17 00:00:00 2001 From: XXXxx7258 <3103908461@qq.com> Date: Sat, 7 Mar 2026 14:21:23 +0800 Subject: [PATCH 1/2] fix: preserve PATHEXT for stdio mcp servers on windows --- astrbot/core/agent/mcp_client.py | 19 ++++++++++ tests/unit/test_mcp_client.py | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tests/unit/test_mcp_client.py diff --git a/astrbot/core/agent/mcp_client.py b/astrbot/core/agent/mcp_client.py index 18f4d47e04..ac19a06826 100644 --- a/astrbot/core/agent/mcp_client.py +++ b/astrbot/core/agent/mcp_client.py @@ -1,5 +1,7 @@ import asyncio import logging +import os +import sys from contextlib import AsyncExitStack from datetime import timedelta from typing import Generic @@ -45,6 +47,22 @@ def _prepare_config(config: dict) -> dict: return config +def _prepare_stdio_env(config: dict) -> dict: + """Preserve Windows executable resolution for stdio subprocesses.""" + if sys.platform != "win32": + return config + + pathext = os.environ.get("PATHEXT") + if not pathext: + return config + + prepared = config.copy() + env = dict(prepared.get("env") or {}) + env.setdefault("PATHEXT", pathext) + prepared["env"] = env + return prepared + + async def _quick_test_mcp_connection(config: dict) -> tuple[bool, str]: """Quick test MCP server connectivity""" import aiohttp @@ -210,6 +228,7 @@ def logging_callback(msg: str) -> None: ) else: + cfg = _prepare_stdio_env(cfg) server_params = mcp.StdioServerParameters( **cfg, ) diff --git a/tests/unit/test_mcp_client.py b/tests/unit/test_mcp_client.py new file mode 100644 index 0000000000..d2e17d85a7 --- /dev/null +++ b/tests/unit/test_mcp_client.py @@ -0,0 +1,64 @@ +import pytest + +from astrbot.core.agent import mcp_client + + +def test_prepare_stdio_env_adds_pathext_on_windows(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(mcp_client.sys, "platform", "win32") + monkeypatch.setenv("PATHEXT", ".COM;.EXE") + + cfg = {"command": "uvx", "args": ["mcp-server-fetch"]} + + prepared = mcp_client._prepare_stdio_env(cfg) + + assert prepared["env"]["PATHEXT"] == ".COM;.EXE" + + +def test_prepare_stdio_env_merges_existing_env_on_windows( + monkeypatch: pytest.MonkeyPatch, +): + monkeypatch.setattr(mcp_client.sys, "platform", "win32") + monkeypatch.setenv("PATHEXT", ".COM;.EXE") + + cfg = { + "command": "uvx", + "args": ["mcp-server-fetch"], + "env": {"HTTP_PROXY": "http://127.0.0.1:7890"}, + } + + prepared = mcp_client._prepare_stdio_env(cfg) + + assert prepared["env"] == { + "HTTP_PROXY": "http://127.0.0.1:7890", + "PATHEXT": ".COM;.EXE", + } + + +def test_prepare_stdio_env_preserves_user_defined_pathext( + monkeypatch: pytest.MonkeyPatch, +): + monkeypatch.setattr(mcp_client.sys, "platform", "win32") + monkeypatch.setenv("PATHEXT", ".COM;.EXE") + + cfg = { + "command": "uvx", + "args": ["mcp-server-fetch"], + "env": {"PATHEXT": ".BAT"}, + } + + prepared = mcp_client._prepare_stdio_env(cfg) + + assert prepared["env"]["PATHEXT"] == ".BAT" + + +def test_prepare_stdio_env_does_not_modify_non_windows( + monkeypatch: pytest.MonkeyPatch, +): + monkeypatch.setattr(mcp_client.sys, "platform", "linux") + monkeypatch.setenv("PATHEXT", ".COM;.EXE") + + cfg = {"command": "uvx", "args": ["mcp-server-fetch"]} + + prepared = mcp_client._prepare_stdio_env(cfg) + + assert "env" not in prepared From fb6fb7b409b6f72658c3c818f2e5ae1110542bf4 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 21 Mar 2026 00:26:35 +0800 Subject: [PATCH 2/2] chore: delete test_mcp_client.py --- tests/unit/test_mcp_client.py | 64 ----------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 tests/unit/test_mcp_client.py diff --git a/tests/unit/test_mcp_client.py b/tests/unit/test_mcp_client.py deleted file mode 100644 index d2e17d85a7..0000000000 --- a/tests/unit/test_mcp_client.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest - -from astrbot.core.agent import mcp_client - - -def test_prepare_stdio_env_adds_pathext_on_windows(monkeypatch: pytest.MonkeyPatch): - monkeypatch.setattr(mcp_client.sys, "platform", "win32") - monkeypatch.setenv("PATHEXT", ".COM;.EXE") - - cfg = {"command": "uvx", "args": ["mcp-server-fetch"]} - - prepared = mcp_client._prepare_stdio_env(cfg) - - assert prepared["env"]["PATHEXT"] == ".COM;.EXE" - - -def test_prepare_stdio_env_merges_existing_env_on_windows( - monkeypatch: pytest.MonkeyPatch, -): - monkeypatch.setattr(mcp_client.sys, "platform", "win32") - monkeypatch.setenv("PATHEXT", ".COM;.EXE") - - cfg = { - "command": "uvx", - "args": ["mcp-server-fetch"], - "env": {"HTTP_PROXY": "http://127.0.0.1:7890"}, - } - - prepared = mcp_client._prepare_stdio_env(cfg) - - assert prepared["env"] == { - "HTTP_PROXY": "http://127.0.0.1:7890", - "PATHEXT": ".COM;.EXE", - } - - -def test_prepare_stdio_env_preserves_user_defined_pathext( - monkeypatch: pytest.MonkeyPatch, -): - monkeypatch.setattr(mcp_client.sys, "platform", "win32") - monkeypatch.setenv("PATHEXT", ".COM;.EXE") - - cfg = { - "command": "uvx", - "args": ["mcp-server-fetch"], - "env": {"PATHEXT": ".BAT"}, - } - - prepared = mcp_client._prepare_stdio_env(cfg) - - assert prepared["env"]["PATHEXT"] == ".BAT" - - -def test_prepare_stdio_env_does_not_modify_non_windows( - monkeypatch: pytest.MonkeyPatch, -): - monkeypatch.setattr(mcp_client.sys, "platform", "linux") - monkeypatch.setenv("PATHEXT", ".COM;.EXE") - - cfg = {"command": "uvx", "args": ["mcp-server-fetch"]} - - prepared = mcp_client._prepare_stdio_env(cfg) - - assert "env" not in prepared