npm package: @insightsuen/dingdoc
Description: MCP server for reading DingTalk knowledge base documents
Latest version: 0.1.0

# dingdoc MCP Server

通过 MCP 协议读取钉钉知识库文档内容，支持两种工作模式：**API 模式**（完整功能）和**仅浏览器模式**（无需凭证）。

## 运行模式

| 模式 | 触发条件 | 可用工具 |
|------|---------|---------|
| **API 模式** | 提供了 AppKey / AppSecret / 用户身份，且验证通过 | 全部 3 个工具 |
| **仅浏览器模式** | 未提供凭证，或凭证验证失败 | 仅 `dingtalk_get_document` |

- 服务启动时自动检测，无需手动切换
- 仅浏览器模式首次使用时会打开登录窗口，完成钉钉扫码登录后 session 持久保存（`~/.insight-mcp/dingdoc-browser/`）

## 在 MCP 客户端中配置

### 仅浏览器模式（最简配置）

```json
{
  "mcpServers": {
    "dingdoc": {
      "command": "node",
      "args": ["/path/to/Insight-MCP/servers/dingdoc/dist/index.js"]
    }
  }
}
```

### API 模式（完整功能）

直接写入凭证：

```json
{
  "mcpServers": {
    "dingdoc": {
      "command": "node",
      "args": ["/path/to/Insight-MCP/servers/dingdoc/dist/index.js"],
      "env": {
        "DINGTALK_APP_KEY": "your_app_key",
        "DINGTALK_APP_SECRET": "your_app_secret",
        "DINGTALK_UNION_ID": "your_union_id"
      }
    }
  }
}
```

引用已有的环境变量名（凭证不写入配置文件）：

```json
{
  "mcpServers": {
    "dingdoc": {
      "command": "node",
      "args": [
        "/path/to/Insight-MCP/servers/dingdoc/dist/index.js",
        "--app-key-env=MY_APP_KEY",
        "--app-secret-env=MY_APP_SECRET",
        "--union-id-env=MY_UNION_ID"
      ]
    }
  }
}
```

## 凭证说明

服务支持三种方式提供凭证，按优先级从高到低：

| 方式 | 示例 |
|------|------|
| CLI 直传明文 | `--app-key=xxx --app-secret=xxx` |
| CLI 指定环境变量名 | `--app-key-env=MY_VAR --app-secret-env=MY_VAR` |
| 默认环境变量 | `DINGTALK_APP_KEY` / `DINGTALK_APP_SECRET` |

操作人身份（三种方式均支持）：

| 凭证 | 默认环境变量 | CLI 明文 | CLI 指定 env 名 |
|------|------------|---------|----------------|
| unionId（推荐） | `DINGTALK_UNION_ID` | `--union-id=xxx` | `--union-id-env=MY_VAR` |
| userId（自动解析为 unionId）| `DINGTALK_USER_ID` | `--user-id=xxx` | `--user-id-env=MY_VAR` |

**unionId 与 userId 的区别：**

- **unionId**：钉钉跨企业唯一用户标识，格式为 Base64-like 字符串（如 `HOqmx7FOhIBxUBjJ580XJgiEiE`）。API 调用时作为操作人身份，直接使用无需额外查询，推荐优先使用。
- **userId**：企业内部的员工工号（如 `STAFF-001`），仅在本企业内唯一。服务会在启动时自动调用 `Contact.User.Read` 接口将其解析为 `unionId`，解析成功后打印结果供日后直接使用。

> **如何获取 unionId？** 配置 `DINGTALK_USER_ID`（工号）启动一次服务，日志中会打印对应的 `unionId`，复制后改用 `DINGTALK_UNION_ID` 配置即可，后续启动不再需要查询接口。

API 权限要求：
- `Wiki.Read`（知识库读取）
- `Contact.User.Read`（用户信息查询，仅使用 userId 时需要）

## 可用工具

### `dingtalk_get_document` _(API 模式 + 仅浏览器模式均可用)_

读取文档内容，返回 Markdown 格式全文。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `nodeId` | string | 二选一 | 文档节点 ID（dentryUuid） |
| `url` | string | 二选一 | 文档 URL，格式：`https://alidocs.dingtalk.com/i/nodes/{nodeId}` |

API 模式优先通过 DingTalk Stream 异步导出获取；若 API 无权限，自动 fallback 到浏览器读取。

---

### `dingtalk_list_spaces` _(仅 API 模式)_

列出当前用户有权限的所有钉钉知识库，返回 `spaceId`、名称、`rootNodeId` 和访问链接。

无需参数，直接调用即可作为浏览目录的起点。

---

### `dingtalk_list_nodes` _(仅 API 模式)_

列出指定节点下的子节点（文件和文件夹）。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `nodeId` | string | 是 | 父节点 ID，从 `dingtalk_list_spaces` 的 `rootNodeId` 开始 |
| `recursive` | boolean | 否 | 是否递归展开整个目录树，默认 `false` |

## 工作原理

### API 模式文档读取

钉钉文档导出是异步的，`dingtalk_get_document` 采用以下机制同步返回结果：

1. 服务启动时建立 **DingTalk Stream WebSocket 长连接**，监听 `doc_content_export_result` 事件
2. 调用工具时，通过 REST API 触发导出任务，获取 `taskId`
3. 等待 Stream 推送的导出完成事件，事件中包含 Markdown 全文
4. 若钉钉因服务端缓存未推送新事件，自动重试一次（总等待上限约 50s）
5. 导出结果在进程内缓存，同一文档再次请求直接返回

### 仅浏览器模式文档读取

使用 Playwright 驱动 Chromium 无头浏览器：

1. 导航到文档页面（`https://alidocs.dingtalk.com/i/nodes/{nodeId}`）
2. 等待 SPA 渲染完成，在 iframe 及主页面中依次查找内容区元素
3. 提取 HTML 内容并转换为 Markdown

## 从 npm 安装

```bash
npx @insightsuen/dingdoc
```

在 MCP 客户端配置中使用（无需本地安装）：

```json
{
  "mcpServers": {
    "dingdoc": {
      "command": "npx",
      "args": ["-y", "@insightsuen/dingdoc@latest"],
      "env": {
        "DINGTALK_APP_KEY": "xxx",
        "DINGTALK_APP_SECRET": "xxx",
        "DINGTALK_UNION_ID": "xxx"
      }
    }
  }
}
```

> **注意**：首次使用浏览器模式时，`npx` 方式无法弹出登录窗口（headless 环境）。建议先在本地完成一次登录，session 保存后再用 `npx` 方式运行。

## 从源码构建

```bash
# 克隆仓库后在项目根目录执行
pnpm install
pnpm --filter @insightsuen/dingdoc build

# 安装 Playwright 浏览器（仅浏览器模式需要）
pnpm --filter @insightsuen/dingdoc exec playwright install chromium
```

## 手动启动 / 调试

```bash
# 使用环境变量
DINGTALK_APP_KEY=xxx DINGTALK_APP_SECRET=xxx DINGTALK_UNION_ID=xxx \
  node servers/dingdoc/dist/index.js

# 使用 CLI 参数
node servers/dingdoc/dist/index.js \
  --app-key=xxx \
  --app-secret=xxx \
  --union-id=xxx

# MCP Inspector
npx @modelcontextprotocol/inspector node servers/dingdoc/dist/index.js
```