diff --git a/condor/acp/pydantic_ai_client.py b/condor/acp/pydantic_ai_client.py index bdd09937..a2697f5c 100644 --- a/condor/acp/pydantic_ai_client.py +++ b/condor/acp/pydantic_ai_client.py @@ -701,17 +701,20 @@ async def start(self) -> None: toolsets = [] for srv_config in self.mcp_server_configs: command = srv_config["command"] - args = srv_config.get("args", []) + # StdioServerParameters requires list[str]; YAML/config may yield ints + # (e.g. numeric hummingbot passwords) that only surface with pydantic-ai + # backends (lmstudio:/ollama:/openrouter:), not ACP agents. + args = [str(a) for a in srv_config.get("args", [])] # Inherit the parent process env (same as ACPClient) so cloud keys # loaded via dotenv — e.g. OPENROUTER_API_KEY — reach MCP tools like # get_available_models. extra_env / per-server env overlay on top. - env = dict(os.environ) + env = {k: str(v) for k, v in os.environ.items()} if self.extra_env: - env.update(self.extra_env) + env.update({k: str(v) for k, v in self.extra_env.items()}) for env_entry in srv_config.get("env", []): if isinstance(env_entry, dict): - env[env_entry["name"]] = env_entry["value"] + env[str(env_entry["name"])] = str(env_entry["value"]) mcp_server = MCPServerStdio( command, diff --git a/handlers/agents/_shared.py b/handlers/agents/_shared.py index fa93c135..5fbddc89 100644 --- a/handlers/agents/_shared.py +++ b/handlers/agents/_shared.py @@ -232,14 +232,38 @@ def _condor_mcp_args( os.environ.get("TELEGRAM_TOKEN", ""), ] if agent_slug: - args.extend(["--agent-slug", agent_slug]) + args.extend(["--agent-slug", str(agent_slug)]) if server_name: - args.extend(["--server-name", server_name]) + args.extend(["--server-name", str(server_name)]) if delegate_worker: args.append("--delegate-worker") return args +def _hummingbot_mcp_args(server: dict[str, Any], server_name: str) -> list[str]: + """Build CLI args for the hummingbot MCP subprocess. + + Username/password must be strings — YAML loads unquoted numerics as int + (e.g. ``password: 123``), and pydantic-ai's StdioServerParameters rejects + non-string args when starting LM Studio / other local-model sessions. + """ + api_url = f"http://{server['host']}:{server['port']}" + return [ + "run", + "python", + "-m", + "mcp_servers.hummingbot_api", + "--url", + api_url, + "--username", + str(server["username"]), + "--password", + str(server["password"]), + "--server-name", + str(server_name), + ] + + def build_mcp_servers_for_session( user_id: int, chat_id: int | str, @@ -314,25 +338,10 @@ def build_mcp_servers_for_session( ) return [condor] - api_url = f"http://{server['host']}:{server['port']}" - mcp_hummingbot = { "name": "mcp-hummingbot", "command": "uv", - "args": [ - "run", - "python", - "-m", - "mcp_servers.hummingbot_api", - "--url", - api_url, - "--username", - server["username"], - "--password", - server["password"], - "--server-name", - server_name, - ], + "args": _hummingbot_mcp_args(server, server_name), "env": [], } diff --git a/tests/test_agents.py b/tests/test_agents.py index c9d667bc..cb8764b8 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -721,3 +721,34 @@ def get_server(self, name): servers = build_mcp_servers_for_session(42, 42) condor = next(s for s in servers if s["name"] == "condor") assert "--agent-slug" not in condor["args"] + + +def test_hummingbot_mcp_args_stringify_numeric_credentials(monkeypatch): + """YAML ``password: 123`` loads as int; StdioServerParameters requires str.""" + import config_manager + + from handlers.agents._shared import build_mcp_servers_for_session + + class _NumericPasswordServer: + def get_accessible_servers(self, user_id): + return ["local"] + + def get_server(self, name): + return { + "host": "localhost", + "port": 8000, + "username": 999, + "password": 123, + } + + monkeypatch.setattr( + config_manager, "get_config_manager", lambda: _NumericPasswordServer() + ) + monkeypatch.setattr(config_manager, "get_effective_server", lambda *a, **k: "local") + + servers = build_mcp_servers_for_session(42, 42) + hb = next(s for s in servers if s["name"] == "mcp-hummingbot") + args = hb["args"] + assert all(isinstance(a, str) for a in args) + assert args[args.index("--password") + 1] == "123" + assert args[args.index("--username") + 1] == "999"