mcp_server_local.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. """
  2. mcp_server_local.py — 本地小智服务端直连 MCP Server
  3. ===================================================
  4. 运行一个 WebSocket MCP 服务器,本地 xiaozhi 服务端直接连接(不走云端 xz-mcp-broker)。
  5. 启动后,把地址 ws://192.168.31.155:8765 填到智控台的 MCP 接入点即可。
  6. """
  7. from __future__ import annotations
  8. import asyncio
  9. import json
  10. import logging
  11. import os
  12. import sys
  13. import websockets
  14. from websockets.asyncio.server import serve
  15. import audio_server
  16. logger = logging.getLogger("MCP_LOCAL")
  17. async def handle_ws(ws):
  18. """处理一个 xiaozhi MCP 客户端连接"""
  19. logger.info("xiaozhi 客户端已连接")
  20. try:
  21. async for raw in ws:
  22. if isinstance(raw, bytes):
  23. raw = raw.decode("utf-8", errors="replace")
  24. logger.info("<- %s", raw[:200])
  25. try:
  26. req = json.loads(raw)
  27. except Exception as e:
  28. logger.warning("parse error: %s", e)
  29. continue
  30. rid = req.get("id")
  31. method = req.get("method")
  32. if rid is None and method and method.startswith("notifications/"):
  33. continue
  34. resp = audio_server.handle_request(req)
  35. if resp is None:
  36. continue
  37. line = json.dumps(resp, ensure_ascii=False, separators=(",", ":")) + "\n"
  38. logger.info("-> %s", line[:200])
  39. await ws.send(line)
  40. except websockets.ConnectionClosed:
  41. logger.info("客户端断开")
  42. async def main():
  43. port = int(os.environ.get("MCP_LOCAL_PORT", "8765"))
  44. logging.basicConfig(level="INFO", format="%(asctime)s - %(levelname)s - %(message)s")
  45. logger.info(f"MCP Local Server 启动在 ws://0.0.0.0:{port}")
  46. logger.info(f"API base = {audio_server.API_BASE}")
  47. async with serve(handle_ws, "0.0.0.0", port):
  48. await asyncio.Future()
  49. if __name__ == "__main__":
  50. asyncio.run(main())