Skip to main content
Glama
README.md
# DeepSeek QR

本项目是一个仅监听本机的多模态识别工作台:通过用户选择的 Edge 或 Chrome 登录会话访问 DeepSeek Web,提供本地 Web 界面、MCP Streamable HTTP 和 MCP stdio 三种入口。

应用不读取或保存浏览器 Cookie、Authorization、localStorage、密码、问题正文、答案或上传文件。浏览器负责登录态和网页令牌的维护;应用只在请求期间创建临时证据目录,并在任务结束后清理。

## 当前能力

| 能力 | 说明 |
| --- | --- |
| 浏览器会话 | macOS Edge、Chrome;专用持久化配置或 loopback CDP 连接 |
| Web 工作台 | 文件选择、问题输入、认证状态、识别结果和错误提示 |
| MCP | `stdio` 桥接器与 Streamable HTTP,共享同一组工具和错误语义 |
| 图片 | PNG、JPEG、WebP、GIF |
| 文档 | PDF、DOCX、TXT、Markdown;保留页码或段落引用 |
| 音频 | MP3、WAV、M4A、AAC、FLAC、OGG;FFmpeg 归一化后交给本地 Whisper 兼容模型 |
| 视频 | MP4、MOV、MKV、WebM;抽样抽帧并合并音轨转写,结果带抽样警告 |
| 并发 | 同一进程最多一个识别任务,重复提交返回 `BUSY` |
| 临时数据 | 只写入请求级临时目录,成功、失败和异常路径都会清理 |

核心识别链路如下:

```mermaid
flowchart LR
    Web[本地 Web 工作台] --> Core[FastAPI 核心服务]
    HTTP[MCP Streamable HTTP] --> Core
    Stdio[MCP stdio] --> Bridge[本地 HTTP 桥接器] --> Core
    Core --> Validate[路径与媒体校验]
    Validate --> Media[文档/音频/视频预处理]
    Media --> Browser[Edge 或 Chrome 会话]
    Browser --> DeepSeek[DeepSeek Web]
```

## 环境要求

- Python `3.12` 至 `3.14`
- macOS Edge 或 Google Chrome,路径分别为 `/Applications/Microsoft Edge.app` 和 `/Applications/Google Chrome.app`
- FFmpeg 与 FFprobe;PDF 扫描页渲染需要 `pdftoppm`
- 已锁定依赖见 `pyproject.toml` 和 `uv.lock`
- 音频/视频转写还需要用户自行准备本地 `faster-whisper` 兼容模型;应用不会自动下载模型

安装项目依赖:

```bash
uv sync --dev
```

如果使用已有虚拟环境,也可以直接使用 `.venv/bin/python` 和 `.venv/bin/pytest`。

## 配置

在项目根目录创建 `config.toml`。`allowed_roots` 至少填写一个已经存在的目录,MCP 只能读取这些目录下的本机绝对路径:

```toml
[app]
browser = "edge"
session_mode = "dedicated"
allowed_roots = ["/Users/your-name/Documents/deepseek-inputs"]
http_port = 8765
```

字段说明:

- `browser`:`edge` 或 `chrome`。命令行也可以在启动时覆盖它。
- `session_mode`:`dedicated` 使用应用专用浏览器配置;`cdp` 连接已经以远程调试模式启动的日常浏览器。
- `cdp_url`:仅在 `cdp` 模式填写,例如 `http://127.0.0.1:9222`;非 loopback 地址会被拒绝。
- `allowed_roots`:MCP 输入文件允许目录;目录和符号链接逃逸都会被拒绝。
- `browser_profile_dir`:可选,默认是配置文件旁的 `.browser-profiles/<browser>`。
- `mcp_token_path`:可选,默认是配置文件旁的 `.runtime/mcp-token`,令牌文件权限为当前用户可读。
- `http_port`:可选,默认 `8765`。服务固定监听 `127.0.0.1`。

## 启动 Web 与 MCP HTTP

启动时显式选择浏览器:

```bash
.venv/bin/python -m deepseek_qr --config config.toml --browser edge
```

省略 `--browser` 会在终端中显示 Edge/Chrome 选择。启动后,应用会打开所选浏览器的专用配置和 DeepSeek 页面;首次使用请在该浏览器中手动登录。

- Web 工作台:<http://127.0.0.1:8765/>
- 存活检查:<http://127.0.0.1:8765/health>
- MCP Streamable HTTP:`http://127.0.0.1:8765/mcp`

MCP HTTP 和内部核心接口要求:

```http
Authorization: Bearer <读取自 .runtime/mcp-token 的令牌>
```

stdio 桥接器不会隐式启动核心服务或浏览器,必须先启动上面的 Web 服务:

```bash
.venv/bin/python -m deepseek_qr --config config.toml --stdio
```

完整本地运行说明见 [`docs/deployment/local-run.md`](docs/deployment/local-run.md)。

## Web 接口

| 方法 | 路径 | 用途 | 认证 |
| --- | --- | --- | --- |
| `GET` | `/` | 返回本地工作台首页 | 本机访问 |
| `GET` | `/health` | 返回 `{"status":"alive"}` | 本机访问 |
| `GET` | `/api/auth/status` | 查询浏览器和 DeepSeek 登录状态 | 本机访问 |
| `POST` | `/api/auth/login` | 打开或恢复登录页 | 本机访问 |
| `POST` | `/api/recognize` | `multipart/form-data` 上传一个文件并提交 `prompt` | 本机访问 |
| `GET` | `/internal/auth/status` | stdio 桥接器使用的认证状态接口 | Bearer 令牌 |
| `POST` | `/internal/auth/login` | stdio 桥接器使用的登录接口 | Bearer 令牌 |
| `POST` | `/internal/recognize` | 接收允许目录内的绝对路径和 `prompt` | Bearer 令牌 |

