Skip to main content
Glama
README.md
# kb-mcp-server

自托管知识库的 TypeScript/Node monorepo。文档经分块与向量化后存入本地 Chroma;AI 客户端通过 **MCP**(stdio 或 Streamable HTTP)进行语义检索,文档的上传与管理走 **REST API / CLI / Web 管理界面**。

## 功能概览

| 能力 | 状态 |
|------|------|
| 文档入库(txt / markdown / 文本层 PDF) | ✅ |
| CherryIn 嵌入(`qwen/qwen3-embedding-8b`) | ✅ |
| 本地 Chroma 向量存储 | ✅ |
| REST API(上传、列表、搜索、健康检查) | ✅ |
| MCP 工具 `search_knowledge` / `read_around` / `read_file`(stdio + HTTP) | ✅ |
| 两阶段检索 + Qwen3 Rerank(`qwen/qwen3-reranker-0.6b`) | ✅ v1.4 |
| Web 管理界面、JWT 登录、可选 API Key(CLI/服务) | ✅ Phase 4–8 |
| MCP 按用户文档隔离(JWT / API_KEY) | ✅ v1.5 |

## 架构

```
┌─────────────┐     ingest      ┌──────────────┐     embed      ┌─────────┐
│ CLI / REST  │ ──────────────► │ @kb/core     │ ─────────────► │ Chroma  │
│  (backend)  │                 │ Ingestion    │                │ :8000   │
└─────────────┘                 └──────────────┘                └────▲────┘
                                                                   │
┌─────────────┐     search      ┌──────────────┐     rerank     ┌──────────┐
│ MCP Client  │ ──────────────► │ SearchService│ ─────────────► │ CherryIn │
│ (stdio/HTTP)│                 │ recall→rank  │                │ rerank   │
└─────────────┘                 └──────────────┘                └──────────┘
                                       │
                                       ▼
                                  ┌─────────┐
                                  │ Chroma  │
                                  └─────────┘
```

| 端口 | 服务 | 说明 |
|------|------|------|
| 8000 | Chroma | 向量数据库(HTTP sidecar) |
| 3000 | Backend | Fastify REST API + Swagger UI;生产/静态模式下同时托管 Web SPA |
| 5173 | Web(开发) | Vite 开发服务器(HMR);`/api` 代理到 `:3000` |
| 3100 | MCP HTTP | Streamable HTTP,`POST /mcp` |
| — | MCP stdio | Cursor 等本地客户端,无端口 |

MCP 层**仅负责检索**;入库、删除等管理操作通过 Backend 或 CLI 完成。

## 检索与 Rerank(v1.4)

搜索采用**两阶段检索**,由 `SearchService` 统一处理(REST `POST /api/v1/search` 与 MCP `search_knowledge` 共用同一路径):

1. **向量召回** — 用 `qwen/qwen3-embedding-8b` 嵌入查询,从 Chroma 取 `RERANK_CANDIDATES` 条候选(默认 30)
2. **精排** — 调用 CherryIn `qwen/qwen3-reranker-0.6b`(免费),对候选 chunk 全文重排,返回最终 `top_k`(默认 5,最大 10)

JWT 用户搜索时,在 rerank 前会按用户文档 ACL 过滤候选。若 `RERANK_ENABLED=false` 或 rerank API 不可用,自动回退为纯向量排序,搜索不会失败。

返回的 `score` 在启用 rerank 时为 rerank 相关度分数;回退时为向量余弦相似度。

## 环境要求

