# Issue #95: MCP 工具简化 — 17→5 个高层工具

- **GitHub**: https://github.com/gladego/index1-dev/issues/95
- **状态**: 🔄 规划中
- **分支**: dev
- **关联 Issue**: #96（统一搜索层，顺序依赖）、#97（learn 统一写入，已完成）

---

## 1. 问题背景

当前 index1 暴露 17 个 MCP 工具（8 docs_* + 9 cog_*），存在三个问题：

1. **Prompt 浪费** — 17 个工具描述占用大量系统提示 token
2. **搜索割裂** — AI 需区分 `docs_search`（代码）和 `cog_recall`（记忆），而用户只想"搜"
3. **死工具** — 根据最坏假设设计原则，Claude 0% 主动调用可选工具，7+ 工具从未被调用

## 2. 开源调研

### 2.1 对标项目

| 项目 | LLM 可见工具数 | 核心设计 | 与 index1 的倍数差 |
|------|---------------|---------|-------------------|
| **Zep** | 1-2 | `get_user_context(thread_id)` — 服务端决定搜索策略 | index1 是 10-17x |
| **LangMem** | 2 | `search_memory` + `manage_memory` — 最简 read/write 分离 | index1 是 8.5x |
| **Mem0** | 5 | `add/search/get/update/delete` — CRUD 模式 | index1 是 3.4x |

### 2.2 工具数量与 LLM 准确率（实测数据）

| 工具数 | LLM 工具选择准确率 | 来源 |
|--------|-------------------|------|
| 10 | 100%（0 错误） | Speakeasy/Dog API benchmark |
| 20 | 95%（开始幻觉） | 同上 |
| 30+ | 急剧下降 | Block Engineering 称"临界阈值" |

