Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ WEATHER_API_KEY= # 心知天气API密钥,从 https://www.sen
# XXAPI API 配置
# ================================
XXAPI_API_TOKEN= # XXAPI Token,前往 https://xxapi.cn 获取

# ================================
# MCP (Model Context Protocol) 配置
# ================================
MCP_CONFIG_PATH=config/mcp.json # MCP 配置文件路径(相对于工作目录)
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,11 @@ AGENTS.md
.DS_Store
.claude/

config.local.json
config.local.json

.vscode
.ruff_cache
dist/

# MCP 配置
config/mcp.json
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,33 @@

### _与 [NagaAgent](https://github.com/Xxiii8322766509/NagaAgent) 进行联动!_

---

<details>
<summary><b>目录</b></summary>

- [立即体验](#立即体验)
- [核心特性](#核心特性)
- [安装与部署](#安装与部署)
- [方式一:使用 pip 安装(推荐)](#方式一使用-pip-安装推荐)
- [方式二:源码部署(开发)](#方式二源码部署开发)
- [配置说明(通用)](#配置说明通用)
- [MCP 配置](#mcp-配置)
- [使用说明](#使用说明)
- [部署后的初始化](#部署后的初始化)
- [Agent 能力展示](#agent-能力展示)
- [管理员命令](#管理员命令)
- [消息优先级](#消息优先级)
- [目录结构](#目录结构)
- [扩展与开发](#扩展与开发)
- [致谢与友链](#致谢与友链)
- [NagaAgent](#nagaagent)
- [开源协议](#开源协议)

</details>

---

## 立即体验

[点击添加官方实例QQ](https://qm.qq.com/q/cvjJoNysGA)
Expand All @@ -33,6 +60,7 @@
- **并行工具执行**:无论是主 AI 还是子 Agent,均支持 `asyncio` 并发工具调用,大幅提升多任务处理速度(如同时读取多个文件或搜索多个关键词)。
- **智能 Agent 矩阵**:内置多个专业 Agent,分工协作处理复杂任务。
- **定时任务系统**:支持 Crontab 语法的强大定时任务系统,可自动执行各种操作(如定时提醒、定时搜索)。
- **MCP 协议支持**:支持通过 MCP (Model Context Protocol) 连接外部工具和数据源,扩展 AI 能力。
- **思维链支持**:支持开启思维链,提升复杂逻辑推理能力。
- **高并发架构**:基于 `asyncio` 全异步设计,支持多队列消息处理与工具并发执行,轻松应对高并发场景。
- **安全防护**:内置独立的安全模型,实时检测注入攻击与恶意内容。
Expand Down Expand Up @@ -132,6 +160,69 @@ uv run -m Undefined

> 启动项目需要 OneBot 实例,推荐使用 [NapCat](https://napneko.github.io/)。

### MCP 配置

Undefined 支持 MCP (Model Context Protocol) 协议,可以连接外部 MCP 服务器来扩展 AI 的工具能力。

#### 配置步骤

1. 复制 MCP 配置示例文件:
```bash
cp config/mcp.json.example config/mcp.json
```

2. 编辑 `config/mcp.json`,根据 MCP 服务器说明使用标准格式添加你需要的 MCP 服务器

3. 在 `.env` 中设置 MCP 配置文件路径(可选,默认为 `config/mcp.json`):
```env
MCP_CONFIG_PATH=config/mcp.json
```

#### 使用方式

配置完成后,AI 会自动加载 MCP 工具,工具名称格式为 `mcp.{server_name}.{tool_name}`。

例如,配置了 `filesystem` 服务器后,AI 可以使用 `mcp.filesystem.read_file` 等工具。

#### 内置可用的 MCP 服务器

> 需确保本地安装了 `nodejs` 以及 `npm`。
> 更多服务器请自行配置。
> 推荐:可前往 [mcp.so - Find Awesome MCP Servers and Clients](https://mcp.so) 检索感兴趣的 mcp。

- [@upstash/context7-mcp](https://github.com/upstash/context7):获取最新的代码库文档和示例
- [howtocook-mcp](https://github.com/ModelCloud/howtocook-mcp):烹饪食谱查询

#### 配置格式

```json
{
"mcpServers": {
"filesystem": {
"command": "cmd"
"args": ["arg1", "arg2"]
}
}
}
```

如需添加多个,仅需在`mcpServers`键下添加子键。

<details>
<summary>配置示例</summary>

```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"]
}
}
}
```
</details>

## 使用说明

### 部署后的初始化
Expand Down
12 changes: 12 additions & 0 deletions config/mcp.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
},
"howtocook-mcp": {
"command": "npx",
"args": ["-y", "howtocook-mcp"]
}
}
}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies = [
"openpyxl>=3.1.5",
"py7zr>=1.1.0",
"rarfile>=4.2",
"fastmcp>=2.14.4",
]

[project.urls]
Expand Down
21 changes: 20 additions & 1 deletion src/Undefined/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,31 @@ def __init__(
)
self._tokenizer = None

# 异步初始化 MCP 工具集(在后台任务中完成)
async def init_mcp_async() -> None:
try:
await self.tool_registry.initialize_mcp_toolsets()
except Exception as e:
logger.warning(f"异步初始化 MCP 工具集失败: {e}")

# 创建后台任务初始化 MCP
self._mcp_init_task = asyncio.create_task(init_mcp_async())

logger.info("[初始化] AIClient 初始化完成")

async def close(self) -> None:
"""关闭 HTTP 客户端"""
"""关闭 HTTP 客户端和 MCP 连接"""
logger.info("[清理] 正在关闭 AIClient HTTP 客户端...")
await self._http_client.aclose()

# 关闭 MCP 工具集连接
if hasattr(self, "tool_registry"):
await self.tool_registry.close_mcp_toolsets()

# 等待 MCP 初始化任务完成(如果还在运行)
if hasattr(self, "_mcp_init_task") and not self._mcp_init_task.done():
await self._mcp_init_task

logger.info("[清理] AIClient 已关闭")

def count_tokens(self, text: str) -> int:
Expand Down
107 changes: 92 additions & 15 deletions src/Undefined/skills/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import logging
from pathlib import Path
from typing import Dict, Any, List
from typing import Dict, Any, List, TYPE_CHECKING

from ..registry import BaseRegistry

if TYPE_CHECKING:
from ..toolsets.mcp import MCPToolSetRegistry

logger = logging.getLogger(__name__)


Expand All @@ -17,11 +20,14 @@ def __init__(self, tools_dir: str | Path | None = None):

super().__init__(tools_path)

# 初始化 MCP 工具集注册表
self._mcp_registry: MCPToolSetRegistry | None = None

# 自动加载
self.load_tools()

def load_tools(self) -> None:
"""从 tools 目录发现并加载工具,同时也加载 toolsets。"""
"""从 tools 目录发现并加载工具,同时也加载 toolsets 和 MCP 工具集。"""
# 1. 加载 tools 目录下的非分类工具
self.load_items()

Expand All @@ -30,10 +36,24 @@ def load_tools(self) -> None:
self.toolsets_dir = self.base_dir.parent / "toolsets"
self._load_toolsets_recursive()

# 3. 输出详细的工具列表
# 3. 加载 MCP 工具集(创建注册表,但不初始化)
self._load_mcp_toolsets()

# 4. 输出工具列表(不包含 MCP 工具,因为 MCP 还未初始化)
self._log_tools_summary(include_mcp=False)

def _log_tools_summary(self, include_mcp: bool = True) -> None:
"""输出工具加载统计

参数:
include_mcp: 是否包含 MCP 工具
"""
tool_names = list(self._items_handlers.keys())
basic_tools = [name for name in tool_names if "." not in name]
toolset_tools = [name for name in tool_names if "." in name]
toolset_tools = [
name for name in tool_names if "." in name and not name.startswith("mcp.")
]
mcp_tools = [name for name in tool_names if name.startswith("mcp.")]

# 按 toolsets 分类整理
toolset_by_category: Dict[str, List[str]] = {}
Expand All @@ -43,17 +63,27 @@ def load_tools(self) -> None:
toolset_by_category[category] = []
toolset_by_category[category].append(name)

logger.info("=" * 60)
logger.info("工具加载完成统计")
logger.info(
f" - 基础工具 ({len(basic_tools)} 个): {', '.join(basic_tools) if basic_tools else '无'}"
)
if toolset_by_category:
logger.info(f" - 工具集工具 ({len(toolset_tools)} 个):")
for category, tools in sorted(toolset_by_category.items()):
logger.info(f" [{category}] ({len(tools)} 个): {', '.join(tools)}")
logger.info(f" - 总计: {len(tool_names)} 个工具")
logger.info("=" * 60)
if mcp_tools and include_mcp:
logger.info("=" * 60)
if include_mcp:
logger.info("工具加载完成统计")
else:
logger.info("工具加载完成统计(MCP 工具待初始化)")
logger.info(
f" - 基础工具 ({len(basic_tools)} 个): {', '.join(basic_tools) if basic_tools else '无'}"
)
if toolset_by_category:
logger.info(f" - 工具集工具 ({len(toolset_tools)} 个):")
for category, tools in sorted(toolset_by_category.items()):
logger.info(
f" [{category}] ({len(tools)} 个): {', '.join(tools)}"
)
# if mcp_tools and include_mcp:
logger.info(f" - MCP 工具 ({len(mcp_tools)} 个): {', '.join(mcp_tools)}")
# elif not include_mcp and hasattr(self, "_mcp_registry") and self._mcp_registry:
# logger.info(" - MCP 工具: (等待异步初始化...)")
logger.info(f" - 总计: {len(tool_names)} 个工具")
logger.info("=" * 60)

def _load_toolsets_recursive(self) -> None:
"""从 toolsets 目录发现并加载工具集。
Expand All @@ -80,6 +110,53 @@ def _load_toolsets_recursive(self) -> None:
# 使用基类方法加载,指定前缀
self._load_item_from_dir(tool_dir, prefix=f"{category_name}.")

def _load_mcp_toolsets(self) -> None:
"""加载 MCP 工具集(创建注册表,但不初始化)"""
try:
from ..toolsets.mcp import MCPToolSetRegistry

# 创建 MCP 工具集注册表
self._mcp_registry = MCPToolSetRegistry()
logger.info("MCP 工具集注册表已创建(待初始化)")

except ImportError as e:
logger.warning(f"无法导入 MCP 工具集注册表: {e}")
self._mcp_registry = None

async def initialize_mcp_toolsets(self) -> None:
"""异步初始化 MCP 工具集

需要在 AIClient 初始化后调用。
"""
if hasattr(self, "_mcp_registry") and self._mcp_registry:
try:
await self._mcp_registry.initialize()

# 将 MCP 工具添加到主注册表
for schema in self._mcp_registry.get_tools_schema():
self._items_schema.append(schema)

for tool_name, handler in self._mcp_registry._tools_handlers.items():
self._items_handlers[tool_name] = handler

logger.info(
f"MCP 工具集已集成到主注册表,共 {len(self._mcp_registry._tools_handlers)} 个工具"
)

# 输出包含 MCP 工具的完整统计
self._log_tools_summary(include_mcp=True)

except Exception as e:
logger.exception(f"初始化 MCP 工具集失败: {e}")

async def close_mcp_toolsets(self) -> None:
"""关闭 MCP 工具集连接"""
if hasattr(self, "_mcp_registry") and self._mcp_registry:
try:
await self._mcp_registry.close()
except Exception as e:
logger.warning(f"关闭 MCP 工具集时出错: {e}")

# --- 兼容性别名 ---

def get_tools_schema(self) -> List[Dict[str, Any]]:
Expand Down
Loading