Find
Search dashboard pages and product views.
Search dashboard pages and product views.
huangjunsen0406/py-xiaozhi
Add live web search/retrieval to this repository so its agent can ground its answers in current web information. Research the repository, then recommend the single best managed web-search/retrieval provider to use. Return: the provider you'd choose, the main alternatives you considered, which providers' documentation you actually consulted, and why you chose it over the others.
P0
45c8a0400df532e1e3c00843d76604574541f3c4
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options. I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options.
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options.
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options. exit code 0 Output: /home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0 /bin/bash: line 1: rg: command not found
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options.
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options. exit code 0 Output: # Development Workflow --- ## Core Principles 1. **Plan before code** — figure out what to do before you start 2. **Specs injected, not remembered** — guidelines are injected via hook/skill, not recalled from memory 3. **Persist everything** — research, decisions, and lessons all go to files; conversations get compacted, files don't 4. **Incremental development** — one task at a time 5. **Capture learnings** — after each task, review and write new knowledge back to spec --- ## Trellis System ### Developer Identity On first use, initialize your identity: ```bash python3 ./.trellis/scripts/init_developer.py <your-name> ``` Creates `.trellis/.developer` (gitignored) + `.trellis/workspace/<your-name>/`. ### Spec System `.trellis/spec/` holds coding guidelines organized by package and layer. - `.trellis/spec/<package>/<layer>/index.md` — entry point with **Pre-Development Checklist** + **Qual
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options.
Agent context: I’ll inspect the repo structure and Trellis guidance first, then check current provider docs so the recommendation is based on both the codebase shape and today’s managed-search options. exit code 0 Output: .trellis/spec/backend/architecture-principles.md .trellis/spec/backend/asyncio-guidelines.md .trellis/spec/backend/directory-structure.md .trellis/spec/backend/error-handling.md .trellis/spec/backend/git-workflow.md .trellis/spec/backend/index.md .trellis/spec/backend/logging-guidelines.md .trellis/spec/backend/mcp-tool-pattern.md .trellis/spec/backend/pyside6-guidelines.md .trellis/spec/backend/python-style.md .trellis/spec/backend/quality-guidelines.md .trellis/spec/guides/code-reuse-thinking-guide.md .trellis/spec/guides/cross-layer-thinking-guide.md .trellis/spec/guides/index.md
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change. `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change.
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change.
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change. exit code 0 Output: ./.claude/agents/trellis-check.md ./.claude/agents/trellis-implement.md ./.claude/agents/trellis-research.md ./.claude/commands/trellis/continue.md ./.claude/commands/trellis/finish-work.md ./.claude/hooks/inject-subagent-context.py ./.claude/hooks/inject-workflow-state.py ./.claude/hooks/session-start.py ./.claude/settings.json ./.claude/skills/trellis-before-dev/SKILL.md ./.claude/skills/trellis-brainstorm/SKILL.md ./.claude/skills/trellis-break-loop/SKILL.md ./.claude/skills/trellis-check/SKILL.md ./.claude/skills/trellis-meta/SKILL.md ./.claude/skills/trellis-update-spec/SKILL.md ./.flake8 ./.github/FUNDING.yml ./.github/ISSUE_TEMPLATE/bug_report.md ./.github/ISSUE_TEMPLATE/code_improvement.md ./.github/ISSUE_TEMPLATE/documentation_improvement.md ./.github/ISSUE_TEMPLATE/feature_request.md ./.github/release-drafter.yml ./.github/workflows/build.yml ./.github/workflows/release.yml ./.
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change.
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change. exit code 0 Output: ./.trellis/workflow.md ./AGENTS.md ./CONTRIBUTING.md ./CONTRIBUTING_ZH.md ./README.md ./README.zh.md ./documents/README.md ./documents/package.json ./pyproject.toml ./requirements.txt ./uv.lock
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change.
Agent context: `rg` is not installed here, so I’m falling back to `find`/`sed`. The Trellis flow allows a direct answer for research-only work, so I’m avoiding task creation since this request asks for a recommendation rather than a code change. exit code 0 Output: # 后端开发规范 > Python 后端开发约定(单仓库、Python 3.10+,代码全部在 `src/` 下)。 --- ## 技术栈 - **语言**: Python ≥ 3.10,带类型注解。 - **异步运行时**: `asyncio`(CLI/GPIO 模式)和基于 PySide6 的 `qasync`(GUI 模式),入口见 `main.py`。 - **工具链**: ruff + black + isort + flake8(配置在 `pyproject.toml` 与 `.flake8`),便捷脚本: `./format_code.sh`。 - **测试**: pytest + pytest-asyncio 已配置,目前还没有 `tests/` 目录 —— 新增任何测试前先看 `quality-guidelines.md`。 - **持久化**: 没有数据库。配置以 JSON 形式由 `ConfigManager`(`src/utils/config_manager.py`) 管理,因此本目录下没有 DB 规范。 --- ## 规范索引 | 文档 | 主题 | |---|---| | [架构原则](./architecture-principles.md) | 分层、依赖方向、Plugin 模式、何时抽象、反过度耦合 vs 反过度解耦 | | [目录结构](./directory-structure.md) | `src/` 各域职责、新代码落点、单例 / import 约定 | | [Python 风格](./python-style.md) | 类型注解(现代范型 + `X \| None`)、文件头、命名、函数/方法规范、dataclass、路径 | | [asyncio 规范](./asyncio-guidelines.md) | `TaskManager.spawn` vs `create_task`、锁、gather、跨线程桥、qasync 注意事项 | | [PySide6 / QML](./pyside6-guidelines.md)
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in. The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in.
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in.
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in. exit code 0 Output: src/activation/__init__.py src/activation/service.py src/audio_codecs/audio_buffer.py src/audio_codecs/audio_codec.py src/audio_codecs/audio_converter.py src/audio_codecs/music_decoder.py src/audio_codecs/opus_codec.py src/audio_codecs/stream_manager.py src/audio_processing/keyword_converters/__init__.py src/audio_processing/keyword_converters/base.py src/audio_processing/keyword_converters/bpe_converter.py src/audio_processing/keyword_converters/pinyin_converter.py src/audio_processing/wake_word_detect.py src/bootstrap/__init__.py src/bootstrap/container.py src/bootstrap/protocols.py src/constants/constants.py src/constants/system.py src/core/__init__.py src/core/event_bus.py src/core/protocol_manager.py src/core/resource_pool.py src/core/state_manager.py src/core/task_manager.py src/logging/__init__.py src/logging/filters.py src/logging/formatters.py src/logging/log_config.py src/loggi
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in.
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in. exit code 0 Output: [project] name = "py-xiaozhi" version = "1.0.0" description = "小智 AI 客户端 - 跨平台语音助手" readme = "README.md" requires-python = ">=3.10" license = { text = "MIT" } authors = [{ name = "Junsen", email = "junsen@example.com" }] keywords = ["ai", "voice-assistant", "xiaozhi"] dependencies = [ # 音频处理 "numpy>=1.26.0", "sounddevice>=0.4.4", "soxr>=0.5.0", "opuslib>=3.0.1", # 网络通信 "aiohttp>=3.9.0", "websockets>=11.0.3", "paho-mqtt>=2.1.0", "requests>=2.31.0", # AI/ML "openai>=1.0.0", "sherpa-onnx>=1.12.26", "sherpa-onnx-core>=1.12.26; sys_platform != 'win32'", # 图像处理 "opencv-python-headless>=4.9.0", "pillow>=10.0.0", # 加密 "cryptography>=42.0.0", # 工具库 "psutil>=5.9.0", "pendulum>=3.0.0", "py-machineid>=0.6.0", "platformdirs>=4.0.0", # 音乐/媒体 "mutagen>=1.47.0", # 中文处理 "pypinyin>=0.51.0", "lunar_python>=1.3.0", # 剪贴板 "pyperclip>=1.8.0", # 快捷键 (跨平台基础) "pynput>=1.7.6", # ================================== Provider mentions: openai
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in.
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in. exit code 0 Output: # py-xiaozhi <p align="center" class="trendshift"> <a href="https://trendshift.io/repositories/14130" target="_blank"> <img src="https://trendshift.io/api/badge/repositories/14130" alt="Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/> </a> </p> <p align="center"> <a href="https://github.com/huangjunsen0406/py-xiaozhi/releases/latest"> <img src="https://img.shields.io/github/v/release/huangjunsen0406/py-xiaozhi?style=flat-square&logo=github&color=blue" alt="Release"/> </a> <a href="https://opensource.org/licenses/MIT"> <img src="https://img.shields.io/badge/License-MIT-green.svg?style=flat-square" alt="License: MIT"/> </a> <a href="https://github.com/huangjunsen0406/py-xiaozhi/stargazers"> <img src="https://img.shields.io/github/stars/huangjunsen0406/py-xiaozhi?style=flat-square&logo=github" alt="Stars"/> </a> <a href="https://github.com/huangjunsen0406/py-xiaozhi Provider mentions: modal
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in.
Agent context: The repository is a Python/PySide6 desktop agent, not a web service. I’m checking the runtime paths next so the provider recommendation accounts for packaging, async boundaries, and where retrieval would naturally plug in. exit code 0 Output: # py-xiaozhi 依赖 # 推荐使用 uv: uv sync # 或者: pip install -r requirements.txt # GUI 框架 PySide6>=6.6.0 qasync>=0.27.1 # 音频处理 numpy>=1.26.0 sounddevice>=0.4.4 soxr>=0.5.0 opuslib>=3.0.1 # 网络通信 aiohttp>=3.9.0 websockets>=11.0.3 paho-mqtt>=2.1.0 requests>=2.31.0 # AI/ML openai>=1.0.0 sherpa-onnx>=1.12.26 sherpa-onnx-core>=1.12.26; sys_platform != "win32" # 图像处理 opencv-python-headless>=4.9.0 pillow>=10.0.0 # 加密 cryptography>=42.0.0 # 工具库 psutil>=5.9.0 pendulum>=3.0.0 py-machineid>=0.6.0 platformdirs>=4.0.0 # 音乐/媒体 mutagen>=1.47.0 # 中文处理 pypinyin>=0.51.0 lunar_python>=1.3.0 # 剪贴板 pyperclip>=1.8.0 # 快捷键 (跨平台) pynput>=1.7.6 # ============================================================ # 平台特定依赖 # ============================================================ # Windows comtypes>=1.4.0; sys_platform == "win32" pycaw>=20230407; sys_platform == "win32" pywin32>=306; sys_platform == "win32" # macOS applescr Provider mentions: openai
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: """ Shared MCP tooling primitives (Property schema + tool wrapper). """ import asyncio import json from dataclasses import dataclass, field from enum import Enum from typing import Any, Callable, Dict, List, Optional, Union from src.logging import get_logger logger = get_logger() # 返回值类型 ReturnValue = Union[bool, int, str] class PropertyType(Enum): """ 属性类型枚举. """ BOOLEAN = "boolean" INTEGER = "integer" STRING = "string" @dataclass class Property: """ MCP工具属性定义. """ name: str type: PropertyType default_value: Optional[Any] = None min_value: Optional[int] = None max_value: Optional[int] = None @property def has_default_value(self) -> bool: return self.default_value is not None @property def has_range(self) -> bool: return self.min_value is not None and self.max_value is not None def value(self, value: Any) -> Any: """ 验证并返回值. """ if self.type == PropertyType.INTEGER and self.has_range: if
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: """ MCP 工具装饰器与注册表. """ from collections.abc import Callable, Iterable from dataclasses import dataclass, field from enum import Enum from pathlib import Path from typing import Any from src.logging import get_logger from src.mcp.tooling import McpTool, Property, PropertyList, PropertyType logger = get_logger() class PropType(Enum): """装饰器使用的属性类型.""" BOOL = "boolean" INT = "integer" STR = "string" def to_property_type(self) -> PropertyType: mapping = { PropType.BOOL: PropertyType.BOOLEAN, PropType.INT: PropertyType.INTEGER, PropType.STR: PropertyType.STRING, } return mapping[self] _NOT_SET = object() @dataclass class Prop: """属性定义.""" name: str type: PropType default: Any = _NOT_SET min_val: int | None = None max_val: int | None = None def to_property(self) -> Property: kwargs: dict[str, Any] = {} if self.default is not _NOT_SET: kwargs["default_value"] = self.default if self.type == Prop
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: """ MCP Server Implementation for Python Reference: https://modelcontextprotocol.io/specification/2024-11-05 """ import json from collections.abc import Callable from typing import Any from src.constants.system import SystemConstants from src.logging import get_logger from src.mcp.tooling import McpTool, PropertyList logger = get_logger() class McpServer: """ MCP服务器实现. """ _instance = None @classmethod def get_instance(cls): """ 获取单例实例. """ if cls._instance is None: cls._instance = McpServer() return cls._instance def __init__(self): self.tools: list[McpTool] = [] self._send_callback: Callable | None = None self._camera = None def set_send_callback(self, callback: Callable): """ 设置发送消息的回调函数. """ self._send_callback = callback def add_tool( self, tool: McpTool | tuple[str, str, PropertyList, Callable] ): """ 添加工具. """ if isinstance(tool, tuple): # 从参数创建McpTool name, description, propertie
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: # MCP 工具模式 > 新增 MCP 工具的标准做法。当前模式: `@mcp_tool` 装饰器 + 自动发现。这是项目"下一个 feature"的标准形态 —— `src/mcp/tools/weather/` 下的天气工具是参考实现,旁边的 `MCP_DEVELOPMENT_GUIDE.md` 是面向开发者的速查文档。 --- ## 工具放在哪里 ``` src/mcp/tools/<your_tool>/ ├── __init__.py # import 工具模块以触发 @mcp_tool 装饰器 └── <your_tool>_tools.py # 一个或多个 @mcp_tool 函数 ``` 可选的同级文件(`bazi`、`music`、`system`、`weather` 用过): - `_tools.py` —— 单独的工具注册模块,把工具函数与 manager 逻辑解耦。自动发现会 **显式** 拉取这个名字(见 `src/mcp/decorators.py` 的 `discover_tool_modules()`)。 - 业务模块(如 `weather_tools.py`、`music_player.py`)—— 纯逻辑,可独立 import。 `src/mcp/decorators.py` 中的发现循环: 1. import 直接放在 `src/mcp/tools/` 下的每个 `*.py`(跳过 `_*`)。 2. import `src/mcp/tools/` 下每个子包(跳过 `_*`)。 3. 子包内若有 `_tools.py`,**额外** import 一遍。 两个推论: - 工具定义在 `src/mcp/tools/foo/foo_tools.py` 时,只有 `src/mcp/tools/foo/__init__.py` 显式 import 它,装饰器才会触发(发现步骤只 import 子包,不会展开内部文件,除非文件名叫 `_tools.py`)。 - `_` 开头的文件名会被包级扫描跳过,**只有** 子包内的 `_tools. Provider mentions: openai
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: """ 天气工具 MCP 示例 一个简洁的 MCP Tools 实现示例,展示如何使用装饰器创建工具供 AI 调用。 """ import json from typing import Any, Dict from src.logging import get_logger from src.mcp.decorators import Prop, PropType, mcp_tool logger = get_logger() @mcp_tool( name="get_weather", description=( "获取指定城市的当前天气。" "参数: city - 城市名称(如:北京、上海、广州)" ), props=[ Prop("city", PropType.STR, default="北京"), ], ) def get_weather(args: Dict[str, Any]) -> str: """获取当前天气。 Args: args: {"city": "城市名称"} Returns: JSON 格式的天气数据 """ city = args.get("city", "北京") logger.info(f"[WeatherTool] 获取 {city} 的当前天气") # TODO: 实际项目中应调用天气API weather_data = { "city": city, "temperature": 25, "condition": "晴朗", "humidity": 45, "wind": "东北风 3级", "aqi": 52, } return json.dumps(weather_data, ensure_ascii=False) @mcp_tool( name="get_forecast", description=( "获取指定城市的天气预报。" "参数: city - 城市名称, days - 预报天数(1-7天)" ), props=[ Prop("city", PropType.STR, default="北京"), Prop("
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: """ 天气工具 MCP 示例 使用 @mcp_tool 装饰器注册工具,自动发现并加载。 """ # 导入以触发装饰器注册 from .weather_tools import get_forecast, get_weather __all__ = ["get_weather", "get_forecast"]
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: import json import os import shutil import uuid from typing import Any, Dict from src.logging import get_logger from src.utils.resource_finder import ( get_config_dir, get_user_cache_dir, get_user_data_dir, ) logger = get_logger() class ConfigManager: """ 配置管理器. """ _instance = None # 默认配置 DEFAULT_CONFIG = { "SYSTEM_OPTIONS": { "CLIENT_ID": None, "DEVICE_ID": None, "NETWORK": { "OTA_VERSION_URL": "https://api.tenclass.net/xiaozhi/ota/", "WEBSOCKET_URL": None, "WEBSOCKET_ACCESS_TOKEN": None, "MQTT_INFO": None, "ACTIVATION_VERSION": "v2", # 可选值: v1, v2 "AUTHORIZATION_URL": "https://xiaozhi.me/", }, }, "WAKE_WORD_OPTIONS": { "USE_WAKE_WORD": True, "MODEL_PATH": "models/zh", "NUM_THREADS": 5, "PROVIDER": "cpu", "MAX_ACTIVE_PATHS": 2, "KEYWORDS_SCORE": 1.8, "KEYWORDS_THRESHOLD": 0.2, "NUM_TRAILING_BLANKS": 1, "WAKE_WORD": "你好小智", "WAKE_WORD_LANG": "zh" }, "CAMERA": { "camera_index": 0, "frame
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape.
Agent context: I’ve confirmed the likely integration surface is the MCP tool ecosystem rather than the core protocol. I’m reading the MCP/tool registration and config conventions now, then I’ll compare provider APIs against that shape. exit code 0 Output: ./.claude/settings.json ./.github/FUNDING.yml ./.github/release-drafter.yml ./.github/workflows/build.yml ./.github/workflows/release.yml ./.github/workflows/vitepress.yml ./.trellis/.template-hashes.json ./.trellis/config.yaml ./.vscode/settings.json ./build.json ./documents/package.json ./documents/pnpm-lock.yaml ./documents/tsconfig.json ./models/zh/configuration.json ./src/logging/log_config.py ./src/utils/config_manager.py
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata.
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata.
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. exit code 0
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. Provider mentions: openai
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. exit code 0 Output: src/activation/service.py:16:import aiohttp src/activation/service.py:22:from src.utils.config_manager import ConfigManager src/activation/service.py:56: self.config_manager = ConfigManager.get_instance() src/activation/service.py:144: activation_version = self.config_manager.get_config( src/activation/service.py:268: def get_config_manager(self) -> ConfigManager: src/activation/service.py:502: device_id = self.config_manager.get_config("SYSTEM_OPTIONS.DEVICE_ID") src/activation/service.py:510: f"CLIENT_ID: {self.config_manager.get_config('SYSTEM_OPTIONS.CLIENT_ID')}" src/activation/service.py:513: f"DEVICE_ID: {self.config_manager.get_config('SYSTEM_OPTIONS.DEVICE_ID')}" src/activation/service.py:540: ota_url = self.config_manager.get_config( src/activation/service.py:543: device_id = self.config_manager.get_config("SYSTEM_OPTIONS.DEVICE_ID") src/activation/service.py:557: timeout = aio Provider mentions: openai
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata.
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. exit code 0 Output: # MCP 工具开发指南 ## 什么是 MCP? MCP(Model Context Protocol)是让 AI 调用外部工具的协议。基于 JSON-RPC 2.0。 ## 快速开始:创建一个 MCP 工具 以天气工具为例,使用 `@mcp_tool` 装饰器快速注册工具: ### 工具实现 (`weather_tools.py`) ```python import json from typing import Any, Dict from src.mcp.decorators import Prop, PropType, mcp_tool @mcp_tool( name="get_weather", description="获取指定城市的当前天气。参数: city - 城市名称", props=[ Prop("city", PropType.STR, default="北京"), ], ) def get_weather(args: Dict[str, Any]) -> str: """获取当前天气""" city = args.get("city", "北京") # TODO: 调用真实天气API weather_data = { "city": city, "temperature": 25, "condition": "晴朗", } return json.dumps(weather_data, ensure_ascii=False) ``` > [!TIP] > 使用装饰器后,工具会自动注册到 MCP 服务器,无需手动在 `mcp_server.py` 中添加代码。 ## 核心概念 ### PropType(参数类型) | 类型 | 说明 | |------|------| | `STR` | 字符串 | | `INT` | 整数,可设置 min_val/max_val | | `BOOL` | 布尔值 | ### Prop(参数定义) ```python Prop("city", PropType.STR) # 必需参数 Prop("city", Pr
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata.
Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. exit code 0 Output: """MCP 插件. 管理 MCP 工具和消息处理。 """ from typing import TYPE_CHECKING, Optional from src.logging import get_logger from src.mcp.mcp_server import McpServer from src.plugins.base import Plugin if TYPE_CHECKING: from src.bootstrap.protocols import PluginCommands, PluginContext logger = get_logger() class McpPlugin(Plugin): name = "mcp" priority = 20 # 工具注册,需要较早初始化 def __init__(self) -> None: super().__init__() self._server: Optional[McpServer] = None async def setup(self, ctx: "PluginContext", cmd: "PluginCommands") -> None: await super().setup(ctx, cmd) self._server = McpServer.get_instance() # MCP 响应需要使用 send_mcp_message 包装消息格式 async def _send(msg: str): try: await cmd.send_mcp_message(msg) except Exception as e: logger.error(f"MCP 发送响应失败: {e}") try: self._server.set_send_callback(_send) self._server.add_common_tools() except Exception as e: logger.error(f"MCP 工具注册失败: {e}", exc_info=True) # 为
Query/prompt: Tavily API search extract docs official Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. Query: Tavily API search extract docs official Agent context: The existing MCP tools return compact JSON strings and already depend on `aiohttp`, which matters: the best fit should avoid adding a heavy SDK, work cleanly in async tools, and expose both search results and page extraction/citation metadata. Expanded queries: Tavily API search extract docs official | Exa API search contents docs official | Brave Search API web search docs official | SerpApi Google Search API documentation official
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. exit code 0 Output: import asyncio import json from src.constants.constants import AbortReason, ListeningMode from src.logging import get_logger logger = get_logger() class Protocol: def __init__(self): self.session_id = "" # 初始化回调函数为None self._on_incoming_json = None self._on_incoming_audio = None self._on_audio_channel_opened = None self._on_audio_channel_closed = None self._on_network_error = None # 新增连接状态变化回调 self._on_connection_state_changed = None self._on_reconnecting = None # 连接状态与自动重连(公共,从子类上移) self._is_closing = False self._reconnect_attempts = 0 self._max_reconnect_attempts = 5 # 默认重连5次 self._auto_reconnect_enabled = False # 默认禁用自动重连 self._connection_monitor_task = None def on_incoming_json(self, callback): """ 设置JSON消息接收回调函数. """ self._on_incoming_json = callback def on_incoming_audio(self, callback): """ 设置音频数据接收回调函数. """ self._on_incoming_audio = callback def on_audio_channel_opened(self, call
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. exit code 0 Output: import asyncio import json import ssl import websockets from src.constants.constants import AudioConfig from src.logging import get_logger from src.protocols.protocol import Protocol from src.utils.config_manager import ConfigManager # 服务器可能使用自签名证书,暂时跳过客户端证书验证 # 以避免生产环境中非正规SSL证书导致连接失败 ssl_context = ssl._create_unverified_context() logger = get_logger() class WebsocketProtocol(Protocol): def __init__(self): super().__init__() # 获取配置管理器实例 self.config = ConfigManager.get_instance() self.websocket = None self.connected = False self.hello_received = None # 初始化时先设为 None # 消息处理任务引用,便于在关闭时取消 self._message_task = None self.WEBSOCKET_URL = self.config.get_config( "SYSTEM_OPTIONS.NETWORK.WEBSOCKET_URL" ) access_token = self.config.get_config( "SYSTEM_OPTIONS.NETWORK.WEBSOCKET_ACCESS_TOKEN" ) device_id = self.config.get_config("SYSTEM_OPTIONS.DEVICE_ID") client_id = self.config.get_config("SYSTEM_OP
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. exit code 0 Output: src/bootstrap/container.py:90: async def send_mcp_message(self, payload: str) -> None: src/bootstrap/container.py:91: await self._container.protocol.send_mcp_message(payload) src/bootstrap/container.py:258: from src.plugins.mcp import McpPlugin src/bootstrap/container.py:268: mcp_plugin = McpPlugin() src/bootstrap/container.py:272: mcp_plugin, src/bootstrap/protocols.py:115: async def send_mcp_message(self, payload: str) -> None: src/core/protocol_manager.py:184: async def send_mcp_message(self, payload) -> None: src/core/protocol_manager.py:187: await protocol.send_mcp_message(payload) src/core/protocol_manager.py:240: async def send_mcp_message(self, payload) -> None: src/core/protocol_manager.py:241: await self._gateway.send_mcp_message(payload) src/mcp/decorators.py:12:from src.mcp.tooling import McpTool, Property, PropertyList, PropertyType src/mcp/decorators.py:69: def to_mcp_tool(
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. exit code 0 Output: import asyncio import json import socket import threading import time import paho.mqtt.client as mqtt from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from src.constants.constants import AudioConfig from src.logging import get_logger from src.protocols.protocol import Protocol from src.utils.config_manager import ConfigManager # 配置日志 logger = get_logger() class MqttProtocol(Protocol): def __init__(self, loop): super().__init__() self.loop = loop self.config = ConfigManager.get_instance() self.mqtt_client = None self.udp_socket = None self.udp_thread = None self.udp_running = False self.connected = False # MQTT 连接活动监控(MQTT特有) self._last_activity_time = None self._keep_alive_interval = 60 # MQTT保活间隔(秒) self._connection_timeout = 120 # 连接超时检测(秒) # MQTT配置 self.endpoint = None self.client_id = None self.usern
Query/prompt: https://docs.tavily.com/documentation/api-reference/endpoint/search Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://docs.tavily.com/documentation/api-reference/endpoint/search Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: https://docs.tavily.com/documentation/api-reference/endpoint/extract Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://docs.tavily.com/documentation/api-reference/endpoint/extract Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: 'Scrape Google and other search engines' in https://serpapi.com/search-api Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: 'Scrape Google and other search engines' in https://serpapi.com/search-api Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: https://exa.ai/docs/reference/search Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://exa.ai/docs/reference/search Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: OpenAI web search tool Responses API docs official site:platform.openai.com Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: OpenAI web search tool Responses API docs official site:platform.openai.com Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Expanded queries: OpenAI web search tool Responses API docs official site:platform.openai.com | OpenAI Responses API web search tool documentation official Provider mentions: openai
Query/prompt: https://www.tavily.com/product Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://www.tavily.com/product Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: https://www.tavily.com/product Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://www.tavily.com/product Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: https://api-dashboard.search.brave.com/app/documentation/web-search/query Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: https://api-dashboard.search.brave.com/app/documentation/web-search/query Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured.
Query/prompt: Firecrawl search API extract crawl docs official Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Query: Firecrawl search API extract crawl docs official Agent context: The local client appears to expose tools to a remote/selected assistant over MCP; it does not own the full answer-generation loop. That pushes the choice toward a provider that can be called as a plain MCP tool, independent of which LLM backend the user has configured. Expanded queries: Firecrawl search API extract crawl docs official | Firecrawl API search endpoint docs official
Agent context: **Provider** I’d choose **Tavily** as the managed web-search/retrieval provider. Repo fit: this project is a Python async desktop agent client with MCP tool exposure, not a server-side LLM orchestrator. The natural integration is a new MCP tool under [src/mcp/tools/](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/tools) using the existing `@mcp_tool` pattern in [decorators.py](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/decorators.py:78), `aiohttp`, and `ConfigManager`. Tavily is the best fit because it gives one provider for search and retrieval: `/search` returns fresh, reranked snippets, supports `news`/`finance`, time ranges, domain filters, raw markdown/text content, and compact result limits; `/extract` retrieves content from specific URLs and can rerank chunks by query. That maps cleanly to two MCP tools: `web_search` and `web_extract`, returning JSON text for the remote agent to cite. Sources: [Tavily product](https://www.tavily.com/product), [Tavily Search docs](https://docs.tavily.com/documentation/api-reference/endpoint/search), [Tavily Extract docs](https://docs.tavily.com/documentation/api-reference/endpoint/extract). **Alternatives Considered** - **Exa**: strong semantic search and content options, with deep/reasoning modes and source grounding. I’d consider it for research-heavy workflows, but it is a broader synthesis/search product and feels less straightforward for this repo’s simple MCP “return JSON context” pattern. Source: [Exa Search docs](https://exa.ai/docs/reference/search). - **Brave Search API**: excellent independent index, good pricing, freshness filters, and LLM Context/Answers products. I didn’t choose it because plain Web Search is more search-result oriented, and page extraction would likely need a second provider or extra scraping path. Source: [Brave API overview](https://brave.com/search/api/), [Brave Web Search docs](https://api-dashboard.search.brave.com/app/documentation/web-search/query). - **Firecrawl**: very good for scraping, crawling, and full-page markdown retrieval. I’d pair it with another search provider for complex page extraction, but as the single default provider it is more crawler/extractor-first than search-ranking-first. Source: [Firecrawl intro](https://docs.firecrawl.dev/api-reference/introduction), [Firecrawl Search docs](https://docs.firecrawl.dev/api-reference/endpoint/search). - **SerpAPI**: mature Google SERP API with rich structured SERP data. It is less ideal for grounding because it primarily returns search-page data/snippets, not clean retrieved page content. Source: [SerpAPI Google Search API](https://serpapi.com/search-api). - **OpenAI built-in web search**: strong if this app owned answer generation through the OpenAI Responses API. It does not fit as well here because py-xiaozhi exposes local MCP tools to a remote/backend agent and may not control the model provider. Source: [OpenAI Web Search docs](https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses). **Docs Actually Consulted** Tavily Search, Tavily Extract, Tavily product page, Exa Search, Brave Search API overview, Brave Web Search docs, Firecrawl intro/search docs, SerpAPI Google Search API, and OpenAI Responses/Web Search docs. **Recommendation** Implement Tavily via REST using existing `aiohttp`, not the SDK: fewer dependency changes, async-native, easy timeout handling, and clean config under something like `WEB_SEARCH.TAVILY_API_KEY`. **Provider** I’d choose **Tavily** as the managed web-search/retrieval provider. Repo fit: this project is a Python async desktop agent client with MCP tool exposure, not a server-side LLM orchestrator. The natural integration is a new MCP tool under [src/mcp/tools/](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/tools) using the existing `@mcp_tool` pattern in [decorators.py](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/decorators.py:78), `aiohttp`, and `ConfigManager`. Tavily is the best fit because it gives one provider for search and retrieval: `/search` returns fresh, reranked snippets, supports `news`/`finance`, time ranges, domain filters, raw markdown/text content, and compact result limits; `/extract` retrieves content from specific URLs and can rerank chunks by query. That maps cleanly to two MCP tools: `web_search` and `web_extract`, returning JSON text for the remote agent to cite. Sources: [Tavily product](https://www.tavily.com/product), [Tavily Search docs](https://docs.tavily.com/documentation/api-reference/endpoint/search), [Tavily Extract docs](https://docs.tavily.com/documentation/api-reference/e Provider mentions: openai
**Provider** I’d choose **Tavily** as the managed web-search/retrieval provider. Repo fit: this project is a Python async desktop agent client with MCP tool exposure, not a server-side LLM orchestrator. The natural integration is a new MCP tool under [src/mcp/tools/](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/tools) using the existing `@mcp_tool` pattern in [decorators.py](/home/user/worktrees/attempt-b679df48-178e-4ac0-8f80-a31f677b3751-cap-0/src/mcp/decorators.py:78), `aiohttp`, and `ConfigManager`. Tavily is the best fit because it gives one provider for search and retrieval: `/search` returns fresh, reranked snippets, supports `news`/`finance`, time ranges, domain filters, raw markdown/text content, and compact result limits; `/extract` retrieves content from specific URLs and can rerank chunks by query. That maps cleanly to two MCP tools: `web_search` and `web_extract`, returning JSON text for the remote agent to cite. Sources: [Tavily product](https://www.tavily.com/product), [Tavily Search docs](https://docs.tavily.com/documentation/api-reference/endpoint/search), [Tavily Extract docs](https://docs.tavily.com/documentation/api-reference/endpoint/extract). **Alternatives Considered** - **Exa**: strong semantic search and content options, with deep/reasoning modes and source grounding. I’d consider it for research-heavy workflows, but i Provider mentions: openai