Skip to main content
Glama
andykair55-byte

netdisk-organizer-mcp

README.md
# netdisk-organizer-mcp

> **官方是地基,本 MCP 是二楼。**

百度官方已把"碰网盘"做成了标准 MCP 数据层(`baidu-netdisk`,SSE: `https://mcp-pan.baidu.com/sse`)。
本项目**不重新实现任何网盘协议**,只在官方数据层之上叠加三层整理智能:

- **语义层**:`pan_scan` / `pan_events` / `pan_ask` —— 事件聚类 + 跨目录认领孤儿照片,补官方 `file_keyword_search` 答不了"去年暑假度假的照片在哪"的缺口
- **安全层**:不提供 delete tool;`pan_move` 内置 `protect_patterns` 拦截(珍藏/合同/发票直接拒绝);journal 自动留痕 + `pan_undo` 可回滚
- **个性化层**:`pan_profile` + 可移植的 `profiles/` 偏好文件,各用户各改各的,互不干扰

---

## 目录

- [快速开始](#1-快速开始)
- [架构](#2-架构)
- [环境要求](#3-环境要求)
- [安装](#4-安装)
- [配置](#5-配置)
- [工具清单(21 个)](#6-工具清单21-个)
- [个性化](#7-个性化)
- [安全模型](#8-安全模型)
- [获取 Access Token](#9-获取-access-token)
- [项目结构](#10-项目结构)
- [开发与测试](#11-开发与测试)
- [License](#12-license)

---

## 1. 快速开始

```bash
# 1. 安装
git clone <your-repository-url>          # 替换为你的仓库地址
cd netdisk-organizer-mcp
pip install -e .

# 2. 配置 token(见「获取 Access Token」)
export BAIDU_NETDISK_ACCESS_TOKEN="你的AccessToken"
export NETDISK_BACKEND="official"        # 默认值,可省略

# 3. 启动 MCP server(stdio)
python server.py
```

然后在你的 MCP 客户端(WorkBuddy / Trae / Claude / Cursor 等)里注册连接器,即可开始使用。

---

## 2. 架构

```
┌─────────────────────────────────────────────────────────────┐
│  你的 agent(WorkBuddy / Trae / 任何标准 MCP 客户端)          │
└───────────────┬─────────────────────────────────────────────┘
                │  stdio MCP
┌───────────────▼─────────────────────────────────────────────┐
│  netdisk-organizer-mcp  (本仓库 · 二楼 · 整理智能)          │
│   pan_scan/events/ask/stat/issues  ·  pan_move/undo/history  │
│   pan_profile  ·  pan_mkdir/save/quota/semantic              │
└───────────────┬─────────────────────────────────────────────┘
                │  委托(OfficialDataClient / BaiduBackend)
┌───────────────▼─────────────────────────────────────────────┐
│  官方数据层(地基 · 百度维护 · 标准 MCP)                     │
│   file_list / file_move / upload_by_url / get_quota /        │
│   file_semantics_search / make_dir ...                       │
└─────────────────────────────────────────────────────────────┘
```

**边界原则**:本仓库只做"整理",绝不复刻"增删改查"。任何纯数据 I/O 都委托给官方数据层。

| 模块 | 职责 |
|---|---|
| `server.py` | MCP stdio server 入口,工具路由 |
| `index.py` | 语义层:扫描 → 打标 → 事件聚类 → 结构化检索 |
| `ops.py` | 安全层:move / undo / history,journal 留痕 |
| `io_ops.py` | 数据层薄封装:委托官方 file_* 工具 |
| `backend.py` | 后端抽象:OfficialDataClient / BaiduBackend / LocalBackend |
| `profiles/` | 可移植整理偏好(JSON),加载 + 严格校验 |

---

## 3. 环境要求

| 依赖 | 版本 |
|---|---|
| Python | ≥ 3.12 |
| `mcp` | ≥ 1.0.0 |
| `httpx` | ≥ 0.27.0 |
| `anyio` | ≥ 4.0.0 |
| `python-dotenv` | ≥ 1.0.0 |

sdk 兜底后端(`NETDISK_BACKEND=sdk`)额外需要:`urllib3` / `certifi` / `six` / `python-dateutil`。

---

## 4. 安装

```bash
git clone <your-repository-url>
cd netdisk-organizer-mcp
pip install -e .          # 或 uv sync
# 若用 sdk 兜底后端(NETDISK_BACKEND=sdk):
pip install -e ".[sdk]"
```

---

## 5. 配置

本 server 是**标准 MCP stdio server**,`mcpServers` 的 JSON schema 所有客户端通用 —— 差异只在配置文件存放路径(各客户端约定),不在本项目的分叉。

```json
{
  "mcpServers": {
    "netdisk-organizer": {
      "command": "python",
      "args": ["/absolute/path/to/netdisk-organizer-mcp/server.py"],
      "env": {
        "BAIDU_NETDISK_ACCESS_TOKEN": "你的AccessToken",
        "NETDISK_BACKEND": "official",
        "NETDISK_DATA_MCP_URL": "https://mcp-pan.baidu.com/sse"
      }
    }
  }
}
```

| 客户端 | 配置文件位置 |
|---|---|
| **WorkBuddy** | 设置 → 连接器 → 添加自定义连接器(stdio 模式) |
| **Trae** | 项目根 `.trae/mcp.json` 或 `~/.trae/mcp.json` |
| **Claude** | `.mcp.json` |
| **Cursor** | `~/.cursor/mcp.json` |
| 其他标准 MCP 客户端 | 同一段 JSON,仅文件路径不同 |

> 若用 `uv` 管理:把 `command` 改为 `uv`,`args` 改为 `["--directory","/absolute/path/to/netdisk-organizer-mcp","run","python","server.py"]`。

### 环境变量

| 变量 | 取值 | 说明 |
|---|---|---|
| `NETDISK_BACKEND` | `official`(默认)/ `sdk` | official=连官方数据层;sdk=vendored BaiduBackend 兜底 |
| `NETDISK_DATA_MCP_URL` | URL | 官方数据层端点,默认 `https://mcp-pan.baidu.com/sse` |
| `BAIDU_NETDISK_ACCESS_TOKEN` | token | `official` 模式必填;个人用户 OAuth 令牌,非应用凭证 |
| `NETDISK_SDK_PATH` | 目录 | 仅 sdk 模式可指定自定义 SDK 位置 |
| `NETDISK_PROFILE` | 名称/路径 | 指定整理偏好(见第 7 节),缺省用内置 `default` |

无 token 又传 `backend_root` → `LocalBackend`(本地测试 / mock,无需账号)。

---

## 6. 工具清单(21 个)

### 语义层(6 · 独有护城河,官方零覆盖)

| 工具 | 作用 | 改写官方 |
|---|---|---|
| `pan_scan` | 扫描网盘/目录建语义索引(事件聚类+标签传播) | 否 |
| `pan_scan_status` | 查看索引状态(已扫文件数/最后扫描时间/覆盖) | 否 |
| `pan_events` | 列出识别出的事件(旅行/节日/纪念/集中拍摄) | 否 |
| `pan_ask` | 结构化检索(时间窗+类别+标签+关键词) | 否 |
| `pan_stat` | 全盘体检报告(类别/来源/风险分级) | 否 |
| `pan_issues` | 扫描已索引数据返回发现(孤儿/垃圾/重复/命名混乱/大文件) | 否 |

### 安全层(3 · 独有护城河)

| 工具 | 作用 | 改写官方 |
|---|---|---|
| `pan_move` | 移动文件(内置 `protect_patterns` 拦截) | ✅ 包官方 `file_move` |
| `pan_undo` | 回滚指定批次的 move(LIFO) | 否 |
| `pan_history` | 查看可回滚的操作历史(按批次聚合) | 否 |

### 个性化层(1 · 独有护城河)

| 工具 | 作用 | 改写官方 |
|---|---|---|
| `pan_profile` | 读取当前生效的整理偏好(只读) | 否 |

### 数据层薄封装(11 · 官方对等,零逻辑复刻)

| 工具 | 作用 | 改写官方 |
|---|---|---|
| `pan_mkdir` | 建整理用目标目录 | 委托 `make_dir` |
| `pan_save` | 转存外链资源进收件箱 | 委托 `upload_by_url` |
| `pan_quota` | 读取容量使用 | 委托 `get_quota` |
| `pan_semantic` | 透传官方语义检索 | 委托 `file_semantics_search` |
| `pan_search_keyword` | 按文件名关键词搜索 | 委托 `file_keyword_search` |
| `pan_meta` | 获取文件详情(按 fs_id,最多 10) | 委托 `file_meta` |
| `pan_upload_text` | 把一段文本直接写成网盘文件 | 委托 `file_upload_by_content` |
| `pan_copy` | 复制文件(`ondup=newcopy` 防覆盖) | 委托 `file_copy` |
| `pan_share_link` | 创建文件分享链接 | 委托 `file_sharelink_set` |
| `pan_user` | 获取已鉴权用户信息(头像/会员/UID) | 委托 `user_info` |
| `pan_del` | 删除/移入回收站 | 委托 `file_del`(⚠️ 见下) |

> **`pan_del` 使用警示**:官方 SSE 数据层**未暴露删除**(仅 vendored `sdk` 后端可用),且删除风险系数高。作者立场:**不把它作为默认分发能力** —— 清理请走隔离目录 `/_待清理_/`(profile 的 `quarantine_dir`)+ 用户手动删除;`pan_del` 仅保留后端完整性,使用需自担风险,且 `pan_undo` 不支持回滚删除(回收站无恢复 API)。

> 官方 16 个数据工具已覆盖 15 个;唯一刻意不提供的是 `file_upload_stdio`(本地文件直传,web 场景用不到)。

### 使用示例

| 用户意图 | 工具调用 |
|---|---|
| 扫描我的网盘 | `pan_scan` |
| 2025年夏季时间段的照片在哪 | agent 解析为 `{years:[2025], months:[7,8], category:"照片", scene:"旅行"}` 调 `pan_ask` |
| 把截图归档到 /整理后/截图/ | `pan_move`(每个 move 自动写 journal) |
| 刚才那批撤回 | `pan_undo(batch=...)` |
| 转存这个链接 | `pan_save({url, to_folder})` |

---

## 7. 个性化

整理偏好是**纯 JSON**,放在 `profiles/`:

- `profiles/default.profile.json` —— 内置默认(保护规则 + 隔离目录 + 软偏好)
- `profiles/schema.json` —— JSON Schema,加载时严格校验(配置非法宁可失败,不带病动用户文件)

自定义:复制 `default.profile.json` 改名,运行时设 `NETDISK_PROFILE=我的.profile.json`。

可改项:额外 `protect_patterns`(永不移动的路径/关键词)、`never_touch`(前缀黑名单)、`quarantine_dir`(清理隔离区)、`preferences`(软偏好,如默认按事件聚类)。

> **内置保护规则不可被 profile 关闭** —— 这是铁律,防止误配把重要文件挪走。

---

## 8. 安全模型

1. **永不删除**:不暴露 delete tool,清理只能 move 到隔离目录
2. **move 内置保护**:`protect_patterns` / `never_touch` 命中直接拒绝(珍藏/纪念/毕业/合同/发票/身份证/微信备份…)
3. **journal 自动留痕**:每次 move 记录,undo 一键回滚(LIFO)
4. **凭证不入库**:token 走 env / `.env`(`.gitignore` 屏蔽),绝不提交
5. **防提示注入**:不提供读取文件内容的工具,文件名当不可信数据

---

## 9. 获取 Access Token

MCP 配置里要的是**「用户」令牌**(代表某百度账号授权访问其网盘),不是开放平台的「应用」凭证(AppKey/SecretKey 不能直接当 token 用)。
个人使用:用你自己的 AppKey + SecretKey 走一次 OAuth,把**你自己的网盘**授权给应用。

```bash
pip install requests
python tools/get_baidu_token.py --app-key 你的AppKey --app-secret 你的SecretKey
# 打开打印的验证 URL,登录百度账号授权 → 脚本自动拿到 access_token / refresh_token
```

token 约 30 天有效;过期用 `--refresh` 续期。(个人应用最多授权 10 个账号,自用绰绰有余。)

> ⚠️ 脚本会**在终端打印** access_token / refresh_token。请勿把终端输出截图发给他人,token 等同于你的网盘访问权限。

---

## 10. 项目结构

```
netdisk-organizer-mcp/
├── server.py                 # MCP stdio server 入口,工具路由
├── index.py                  # 语义层:扫描/事件聚类/检索/体检
├── ops.py                    # 安全层:move/undo/history + journal
├── io_ops.py                 # 数据层薄封装(委托官方)
├── backend.py                # 后端抽象(Official/Baidu/Local)
├── tools/
│   └── get_baidu_token.py    # OAuth 设备码取 token 脚本
├── profiles/
│   ├── __init__.py           # profile 加载器 + 严格校验
│   ├── default.profile.json  # 内置默认偏好
│   └── schema.json           # JSON Schema
├── openapi_client/           # vendored 官方 SDK(sdk 兜底模式用)
├── pyproject.toml
├── .env.example              # 环境变量模板
└── .gitignore
```

---

## 11. 开发与测试

```bash
pip install -e ".[dev]"
python -m pytest -q
```

> **说明**:当前分享版为精简裁剪,**未包含测试套件与 CI 配置**。若需完整开发环境,请基于 `backend.py` 的 `FakeDataClient` 注入点自行补充单元测试(可离线验证代理逻辑,无需真实 token / 网络)。

---

## 12. License

[MIT](LICENSE)