""" mcp_pipe.py — 小智AI MCP 接入点桥接(v8 in-process) 不用 subprocess。直接 import audio_server 的工具, 在主进程里用 websockets.sync 跟 xiaozhi.me 通信。 完全绕开 Windows + Python 3.14 的 stdio pipe 怪行为。 """ from __future__ import annotations import json import logging import os import sys import time import websockets.sync.client as ws_sync import audio_server logger = logging.getLogger("MCP_PIPE") HERE = os.path.dirname(os.path.abspath(__file__)) RECONNECT_DELAY = 5 # 断开后等 N 秒重连 def run_session(endpoint: str, raw_log) -> bool: """单次 WS 会话,返回 True=正常退出(KeyboardInterrupt),False=断线需要重连""" try: ws = ws_sync.connect(endpoint, max_size=4 * 1024 * 1024) except Exception as e: logger.warning("WS connect failed: %s, will retry in %ds", e, RECONNECT_DELAY) return False logger.info("WS connected") logger.info("READY: ready to receive xiaozhi MCP requests") try: while True: try: raw = ws.recv(timeout=300) except TimeoutError: logger.info("WS recv timeout 5min, reconnecting...") return False except Exception as e: logger.warning("WS recv failed: %s", e) return False if not raw: logger.warning("WS recv empty (EOF)") return False if isinstance(raw, bytes): raw = raw.decode("utf-8", errors="replace") logger.info("RAW: %s", raw[:300]) raw_log.write(f"[<-] {raw[:300]}\n") raw_log.flush() try: req = json.loads(raw) except Exception as e: logger.warning("parse err: %s", e) continue method = req.get("method") rid = req.get("id") logger.info("<- method=%s id=%s", method, rid) if rid is None and method and method.startswith("notifications/"): logger.info(" (notification, no response)") continue resp = audio_server.handle_request(req) if resp is None: continue line = (json.dumps(resp, ensure_ascii=False, separators=(",", ":")) + "\n").encode("utf-8") logger.info("-> response for %s id=%s (%d bytes)", method, rid, len(line)) raw_log.write(f"[->] {line.decode('utf-8', errors='replace')[:300]}\n") raw_log.flush() try: ws.send(line) except Exception as e: logger.warning("WS send failed (connection likely closed): %s", e) return False except KeyboardInterrupt: return True except Exception as e: logger.warning("Unexpected error in session: %s", e) return False finally: try: ws.close() except Exception: pass return False def main() -> int: for k in ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY"): os.environ.pop(k, None) endpoint = os.environ.get("MCP_ENDPOINT") if not endpoint: print("ERROR: MCP_ENDPOINT 必填") return 2 logging.basicConfig( level=os.environ.get("MCP_LOG_LEVEL", "INFO"), format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) raw_log_path = os.path.join(HERE, "mcp-pipe-utf8.log") raw_log = open(raw_log_path, "a", encoding="utf-8") raw_log.write(f"=== mcp_pipe started (auto-reconnect) ===\n") raw_log.flush() logger.info("AudioBook MCP server connecting to %s", endpoint) logger.info("API base = %s", audio_server.API_BASE) while True: should_exit = run_session(endpoint, raw_log) if should_exit: logger.info("KeyboardInterrupt, exiting") break logger.info("Disconnected, reconnecting in %ds...", RECONNECT_DELAY) time.sleep(RECONNECT_DELAY) return 0 if __name__ == "__main__": sys.exit(main())