# clickup-mcp-server **Repository Path**: roarer/clickup-mcp-server ## Basic Information - **Project Name**: clickup-mcp-server - **Description**: 本地 STDIO MCP 服务:直连 ClickUp 公开 API,凭据只存系统钥匙串,写操作带幂等、读回校验与限流 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-24 - **Last Updated**: 2026-09-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # clickup-mcp-server 一个跑在本机的 **MCP(Model Context Protocol)STDIO 服务**,把 ClickUp 公开 API 收敛成 12 个工具交给 AI Agent 使用。 它解决的是一件很具体的事:让 Agent 能读 ClickUp、能按确认后的内容回写,但**不会静默失败、不会重复写、不会把 token 留在配置文件里**。 ## 为什么不用官方的远端 MCP ClickUp 官方 MCP 是远端 HTTP 服务,有它自己的额度上限,超了就整体不可用。本服务直连 ClickUp **公开 REST API**,不受该额度约束(但仍然遵守公开 API 按 token 的限流,默认按 60 次/分钟、并发 2 做本地预算)。 代价是要自己负责凭据与安全边界——所以这个项目把这些事做在了服务端,而不是留给调用方自觉。 ## 特性 - **凭据不进配置**:token 只存系统钥匙串(macOS Keychain / Windows DPAPI),不写进任何 JSON、命令行参数或仓库。 - **失败是真失败**:所有错误都带 MCP 的 `isError: true`。只把错误码塞进正文是不够的——没有 `isError`,客户端会把鉴权失败和限流一律渲染成"调用成功",对写工具来说就是"没建单却显示建单成功"。 - **写操作带幂等键**:每个写工具都要 `request_id`。同 id 同参数直接返回上次结果;同 id 不同参数拒绝;上次结果未知时拒绝重发,要求先只读核实。 - **写后读回校验**:写入成功后读回最新值放进结果信封。读回失败不会把整个调用伪装成失败(那会诱导 Agent 重试已成功的写),而是成功信封 + `readback_error`。 - **跨进程限流与串行**:多个 Agent 进程共享同一份限流预算;同一个 Task 的并发修改会串行化。 - **只注册 12 个工具**:没有删除 Task、合并、聊天、任意请求等能力,能力面在服务端就收住了,不靠客户端黑名单。 - **可选身份闸门**:不配置时任何有效 token 都能写;配置了账号 ID 后,写操作只放行该账号。 ## 快速开始 前置:Node.js ≥ 22。 ### 第 1 步:获取 ClickUp Personal API Token 本服务使用 ClickUp 的 **Personal API Token**(`pk_` 开头)做授权。ClickUp 官方只有这一种个人凭据,就是常说的「API key」: 1. 打开 **https://app.clickup.com/settings/apps**(ClickUp 左下角头像 → **Settings** → **Apps**) 2. 找到 **Personal API Token**:没有则点 **Generate** 生成;已生成过的点 **Copy** 复制 3. 复制出来的字符串形如 `pk_12345678_ABCDEFGHIJKLMNOPQRSTUVWXYZ...`(`pk_` 开头,约 45 字符) 官方鉴权文档:https://developer.clickup.com/docs/authentication 注意: - Token 等同于你 ClickUp 账号的**完整读写权限**,泄露后他人可直接操作你的全部 Workspace。 - 只粘贴进 `auth:set` 的隐藏输入框,不要发到聊天、issue、截图或任何配置文件里。 - 怀疑泄露时回同一页面 **Regenerate**,旧 token 立即失效。 ### 第 2 步:构建并写入凭据 ```bash git clone <你的仓库地址> clickup-mcp-server cd clickup-mcp-server npm ci npm run build ``` 写入凭据(**必须由本人执行**,输入隐藏,token 不进入 shell 历史): ```bash npm run auth:set # macOS 写入 Keychain;Windows 写入 DPAPI 加密文件 npm run auth:check # 只打印账号 ID、用户名与可访问 Workspace ``` 设置默认 Workspace(自定义 Task ID 如 `DEV-1234` 需要它来定位): ```bash npm run auth:workspace # 列出可访问 Workspace npm run auth:workspace [名称] npm run auth:workspace clear ``` ## 接入客户端 在 MCP 配置里加一条 stdio server,指向构建产物: ```json { "mcpServers": { "clickup": { "command": "node", "args": ["/absolute/path/to/clickup-mcp-server/dist/server.js"] } } } ``` 各客户端的配置文件位置: | 客户端 | 配置文件 | 说明 | | ----------- | ----------------------- | --------------------------- | | Claude Code | 项目根 `.mcp.json` | 也可放用户级 `~/.claude.json` | | Cursor | 项目根 `.cursor/mcp.json` | | | Codex | `.codex/config.toml` | 用 `[mcp_servers.clickup]` 段 | | OpenCode | 项目根 `opencode.json` | 用 `mcp` 段 | | WorkBuddy | `~/.workbuddy/mcp.json` | 只有用户级,会对该用户的所有项目生效 | 新增 server 后通常需要在客户端的连接器/MCP 管理页点一次"信任"才会真正启用。配置写入本身不会自动激活。 **换机器或重新构建后**:重新 `npm run build`,确认配置里的 entrypoint 指向新的 `dist/server.js`,然后重载客户端。 ## 工具清单 | 工具 | 类型 | 说明 | | ------------------------------ | -- | --------------------------------------------- | | `clickup_get_current_user` | 读 | 核实当前 token 对应账号 | | `clickup_get_workspaces` | 读 | 可访问 Workspace 与成员,附带本机默认 Workspace | | `clickup_get_list` | 读 | List / Space / 状态元数据 | | `clickup_get_task` | 读 | Task 直读(支持链接或 ID),含 Markdown 正文,`fresh` 可绕过缓存 | | `clickup_filter_tasks` | 读 | 按 List 筛选,支持状态/标签/负责人/名称,最多 5 个上游页 | | `clickup_get_task_comments` | 读 | 分页读评论,保留作者 ID | | `clickup_get_space_tags` | 读 | Space 可用标签 | | `clickup_create_task` | 写 | 创建 Task,不套用业务模板,默认值由调用方工作流提供 | | `clickup_update_task` | 写 | 按字段修改 Task,只发送提供的字段 | | `clickup_create_task_comment` | 写 | 追加评论 | | `clickup_add_tag_to_task` | 写 | 添加已有 Space 标签 | | `clickup_remove_tag_from_task` | 写 | 解除标签关系(不删除 Space 标签) | 写工具都需要 `request_id`。Task 参数接受 ID 或 `app.clickup.com/t/` 链接。 ## 写安全是怎么保证的 一次写调用会依次经过: 1. **身份闸门**(可选,见环境变量) 2. **本期限流预算**,超限返回 `RATE_LIMITED` 并带 `retry_at` 3. **幂等登记**:以「token 指纹 + request_id」为键,同键不同参数 → `IDEMPOTENCY_CONFLICT`;该键上次为 `pending` → `WRITE_RESULT_UNKNOWN`,要求先只读核实 4. **Task 写锁**:同一 Task 的并发修改串行化,超时返回 `TASK_LOCK_TIMEOUT` 5. **真实请求**:GET 可有限退避重试;写操作遇到超时或 5xx **不自动重发**,直接返回 `WRITE_RESULT_UNKNOWN` 6. **读回校验**:成功则把最新值读回放进信封;读回失败返回成功信封 + `readback_error` 结果信封形如: ```json { "write_verified": true, "result": { "id": "...", "url": "...", "status": { "status": "in progress" } }, "readback": { "id": "...", "url": "...", "status": { "status": "in progress" } } } ``` `write_verified: false` 或出现 `readback_error` 时,**先只读核实,不要盲目重试**。 ## 错误码 | 错误码 | 含义与正确处理 | | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | `AUTH_NOT_CONFIGURED` | 本机没写凭据,重新 `npm run auth:set` | | `AUTH_REJECTED` | 上游 401/403,token 无效或无权访问 | | `IDENTITY_MISMATCH` | token 账号不是允许的账号,写操作已禁用 | | `RATE_LIMITED` | 限流,按返回的 `retry_at` 等待 | | `IDEMPOTENCY_CONFLICT` | 同一 `request_id` 用过不同参数,换新 id | | `WRITE_RESULT_UNKNOWN` | 写结果未知,**先只读核实,不要重发** | | `READBACK_FAILED` | 写入成功但读回校验失败,同上 | | `TASK_LOCK_TIMEOUT` | 同一 Task 正被其他进程修改,稍后重试 | | `STATE_LOCK_TIMEOUT` | 本机状态锁等待超时 | | `NETWORK_ERROR` / `UPSTREAM_ERROR` / `API_ERROR` | 网络、上游 5xx、其他 API 错误 | | `LIST_IDS_REQUIRED` | `filter_tasks` 必须给 `list_ids`,禁止全量扫描 | | `EMPTY_CHANGES` / `UNKNOWN_CHANGE` / `INVALID_PRIORITY` / `INVALID_ASSIGNEES` / `NULL_NOT_SUPPORTED` / `INVALID_TAG` | 参数校验不通过 | ## 环境变量 | 变量 | 默认 | 用途 | | ---------------------------------- | ------- | --------------------------------------------------- | | `CLICKUP_MCP_EXPECTED_USER` | 空 | 设置后只放行该 ClickUp 账号 ID 的写操作,其余返回 `IDENTITY_MISMATCH` | | `CLICKUP_MCP_TOKEN` | 无 | **仅测试用**,注入假 token 以绕过钥匙串;生产不要设 | | `CLICKUP_MCP_TEST_FILE_TIMEOUT_MS` | `60000` | 单测试文件超时上限 | `CLICKUP_MCP_EXPECTED_USER` 防的是"拿错 token 写错空间",它**不能**代替业务侧的创建人/审批人校验。 ## 本机数据存放 | 路径 | 内容 | | --------------------------------------------------------- | ------------------ | | macOS Keychain:`clickup-mcp-server` / `personal-token` | token(不落盘明文) | | Windows `%USERPROFILE%\.clickup-mcp-server\clickup-token` | token,DPAPI 当前用户加密 | | `~/.clickup-mcp-server/settings.json` | 默认 Workspace(非密钥) | | `~/.clickup-mcp-server/state//` | 限流预算、写请求记录、锁 | 状态目录名是 token 的 SHA-256 前 16 位,**不含 token 明文**。删掉只会丢失幂等记录与预算统计。 ## 开发 ```bash npm run build # 编译 src -> dist npm run build:test # 编译 src + test -> dist-test npm test # 编译并跑全部测试(8 个文件,含 mock 的协议/幂等/限流用例) ``` 测试全部使用 mock 的 fetch 与临时 HOME,不会触碰真实 ClickUp 或真实用户目录。 ## 安全与隐私 - token 只读进进程内存,不写日志、不进 argv、不进任何配置文件。 - stdout 只输出 MCP 协议;诊断信息写 stderr 且已脱敏(只含状态码与方法+路径,不含 header/query/body)。 - 错误信息统一截断到 200 字符,避免把上游响应体带出来。 - 发现安全问题请优先私信维护者,不要在 issue 里贴 token、Workspace ID 或任务内容。 ## 许可证 [MIT](./LICENSE)