# app-knowledge-mcp — 聚合 MCP

聚合云端知识库与本地 `local_knowledge/` 的统一 MCP 网关。云端能力全部平替 [xtc_knowledge_engine](http://172.28.1.116:9999/App/xtc_knowledge_engine)，同时新增本地语义检索。

## 与云端 MCP 的异同

| 维度 | 云端 MCP (xtc_knowledge_engine) | 聚合 MCP (本项目) |
|------|-------------------------------|-------------------|
| 架构 | Gateway → 3 微服务 (retrieval/rule/skill) | FastMCP stdio 本地进程 |
| 知识检索 | FTS5 + 同义词扩展，直连 retrieval:18001 | 并行直连 retrieval + skill，逻辑完全一致 |
| Skill 检索 | triggers 打分，直连 skill:18003 | 同上 |
| 规则校验 | K2 关键词匹配，直连 rule:18002 | 代理云端 MCP |
| 知识同步 | profile 匹配，直连 retrieval:18001 | 代理云端 MCP |
| 本地检索 | ❌ 无 | ✅ 词法 OR + bge-small-zh-v1.5 语义 embedding |
| 内置模型 | ❌ 无 | ✅ 90MB ONNX，零下载 |
| 工具数量 | 4 个（search/skill_get/rule_check/knowledge_sync） | 9 个（+ cloud_search/local_search/knowledge_get/local_read 等） |
| 获取全文 | 脚本 `fetch_doc.py` → 读缓存 | MCP 工具 `knowledge_get(doc_id)` 直接返回 |
| Skill 文件 | `fetch_doc.py --skill-id` 两步 | `skill_get(include_prompt=True)` 一步并行 |
| 平台命令 | 无 | `/k-search` 双检索命令（OpenCode / Claude Code / Cursor） |
| 安装方式 | 脚本拷贝 → pip install | `pip install xtc-app-knowledge-mcp` + `app-knowledge-mcp init` |
| 传输方式 | streamable-http (:18000) | stdio 本地进程 |
| 安装模式 | — | `pip install -e` 可编辑模式，改源码即时生效，无需重装 |

**在线时效果一致**，聚合 MCP 的云端能力完全平替云端 MCP。离线时聚合 MCP 保留本地检索。

## 工具清单

### 云端工具（5 个）

| 工具 | 触发时机 | 说明 |
|------|---------|------|
| `cloud_search` | 每次知识检索必须调用 | 并行直连 retrieval + skill，返回知识文档 + Skill 候选 |
| `skill_get` | search 返回 skill_id 后 | 获取 Skill 详情，include_prompt 时并行拉取全量文件 |
| `rule_check` | 每次代码修改完成后 | 对 diff 进行 K2/K5/K7 编码规范校验 |
| `knowledge_sync` | 本地缓存缺失或过期时 | 按 profile 批量同步云端知识文档 |
| `knowledge_get` | search 返回云端 doc_id 后 | 获取云端知识文档全文 |

### 本地工具（4 个）

| 工具 | 触发时机 | 说明 |
|------|---------|------|
| `local_search` | 用户提到任何业务功能、模块、接口、领域问题时可调用 | 词法 OR + bge-small-zh-v1.5 语义混合检索 |
| `local_read` | local_search 返回 file_path 后 | 读取本地文件全文 |
| `local_list_domains` | 了解本地域覆盖时 | 列出 `local_knowledge/` 下所有业务域及概览 |
| `local_list_files` | 浏览本地文件时 | 列出所有知识文件路径 |

## 调用规则

**当用户提到任何业务功能、模块、接口、领域相关问题时（如“查一下上课禁用的接口”、“健康模块怎么用”、“睡眠模式的API”），即可触发 `local_search`**。
建议先拆成多个词后再传入，以提升检索效果。需要同时检索云端知识时，再配合调用 `cloud_search`。

```
cloud_search(query, ...) / local_search(query)
  → 查看元信息 → 判断相关性
  → knowledge_get(doc_id) 获取云端全文
  → local_read(path) 获取本地全文
  → skill_get → rule_check → 重复直到 passed
```

## 架构

```mermaid
flowchart TB
    subgraph USER["用户 / LLM"]
        CMD["/k-search 命令"]
        INS["instructions（云端+本地描述）"]
        TOOLS["tools/list（9 个工具）"]
        CMD --> LLM["LLM 触发"]
        INS --> LLM
        TOOLS --> LLM
    end

    subgraph GATEWAY["knowledge-gateway · FastMCP stdio"]
        MW["LoggingMiddleware"]
        MW --> LOG["→ .knowledge/logs/mcp-calls.log"]

        subgraph CLOUD_TOOLS["云端工具（5 个）"]
            CS["cloud_search(query, platform, domain, ...)"]
            SG["skill_get(skill_id, include_prompt)"]
            RC["rule_check(content, rule_types)"]
            KS["knowledge_sync(repo_id, profile)"]
            KG["knowledge_get(doc_id)"]
        end

        subgraph LOCAL_TOOLS["本地工具（4 个）"]
            LS["local_search(query)"]
            LR["local_read(path)"]
            LD["local_list_domains()"]
            LF["local_list_files()"]
        end

        subgraph RESOURCES["资源（4 个）"]
            R1["knowledge://local/files"]
            R2["knowledge://local/domains"]
            R3["knowledge://local/meta/{path*}"]
            R4["knowledge://local/{path*}"]
        end
    end

    subgraph CLOUD["云端后端"]
        RET["retrieval :18001/search\nFTS5 · 同义词扩展 · bm25 排序\nmetadata 过滤 · LIKE 兜底"]
        SKL["skill :18003/search\ntriggers 多维打分\nkeywords · filePatterns · changeTypes"]
        DOC["retrieval :18001/doc\nSQLite → GitLab fallback"]
        RUL["rule :18002/check\nK2 关键词匹配"]
        SYNC["retrieval :18001/sync-profile-docs\nprofile 展开 · diff 比对"]
    end

    subgraph LOCAL["本地 local_knowledge/"]
        LEX["词法 OR 召回\nfm 4x · body 1x"]
        SEM["语义召回\nbge-small-zh-v1.5 embedding\ncosine similarity"]
        MERGE["blend 排序\n40% 词法 + 60% 语义\nsem-only ×0.7 discount"]
    end

    LLM -->|"每次必调"| CS
    LLM -->|"每次必调"| LS
    LLM --> SG
    LLM --> RC
    LLM --> KS
    LLM --> KG
    LLM --> LR
    LLM --> LD
    LLM --> LF
    LLM --> R1
    LLM --> R2
    LLM --> R3
    LLM --> R4

    CS -->|"asyncio.gather 并行"| RET
    CS -->|"return_exceptions=True"| SKL
    KG --> DOC
    RC --> RUL
    KS --> SYNC
    SG -->|"MCP 代理"| SKL

    LS --> LEX --> MERGE
    LS --> SEM --> MERGE

    MW --> CS
    MW --> LS
    MW --> KG
    MW --> SG
    MW --> RC
    MW --> KS
    MW --> LR
    MW --> LD
    MW --> LF
```

### 安装/初始化/更新流程

```mermaid
flowchart LR
    subgraph INSTALL["安装包 · python3 -m pip install xtc-app-knowledge-mcp"]
        I1["安装 CLI / MCP 服务端 / 模板 / 依赖"]
    end

    subgraph INIT["项目初始化 · app-knowledge-mcp init /path/to/project"]
        P1["平台检测\nflutter/android/ios/harmonyos"] --> P2["创建 local_knowledge/"]
        P2 --> P3["创建 .knowledge/profile.yaml + user.json"]
        P3 --> P4["部署 dev-toolkit 模板"]
        P4 --> P5["创建 .opencode/agents/ 软链接"]
        P5 --> P6["创建 .opencode/skills/ 软链接"]
        P6 --> P7["写入 opencode.json / .mcp.json"]
        P7 --> P8["写入 /k-search 命令和 Cursor 规则"]
        P8 --> P9["首次 local embedding 落盘检测\n无产物则全量构建"]
    end

    subgraph RUNTIME["MCP 启动 · python3 -m app_knowledge_mcp.server"]
        R1["ensure_project_ready 自修复"] --> R2["模型预热"]
        R2 --> R3["索引检测\n无改动跳过"]
        R3 --> R4["有增量则后台更新\n写入 index_event 日志"]
    end

    subgraph UPDATE["包更新 · python3 -m pip install -U xtc-app-knowledge-mcp"]
        U1["升级 Python 包版本"] --> U2["下次 init / MCP 启动时自动补模板"]
    end

    subgraph UNINSTALL["卸载现状"]
        X1["支持 disable：移除全局 opencode 配置"] --> X2["当前无完整项目 uninstall 子命令"]
    end
```

### 本地语义检索管线

```mermaid
flowchart TD
    Q["query: '摔倒 SOS'"] --> T["分词: ['摔倒', 'sos']"]
    T --> LEX["词法召回\n扫描 local_knowledge/ 所有 .md"]
    T --> SEM_EMB["语义 embedding\nbge-small-zh-v1.5 → 512d"]
    
    LEX --> LEX_MATCH{"关键词命中?"}
    LEX_MATCH -->|"有候选"| CAND["候选列表\nfile_path · name · relevance"]
    LEX_MATCH -->|"无候选"| CAND_EMPTY["candidates = []"]
    
    SEM_EMB --> SEM_ALL["全量 cosine similarity\n遍历所有文件"]
    SEM_ALL --> SEM_SCORE["{file_path: similarity}"]

    CAND --> BLEND["blend 融合\n40% lex_norm + 60% sem"]
    CAND_EMPTY --> SEM_ONLY["纯语义构建\nsem ≥ 0.2 阈值"]
    SEM_SCORE --> BLEND
    SEM_SCORE --> SEM_ONLY

    BLEND --> SORT["sort by relevance"]
    SEM_ONLY --> SORT
    SORT --> TOPK["top_k 截断 → results"]
```


## 快速接入

```bash
# 1) 安装 Python 包
python3 -m pip install --break-system-packages xtc-app-knowledge-mcp

# 2) 初始化某个业务项目
app-knowledge-mcp init /path/to/your-project

# 3) 可选：注册全局 MCP（进任意项目都能自动启动）
app-knowledge-mcp enable

# 4) 可选：移除全局 MCP
app-knowledge-mcp disable

# 5) 更新包版本
python3 -m pip install --break-system-packages -U xtc-app-knowledge-mcp
```

当前流程说明：
1. `pip install xtc-app-knowledge-mcp` 只负责把 CLI、MCP 服务端、模板和依赖装进 Python 环境
2. `app-knowledge-mcp init <project_dir>` 才会把某个业务项目接入为可用状态
3. `init` 会创建 `.knowledge/`、`local_knowledge/`、MCP 配置、命令文件和 dev-toolkit 模板
4. `init` 结束后会立刻执行一次本地 embedding 落盘检测，无产物则全量构建
5. 后续每次 MCP 启动会继续做索引检测：无改动跳过，有增量则后台更新

## 性能特性

- **异步日志**：`_log_call` 通过 `asyncio.Queue` 推送，后台 worker 批量写文件，工具调用不阻塞
- **文件缓存**：`local_search` 按 mtime+size 缓存文件内容，同会话重复搜索不读盘
- **语义模型预热**：服务启动时加载 bge-small-zh-v1.5 ONNX 模型，首次搜索免等待
- **落盘索引**：`init` 后首次构建 `/.knowledge/cache/local_embeddings/`，后续查询优先读 `vectors.npy`
- **后台增量更新**：MCP 启动时有索引则只做快速检测，无改动跳过，有增量在后台更新，不阻塞服务可用
- **索引事件日志**：后台索引刷新会写入 `.knowledge/logs/mcp-calls.log`，记录开始/结束/失败、重建数量和耗时
- **manifest 缓存**：`.knowledge/manifest.json` 带 mtime 校验，避免每次搜索读磁盘

## 手动配置

`opencode.json`:

```json
{
  "mcp": {
    "knowledge-gateway": {
      "type": "local",
      "command": ["python3", "-m", "app_knowledge_mcp.server"],
      "enabled": true
    }
  }
}
```

## 项目结构

```
app-knowledge-mcp-repo/
├── app_knowledge_mcp/
│   ├── __init__.py
│   ├── server.py              # FastMCP 入口，9 工具 + 4 资源 + 异步日志 + _return_timed
│   ├── cloud_client.py        # 云端直连（retrieval+skill 并行 REST）
│   ├── local_search.py        # 词法 OR + 语义 embedding 混合检索（带文件内容缓存）
│   ├── local_embedding.py     # fastembed 封装 + 落盘向量索引（全量/增量构建）
│   ├── frontmatter.py         # YAML frontmatter 解析
│   ├── installer.py           # init / enable / disable / 自修复逻辑
│   ├── models/                # 90MB 内置 ONNX 模型
│   └── templates/
│       └── dev-toolkit/       # 部署到项目的 dev-toolkit 模板
│           ├── agents/        # Agent 定义（lee, kai, nio 等）
│           └── skills/        # Skill 定义（ai-concept-crafter 等）
├── docs/
│   └── local-embedding-disk-index-design.md
├── tests/
│   └── test_local_embedding_index.py
└── pyproject.toml             # 依赖：fastmcp + fastembed + httpx + pyyaml
```

## 依赖

- Python >= 3.11
- fastmcp >= 0.1.0
- fastembed >= （hard dependency，内置模型无需下载）
- httpx >= 0.27.0
- pyyaml >= 6.0