识别成功结果包含:

```json
{
  "request_id": "随机请求标识",
  "answer": "最终答案",
  "media_type": "image|document|audio|video",
  "references": ["page:3", "00:00:12"],
  "warnings": [],
  "elapsed_ms": 1234
}
```

## MCP 工具

两种 MCP 传输注册相同的工具:

### `deepseek_recognize(file_path, prompt)`

识别允许目录内的本机绝对路径文件。`file_path` 不接受 URL、目录、Base64 或允许目录之外的路径;`prompt` 不能为空。

### `deepseek_auth_status()`

返回 `ready`、`auth_required` 或 `unavailable`,同时返回浏览器类型和会话模式。

### `deepseek_open_login()`

打开 DeepSeek 页面并返回当前认证状态。登录、验证码、风控和限流由用户和官方网页处理,应用不会自动绕过。

## 媒体限制与处理

- 图片最大 `20 MiB`。
- 文档最大 `100 MiB`,PDF 最多 `100` 页;优先提取文本,扫描页渲染为临时 PNG。
- 音频最大 `200 MiB`,最长 `2` 小时;模型未准备好时返回 `MODEL_NOT_READY`,不会静默下载。
- 视频最大 `2 GiB`,最长 `2` 小时,最多抽样 `240` 帧,默认每 `5` 秒抽取一帧;结果明确标记为抽样语义理解,不是逐帧取证。
- 文档、音频和视频产生的文本、帧和转码文件只存在于当前请求临时目录。
- 证据超过单次提交能力时按原始顺序分批,再由 DeepSeek Web 会话综合。

## 浏览器会话与 Cookie

### 专用配置模式

应用为 Edge 或 Chrome 创建独立配置目录,避免读取日常浏览器配置。浏览器自动持久化登录态和 Cookie;应用只控制自己创建的标签页。

### CDP 模式

用户自行以远程调试参数启动 Edge 或 Chrome,应用只连接 loopback `cdp_url`,不读取浏览器 Cookie 数据库,也不强制重启日常浏览器。

如果登录失效,状态会变为 `AUTH_REQUIRED`,Web 可以打开登录页,MCP 返回稳定错误码。DeepSeek 页面控件或流式协议变化时返回 `UPSTREAM_CHANGED`,应用不会猜测新的选择器或重放已提交请求。

## 错误码

| 错误码 | 含义 |
| --- | --- |
| `INVALID_INPUT` | 路径、问题或配置无效 |
| `UNSUPPORTED_MEDIA` | 文件格式或内容不受支持 |
| `FILE_TOO_LARGE` | 文件、页数、时长或帧数超过限制 |
| `BROWSER_UNAVAILABLE` | 浏览器未安装、未启动或已断开 |
| `AUTH_REQUIRED` | DeepSeek 登录已失效 |
| `MODEL_NOT_READY` | 本地音频模型未准备好 |
| `PREPROCESS_FAILED` | 文档、音频或视频预处理失败 |
| `SERVICE_UNAVAILABLE` | 核心服务或 stdio 依赖不可用 |
| `BUSY` | 已有识别任务运行 |
| `UPSTREAM_LIMITED` | DeepSeek 限流或请求受限 |
| `UPSTREAM_CHANGED` | DeepSeek 页面或流式协议不匹配 |
| `INTERNAL_ERROR` | 未分类内部错误 |

## 安全边界

- HTTP 只绑定 `127.0.0.1`,MCP Streamable HTTP 校验 Host/Origin 和 Bearer 令牌。
- MCP 只接受允许目录中的本机绝对路径,并解析符号链接后再次检查路径归属。
- 不保存 Cookie、Bearer Token、密码、localStorage、问题、答案、完整上传路径或原始上游响应。
- 上传文件、转码音频、转写文本、PDF 页面和视频帧在请求结束后清理。
- 不自动填写账号密码,不绕过验证码、风控、PoW 或限流,不自动联网下载语音模型。

## 测试与验证

不启动真实浏览器的回归测试:

```bash
.venv/bin/pytest -o addopts= --ignore=tests/test_deepseek_adapter.py -q
.venv/bin/python -m compileall -q src tests
git diff --check
```

浏览器适配器测试使用本地伪 DeepSeek 页面;真实 Edge/Chrome 会话需要用户手动登录和明确的外部浏览器权限。不要在 macOS 沙箱中反复启动系统 Chrome,否则可能出现“Google Chrome 意外退出”报告窗口。

## 已知限制

- 当前默认运行时没有自动配置 `faster-whisper` 模型目录,因此音视频识别在模型未注入时会返回 `MODEL_NOT_READY`。
- DeepSeek Web 的视觉模式控件是非公开网页契约;如果实际页面控件不再匹配,适配器会安全失败并返回 `UPSTREAM_CHANGED`,不会使用未经验证的选择器。
- 应用只保证本地不持久化请求内容;DeepSeek 账号侧是否保存会话记录由其产品和数据政策决定。