来源：
- [Speakeasy: Why Less Is More for MCP](https://www.speakeasy.com/mcp/tool-design/less-is-more)
- [Block's Playbook for Designing MCP Servers](https://engineering.block.xyz/blog/blocks-playbook-for-designing-mcp-servers)
- [RAG-MCP: Mitigating Prompt Bloat](https://arxiv.org/abs/2505.03275)

### 2.3 行业共识

| 来源 | 推荐工具数 | 关键观点 |
|------|-----------|---------|
| Philipp Schmid (Hugging Face) | 5-15 per server | "One server, one job" |
| Block Engineering | 减到 2 个 | Linear MCP: readonly_query + mutation_query |
| OpenAI | <20 | "Aim for fewer than 20 functions" |
| Docker | 按用途精选 | "Smaller, focused servers" |

### 2.4 工具设计模式

| 模式 | 代表 | 做法 |
|------|------|------|
| **Pattern A: 统一查询** | Zep | 1 个 search → 服务端决定策略 |
| **Pattern B: Read/Write 分离** | LangMem | 2 个工具：search + manage |
| **Pattern C: CRUD** | Mem0 | 5 个 REST 风格工具 |
| **反模式: 端点镜像** | — | REST API 1:1 转 MCP 工具（Block 明确反对） |

**index1 当前属于"反模式"** — 16 个底层函数 1:1 映射为 MCP 工具。

### 2.5 参数设计最佳实践

| 实践 | 理由 |
|------|------|
| 扁平参数（不嵌套） | LLM 难以构造嵌套 JSON |
| Literal 类型约束枚举 | 减少自由度 |
| 尽量用默认值 | 每个参数 = AI 的一个决策点 |
| 2-4 参数最佳 | 更多 = 更多出错机会 |

## 3. 当前 17 工具的使用分析

### 3.1 核心事实：Hooks 不调用任何 MCP 工具

hooks.py 的 awaken/orient/observe/reflect 全部直接调用底层 DB 函数（`db_cog.insert_fact`, `db_cog.fts_search_facts` 等），**0 个 MCP 工具被 hook 调用**。

### 3.2 实际调用率分析（最坏假设）

| 调用方 | 工具 | 调用率 | 备注 |
|--------|------|--------|------|
| 用户显式请求 | docs_search | 用户驱动 | 用户说"搜 X"时 Claude 执行 |
| 用户显式请求 | docs_get | 用户驱动 | 读完整内容 |
| 用户显式请求 | learn | 用户驱动 | 用户说"记住这个" |
| 用户显式请求 | docs_status | 偶尔 | "索引状态" |
| **Claude 主动** | **所有工具** | **0%** | **已证明：Claude 永不主动调用可选工具** |
| Hooks 自动 | 底层函数（非 MCP） | 100% | insert_fact, fts_search 等 |

### 3.3 工具分类

```
高价值（用户会显式触发）:
  docs_search, docs_get, cog_recall, learn, docs_status, docs_reindex, docs_config
  → 合并为 5 个: recall, learn, status, reindex, config

低价值（用户不知道、Claude 不调用）:
  docs_expand, docs_symbols, docs_repomap        → 图能力并入 recall 自动增强
  cog_save_fact, cog_save_model, cog_save_tactic  → hooks 已自动覆盖
  cog_report_outcome                              → hooks 已自动覆盖
  cog_get_model, cog_find_tactic, cog_trace       → 并入 recall 结果
```

## 4. 设计决策

### 4.1 去掉 `think` 工具（原 Issue 方案 vs 修订方案）

| 维度 | 原方案的 `think` | 结论 |
|------|-----------------|------|
| 需要 L1 模型做意图理解 | 违反 L0 自足原则 | ❌ |
| L0 降级 = recall + 模板 | 本质就是 recall 多包一层 | 冗余 |
| Claude 本身就是思考层 | 让工具替 Claude 思考没意义 | 冗余 |
| 开源项目无先例 | Mem0/Zep/LangMem 都没有 | ❌ |
| Claude 0% 主动调用 | 设计了也不会被调用 | ❌ |

**结论：去掉 `think`。**

### 4.2 移除而非 deprecated

| 方案 | 分析 |
|------|------|
| **deprecated（保留 + 标记）** | Claude 不读 deprecated 标记；保留 = 继续浪费 prompt token；增加维护负担 |
| **直接移除 MCP 注册** | 底层函数保留给 hooks/CLI 用；MCP 工具表从 17→5；prompt 节省 ~70% |

**结论：MCP 工具直接移除（不注册），底层函数保留。**

### 4.3 `recall` 的核心消费者是 hooks，不是 Claude

```
orient hook（100% 自动触发）
  └── 当前调用 search_cog.recall()（只搜 cognition）
  └── 升级为 unified_recall()（搜 corpus + cognition）
  └── 自动获得更丰富的上下文注入
  └── 无需 Claude 参与

用户显式请求（偶尔）
  └── "搜 X" → Claude 调用 recall MCP 工具
  └── recall 包装 unified_recall()
  └── 一次搜索 = 代码 + 经验
```

### 4.4 #95 与 #96 的关系

```
#96 统一搜索层（技术层）  →  #95 MCP 工具简化（接口层）
         ↓                           ↓
  unified_recall() 函数           recall MCP 工具
  并行 corpus + cognition          包装 unified_recall()
  RRF 跨库融合                    替代 9+ 旧搜索工具
```

**不是重叠，是顺序依赖。建议合并实现。**

## 5. 最终方案：17 → 5 工具

### 5.1 保留的 5 个工具

| 工具 | 类型 | 参数 | 替代的旧工具 |
|------|------|------|-------------|
| **`recall`** | 读 | `query`, `collection?`, `top_k?` | docs_search, docs_get, cog_recall, cog_find_tactic, cog_get_model, cog_trace, docs_expand, docs_symbols, docs_repomap (9 个) |
| **`learn`** | 写 | `insight`, `context?`, `session_id?` | ✅ 已完成 (#97)。替代 cog_save_fact, cog_save_model, cog_save_tactic, cog_report_outcome (4 个) |
| **`status`** | 管理 | 无参数 | docs_status (1 个) |
| **`reindex`** | 管理 | `path?`, `collection?` | docs_reindex (1 个) |
| **`config`** | 管理 | `key?`, `value?` | docs_config (1 个) |

### 5.2 删除的 12 个工具

直接从 MCP 注册移除，底层函数保留给 hooks/CLI：

| 删除的工具 | 原因 | 底层函数保留位置 |
|-----------|------|-----------------|
| docs_search | 并入 recall | search.py `search()` |
| docs_get | 并入 recall（返回足够内容） | db.py `get_chunk()` |
| docs_expand | recall 自动图扩展 | graph.py `expand_context()` |
| docs_symbols | recall 自动包含符号 | db.py `get_symbols_by_doc()` |
| docs_repomap | CLI 保留，MCP 移除 | graph.py `generate_repo_map()` |
| cog_save_fact | hooks 自动写入 + learn 替代 | db_cog.py `insert_fact()` |
| cog_save_model | hooks 自动创建 | db_cog.py `upsert_mental_model()` |
| cog_save_tactic | hooks 自动提取 | db_cog.py `insert_tactic()` |
| cog_report_outcome | hooks 自动追踪 | db_cog.py `update_tactic_outcome()` |
| cog_recall | 并入 recall | search_cog.py `recall()` |
| cog_get_model | 并入 recall | db_cog.py `get_mental_model()` |
| cog_find_tactic | 并入 recall | db_cog.py `search_tactics()` |
| cog_trace | 并入 recall | db_cog.py `trace_facts()` |

### 5.3 `recall` 设计

```python
@mcp.tool()
async def recall(
    query: str,
    ctx: Context,
    collection: str | None = None,
    top_k: int = 10,
) -> dict:
    """搜索项目记忆 — 代码 + 经验一次检索

    自动跨库搜索（corpus 代码索引 + cognition 认知事实），
    服务端决定搜索策略，AI 无需关心数据在哪。
    """
```

**返回值：按类型分组（结构即语义）**

不用扁平列表 + type 字段，而是按类型分组。Claude 不需要解析 type 来理解每条结果：

```python
{
    "code": [                    # corpus 命中（代码片段）
        {
            "id": 42,
            "score": 0.87,
            "source": "src/index1/db.py",
            "section": "class Database",
            "content": "前 500 字符...",
            "symbols": ["Database", "connect"],  # 自动附带符号
        },
    ],
    "facts": [                   # 认知事实
        {
            "id": 15,
            "score": 0.82,
            "assertion": "SQLite FTS5 contentless 模式需要手动同步索引",
            "category": "root_cause",
            "confidence": 0.9,
        },
    ],
    "tactics": [                 # 相关策略（如果匹配到）
        {
            "id": 3,
            "trigger_pattern": "FTS5 search returns empty",
            "steps": "1. Check contentless sync...",
            "success_rate": 0.8,
        },
    ],
    "model": {                   # 相关心智模型（如果匹配到，单个）
        "scope": "src/index1/db.py",
        "subject": "数据库模块",
        "understanding_level": 0.7,
    },
    "mode": "hybrid",            # 搜索模式
    "query_ms": 42,
}
```

**设计理由（AI-first）**：
- AI 是第一用户 — 返回结构为 AI 消费优化，不是为人类阅读优化
- code/facts/tactics/model 是不同维度的上下文（"代码长什么样"/"上次学到什么"/"应该怎么做"），不是同一问题的候选答案
- 分组结构自带语义 → AI 直接读 `facts` 就知道是历史经验，空 `tactics: []` 直接表达"没有已知解法"
- 一次调用给齐所有维度 → 补偿 AI 不会主动多调几次的缺陷

**为什么不做跨类型 RRF 融合**：
- corpus 有图增强加成（PageRank×0.2 + structural×0.2），cognition 没有 → 分数尺度不同
- 即使用 RRF（基于排名），同等语义相关度下 corpus 排名系统性靠前
- 根本原因：code 和 fact 不是竞争关系，是互补维度。跨类型排序 = 比较苹果和橘子

内部流程：
1. 并行搜索：corpus(search.py) + cognition(search_cog.py) + tactics + models
2. 各维度独立搜索、独立排序 — RRF 只在每个后端内部（BM25 rank + vector rank → 融合）
3. 图增强：通过 scope_files 桥接，code 结果附带符号信息
4. 内容填充：chunk 前 500 字符 + source/section，fact 完整 assertion
5. L0 hint 生成：规则模板提示 AI 先看哪个维度（如 "发现 2 条相关经验"）
6. 降级：无 embedder → BM25；无 cognition → code-only（facts/tactics/model 为空）

### 5.4 降级矩阵

| 场景 | recall 行为 |
|------|------------|
| ONNX + cognition 可用 | corpus(BM25+vec) + cognition(BM25+vec) → RRF 融合 |
| 无 embedder | corpus(BM25) + cognition(BM25) → RRF 融合 |
| cognition 未启用 | corpus-only（等同于当前 docs_search） |
| 全部不可用 | 返回空结果 + hint |

## 6. 实现计划

### Task 清单

| # | 任务 | 文件 | 预估行数 | 依赖 |
|---|------|------|---------|------|
| 1 | unified_recall() 统一搜索函数 | `unified_search.py`（新建） | ~150 | 无 |
| 2 | `recall` MCP 工具 + hooks orient 升级 | `mcp_server.py` + `hooks.py` | ~80 | T1 |
| 3 | 移除 12 个旧 MCP 工具 | `mcp_server.py` | -300 | T2 |
| 4 | `status` 简化（合并 corpus+cognition 信息） | `mcp_server.py` | ~20 | T3 |
| 5 | 测试 | `test_unified_search.py`（新建）+ 修改现有测试 | ~200 | T1-T4 |
| 6 | 文档更新 | CLAUDE.md + MEMORY.md | ~30 | T5 |

### 依赖关系

```
Task 1 (unified_search.py)
  → Task 2 (recall MCP + hooks 升级)
    → Task 3 (移除旧工具)
      → Task 4 (status 简化)
        → Task 5 (测试)
          → Task 6 (文档)
```

## 7. 验证方式

1. `pytest tests/test_unified_search.py -v` — 统一搜索单元测试
2. `pytest tests/test_mcp_cog.py -v -k recall` — recall MCP 测试
3. `pytest tests/ -x -q` — 全量回归（662+ tests，预计因移除工具减少部分旧测试）
4. Code review（index1 专用审查）

## 8. 记录

- 2026-02-10: 开源调研完成，方案讨论确定
- 核心决策：去掉 think、移除而非 deprecated、hooks 是 recall 主要消费者
- 最坏假设原则贯穿：所有设计默认 Claude 0% 主动调用
