csdn-mcp
# csdn-mcp
让 AI Agent 通过 MCP 协议管理 CSDN 博客:发布、查询、修改、删除、上传图片。
[](https://github.com/Leaderxin/csdn-mcp/blob/main/LICENSE)
[](https://nodejs.org/)
[](https://github.com/Leaderxin/csdn-mcp/actions/workflows/ci.yml)
[](https://modelcontextprotocol.io/)
## 30 秒快速开始
```bash
git clone https://github.com/Leaderxin/csdn-mcp.git
cd csdn-mcp
npm install
npm run build
```
然后把下面这段加进 MCP 客户端配置(`args` 必须是 `dist/index.js` 的**绝对路径**):
```json
{
"mcpServers": {
"csdn": {
"command": "node",
"args": ["/absolute/path/to/csdn-mcp/dist/index.js"],
"env": {
"CSDN_COOKIE": "把整段 Cookie 粘到这里",
"CSDN_LOG_LEVEL": "warn"
}
}
}
}
```
`CSDN_COOKIE` 是唯一必填项,没有它任何写操作都会返回 `AUTH_MISSING`。获取方式见 [docs/CONFIGURATION.md](docs/CONFIGURATION.md#cookie-怎么取)。
## 工具一览
11 个工具,全部走 stdio。参数、返回结构、错误码的完整定义见 [docs/TOOLS.md](docs/TOOLS.md)。
| 工具 | 说明 |
| ----------------- | -------------------------------------------------------------------- |
| `auth_login` | 运行期设置 Cookie,随后所有请求立即生效,无需重启进程 |
| `auth_status` | 检查当前 Cookie 是否已配置、格式是否合法、账号名是什么 |
| `publish_article` | 新建文章;`mode` 默认 `draft`,发布必须显式传 `mode: "publish"` |
| `update_article` | 更新已有文章;改标题/标签/摘要/封面 + 草稿副本 |
| `get_article` | 按 ID 读取单篇文章,可选是否带正文 |
| `list_articles` | 列出文章;`scope=published` 只看已发布,`scope=all` 含草稿与分类计数 |
| `delete_article` | 删除文章,默认进回收站,`permanent: true` 彻底删除 |
| `upload_image` | 上传本地图片,返回公网 URL,分 `cover` / `body` 两条通道 |
| `list_categories` | 列出文章分类;接口不可用时回退到内置列表 |
| `list_tags` | 列出常用标签;接口不可用时回退到内置列表 |
| `verify_article` | 回查 API 状态 + 公开页 HTTP 码,确认文章真实状态 |
## 一次真实的 Agent 调用流程
```
用户:把这篇《MCP 协议入门》给我发成草稿,先别公开。
Agent → publish_article({
"title": "MCP 协议入门",
"markdown": "# MCP 协议入门\n\n...",
"tags": ["MCP", "TypeScript"],
"categories": "后端"
})
← { "articleId": "149234567",
"url": "https://blog.csdn.net/Leaderxin/article/details/149234567",
"state": "draft",
"verification": { "articleId": "149234567", "state": "draft", "statusCode": 2,
"publicStatusCode": 404, "consistent": true,
"message": "草稿已确认:接口 status=2,公开页 404" } }
Agent → verify_article({ "article_id": "149234567", "expected": "draft" })
← { "articleId": "149234567", "state": "draft", "statusCode": 2,
"publicStatusCode": 404, "consistent": true, "message": "..." }
用户:内容我看过了,发布吧。
Agent → update_article({ "article_id": "149234567", "mode": "publish" })
← { "articleId": "149234567",
"url": "https://blog.csdn.net/Leaderxin/article/details/149234567",
"state": "published" }
Agent → verify_article({ "article_id": "149234567", "expected": "publish" })
← { "articleId": "149234567", "state": "published", "statusCode": 1,
"publicStatusCode": 200, "consistent": true, "message": "..." }
```
注意 `publish_article` 在步骤 1 里**没有**传 `mode` —— 默认就是草稿。只有用户在步骤 3 明确要求之后,Agent 才发出版本。原因见 [docs/TOOLS.md](docs/TOOLS.md#publish_article)。
## 配置项
完整说明(含"什么时候需要改")见 [docs/CONFIGURATION.md](docs/CONFIGURATION.md)。
| 变量 | 默认值 | 作用 |
| ----------------------- | ---------------------------------- | ------------------------------------------------------------- |
| `CSDN_COOKIE` | `""` | 认证凭据。所有写操作的必填项 |
| `CSDN_USERNAME` | `""` | Cookie 里没有 `UserName` 时使用 |
| `CSDN_LOG_LEVEL` | `warn` | `silent` / `error` / `warn` / `info` / `debug`,日志走 stderr |
| `CSDN_TIMEOUT_MS` | `20000` | 单次请求超时 |
| `CSDN_MAX_RETRIES` | `2` | 可重试失败的额外尝试次数 |
| `CSDN_MIN_INTERVAL_MS` | `250` | 普通请求之间的最小间隔 |
| `CSDN_SAVE_INTERVAL_MS` | `11000` | 写操作之间的最小间隔(CSDN 约 10s 内会拒绝) |
| `CSDN_API_BASE` | `https://bizapi.csdn.net` | bizapi 网关地址 |
| `CSDN_BLOG_BASE` | `https://blog.csdn.net` | 公开页地址,用于写后校验 |
| `CSDN_COMMUNITY_BASE` | `https://blog.csdn.net` | 社区列表接口地址 |
| `CSDN_USER_AGENT` | Chrome 131 UA | 请求头 `User-Agent` |
| `CSDN_APP_KEY` | `203803574` | bizapi 网关 app key(公开常量) |
| `CSDN_APP_SECRET` | `9znpamsyl2c7cdrr9sas0le9vbc3r6ba` | bizapi 网关签名密钥 |
## 它是怎么工作的
**HMAC 签名。** 所有走 `bizapi.csdn.net` 的请求都要带 `X-Ca-Signature`。签名串由 method、accept、content-type、`x-ca-key`、`x-ca-nonce` 和含查询串的 uri 按固定换行拼成,再做 `base64(HMAC-SHA256(appSecret, stringToSign))`;同时必须带 `X-Ca-Signature-Headers: x-ca-key,x-ca-nonce` 告诉网关哪几个 header 参与了签名。少一行就是 `401 HMAC signature does not match`。完整格式见 [docs/API-NOTES.md](docs/API-NOTES.md#hmac-签名规范串)。
**两步图片上传。** 先 POST `/resource-api/v1/image/direct/upload/signature` 拿上传凭据,再 multipart 直传到返回的存储 host,响应里的 `data.imageUrl` 才是公网地址。正文图和封面图是两条不同的通道(`appName` 不同),不可互换。见 [docs/TOOLS.md](docs/TOOLS.md#upload_image)。
**为什么每次写入都要回查公开页。** `saveArticle` 返回 200 不等于成功。接口里的 `status` 表达的是"这篇文章是否对外可见",不是"这次调用算不算草稿"——两者共用同一组数字编码(`0` / `1` 已发布,`2` 草稿,`6` 审核拒绝,`16` 审核中),写入用的值和 `getArticle` 报回来的值含义并不对称(发布时发的是 `0`,`getArticle` 报的是 `1`)。所以 `publish_article` / `update_article` 默认跑一次校验:既看 API 返回的 `status`,也真的去请求一次公开页,只有两边都说"是草稿"(公开页 404)或都说"已发布"(公开页 200)才判定 `consistent: true`。任何一边不一致都会如实报告,不会因为一个 200 就宣称"发布成功"。
## 文档
| 文档 | 内容 |
| -------------------------------------------------- | ------------------------------------------ |
| [docs/README.md](docs/README.md) | 文档索引 |
| [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) | 从零到第一次成功调用 |
| [docs/TOOLS.md](docs/TOOLS.md) | 11 个工具的参数、返回、错误码 |
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | 环境变量 + 各客户端配置 |
| [docs/API-NOTES.md](docs/API-NOTES.md) | 逆向记录:签名串、saveArticle 字段、状态码 |
| [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | 按症状排查 |
| [docs/FAQ.md](docs/FAQ.md) | 常见问题与能力边界 |
| [docs/CHANGELOG.md](docs/CHANGELOG.md) | 版本变更 |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 分层、模块边界、冻结的工具面 |
| [docs/KNOWN-ISSUES.md](docs/KNOWN-ISSUES.md) | 已修项复核、历史问题回归表、待线上验证 |
## 许可
MIT,见 [LICENSE](LICENSE)。
TDQS
Scored across 11 tools
The tools target distinct operations: authoring (publish/update), retrieval (get/list), verification, auth, metadata lookup, and image upload. Descriptions explicitly draw boundaries between similar tools such as get_article vs verify_article vs list_articles, so misselection is unlikely.
Most names follow a consistent snake_case action_object pattern (publish_article, delete_article, list_articles, upload_image). The auth_login/auth_status pair and list_categories/list_tags remain readable and stylistically consistent, though auth_status deviates slightly from strict verb_noun naming.
11 tools for a CSDN publishing integration covering article CRUD, auth, categories, tags, and image upload is well-scoped. There is no obvious bloat, and the count sits comfortably in the ideal 3-15 range.
Core article lifecycle is well covered, including draft/publish modes, delete with recycle/permanent options, update, list, verify, auth, metadata, and image upload. Minor gaps exist around explicitly restoring articles from recycle and setting/updating an article cover beyond uploading the image.