- **Node.js** 24.x
- **pnpm** 11.x
- **Python 3**(Windows 上运行 Chroma:`pnpm setup:chroma` 安装 `chromadb`)
- **CherryIn API Key**([open.cherryin.cc](https://open.cherryin.cc))

## 快速开始

### 1. 安装依赖

```bash
git clone https://github.com/laneinrain/kb-mcp-server.git
cd kb-mcp-server
pnpm install
```

`better-sqlite3` 使用仓内 [`vendor/better-sqlite3`](vendor/better-sqlite3/) 预编译包(Linux/Windows x64 + Node 24 ABI 137),`postinstall` 自动解压;私网安装无需访问 GitHub Releases。

`concurrently@10.0.1` 使用仓内 [`vendor/concurrently/concurrently-10.0.1.tgz`](vendor/concurrently/)(`package.json` 的 `file:` 依赖),私网 `pnpm install` 无需从公网拉取该包。

### 2. 配置环境变量

```bash
cp .env.example .env
# 编辑 .env,至少填写 CHERRYIN_API_KEY
```

### 3. 安装 Chroma(Windows 推荐 Python 方式)

```bash
pnpm setup:chroma
```

### 4. 构建并启动开发环境

```bash
pnpm build
pnpm dev
```

`pnpm dev` 会依次启动 Chroma、Backend(`:3000`)、MCP HTTP(`:3100`)和 **Web 动态前端**(Vite `:5173`)。日常开发在浏览器打开 **http://localhost:5173**。

### 5. 入库文档

```bash
pnpm ingest path/to/document.pdf
pnpm ingest path/to/notes.md --collection default
```

或通过 REST 上传:

```bash
curl -F "file=@./scripts/fixtures/sample.md" http://127.0.0.1:3000/api/v1/documents
```

### 6. 验证搜索

REST:

```bash
curl -X POST http://127.0.0.1:3000/api/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "your question", "topK": 5}'
```

健康检查:

```bash
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/health/chroma
curl http://127.0.0.1:3000/health/embeddings
```

API 文档:http://127.0.0.1:3000/docs

## Web 管理界面:动态前端 vs 静态前端

Web 应用(`@kb/web`)有两种运行方式:

| | **动态前端(开发)** | **静态前端(生产 / 一体化)** |
|---|---|---|
| **是什么** | Vite 开发服务器,支持热更新(HMR) | 先 `vite build` 生成静态文件,由 **Backend 托管** |
| **访问地址** | http://localhost:5173 | http://127.0.0.1:3000 |
| **API** | Vite 将 `/api` **代理**到 Backend `:3000` | 与 REST API **同端口**(Backend 同时提供 API + SPA) |
| **何时启用** | 默认 `pnpm dev` | `NODE_ENV=production` 或 `SERVE_WEB=true` |

```
开发(动态):
  浏览器 → :5173 (Vite) ──proxy /api──→ :3000 (Backend)

生产/静态:
  浏览器 → :3000 (Backend)
              ├── /api/v1/*     REST
              └── /*            apps/web/dist 静态 SPA
```

### 动态前端(开发,日常推荐)

```bash
# 全栈:Chroma + Backend + MCP + Web(Vite :5173)
pnpm dev

# 仅 Web 动态前端(需 Backend 已在 :3000 运行)
pnpm dev:web
# 等价于:
pnpm --filter @kb/web dev
```

浏览器打开 **http://localhost:5173** 登录并使用管理界面。

### 静态前端(构建后由 Backend 托管)

```bash
# 构建 Web 静态资源 → apps/web/dist/
pnpm --filter @kb/web build

# 全量构建(turbo 会先 build @kb/web 再 build @kb/backend)
pnpm build
```

**一键生产/一体化启动(推荐):**

```bash
pnpm start:prod
```

等价于:`pnpm build` → 设置 `NODE_ENV=production` → 启动 Chroma + Backend(`:3000` 托管 API + 静态 Web)+ MCP HTTP(`:3100`)。

已构建过可跳过编译:

```bash
pnpm start:prod -- --skip-build
```

浏览器打开 **http://127.0.0.1:3000**(页面与 API 同端口)。

**手动分步(可选):**

```bash
# 方式 A:开发环境验证「单端口部署」(PowerShell)
pnpm --filter @kb/web build
$env:SERVE_WEB="true"; pnpm --filter @kb/backend dev

# 方式 B:仅 Backend 生产模式(需自行启动 Chroma / MCP)
pnpm build
$env:NODE_ENV="production"; pnpm --filter @kb/backend start
```

Linux / macOS 将 `$env:SERVE_WEB="true"` 换成 `SERVE_WEB=true` 前缀即可。

> 默认只运行 `pnpm --filter @kb/backend dev` 且未设置 `SERVE_WEB` 时,`:3000` **仅提供 REST API**,不提供 Web 界面。

### 辅助命令(可选)

```bash
# 预览已 build 的 dist(Vite 自带,默认 :5173,通常不含 /api 代理)
pnpm --filter @kb/web preview
```

### 场景速查

| 场景 | 命令 | 浏览器打开 |
|------|------|------------|
| 改 Web UI、本地上传/搜索测试 | `pnpm dev` | http://localhost:5173 |
| 验证单端口部署形态 | `pnpm start:prod` 或 `SERVE_WEB=true` 启动 backend | http://127.0.0.1:3000 |
| 正式构建产物 | `pnpm start:prod` | Backend :3000 + MCP :3100 |

## MCP 配置

MCP 暴露三个检索工具:

| 工具 | 说明 |
|------|------|
| `search_knowledge` | 语义搜索;参数 `query`(必填)、`top_k`(1–10)、`collection` |
| `read_around` | 按 `document_id` + `chunk_index` 展开邻近块 |
| `read_file` | 按 `document_id` 读取有界全文(块序) |

### MCP 用户隔离(v1.5)

当 `USER_AUTH_ENABLED=true` 且 `MCP_AUTH_REQUIRED=true`(默认)时,MCP 与 REST 一样按用户隔离文档:

| 凭据 | 文档范围 |
|------|----------|
| JWT(`Authorization: Bearer <accessToken>`) | 自己的文档 + 系统共享历史文档 |
| `API_KEY`(`AUTH_ENABLED=true`) | 全局(服务账号) |
| 无凭据 | HTTP 401 / stdio 启动失败 |

JWT 可通过 Web 登录或 `POST /api/v1/auth/login` 获取。

逃生阀:`MCP_AUTH_REQUIRED=false` 时 MCP 保持全局语料(不要求 token),REST/Web 仍可按 JWT 隔离。

### 方式一:stdio(Cursor 本地,推荐)

**推荐:使用仓库内项目级配置**(已包含在 [`.cursor/mcp.json`](.cursor/mcp.json),无绝对路径):

```json
{
  "mcpServers": {
    "kb-mcp-server": {
      "command": "pnpm",
      "args": ["--filter", "@kb/mcp-server", "dev:stdio"],
      "env": {
        "DOTENV_CONFIG_QUIET": "true",
        "MCP_USER_TOKEN": "<jwt>"
      }
    }
  }
}
```

当 `USER_AUTH_ENABLED=true` 且 `MCP_AUTH_REQUIRED=true` 时,必须设置 `MCP_USER_TOKEN`(进程级,单用户)。关闭用户认证或设置 `MCP_AUTH_REQUIRED=false` 时可省略。

用 Cursor 打开本仓库根目录即可;工作区根目录即 `cwd`,会自动加载根目录 `.env`。

**生产/已构建**(需先 `pnpm --filter @kb/mcp-server build`):

```json
{
  "mcpServers": {
    "kb-mcp-server": {
      "command": "node",
      "args": ["apps/mcp-server/dist/stdio.js"],
      "env": {
        "DOTENV_CONFIG_QUIET": "true",
        "MCP_USER_TOKEN": "<jwt>"
      }
    }
  }
}
```

> **说明**
>
> - 配置放在 **项目** `.cursor/mcp.json`,不要写 `D:/...` 或 `node.exe` 绝对路径。
> - 全局 `~/.cursor/mcp.json` 仅适合与路径无关的 HTTP 方式(见方式二);stdio 请用项目级配置。
> - Windows 若 Cursor 找不到 `pnpm`/`node`:将 Node 安装目录加入系统 PATH,或从「以管理员身份运行」的终端启动 Cursor。

### 方式二:Streamable HTTP(远程 / 多客户端)

先运行 `pnpm dev` 或单独启动 MCP HTTP:

```bash
pnpm --filter @kb/mcp-server dev
```

Cursor MCP 配置示例(用户认证开启时带 Bearer):

```json
{
  "mcpServers": {
    "kb-mcp-server": {
      "url": "http://127.0.0.1:3100/mcp",
      "headers": {
        "Authorization": "Bearer <jwt-or-api-key>"
      }
    }
  }
}
```

每个 `/mcp` 请求都会校验 Bearer;会话内 SSE(`GET /mcp`)同样需要有效 token。

> 浏览器直接访问未带 session 的 `GET /mcp` 可能返回 405/401,这是预期行为;请用 MCP 客户端连接。

## 环境变量

完整示例见 [`.env.example`](.env.example)。常用项:

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `CHERRYIN_API_KEY` | — | CherryIn 嵌入 API 密钥(必填) |
| `CHROMA_HOST` / `CHROMA_PORT` | `localhost` / `8000` | Chroma 地址 |
| `BACKEND_HOST` / `BACKEND_PORT` | `127.0.0.1` / `3000` | REST API |
| `MCP_HTTP_HOST` / `MCP_HTTP_PORT` | `127.0.0.1` / `3100` | MCP HTTP |
| `MCP_AUTH_REQUIRED` | `true` | `USER_AUTH_ENABLED=true` 时是否要求 MCP 鉴权;`false` 时 MCP 全局 |
| `MCP_USER_TOKEN` | — | stdio 进程环境变量(JWT);非 AppConfig 字段,见 `.env.example` |
| `DEFAULT_COLLECTION` | `default` | 默认向量集合 |
| `EMBEDDING_MODEL` | `qwen/qwen3-embedding-8b` | 嵌入模型 |
| `RERANK_ENABLED` | `true` | 是否启用两阶段 rerank |
| `RERANK_CANDIDATES` | `30` | 向量召回条数(1–50),再精排为 `top_k` |
| `RERANK_MODEL` | `qwen/qwen3-reranker-0.6b` | CherryIn rerank 模型(与嵌入共用 API Key) |
| `CHUNK_SIZE` / `CHUNK_OVERLAP` | `1024` / `154` | 分块参数 |
| `DATA_DIR` | `./data` | 本地数据目录(SQLite、上传缓存) |
| `AUTH_ENABLED` | `false` | 全局 API Key(CLI/服务访问) |
| `USER_AUTH_ENABLED` | `false` | Web 工号登录 + JWT;与 `AUTH_ENABLED` 独立 |

### 鉴权矩阵

| 客户端 | 凭据 | 文档范围 |
|--------|------|----------|
| **Web 管理界面** | JWT(`POST /api/v1/auth/login`) | 自己的文档 + 系统共享历史文档 |
| **CLI / 自动化** | `AUTH_ENABLED` + `API_KEY`(Bearer) | 全局(服务账号) |
| **MCP HTTP** | `Authorization: Bearer` JWT 或 `API_KEY` | JWT → 用户文档集;API_KEY → 全局 |
| **MCP stdio** | 环境变量 `MCP_USER_TOKEN`(JWT) | 同上(进程绑定单用户) |

当 `USER_AUTH_ENABLED=true` 时,Web 必须使用 JWT 登录;CLI 需同时设置 `AUTH_ENABLED=true` 与 `API_KEY` 才能通过 REST 入库。MCP 默认同步要求鉴权(`MCP_AUTH_REQUIRED=true`)。

**Mock 模式(`CAS_MOCK=true`)额外能力(v1.3):**

- 管理员:工号 `00000` / 密码 `admin123`(启动时自动创建,仅脚手架)
- 注册:`POST /api/v1/auth/register`(本地用户,bcrypt,密码至少 8 位)
- JWT 含 `role: admin | user`;JIT CAS 用户仍任意非空密码首次登录
- 管理 API(需 `role=admin` JWT 或 `API_KEY`):
  - `GET /api/v1/admin/users` — 用户列表 + 文档数
  - `GET /api/v1/admin/users/:userId/documents` — 指定用户文档
  - `GET /api/v1/admin/documents/:documentId` — 任意文档详情
  - `DELETE /api/v1/admin/documents/:documentId` — 删除任意文档
  - `POST /api/v1/admin/users/:userId/documents` — 代用户上传

**切勿将 `.env` 提交到 Git。**

## REST API 摘要

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/health` | 服务存活 |
| `GET` | `/health/chroma` | Chroma 连通性 |
| `GET` | `/health/embeddings` | 嵌入 API 连通性 |
| `POST` | `/api/v1/documents` | 上传文档(multipart) |
| `GET` | `/api/v1/documents` | 列出已入库文档 |
| `GET` | `/api/v1/documents/:id` | 文档详情 |
| `DELETE` | `/api/v1/documents/:id` | 删除文档及向量 |
| `POST` | `/api/v1/search` | 语义搜索 |

## 项目结构

```
kb-mcp-server/
├── apps/
│   ├── backend/          # Fastify REST API(可选托管 Web 静态资源)
│   ├── web/              # React 管理界面(Vite)
│   ├── cli/              # 命令行入库
│   └── mcp-server/       # MCP stdio + Streamable HTTP
├── packages/
│   ├── auth/             # 用户认证(JWT / Mock CAS)
│   ├── config/           # 环境变量与常量
│   └── core/             # 入库、检索、Chroma、嵌入客户端
├── scripts/
│   ├── ingest.ts         # CLI 入库
│   ├── start-chroma.ts   # 启动 Chroma sidecar
│   └── wait-for-chroma.ts
├── .env.example
└── package.json          # pnpm workspace 根
```

## 开发与测试

```bash
# 全量构建
pnpm build

# 全量测试
pnpm test

# 单独启动各服务
pnpm --filter @kb/backend dev
pnpm dev:web                              # Web 动态前端 :5173
pnpm --filter @kb/mcp-server dev          # HTTP :3100
pnpm --filter @kb/mcp-server dev:stdio    # stdio

# 等待 Chroma 就绪
pnpm wait:chroma
```

## 路线图

- **Phase 1–8**(已完成):平台基础、REST 检索、MCP、Web 管理、用户 JWT 多租户
- **Phase 9+**(规划中):文件名内容哈希去重等

## 许可证

尚未指定开源许可证。如需公开协作,请自行添加 `LICENSE` 文件。