Doramagic 项目包 · 项目说明书
content-distribution-mcp 项目
生成时间:2026-05-27 16:37:37 UTC
首页
多渠道内容分发 MCP 服务器 | GitHub 仓库
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
content-distribution-mcp
多渠道内容分发 MCP 服务器 | GitHub 仓库
项目概述
content-distribution-mcp 是一个基于 Model Context Protocol (MCP) 的服务器,用于将单一内容自动分发到 8+ 个平台(DEV.to、Hashnode、GitHub Discussions、Reddit、Bluesky、LinkedIn、Medium、Twitter/X),支持平台自适应、幂等发布、社区反垃圾规则和集中式状态管理。
资料来源:README.md
它解决的问题
内容规模化发布存在以下痛点:
| 痛点 | 描述 |
|---|---|
| 格式差异 | Reddit 清除格式、Twitter 有字符限制、DEV.to 支持嵌入和富媒体 |
| 平台规则 | 子版块强制冷却期和标签要求、社区有发帖模式和自动审核 |
| 状态混乱 | 哪些帖子发布成功?失败后重试是否会重复发布? |
资料来源:README.md
工作原理
graph LR
A[AI Agent 生成<br/>平台适配内容] --> B[调用 MCP 工具<br/>post.publish]
B --> C{平台适配器}
C -->|DEV.to| D[API 直接发布]
C -->|Hashnode| E[GraphQL 发布]
C -->|Reddit| F[检查冷却期<br/>API 发布]
C -->|Bluesky| G[AT Protocol 发布]
C -->|LinkedIn| H[浏览器回退<br/>compose_url]
C -->|Twitter| H
C -->|Medium| H
D --> I[返回 live_url]
E --> I
F --> I
G --> I
H --> J[返回 compose_url<br/>需手动完成]
I --> K[存储发布状态<br/>YAML backend]
J --> K资料来源:README.md
核心功能
content-distribution-mcp 是一个基于 Model Context Protocol (MCP) 的多平台内容分发服务器。它通过单一的内容输入,自动将内容适配并发布到 8+ 个平台,同时保证幂等性、状态追踪和平台规则合规。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
功能概述
本项目的核心功能包括:
| 功能分类 | 具体内容 |
|---|---|
| 多平台发布 | DEV.to、Hashnode、GitHub Discussions、Reddit、Bluesky、LinkedIn、Medium、Twitter/X |
| 幂等性保证 | 基于 (content.id, channel) 的唯一键防止重复发布 |
| 智能调度 | 支持 ISO-8601 定时发布,通过 cron drain 触发 |
| 平台适配 | 每个平台独立的字符限制、Markdown 支持、标签词汇表 |
| 状态追踪 | 统一的发布状态管理(live/queued/needs_browser/failed) |
| 凭证管理 | 基于 YAML 配置文件的分布配置文件(Profile)系统 |
系统架构
整体架构图
graph TD
A[AI Agent / 调用方] -->|MCP 工具调用| B[content-distribution-mcp Server]
B --> C[State Backend<br/>~/.distribution-mcp/]
B --> D[Channel Adapters]
D --> E1[DEV.to Adapter<br/>API 集成]
D --> E2[Hashnode Adapter<br/>GraphQL]
D --> E3[GitHub Discussions<br/>GraphQL]
D --> E4[Reddit Adapter<br/>OAuth + API]
D --> E5[Bluesky Adapter<br/>AT Protocol]
D --> E6[Browser Fallback<br/>LinkedIn/Medium/Twitter]
C --> F1[profiles.yaml<br/>凭证配置]
C --> F2[state.yaml<br/>发布状态]
C --> F3[scheduled.yaml<br/>定时任务]
C --> F4[subreddits.yaml<br/>子版块目录]适配器架构
每个平台对应一个独立的适配器,实现统一的 ChannelAdapter 接口:
classDiagram
class ChannelAdapter {
<<interface>>
+hints() ChannelHints
+publish(variant, profile) PublishResult
+unpublish(liveUrl, profile) [boolean, string]
}
class DevToAdapter {
+hints() ChannelHints
+publish(variant, profile) PublishResult
+unpublish(liveUrl, profile) [boolean, string]
}
class HashnodeAdapter {
+hints() ChannelHints
+publish(variant, profile) PublishResult
+unpublish(liveUrl, profile) [boolean, string]
}
class BlueskyAdapter {
+hints() ChannelHints
+publish(variant, profile) PublishResult
+unpublish(liveUrl, profile) [boolean, string]
}
class BrowserAdapter {
+hints() ChannelHints
+publish(variant, profile) PublishResult
+unpublish(liveUrl, profile) [boolean, string]
}
ChannelAdapter <|.. DevToAdapter
ChannelAdapter <|.. HashnodeAdapter
ChannelAdapter <|.. BlueskyAdapter
ChannelAdapter <|.. BrowserAdapter适配器注册表通过 buildAdapterMap() 函数构建,支持多个别名映射:
| 平台标识 | 实际适配器 |
|---|---|
devto | DevToAdapter |
hashnode | HashnodeAdapter |
github_discussions / github-discussions | GitHubDiscussionsAdapter |
reddit | RedditAdapter |
bluesky | BlueskyAdapter |
medium / medium_browser / medium-browser | BrowserAdapter (Medium) |
linkedin / linkedin_browser / linkedin-browser | BrowserAdapter (LinkedIn) |
twitter / x / twitter_browser / twitter-browser | BrowserAdapter (Twitter) |
*资料来源:src/adapters/index.ts:17-40*
MCP 工具接口
本服务器暴露 8 个 MCP 工具,采用点号命名空间组织:
| 工具名称 | 功能描述 | 只读 | 幂等 |
|---|---|---|---|
post.publish | 立即发布内容到指定渠道 | ❌ | ✅ |
post.schedule | 调度内容发布(定时立即执行) | ❌ | ✅ |
post.drain | 触发所有到期定时任务 | ❌ | ❌ |
post.status | 查询内容发布状态 | ✅ | ✅ |
post.unpublish | 取消发布(最佳努力) | ❌ | ❌ |
channel.hints | 获取平台元数据(字符限制、Markdown 支持等) | ✅ | ✅ |
profile.list | 列出配置的分布配置文件 | ✅ | ✅ |
subreddit.list | 子版块目录查询(冷却期、标签词汇) | ✅ | ✅ |
*资料来源:src/server.ts - 工具注册部分*
post.publish 工具
立即发布内容到指定渠道,同一 content.id + channel 组合不会重复发布。
输入参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | ContentInput | ✅ | 内容对象 |
variants | VariantInput[] | ✅ | 渠道适配变体数组 |
profile_name | string | ✅ | 分布配置文件名 |
ContentInput 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 内容唯一标识符 |
title | string | 内容标题 |
subtitle | string | 副标题(用于 Hashnode、DEV.to) |
body_md | string | Markdown 格式正文 |
cover_image | string (URL) | 封面图片 URL |
tags | string[] | 标签列表 |
canonical_url | string (URL) | 原始出处 URL |
cta_block | string | 行动号召块(Markdown) |
author | string | 作者显示名 |
source_task_id | string | 溯源任务 ID(不发送至平台) |
VariantInput 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
channel | string | 渠道标识,如 devto:main、reddit:ClaudeAI |
title | string | 渠道特定标题 |
body | string | 渠道特定正文 |
tags | string[] | 渠道特定标签(覆盖 content.tags) |
canonical_url | string (URL) | 该渠道的原始 URL(可选覆盖) |
cta_block | string | 该渠道的 CTA 块(可选覆盖) |
schedule_at | string | ISO-8601 调度时间 |
extras | object | 渠道特定参数(flair、category 等) |
输出结构:
{
results: PublishResult[] // 每条变体的发布结果
}
PublishResult = {
channel: string, // 渠道标识
state: "live" | "queued" | "needs_browser" | "failed",
live_url: string | null, // 已发布 URL
draft_path: string | null, // 本地草稿路径(浏览器回退)
compose_url: string | null, // 平台编辑 URL(浏览器回退)
error: string | null, // 错误信息
published_at: string | null // UTC ISO-8601 时间戳
}
channel.hints 工具
返回静态的平台元数据,帮助 AI Agent 在生成变体时遵守平台约束:
graph LR
A[Agent 准备发布] --> B[调用 channel.hints<br/>获取平台限制]
B --> C[了解字符限制]
B --> D[了解 Markdown 支持]
B --> E[了解标签词汇表]
C --> F[生成合规变体]
D --> F
E --> F返回字段:
| 字段 | 说明 | 示例 |
|---|---|---|
max_length | 最大字符数 | Twitter: 280, DEV.to: 100000 |
supported_md_features | 支持的 Markdown 特性 | ["bold", "italic", "links"] |
tag_vocab | 推荐标签词汇 | ["ai", "mcp", "tutorial"] |
cta_placement | CTA 放置位置 | top / bottom / footer / none |
canonical_url_supported | 是否支持原始 URL | DEV.to: true, Twitter: false |
browser_only | 是否需要浏览器回退 | LinkedIn/Medium/Twitter: true |
平台适配器详解
自动 API 集成(支持 API)
#### DEV.to 适配器
通过 REST API 发布文章,支持完整的 Markdown:
- 认证:
DEV_TO_API_KEY - API 端点:
https://dev.to/api/articles - 特点: 支持 canonical_url 设置系列(series)
// 资料来源:src/adapters/devto.ts
const res = await fetch(`${API}/articles`, {
method: "POST",
headers: { "Content-Type": "application/json", "api-key": apiKey },
body: JSON.stringify({
article: {
title: variant.title,
body_markdown: variant.body,
tags: variant.tags.slice(0, 4), // 最多 4 个标签
canonical_url: variant.canonical_url,
published: true,
series: variant.extras?.series,
},
}),
});
#### Hashnode 适配器
通过 GraphQL API 发布:
- 认证:
HASHNODE_TOKEN+HASHNODE_PUBLICATION_ID - 端点:
https://gql.hashnode.com - 特点: 最多 5 个标签,标签自动转换为 slug 格式
#### Bluesky 适配器
基于 AT Protocol 的去中心化社交网络:
- 认证:
BLUESKY_IDENTIFIER+BLUESKY_PASSWORD - 流程: 创建会话 → 获取 JWT → 创建记录
- 限制: 正文 300 字符,仅支持链接
#### GitHub Discussions 适配器
通过 GraphQL API 在指定仓库创建 Discussion:
- 认证:
GITHUB_TOKEN - 参数:
GITHUB_DISCUSSION_REPO
#### Reddit 适配器
支持 OAuth 认证,自动处理子版块冷却期和标签限制:
- 认证:
REDDIT_CLIENT_ID/SECRET/USERNAME/PASSWORD - 特点: 支持子版块级别冷却期追踪(防止被识别为垃圾信息)
浏览器回退适配器
以下平台当前没有公共 API,使用浏览器回退模式:
| 平台 | 状态 | 说明 |
|---|---|---|
needs_browser | 返回 compose_url 指向 linkedin.com/post/new | |
| Medium | needs_browser | 返回 compose_url 指向 medium.com/new-story |
| Twitter/X | needs_browser | 返回预填充文本的推文编辑 URL |
发布结果示例:
{
"channel": "twitter",
"state": "needs_browser",
"compose_url": "https://twitter.com/compose/tweet?text=..."
}
社区规划:社区正在追踪 Twitter/X、LinkedIn 和 Medium 的原生 API 支持(当前为浏览器回退模式)。
幂等性保证
核心设计原则:同一 (content.id, channel) 组合不会产生重复发布。
幂等性流程
graph TD
A[post.publish 调用] --> B{检查 state.yaml<br/>content.id + channel}
B -->|已存在且 live| C[返回现有 live_url]
B -->|已存在且 queued| D[返回队列状态]
B -->|已存在且 failed| E[尝试重新发布]
B -->|不存在| F[调用平台 API]
F -->|成功| G[写入 state.yaml]
F -->|失败| H[记录错误状态]状态持久化
发布状态存储在 ~/.distribution-mcp/state.yaml:
# 示例结构
content-id-here:
devto:main:
state: live
live_url: https://dev.to/user/article-slug
published_at: "2026-05-20T10:30:00Z"
reddit:ClaudeAI:
state: failed
error: "Rate limit exceeded"
调度机制
schedule_at 参数
在变体中指定 schedule_at 可实现定时发布:
{
"variants": [
{
"channel": "devto:main",
"title": "定时发布文章",
"body": "...",
"schedule_at": "2026-05-21T09:00:00+01:00"
}
]
}
drain 命令
定时任务通过 post.drain 工具触发:
# 每 5 分钟检查并执行到期任务
*/5 * * * * npx -y content-distribution-mcp drain
调度数据存储在 ~/.distribution-mcp/scheduled.yaml。
凭证管理
分布配置文件
凭证通过 YAML 配置文件管理(默认位置:~/.distribution-mcp/profiles.yaml):
default:
credentials:
DEV_TO_API_KEY: "your-devto-api-key"
HASHNODE_TOKEN: "your-hashnode-token"
HASHNODE_PUBLICATION_ID: "your-pub-id"
GITHUB_TOKEN: "ghp_..."
GITHUB_DISCUSSION_REPO: "owner/repo"
REDDIT_CLIENT_ID: "..."
REDDIT_CLIENT_SECRET: "..."
REDDIT_USERNAME: "..."
REDDIT_PASSWORD: "..."
BLUESKY_IDENTIFIER: "you.bsky.social"
BLUESKY_PASSWORD: "..."
subreddits:
- ClaudeAI
- LocalLLaMA
凭证需求对照表
| 平台 | 认证方式 | 必需凭证 |
|---|---|---|
| DEV.to | API Key | DEV_TO_API_KEY |
| Hashnode | Token + Publication ID | HASHNODE_TOKEN, HASHNODE_PUBLICATION_ID |
| GitHub Discussions | Personal Access Token | GITHUB_TOKEN, GITHUB_DISCUSSION_REPO |
| OAuth | REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USERNAME, REDDIT_PASSWORD | |
| Bluesky | App Password | BLUESKY_IDENTIFIER, BLUESKY_PASSWORD |
| 浏览器回退 | 无 | |
| Medium | 浏览器回退 | 无 |
| Twitter/X | 浏览器回退 | 无 |
*资料来源:README.md - 配置凭证部分*
常见失败模式
API 认证失败
{
"channel": "devto",
"state": "failed",
"error": "DEV_TO_API_KEY not set in profile"
}
解决: 确保 profiles.yaml 中包含对应平台的凭证。
平台速率限制
Reddit 和部分平台有严格的速率限制。适配器会返回 429 状态码:
{
"channel": "reddit",
"state": "failed",
"error": "reddit 429: Too Many Requests"
}
解决: 等待冷却期后重试,或使用 subreddit.list 查询子版块的 cooldown_days。
浏览器回退平台
LinkedIn、Medium、Twitter 当前只能返回 needs_browser 状态:
{
"channel": "twitter",
"state": "needs_browser",
"compose_url": "https://twitter.com/compose/tweet?text=..."
}
说明: 用户需要手动在浏览器中完成发布操作。
核心数据类型
ContentInput(内容输入)
{
id: string; // 内容唯一标识
title: string; // 标题
subtitle?: string; // 副标题
body_md: string; // Markdown 正文
cover_image?: string; // 封面图片 URL
tags: string[]; // 标签列表
canonical_url?: string; // 原始出处 URL
cta_block?: string; // 行动号召块
author: string; // 作者名
source_task_id?: string; // 溯源任务 ID
}
VariantInput(渠道变体)
{
channel: string; // 'platform' 或 'platform:account'
title: string; // 渠道特定标题
body: string; // 渠道特定正文
tags?: string[]; // 渠道特定标签
canonical_url?: string; // 渠道特定原始 URL
cta_block?: string; // 渠道特定 CTA
schedule_at?: string; // ISO-8601 调度时间
extras?: Record<string, unknown>; // 渠道特定参数
}
ChannelHints(平台提示)
{
max_length: number; // 最大字符数
supported_md_features: string[]; // 支持的 Markdown 特性
tag_vocab?: string[]; // 推荐标签词汇
cta_placement: "top" | "bottom" | "footer" | "none"; // CTA 放置位置
canonical_url_supported: boolean; // 是否支持原始 URL
browser_only: boolean; // 是否需要浏览器回退
}
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
DISTRIBUTION_BACKEND | yaml | 状态后端类型 |
DISTRIBUTION_DIR | ~/.distribution-mcp | 配置目录位置 |
相关链接
来源:https://github.com/AutomateLab-tech/content-distribution-mcp / 项目说明书
系统架构
content-distribution-mcp 是一个基于 Model Context Protocol (MCP) 的多渠道内容分发服务器。其核心设计目标是:将单一内容自动适配并发布到 8+ 个不同平台,同时保证幂等性、状态追踪和平台规则合规性。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概览
content-distribution-mcp 是一个基于 Model Context Protocol (MCP) 的多渠道内容分发服务器。其核心设计目标是:将单一内容自动适配并发布到 8+ 个不同平台,同时保证幂等性、状态追踪和平台规则合规性。
该系统不包含 LLM 调用,仅负责内容分发逻辑的执行。内容创作和平台适配决策由外部 Agent 完成,系统提供各平台约束提示(字符限制、标签词汇表、排版规则等),由调用方根据提示生成平台化内容变体。
资料来源:README.md:1-15
架构设计原则
| 原则 | 描述 |
|---|---|
| 适配器模式 | 每个平台封装为独立 Adapter,解耦平台差异 |
| 幂等性发布 | 相同 content.id + channel 组合不会重复发布 |
| 状态后端抽象 | 状态存储支持 YAML 文件(默认)和可扩展的其他后端 |
| 工具化接口 | 所有功能通过 MCP 工具暴露,支持类型检查和注解 |
| 无 LLM 依赖 | 服务器纯执行分发逻辑,不含任何 AI 调用 |
资料来源:README.md:30-35
核心组件
系统组件概览
graph TB
subgraph "MCP Host (Claude Desktop / Cursor / n8n)"
A[Agent / User]
end
subgraph "content-distribution-mcp"
B[MCP Server<br/>src/server.ts]
C[Tool Registry]
D[State Backend<br/>YAML]
E[Adapter Map]
F[DevToAdapter]
G[HashnodeAdapter]
H[GitHubDiscussionsAdapter]
I[RedditAdapter]
J[BlueskyAdapter]
K[BrowserAdapter<br/>Medium / LinkedIn / Twitter]
end
A --> B
B --> C
C --> D
C --> E
E --> F
E --> G
E --> H
E --> I
E --> J
E --> K
F --> L[DEV.to API]
G --> M[Hashnode GraphQL]
H --> N[GitHub API]
I --> O[Reddit API]
J --> P[Bluesky AT Protocol]
K --> Q[Browser Fallback]MCP 服务器 (server.ts)
MCP 服务器是系统的中央调度器,负责:
- 工具注册 — 向 MCP Host 暴露 8 个标准化工具
- 请求路由 — 将工具调用分发给对应的适配器
- 响应格式化 — 统一返回结构化 JSON 响应
graph LR
A[post.publish] --> B[构建 Variant]
A --> C[查找适配器]
C --> D[检查幂等性]
D -->|已存在| E[返回 live_url]
D -->|不存在| F[调用 Adapter.publish]
F --> G[保存状态]
G --> H[返回 PublishResult]资料来源:src/server.ts:1-50
工具接口设计
系统定义了 8 个 MCP 工具,采用点号命名法形成导航树:
| 工具名 | 用途 | 副作用 | 幂等性 |
|---|---|---|---|
post.publish | 立即发布内容变体 | 写入发布状态 | ✅ 基于 (content.id, channel) |
post.schedule | 排程发布 + 立即发布混合 | 写入排程状态 | ✅ |
post.drain | 触发所有到期排程任务 | 状态变更 | ❌ |
post.status | 查询发布状态 | 只读 | ✅ |
post.unpublish | 取消发布 | 状态变更 | ❌ |
channel.hints | 获取平台约束元数据 | 只读 | ✅ |
profile.list | 列出配置的分发配置 | 只读 | ✅ |
subreddit.list | Reddit 子版块目录 | 只读 | ✅ |
每个工具都声明了 inputSchema、outputSchema 和 MCP 注解(readOnlyHint、destructiveHint、idempotentHint、openWorldHint)。
资料来源:src/server.ts:60-140
适配器系统架构
#### 适配器接口定义
所有平台适配器实现统一的 ChannelAdapter 接口:
interface ChannelAdapter {
hints(): ChannelHints; // 静态平台元数据
publish(variant: Variant, profile: Profile): Promise<PublishResult>;
unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string | undefined]>;
}
资料来源:src/adapters/index.ts:7-13
#### 适配器注册表
适配器通过 buildAdapterMap() 构建,支持平台别名和变体:
return {
devto: new DevToAdapter(),
hashnode: new HashnodeAdapter(),
github_discussions: new GitHubDiscussionsAdapter(),
"github-discussions": new GitHubDiscussionsAdapter(), // 别名
reddit: new RedditAdapter(),
bluesky: new BlueskyAdapter(),
medium: makeBrowserAdapter("medium"),
medium_browser: medium,
"medium-browser": medium, // 别名
linkedin: makeBrowserAdapter("linkedin"),
twitter: makeBrowserAdapter("twitter"),
x: twitter, // Twitter 别名
};
资料来源:src/adapters/index.ts:15-38
#### 平台分类
| 平台 | 适配器类型 | API 方式 | 认证需求 |
|---|---|---|---|
| DEV.to | DevToAdapter | REST API | DEV_TO_API_KEY |
| Hashnode | HashnodeAdapter | GraphQL | HASHNODE_TOKEN + HASHNODE_PUBLICATION_ID |
| GitHub Discussions | GitHubDiscussionsAdapter | GraphQL | GITHUB_TOKEN + GITHUB_DISCUSSION_REPO |
RedditAdapter | REST API | OAuth (client_id/secret/username/password) | |
| Bluesky | BlueskyAdapter | AT Protocol | BLUESKY_IDENTIFIER + BLUESKY_PASSWORD |
| Medium | BrowserAdapter | 浏览器回退 | 无 (返回 compose URL) |
BrowserAdapter | 浏览器回退 | 无 (返回 compose URL) | |
| Twitter/X | BrowserAdapter | 浏览器回退 | 无 (返回 compose URL) |
资料来源:README.md:80-90
适配器实现详解
API 适配器
#### DEV.to 适配器
sequenceDiagram
participant Agent
participant Server
participant DevToAdapter
participant DEV_API
Agent->>Server: post.publish (devto:main)
Server->>DevToAdapter: publish(variant, profile)
DevToAdapter->>DEV_API: POST /api/articles
DEV_API-->>DevToAdapter: { url, id }
DevToAdapter-->>Server: PublishResult { state: "live", live_url }
Server-->>Agent: JSON response关键特性:
- 标签限制 4 个
- 支持
canonical_url设置 SEO 权威链接 - 支持
series额外字段 - 反发布通过 PUT
/articles/:id设置published: false
资料来源:src/adapters/devto.ts:1-60
#### Hashnode 适配器
使用 GraphQL mutation publishPost,标签限制 5 个,支持:
- Markdown 完整格式
canonical_urlSEO 设置repo和series额外字段
const query = `
mutation PublishPost($input: PublishPostInput!) {
publishPost(input: $input) { post { url } }
}
`;
资料来源:src/adapters/hashnode.ts:1-50
#### Bluesky 适配器
使用 AT Protocol (atproto):
- 创建会话获取
accessJwt和did - 调用
com.atproto.repo.createRecord发布帖子
const sessionRes = await fetch(`${BSKY}/com.atproto.server.createSession`, {
method: "POST",
body: JSON.stringify({ identifier, password }),
});
资料来源:src/adapters/bluesky.ts:1-50
浏览器回退适配器
对于没有公开 API 或认证复杂的平台(Medium、LinkedIn、Twitter),系统采用浏览器回退策略:
async publish(variant: Variant): Promise<PublishResult> {
return {
channel: variant.channel,
state: "needs_browser",
compose_url: COMPOSE_URLS[platform](variant),
};
}
返回 state: "needs_browser" 和 compose_url,由用户手动完成发布。
资料来源:src/adapters/browser.ts:20-30
路线图提示:根据社区反馈,Twitter/X、LinkedIn 和 Medium 的原生 API 支持已在路线图中规划。
资料来源:社区 Issue #1
状态后端设计
后端抽象
状态后端通过 StateBackend 接口抽象,支持可插拔实现:
interface StateBackend {
read(): Promise<DistributionState>;
write(state: DistributionState): Promise<void>;
readScheduled(): Promise<ScheduledItem[]>;
writeScheduled(items: ScheduledItem[]): Promise<void>;
}
资料来源:src/backends/base.ts:1-20
默认实现:YAML 后端
默认使用 YAML 文件存储,文件位置:
| 文件 | 用途 |
|---|---|
~/.distribution-mcp/state.yaml | 发布状态追踪 |
~/.distribution-mcp/scheduled.yaml | 排程任务队列 |
~/.distribution-mcp/profiles.yaml | 分发配置和凭证 |
function buildBackend(): StateBackend {
const name = (process.env.DISTRIBUTION_BACKEND ?? "yaml").toLowerCase();
// 支持扩展其他后端实现
}
发布结果数据结构
const publishResultShape = {
channel: z.string(),
state: z.enum(["live", "queued", "needs_browser", "failed"]),
live_url: z.string().nullable(),
draft_path: z.string().nullable(),
compose_url: z.string().nullable(),
error: z.string().nullable(),
published_at: z.string().nullable(),
};
| 状态值 | 含义 |
|---|---|
live | 成功发布,返回 live_url |
queued | 已加入排程队列,等待 post.drain 触发 |
needs_browser | 需要浏览器手动操作,返回 compose_url |
failed | 发布失败,error 字段包含原因 |
配置系统
分发配置文件
配置存储在 ~/.distribution-mcp/profiles.yaml:
default:
credentials:
DEV_TO_API_KEY: "your-devto-api-key"
HASHNODE_TOKEN: "your-hashnode-token"
HASHNODE_PUBLICATION_ID: "your-pub-id"
GITHUB_TOKEN: "ghp_..."
GITHUB_DISCUSSION_REPO: "owner/repo"
REDDIT_CLIENT_ID: "..."
REDDIT_CLIENT_SECRET: "..."
REDDIT_USERNAME: "..."
REDDIT_PASSWORD: "..."
BLUESKY_IDENTIFIER: "you.bsky.social"
BLUESKY_PASSWORD: "..."
subreddits:
- ClaudeAI
- LocalLLaMA
资料来源:README.md:55-75
环境变量配置
| 变量 | 默认值 | 用途 |
|---|---|---|
DISTRIBUTION_BACKEND | yaml | 状态后端类型 |
DISTRIBUTION_PROFILES_PATH | ~/.distribution-mcp/profiles.yaml | 配置文件路径 |
内容变体模型
Variant 数据结构
graph TB
A[Variant] --> B[channel: string]
A --> C[title: string]
A --> D[body: string]
A --> E[tags: string[]]
A --> F[canonical_url?: string]
A --> G[cta_block?: string]
A --> H[schedule_at?: string]
A --> I[extras: Record]| 字段 | 类型 | 描述 |
|---|---|---|
channel | string | 频道标识符,格式 platform 或 platform:account |
title | string | 平台适配后的标题 |
body | string | 平台适配后的正文 |
tags | string[] | 平台适配后的标签 |
canonical_url | string? | SEO 权威链接 |
cta_block | string? | 行动号召块 |
schedule_at | string? | ISO-8601 排程时间 |
extras | object | 平台特定参数(flair、category 等) |
平台约束元数据 (Channel Hints)
通过 channel.hints 工具获取平台约束:
hints(): ChannelHints {
return {
max_length: 300,
supported_md_features: ["links"],
cta_placement: "bottom",
canonical_url_supported: false,
browser_only: false,
};
}
| 平台 | 最大长度 | Markdown 支持 | CTA 位置 | 浏览器回退 |
|---|---|---|---|---|
| DEV.to | 100,000 | 完整 | bottom | 否 |
| Hashnode | 50,000 | 完整 | bottom | 否 |
| GitHub Discussions | 10,000 | 基础 | bottom | 否 |
| 40,000 | 无 | none | 否 | |
| Bluesky | 300 | 仅链接 | bottom | 否 |
| Medium | 100,000 | 完整 | bottom | 是 |
| 3,000 | 粗体/斜体/链接 | bottom | 是 | |
| Twitter/X | 280 | 仅链接 | none | 是 |
发布流程
完整发布时序
sequenceDiagram
participant Agent
participant Server
participant Backend
participant Adapter
participant PlatformAPI
Note over Agent: 准备 content + variants
Agent->>Server: post.publish(content, variants, profile_name)
Server->>Backend: read() 获取当前状态
Backend-->>Server: currentState
loop 每个 Variant
Server->>Server: 检查幂等性 (content.id + channel)
alt 已发布
Server-->>Agent: 返回现有 live_url
else 未发布
Server->>Adapter: 查找对应平台的 Adapter
Adapter->>PlatformAPI: publish() API 调用
alt 发布成功
PlatformAPI-->>Adapter: live_url
Adapter-->>Server: PublishResult { state: "live" }
Server->>Backend: write() 保存状态
Server-->>Agent: 返回成功结果
else 发布失败
PlatformAPI-->>Adapter: error
Adapter-->>Server: PublishResult { state: "failed" }
Server-->>Agent: 返回错误详情
else 需要浏览器
Adapter-->>Server: PublishResult { state: "needs_browser" }
Server-->>Agent: 返回 compose_url
end
end
end
Note over Agent: 使用 live_url 完成后续工作流排程发布流程
graph LR
A[post.schedule] --> B{schedule_at 字段检查}
B -->|有 schedule_at| C[写入 scheduled.yaml]
B -->|无 schedule_at| D[立即 publish]
C --> E[返回 queued 状态]
E --> F[cron: post.drain]
F --> G{检查到期任务}
G -->|到期| H[执行发布]
G -->|未到期| I[跳过]排程任务通过 cron 触发:
# 每 5 分钟检查并发布到期任务
*/5 * * * * npx -y content-distribution-mcp drain
安全性设计
凭证隔离
- 凭证存储在本地
profiles.yaml,不进入源码 - 每个 profile 独立配置,支持多账号
- 通过环境变量或配置文件注入,无硬编码密钥
平台合规
| 平台 | 合规机制 |
|---|---|
| 子版块冷却期 (cooldown_days)、标签词汇表、最后发布时间追踪 | |
| 所有平台 | 幂等性防止重复发布、速率限制感知 |
扩展架构
添加新平台适配器
- 在
src/adapters/创建新文件,实现ChannelAdapter接口 - 在
src/adapters/index.ts的buildAdapterMap()中注册 - 在
profiles.yaml添加对应凭证配置
export class NewPlatformAdapter implements ChannelAdapter {
hints(): ChannelHints { /* 返回平台约束 */ }
async publish(variant: Variant, profile: Profile): Promise<PublishResult> { /* 发布逻辑 */ }
async unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string?>] { /* 取消逻辑 */ }
}
添加新状态后端
实现 StateBackend 接口并在 buildBackend() 工厂函数中注册:
interface StateBackend {
read(): Promise<DistributionState>;
write(state: DistributionState): Promise<void>;
readScheduled(): Promise<ScheduledItem[]>;
writeScheduled(items: ScheduledItem[]): Promise<void>;
}
项目结构
content-distribution-mcp/
├── src/
│ ├── server.ts # MCP 服务器主入口
│ ├── index.ts # CLI 入口和 stdio 模式
│ ├── models.ts # 数据类型定义
│ ├── adapters/
│ │ ├── index.ts # 适配器注册表
│ │ ├── devto.ts # DEV.to 适配器
│ │ ├── hashnode.ts # Hashnode 适配器
│ │ ├── github-discussions.ts
│ │ ├── reddit.ts
│ │ ├── bluesky.ts
│ │ ├── browser.ts # 浏览器回退适配器
│ │ └── ...
│ └── backends/
│ ├── base.ts # 后端接口定义
│ └── yaml.ts # YAML 后端实现
├── README.md
└── package.json
参见
资料来源:README.md:1-15
数据流与状态管理
Content Distribution MCP 的核心职责之一是管理多平台内容发布的状态与数据流。系统需要解决以下关键问题:
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
Content Distribution MCP 的核心职责之一是管理多平台内容发布的状态与数据流。系统需要解决以下关键问题:
- 幂等性保障:同一内容在相同渠道的重复发布应返回已有结果,而非重复创建
- 状态追踪:记录每个渠道的发布状态(live、queued、needs_browser、failed)
- 去重机制:通过
content.id+channel组合键实现发布去重 - 调度管理:支持定时发布,将未来时间的发布请求入队待执行
资料来源:src/models.ts:1-45
核心数据模型
Content 内容模型
interface Content {
id: string; // 内容唯一标识符
title: string; // 内容标题
subtitle?: string; // 副标题(Hashnode、DEV.to 支持)
body_md: string; // Markdown 格式的正文
cover_image?: string; // 封面图片 URL
tags: string[]; // 标签列表
canonical_url?: string; // 权威原文 URL
cta_block?: string; // 行动号召区块
author: string; // 作者显示名称
source_task_id?: string; // 溯源任务 ID(如 Notion 任务 ID)
}
资料来源:src/models.ts:1-14
Variant 变体模型
每个渠道的适配内容称为变体,包含渠道特定的调整:
interface Variant {
channel: string; // 渠道标识符,格式为 'platform' 或 'platform:account'
title: string; // 渠道特定标题
body: string; // 渠道适配后的正文
tags: string[]; // 渠道特定标签
canonical_url?: string; // 该渠道的权威 URL 覆盖
cta_block?: string; // 该渠道的 CTA 覆盖
schedule_at?: string; // ISO-8601 定时发布时间
extras: Record<string, unknown>; // 渠道特定参数(flair、category 等)
}
资料来源:src/models.ts:16-28
PublishState 发布状态枚举
| 状态值 | 含义 | 触发场景 |
|---|---|---|
live | 已成功发布 | 平台 API 返回成功响应 |
queued | 已加入调度队列 | 设置了未来时间的 schedule_at |
needs_browser | 需要浏览器操作 | LinkedIn、Medium、Twitter 等无 API 的渠道 |
failed | 发布失败 | API 调用失败、认证缺失、平台限流等 |
资料来源:src/models.ts:30
PublishResult 发布结果
interface PublishResult {
channel: string; // 变体对应的渠道标识符
state: PublishState; // 当前状态
live_url?: string; // 线上帖子 URL(state=live 时)
draft_path?: string; // 本地草稿路径(浏览器回退渠道)
compose_url?: string; // 平台编辑 URL(needs_browser 时)
error?: string; // 错误信息(state=failed 时)
published_at?: string; // UTC ISO-8601 发布时间
}
资料来源:src/models.ts:32-45
架构组件
适配器架构
系统采用适配器模式,每种发布渠道对应一个适配器实现:
graph TD
A[post.publish / post.schedule] --> B[Server 核心逻辑]
B --> C{幂等性检查}
C -->|已有发布记录| D[返回缓存结果]
C -->|新发布请求| E[选择适配器]
E --> F[devto: DevToAdapter]
E --> G[hashnode: HashnodeAdapter]
E --> H[reddit: RedditAdapter]
E --> I[bluesky: BlueskyAdapter]
E --> J[browser: makeBrowserAdapter]
F --> K[平台 API]
G --> K
H --> K
I --> K
J --> L[返回 compose_url]
K --> M[写入状态后端]
M --> N[返回 PublishResult]资料来源:src/adapters/index.ts:1-30
适配器接口定义
interface ChannelAdapter {
hints(): ChannelHints; // 静态渠道元数据
publish(variant: Variant, profile: Profile): Promise<PublishResult>;
unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string | undefined]>;
}
| 方法 | 副作用 | 确定性 |
|---|---|---|
hints() | 只读,无 HTTP 调用 | 编译时常量,完全确定性 |
publish() | 写操作,调用平台 API | 依赖外部状态 |
unpublish() | 写操作,调用平台 API | 依赖外部状态 |
资料来源:src/adapters/index.ts:1-12
状态后端架构
后端类型
系统支持可插拔的状态后端,通过环境变量 DISTRIBUTION_BACKEND 配置:
| 后端类型 | 默认值 | 说明 |
|---|---|---|
yaml | ✅ | 使用 YAML 文件存储状态(~/.distribution-mcp/) |
function buildBackend(): StateBackend {
const name = (process.env.DISTRIBUTION_BACKEND ?? "yaml").toLowerCase();
// 根据配置构建对应后端实例
}
状态存储结构
状态后端管理以下数据:
graph LR
A[published.yaml] --> B[content.id → channel → PublishResult]
C[scheduled.yaml] --> D[schedule_at → Variant 列表]
E[profiles.yaml] --> F[profile_name → credentials + config]published.yaml 存储已发布状态,键结构为 content.id → channel:
"n8n-webhook-setup@2026-05-20":
devto:main:
state: live
live_url: "https://dev.to/xxx"
published_at: "2026-05-20T10:30:00Z"
scheduled.yaml 存储待调度任务:
- channel: reddit:ClaudeAI
schedule_at: "2026-05-21T09:00:00+01:00"
content: {...}
variant: {...}
资料来源:README.md:60-80
幂等性机制
幂等性原理
系统的幂等性基于 (content.id, channel) 组合键实现:
sequenceDiagram
participant Agent
participant Server
participant Backend
participant Platform
Agent->>Server: post.publish(content, variants)
Server->>Backend: 查询 published.yaml
Backend-->>Server: 已有记录?
alt 已有发布记录
Server-->>Agent: 返回缓存的 live_url
else 无记录
Server->>Platform: 调用平台 API
Platform-->>Server: 发布结果
Server->>Backend: 写入状态
Server-->>Agent: 返回新结果
end幂等性特点
| 特性 | 说明 |
|---|---|
| 组合键去重 | content.id + channel 共同决定唯一性 |
| 缓存返回 | 重复请求直接返回缓存的 live_url |
| 安全重试 | 发布失败后可安全重试,不会重复创建 |
| 跨会话持久化 | 状态存储在 YAML 文件中,重启后保持 |
资料来源:README.md:45-55
调度工作流
调度发布流程
使用 post.schedule 时,带有 schedule_at 的变体会被加入调度队列:
graph LR
A[post.schedule] --> B{schedule_at?}
B -->|有未来时间| C[写入 scheduled.yaml]
B -->|立即发布| D[调用适配器]
C --> E[drain 触发]
E --> F{到时检查}
F -->|未到时| G[跳过]
F -->|已到时| H[执行发布]
H --> I[写入 published.yaml]
H --> J[从 scheduled.yaml 移除]Drain 命令
post.drain 工具检查所有待调度的发布,触发已到期的任务:
# 建议通过 cron 每 5 分钟执行一次
*/5 * * * * npx -y content-distribution-mcp drain
调度格式要求:
- 必须包含时区偏移量(如
+08:00) - 正确格式:
2026-05-21T09:00:00+01:00 - 缺少时区会导致不可预期行为
资料来源:README.md:56-70
渠道适配器详解
API 直连渠道
以下渠道通过原生 API 发布:
| 渠道 | 适配器 | 认证要求 |
|---|---|---|
| DEV.to | DevToAdapter | DEV_TO_API_KEY |
| Hashnode | HashnodeAdapter | HASHNODE_TOKEN + HASHNODE_PUBLICATION_ID |
| GitHub Discussions | GitHubDiscussionsAdapter | GITHUB_TOKEN + GITHUB_DISCUSSION_REPO |
RedditAdapter | REDDIT_CLIENT_ID/SECRET/USERNAME/PASSWORD | |
| Bluesky | BlueskyAdapter | BLUESKY_IDENTIFIER + BLUESKY_PASSWORD |
#### DEV.to 发布流程示例
const res = await fetch(`${API}/articles`, {
method: "POST",
headers: { "Content-Type": "application/json", "api-key": apiKey },
body: JSON.stringify({
article: {
title: variant.title,
body_markdown: variant.body,
tags: variant.tags.slice(0, 4), // 最多 4 个标签
canonical_url: variant.canonical_url,
published: true,
},
}),
});
资料来源:src/adapters/devto.ts:20-38
#### Bluesky 发布流程示例
// 1. 创建会话获取 JWT
const sessionRes = await fetch(`${BSKY}/com.atproto.server.createSession`, {
method: "POST",
body: JSON.stringify({ identifier: BLUESKY_IDENTIFIER, password: BLUESKY_PASSWORD }),
});
const session = await sessionRes.json();
// 2. 使用 JWT 创建帖子
const postRes = await fetch(`${BSKY}/com.atproto.repo.createRecord`, {
headers: { "Authorization": `Bearer ${session.accessJwt}` },
body: JSON.stringify({...}),
});
资料来源:src/adapters/bluesky.ts:20-35
浏览器回退渠道
以下渠道无公开 API,需要浏览器操作:
| 渠道 | 返回状态 | 返回字段 |
|---|---|---|
| Medium | needs_browser | compose_url → https://medium.com/new-story |
needs_browser | compose_url → https://www.linkedin.com/post/new | |
| Twitter/X | needs_browser | compose_url → 预填充推文链接 |
const COMPOSE_URLS: Record<Platform, (v: Variant) => string> = {
medium: () => "https://medium.com/new-story",
linkedin: () => "https://www.linkedin.com/post/new",
twitter: (v) => `https://twitter.com/compose/tweet?text=${encodeURIComponent(v.body.slice(0, 280))}`,
};
资料来源:src/adapters/browser.ts:1-10
社区关注:根据路线图规划,Twitter/X、LinkedIn、Medium 的原生 API 支持正在计划中。资料来源:社区上下文 - Roadmap & feature tracker
工具接口
MCP 工具列表
| 工具 | 用途 | 副作用 |
|---|---|---|
post.publish | 立即发布所有变体 | 幂等写操作 |
post.schedule | 队列定时发布 + 立即发布 | 写 scheduled.yaml + 幂等写操作 |
post.drain | 触发所有到期调度 | 执行 queued → live |
post.status | 查询内容/渠道状态 | 只读 |
post.unpublish | 尝试取消发布 | 平台删除操作 |
channel.hints | 渠道静态元数据 | 只读,无 HTTP |
profile.list | 列出分发配置 | 只读 |
subreddit.list | Reddit 子版块目录 | 只读 |
发布结果输出模式
const publishOutputShape = {
results: z.array(z.object({
channel: z.string(),
state: z.enum(["live", "queued", "needs_browser", "failed"]),
live_url: z.string().nullable(),
draft_path: z.string().nullable(),
compose_url: z.string().nullable(),
error: z.string().nullable(),
published_at: z.string().nullable(),
})),
};
资料来源:src/server.ts:95-110
常见故障模式
认证失败
| 渠道 | 缺失凭证 | 错误表现 |
|---|---|---|
| DEV.to | DEV_TO_API_KEY | state: "failed", error: "DEV_TO_API_KEY not set" |
| Hashnode | HASHNODE_TOKEN | state: "failed", error: "HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set" |
| Bluesky | BLUESKY_IDENTIFIER | state: "failed", error: "BLUESKY_IDENTIFIER and BLUESKY_PASSWORD required" |
平台限流
当平台返回非 200 状态码时:
{
"channel": "devto:main",
"state": "failed",
"error": "devto 429: Rate limit exceeded"
}
Reddit 反垃圾机制
Reddit 渠道具有自动限流检测:
- 子版块冷却期(cooldown_days)
- 发帖频率超过限制时返回
needs_browser - 可通过
subreddit.list查看上次成功发帖时间
浏览器回退渠道状态
对于 needs_browser 状态的应用流程:
graph TD
A[post.publish 返回 needs_browser] --> B[获取 compose_url]
B --> C[用户在浏览器中手动发布]
D[可选: 手动记录 live_url] --> E[后续请求可识别为 live]资料来源:src/adapters/browser.ts:15-30
最佳实践
1. 设计稳定的 content.id
推荐格式:{slug}@{date},例如:
{
"id": "n8n-webhook-setup@2026-05-20"
}
2. 合理使用调度
- 避免过密的调度间隔
- 建议 Reddit 渠道间隔至少 24-48 小时
- 时区必须明确指定
3. 渠道特定调整
发布前使用 channel.hints 获取约束:
{
"channel": "devto",
"result": {
"max_length": 100000,
"supported_md_features": ["bold", "italic", "code_inline", "links"],
"tag_vocab": ["ai", "automation", "mcp", "claude"],
"cta_placement": "bottom",
"canonical_url_supported": true,
"browser_only": false
}
}
4. 错误处理策略
const result = await callTool("post.publish", {...});
// 检查状态
if (result.state === "failed") {
console.error(`发布失败: ${result.error}`);
} else if (result.state === "needs_browser") {
console.log(`请手动发布: ${result.compose_url}`);
} else if (result.state === "live") {
console.log(`已发布: ${result.live_url}`);
}
相关文档
参见
资料来源:src/models.ts:1-45
适配器概述
适配器(Adapter)是 content-distribution-mcp 项目的核心组件,负责将统一格式的内容变体(Variant)发布到不同的内容平台。每个平台适配器封装了该平台的 API 调用规范、认证方式、格式限制和发布逻辑,使 MCP 服务器能够以一致的方式处理多平台内容分发。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
适配器(Adapter)是 content-distribution-mcp 项目的核心组件,负责将统一格式的内容变体(Variant)发布到不同的内容平台。每个平台适配器封装了该平台的 API 调用规范、认证方式、格式限制和发布逻辑,使 MCP 服务器能够以一致的方式处理多平台内容分发。
适配器系统采用适配器模式(Adapter Pattern),将平台差异抽象为统一的接口。调用方只需通过 ChannelAdapter 接口与各平台交互,无需关心底层 API 的具体实现细节。资料来源:src/adapters/index.ts:1-20
架构设计
适配器接口
所有平台适配器必须实现 ChannelAdapter 接口,该接口定义了三个核心方法:
| 方法 | 返回类型 | 说明 |
|---|---|---|
hints() | ChannelHints | 返回平台的静态元数据(字符限制、Markdown 支持等) |
publish(variant, profile) | Promise<PublishResult> | 将内容变体发布到目标平台 |
unpublish(liveUrl, profile) | Promise<[boolean, string?]> | 取消发布(返回成功状态和可选错误信息) |
资料来源:src/adapters/index.ts:11-15
适配器注册机制
MCP 服务器通过 buildAdapterMap() 函数构建所有适配器的映射表,并将其存储在内存中供工具处理器调用:
export function buildAdapterMap(): Record<string, ChannelAdapter> {
const medium = makeBrowserAdapter("medium");
const linkedin = makeBrowserAdapter("linkedin");
const twitter = makeBrowserAdapter("twitter");
return {
devto: new DevToAdapter(),
hashnode: new HashnodeAdapter(),
github_discussions: new GitHubDiscussionsAdapter(),
"github-discussions": new GitHubDiscussionsAdapter(),
reddit: new RedditAdapter(),
bluesky: new BlueskyAdapter(),
medium,
medium_browser: medium,
"medium-browser": medium,
linkedin,
linkedin_browser: linkedin,
"linkedin-browser": linkedin,
twitter,
twitter_browser: twitter,
"twitter-browser": twitter,
x: twitter,
};
}
资料来源:src/adapters/index.ts:24-48
注意:部分平台提供了多个别名(如github_discussions和github-discussions),以兼容不同的命名习惯。
适配器选择流程
当用户调用 post.publish 或 post.schedule 工具时,服务器根据变体中的 channel 字段选择对应的适配器:
graph TD
A[用户调用 post.publish] --> B[解析 channel 字段<br/>格式: 'platform' 或 'platform:account']
B --> C[提取平台前缀<br/>如 'devto', 'reddit', 'linkedin']
C --> D[从 adapters 映射表<br/>查找对应适配器]
D --> E{适配器是否存在?}
E -->|是| F[调用 adapter.publish]
E -->|否| G[抛出错误: No adapter for 'channel']
F --> H[返回 PublishResult]资料来源:src/server.ts:server.registerTool 调用逻辑
平台适配器分类
API 直连型适配器
以下平台具有官方公开 API,支持自动化发布:
| 平台 | 适配器类 | API 类型 | 必需凭证 |
|---|---|---|---|
| DEV.to | DevToAdapter | REST API | DEV_TO_API_KEY |
| Hashnode | HashnodeAdapter | GraphQL | HASHNODE_TOKEN + HASHNODE_PUBLICATION_ID |
| GitHub Discussions | GitHubDiscussionsAdapter | GraphQL | GITHUB_TOKEN + GITHUB_DISCUSSION_REPO |
RedditAdapter | OAuth REST | REDDIT_CLIENT_ID/SECRET/USERNAME/PASSWORD | |
| Bluesky | BlueskyAdapter | AT Protocol (REST) | BLUESKY_IDENTIFIER + BLUESKY_PASSWORD |
资料来源:src/adapters/index.ts:26-31
浏览器回退型适配器
以下平台目前没有公开或稳定的内容发布 API,采用浏览器回退机制:
| 平台 | 状态 | 返回值 | 用户操作 |
|---|---|---|---|
| 浏览器回退 | needs_browser + compose_url | 手动在浏览器中完成发布 | |
| Medium | 浏览器回退 | needs_browser + compose_url | 手动在浏览器中完成发布 |
| Twitter/X | 浏览器回退 | needs_browser + compose_url | 手动在浏览器中完成发布 |
资料来源:src/adapters/browser.ts:1-45
社区规划:路线图与功能追踪 显示社区正在计划为这些平台添加原生 API 支持。
平台 hints 元数据
每个适配器的 hints() 方法返回平台静态元数据,帮助调用方了解平台约束:
ChannelHints 数据结构
interface ChannelHints {
max_length: number; // 正文字符上限
supported_md_features: string[]; // 支持的 Markdown 特性列表
cta_placement: "top" | "bottom" | "footer" | "none"; // CTA 放置位置
canonical_url_supported: boolean; // 是否支持 canonical_url
browser_only: boolean; // 是否为浏览器回退模式
tag_vocab?: string[]; // 可选:标签词汇表
}
各平台 hints 对比
| 平台 | 最大长度 | Markdown 特性 | CTA 位置 | canonical_url | 浏览器回退 |
|---|---|---|---|---|---|
| DEV.to | 100,000 | bold, italic, code_inline, code_block, links, headers, images, lists, tables, blockquote | bottom | ✓ | ✗ |
| Hashnode | 50,000 | bold, italic, code_inline, code_block, links, headers, images, lists, tables, blockquote | bottom | ✓ | ✗ |
| GitHub Discussions | 65,536 | bold, italic, code_inline, code_block, links, headers, lists | bottom | ✗ | ✗ |
| 40,000 | bold, italic, code_inline, links | bottom | ✗ | ✗ | |
| Bluesky | 300 | links | bottom | ✗ | ✗ |
| 3,000 | bold, italic, links | bottom | ✗ | ✓ | |
| Medium | 100,000 | bold, italic, code_inline, code_block, links, headers, images | bottom | ✓ | ✓ |
| Twitter/X | 280 | links | none | ✗ | ✓ |
资料来源:src/adapters/devto.ts:11-18, src/adapters/hashnode.ts:11-17, src/adapters/bluesky.ts:11-16, src/adapters/browser.ts:15-43
发布结果状态
publish() 方法返回 PublishResult 对象,包含发布操作的结果信息:
| 状态 | 含义 | live_url | compose_url | draft_path |
|---|---|---|---|---|
live | 发布成功 | ✓ 公开链接 | ✗ | ✗ |
queued | 已加入定时队列 | ✗ | ✗ | ✗ |
needs_browser | 需手动浏览器发布 | ✗ | ✓ 平台编辑链接 | ✗ |
failed | 发布失败 | ✗ | ✗ | ✗ |
资料来源:src/server.ts:publishResultShape 定义
认证机制
API Key 认证
DEV.to 使用简单的 API Key 认证:
const res = await fetch(`${API}/articles`, {
method: "POST",
headers: { "Content-Type": "application/json", "api-key": apiKey },
body: JSON.stringify({ article: { ... } }),
});
资料来源:src/adapters/devto.ts:22-29
OAuth 认证
Reddit 使用 OAuth 2.0 认证流程获取访问令牌:
const tokenRes = await fetch("https://www.reddit.com/api/v1/access_token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": basicAuth },
body: new URLSearchParams({
grant_type: "password",
username: redditUsername,
password: redditPassword,
}),
});
会话认证
Bluesky 使用会话认证,先创建会话获取 JWT,再使用 JWT 发布内容:
const sessionRes = await fetch(`${BSKY}/com.atproto.server.createSession`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ identifier: BLUESKY_IDENTIFIER, password: BLUESKY_PASSWORD }),
});
const session = await sessionRes.json() as { accessJwt: string; did: string };
const postRes = await fetch(`${BSKY}/com.atproto.repo.createRecord`, {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${session.accessJwt}` },
body: JSON.stringify({ ... }),
});
资料来源:src/adapters/bluesky.ts:23-37
浏览器回退适配器实现
对于尚未支持原生 API 的平台,makeBrowserAdapter() 工厂函数创建浏览器回退适配器:
export function makeBrowserAdapter(platform: Platform) {
return {
hints(): ChannelHints {
return HINTS[platform];
},
async publish(variant: Variant): Promise<PublishResult> {
return {
channel: variant.channel,
state: "needs_browser",
compose_url: COMPOSE_URLS[platform](variant),
};
},
async unpublish(_liveUrl: string): Promise<[boolean, string]> {
return [false, `${platform} posts must be deleted manually via the platform UI`];
},
};
}
关键特性:
publish()始终返回needs_browser状态compose_url包含预填充的内容(如 Twitter 可携带推文草稿)unpublish()永远返回失败,需用户手动在平台界面操作
资料来源:src/adapters/browser.ts:45-65
定时发布支持
适配器本身不处理定时逻辑,定时功能由后端状态管理处理。适配器只需正确处理 schedule_at 字段:
- 当
schedule_at存在时,服务器将变体存入scheduled.yaml - 定时时间到达后,
post.drain工具读取队列并调用适配器publish() - 适配器无需感知定时机制
常见失败模式
认证失败
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
DEV_TO_API_KEY not set in profile | 未配置 API Key | 在 profiles.yaml 中配置 DEV_TO_API_KEY |
Bluesky auth failed: 401 | Bluesky 凭证无效 | 检查 BLUESKY_IDENTIFIER 和 BLUESKY_PASSWORD |
HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set | Hashnode 配置不完整 | 配置 HASHNODE_TOKEN 和 HASHNODE_PUBLICATION_ID |
平台限制
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
devto 422: ... | DEV.to 验证失败 | 检查标签数量(最多4个)、标题长度 |
| Reddit cooldown | 同一 subreddit 发帖过于频繁 | 遵守 cooldown_days 间隔 |
扩展新平台适配器
如需添加新平台支持,需创建新适配器类并注册到 buildAdapterMap():
- 在
src/adapters/目录创建新文件(如newplatform.ts) - 实现
ChannelAdapter接口的三个方法 - 在
src/adapters/index.ts中导入并添加到返回对象 - 在
src/server.ts的工具处理逻辑中添加对应的工具名处理
相关文档
DEV.to 适配器
DEV.to 适配器是 content-distribution-mcp 项目中负责将内容发布到 DEV.to 平台的适配器实现。它属于自动发布层级(Auto Tier),通过 DEV.to 原生 REST API 实现内容发布,支持完整的 Markdown 渲染、标签管理、系列文章组织以及规范化 URL(canonical URL)设置。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
DEV.to 适配器是 content-distribution-mcp 项目中负责将内容发布到 DEV.to 平台的适配器实现。它属于自动发布层级(Auto Tier),通过 DEV.to 原生 REST API 实现内容发布,支持完整的 Markdown 渲染、标签管理、系列文章组织以及规范化 URL(canonical URL)设置。
DEV.to 适配器位于适配器架构的自动发布通道,与 Hashnode、GitHub Discussions、Reddit、Bluesky 同属一类——即平台提供了公开 API,可直接通过 HTTP 请求完成发布,无需浏览器交互。
资料来源:src/adapters/index.ts:8-11
技术架构
适配器定位
graph TD
A[post.publish / post.schedule] --> B{平台类型判定}
B -->|browser_only: false| C[自动发布通道]
B -->|browser_only: true| D[浏览器回退通道]
C --> E[DEV.to 适配器]
C --> F[Hashnode 适配器]
C --> G[GitHub Discussions 适配器]
C --> H[Reddit 适配器]
C --> I[Bluesky 适配器]
style E fill:#90EE90DEV.to 适配器实现了 ChannelAdapter 接口,提供了三个核心方法:
| 方法 | 功能 | 副作用 |
|---|---|---|
hints() | 返回平台元数据(字符限制、Markdown 支持特性、标签词汇表等) | 无,仅读取编译时常量 |
publish(variant, profile) | 将内容变体发布到 DEV.to | 写入外部 API |
unpublish(liveUrl, profile) | 将已发布的文章设为草稿状态 | 写入外部 API |
资料来源:src/adapters/devto.ts:1-70
通道标识符
在 content-distribution-mcp 中,DEV.to 通道使用以下标识符:
| 标识符 | 说明 | 示例 |
|---|---|---|
devto | 主通道标识符 | "devto" |
devto:account | 带账户后缀的完整标识符 | "devto:main", "devto:username" |
在使用 post.publish 或 post.schedule 时,variants[].channel 字段可接受上述格式。
资料来源:src/server.ts:25-27
ChannelHints 元数据
DEV.to 适配器的 hints() 方法返回以下平台约束信息:
graph LR
subgraph "DEV.to 平台特性"
A["max_length: 100,000"]
B["tag_vocab: 10个推荐标签"]
C["cta_placement: bottom"]
D["canonical_url_supported: true"]
E["supported_md_features: bold, italic, code_inline..."]
end平台约束参数表
| 参数 | 值 | 说明 |
|---|---|---|
max_length | 100,000 | 正文字符最大长度 |
supported_md_features | bold, italic, code_inline, code_block, links, headers, images, lists, tables, blockquote | 支持的 Markdown 功能完整列表 |
tag_vocab | ai, automation, mcp, claude, llm, devops, tutorial, productivity, javascript, python | 推荐使用的标签词汇 |
cta_placement | bottom | CTA 区块放置位置(正文底部) |
canonical_url_supported | true | 是否支持规范化 URL |
browser_only | false | 是否需要浏览器回退(否,可自动发布) |
资料来源:src/adapters/devto.ts:9-22
发布流程
发布状态流转
stateDiagram-v2
[*] --> 认证检查
认证检查 --> |API Key 存在| API调用
认证检查 --> |API Key 缺失| 认证失败: state: "failed"
API调用 --> |HTTP 200| 发布成功: state: "live"
API调用 --> |HTTP !200| 发布失败: state: "failed"
发布成功 --> 返回: live_url, published_at
认证失败 --> 返回: error message
发布失败 --> 返回: error messagepublish 方法详解
async publish(variant: Variant, profile: Profile): Promise<PublishResult>
认证要求:
DEV.to API 需要 DEV_TO_API_KEY 凭证,该凭证存储在分发配置文件的 credentials 字段中:
# ~/.distribution-mcp/profiles/default.yaml
credentials:
DEV_TO_API_KEY: "your-devto-api-key-here"
资料来源:src/adapters/devto.ts:24-26
API 请求构建:
适配器向 https://dev.to/api/articles 端点发送 POST 请求,请求体结构如下:
| 字段 | 来源 | 说明 |
|---|---|---|
article.title | variant.title | 文章标题 |
article.body_markdown | variant.body | Markdown 正文 |
article.tags | variant.tags.slice(0, 4) | 标签(最多4个,超出部分截断) |
article.canonical_url | variant.canonical_url(可选) | 规范化 URL |
article.published | true | 立即发布 |
article.series | variant.extras.series(可选) | 系列名称 |
资料来源:src/adapters/devto.ts:28-44
响应处理:
- 成功响应(HTTP 200):解析 JSON 返回的
url字段,构建PublishResult对象 state:"live"live_url: 解析得到的文章 URLpublished_at: 当前 UTC 时间戳
- 认证失败:返回
state: "failed"+ 错误信息"DEV_TO_API_KEY not set in profile"
- API 错误:返回
state: "failed"+ 错误信息"devto {status}: {response_text}"
资料来源:src/adapters/devto.ts:45-52
幂等性保证
DEV.to 适配器支持幂等发布。当相同 content.id + channel 组合的内容被再次提交时,post.publish 工具会先检查状态后端,如果该内容已在 DEV.to 上线,则直接返回现有的 live_url,避免重复发布。
下线(Unpublish)流程
unpublish 方法详解
async unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string | undefined]>
DEV.to 平台不支持直接删除文章,适配器采用设为草稿的方式实现下线:
sequenceDiagram
参与者 A as 调用方
参与者 B as DEV.to API
A->>B: GET /articles/me/published?per_page=100
B-->>A: 文章列表 [{id, slug}, ...]
alt 找到匹配文章
A->>B: PUT /articles/{id} {article: {published: false}}
B-->>A: 200 OK
A-->>A: return [true, undefined]
else 未找到匹配
A-->>A: return [false, "article not found"]
end下线步骤:
- 使用
DEV_TO_API_KEY调用GET /articles/me/published?per_page=100获取当前用户已发布的文章列表 - 在列表中匹配
liveUrl中包含的文章 slug - 若找到匹配项,调用
PUT /articles/{id}将published设为false - 返回
[true, undefined]表示成功,或[false, errorMessage]表示失败
资料来源:src/adapters/devto.ts:54-76
配置指南
环境变量与凭证配置
| 配置项 | 说明 | 必需 |
|---|---|---|
DEV_TO_API_KEY | DEV.to 个人 API Key | 是 |
获取 API Key 的步骤:
- 登录 DEV.to
- 进入 Settings → Extensions → DEV API Keys
- 创建一个新的 API Key
profile.yaml 配置示例
# ~/.distribution-mcp/profiles/my-profile.yaml
credentials:
DEV_TO_API_KEY: "ABCdefGHIjklMNOpqrsTUVwxyz123456"
Variant 配置示例
{
"content": {
"id": "my-article-2026-05-20",
"title": "Getting Started with MCP Servers",
"body_md": "# Introduction\n\nThis is the full article body...",
"tags": ["mcp", "automation", "tutorial", "devops", "productivity"],
"canonical_url": "https://myblog.com/mcp-getting-started",
"author": "Your Name"
},
"variants": [
{
"channel": "devto:main",
"title": "Getting Started with MCP Servers",
"body": "# Introduction\n\nThis is the DEV.to specific content...",
"tags": ["mcp", "automation", "tutorial"],
"canonical_url": "https://myblog.com/mcp-getting-started",
"extras": {
"series": "MCP Tutorial Series"
}
}
],
"profile_name": "my-profile"
}
资料来源:README.md:52-75
平台特性与限制
DEV.to 特有的 extras 参数
| 参数 | 类型 | 说明 |
|---|---|---|
series | string | 将文章分配到某个系列(Series),用于组织系列教程或专题内容 |
Markdown 支持详情
DEV.to 是所有通道中 Markdown 支持最完整的平台之一:
| 功能 | 支持 | 说明 | |||
|---|---|---|---|---|---|
粗体 text | ✅ | ||||
斜体 *text* | ✅ | ||||
行内代码 ` code ` | ✅ | ||||
代码块 ``` `lang ``` | ✅ | 支持语法高亮 | |||
链接 text | ✅ | ||||
标题 # ## ### | ✅ | 支持 h1-h6 | |||
图片 !alt | ✅ | ||||
列表 - item / 1. item | ✅ | ||||
| 表格 ` | col | col | ` | ✅ | |
引用 > quote | ✅ |
资料来源:src/adapters/devto.ts:16-18
标签处理规则
- 每个变体的
tags数组最多保留 4 个标签 - 超出限制的标签将被自动截断
- 建议使用
channel.hints返回的tag_vocab中的标签以提高发现率
CTA 区块处理
DEV.to 的 cta_placement 为 bottom,意味着 content.cta_block 或 variant.cta_block 的内容会被追加到文章正文的末尾。
常见问题与故障排除
认证失败
症状: 返回 state: "failed" + "DEV_TO_API_KEY not set in profile"
解决方案:
- 确认
profile.yaml中credentials.DEV_TO_API_KEY已正确配置 - 确认 API Key 未过期,可前往 DEV.to 设置页面重新生成
- 确认使用的 profile 名称与配置一致
文章未找到(下线失败)
症状: unpublish 返回 [false, "article not found in published list"]
原因:
- 传入的
liveUrl中的 slug 与已发布列表中的任何文章都不匹配 - 文章已被平台手动删除
解决方案:
- 手动在 DEV.to 网站上将文章设为草稿
API 限流
症状: 返回 "devto 429: ..."
解决方案:
- DEV.to API 有请求频率限制
- 等待一段时间后重试
- 避免短时间内发布大量文章
与其他适配器的对比
| 特性 | DEV.to | Hashnode | GitHub Discussions | Bluesky | |
|---|---|---|---|---|---|
| 自动化等级 | Auto | Auto | Auto | Auto-gated | Auto |
| API 类型 | REST | GraphQL | GraphQL | OAuth | AT Protocol |
| 正文上限 | 100,000 | 50,000 | 65,000 | 40,000 | 300 |
| Markdown 支持 | 完整 | 完整 | 完整 | 受限 | 仅链接 |
| 标签限制 | 4 | 5 | 无限制 | 多个 | 无 |
| canonical URL | ✅ | ✅ | ❌ | ❌ | ❌ |
| 系列文章 | ✅ (extras.series) | ✅ (extras.series) | ❌ | ❌ | ❌ |
| 下线支持 | 设为草稿 | ❌ | ❌ | ❌ | ❌ |
路线图与社区计划
根据社区路线图,DEV.to 适配器目前处于稳定状态,暂无已计划的改进项。
社区关注的其他平台改进方向:
- [ ] Twitter/X 原生 API 支持(目前使用浏览器回退)
- [ ] LinkedIn 原生 API 支持(目前使用浏览器回退)
- [ ] Medium 原生 API 支持(目前使用浏览器回退)
资料来源:社区路线图
相关页面
Hashnode 适配器
Hashnode 适配器是 content-distribution-mcp 项目中用于与 Hashnode 博客平台进行集成的核心组件。它负责将内容变体(Variant)发布到 Hashnode 出版物(Publication),并提供平台特定的功能支持,包括标签规范化、系列文章关联和规范化 URL 设置。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
Hashnode 适配器是 content-distribution-mcp 项目中用于与 Hashnode 博客平台进行集成的核心组件。它负责将内容变体(Variant)发布到 Hashnode 出版物(Publication),并提供平台特定的功能支持,包括标签规范化、系列文章关联和规范化 URL 设置。
Hashnode 是 content-distribution-mcp 支持的 8+ 渠道之一,属于自动发布渠道类别,即该渠道拥有公开 API,无需浏览器回退即可完成发布操作。资料来源:README.md
架构设计
适配器角色
Hashnode 适配器实现了 ChannelAdapter 接口,该接口定义了所有渠道适配器必须实现的标准方法。适配器作为 MCP 服务器与 Hashnode GraphQL API 之间的桥梁,负责:
- 转换:将通用的内容模型转换为 Hashnode API 所需的 GraphQL 变异格式
- 验证:确保必填凭证存在且有效
- 发布:通过 GraphQL 变异将内容发布到 Hashnode 出版物
- 反解析:提供渠道特定的元数据(ChannelHints)
graph TD
A[MCP 服务器] -->|post.publish| B[Hashnode 适配器]
B --> C{Hashnode GraphQL API}
C -->|成功| D[返回 live_url]
C -->|失败| E[返回 state: failed + error]
F[Profile 凭证] -->|HASHNODE_TOKEN<br>HASHNODE_PUBLICATION_ID| B
G[channel.hints] -->|静态元数据| H[调用方 Agent]接口实现
Hashnode 适配器实现了 ChannelAdapter 接口定义的三个核心方法:
| 方法 | 作用 | 是否涉及网络请求 |
|---|---|---|
hints() | 返回平台静态元数据 | 否(纯函数) |
publish(variant, profile) | 发布内容到 Hashnode | 是 |
unpublish(liveUrl, profile) | 取消发布文章 | 是 |
资料来源:src/adapters/index.ts:5-12
渠道元数据(ChannelHints)
静态元数据详解
hints() 方法返回的 ChannelHints 对象描述了 Hashnode 平台的约束和功能支持情况:
| 属性 | 值 | 说明 |
|---|---|---|
max_length | 50,000 | 正文字符最大长度限制 |
supported_md_features | bold, italic, code_inline, code_block, links, headers, images, lists, tables, blockquote | 支持的 Markdown 功能 |
tag_vocab | ai, automation, mcp, claude, devops, tutorial, productivity, llm | 推荐标签词汇表 |
cta_placement | bottom | 行动号召块(CTA)放置位置 |
canonical_url_supported | true | 是否支持规范化 URL |
browser_only | false | 是否需要浏览器回退(否) |
资料来源:src/adapters/hashnode.ts:8-17
与其他渠道的对比
graph LR
A[Hashnode] -->|max_length| B[50,000]
A -->|Markdown| C[完整支持]
A -->|canonical| D[✓ 支持]
E[Twitter] -->|max_length| F[280]
E -->|Markdown| G[仅 links]
E -->|canonical| H[✗ 不支持]
I[LinkedIn] -->|max_length| J[3,000]
I -->|Markdown| K[bold, italic, links]
I -->|canonical| L[✗ 不支持]发布流程
发布逻辑详解
publish 方法遵循以下执行流程:
flowchart TD
A[publish 调用] --> B{凭证检查}
B -->|HASHNODE_TOKEN 缺失| C[返回 failed]
B -->|HASHNODE_PUBLICATION_ID 缺失| C
B -->|凭证完整| D[构建 GraphQL 变异]
D --> E[发送 POST 请求到 gql.hashnode.com]
E --> F{HTTP 响应}
F -->|ok| G[解析 response.data]
F -->|!ok| H[返回 failed + 状态码]
G --> I{url 存在?}
I -->|是| J[返回 live, live_url, published_at]
I -->|否| K[返回 failed + errors]GraphQL 变异构造
Hashnode 适配器使用 GraphQL mutation 将内容发布到 Hashnode API。核心变异语句结构如下:
mutation PublishPost($input: PublishPostInput!) {
publishPost(input: $input) {
post { url }
}
}
请求体构建
适配器根据 Variant 数据构建 GraphQL 变量对象:
| Variant 字段 | 映射到 Hashnode API | 处理逻辑 |
|---|---|---|
variant.title | input.title | 直接映射 |
variant.body | input.contentMarkdown | 直接映射 |
variant.tags | input.tags | 截取前 5 个,转换为 {slug, name} 格式 |
variant.canonical_url | input.canonicalUrl | 仅当存在时添加 |
variant.extras.series | input.seriesId | 仅当 extras.series 存在时添加 |
资料来源:src/adapters/hashnode.ts:19-43
标签转换逻辑
Hashnode 的标签格式要求与通用格式不同,需要进行转换:
tags: variant.tags.slice(0, 5).map(t => ({
slug: t.toLowerCase().replace(/\s+/g, "-"), // 转小写,空格替换为连字符
name: t // 保留原始名称
}))
发布结果
成功发布后,适配器返回以下结构:
{
channel: variant.channel, // 渠道标识,如 "hashnode:main"
state: "live", // 状态:已上线
live_url: json.data.publishPost.post.url, // Hashnode 文章 URL
published_at: new Date().toISOString() // UTC 时间戳
}
反发布支持
Hashnode 适配器实现了 unpublish 方法,但当前版本存在限制:
async unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string]> {
return [false, "Hashnode unpublish not yet implemented — use the Hashnode UI or API directly"];
}
当前行为:返回 [false, "Hashnode unpublish not yet implemented — use the Hashnode UI or API directly"]
这意味着:
- 无法通过 MCP 服务器直接取消发布 Hashnode 文章
- 用户需要手动在 Hashnode 后台进行操作
- 这是一个已知的功能缺失,社区 roadmap 中未列出此功能
资料来源:src/adapters/hashnode.ts:57-59
凭证配置
所需环境变量
Hashnode 适配器需要在分发配置文件(~/.distribution-mcp/profiles.yaml)中配置两个凭证:
| 凭证名称 | 说明 | 获取方式 |
|---|---|---|
HASHNODE_TOKEN | 个人访问令牌 | Hashnode Settings → Access Tokens → Generate new access token |
HASHNODE_PUBLICATION_ID | 出版物 ID | Hashnode 出版物设置页面的 URL 中获取 |
配置示例
# ~/.distribution-mcp/profiles.yaml
default:
credentials:
HASHNODE_TOKEN: "your-hashnode-token"
HASHNODE_PUBLICATION_ID: "your-publication-id"
subreddits: []
凭证验证失败处理
当凭证缺失时,适配器返回:
{
channel: variant.channel,
state: "failed",
error: "HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set in profile"
}
适配器注册与路由
适配器映射表
Hashnode 适配器通过 buildAdapterMap() 函数注册到主适配器映射中:
return {
devto: new DevToAdapter(),
hashnode: new HashnodeAdapter(), // ← Hashnode 主适配器
github_discussions: new GitHubDiscussionsAdapter(),
reddit: new RedditAdapter(),
bluesky: new BlueskyAdapter(),
// ... 其他渠道
};
注意:Hashnode 适配器仅在 hashnode 键下注册,不提供 hashnode-browser 等变体,因为该渠道原生支持 API。
资料来源:src/adapters/index.ts:14-26
平台路由解析
在 MCP 服务器中,渠道标识符通过冒号分隔的平台前缀进行解析:
const platform = channel.split(":")[0]; // "hashnode:main" → "hashnode"
const adapter = adapters[platform]; // 获取 HashnodeAdapter 实例
这意味着用户可以使用 hashnode 或 hashnode:your-account 格式来指定 Hashnode 渠道。
错误处理
常见错误场景
| 场景 | 返回结果 | 错误消息示例 |
|---|---|---|
HASHNODE_TOKEN 未设置 | state: "failed" | "HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set in profile" |
HASHNODE_PUBLICATION_ID 未设置 | state: "failed" | "HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set in profile" |
| GraphQL 请求失败 | state: "failed" + HTTP 状态码 | "hashnode {status}" |
| GraphQL 返回错误 | state: "failed" + errors | JSON.stringify(result.errors ?? "no URL returned") |
API 限流
Hashnode GraphQL API 可能有速率限制。当遇到限流时,适配器会返回非 200 状态码,触发错误处理流程。
与其他适配器的比较
| 特性 | Hashnode | DEV.to | GitHub Discussions | Bluesky |
|---|---|---|---|---|
| API 方式 | GraphQL | REST | GraphQL | AT Protocol |
| 正文长度限制 | 50,000 | 100,000 | 依 GitHub | 300 |
| Markdown 支持 | 完整 | 完整 | 基础 | 仅链接 |
| 规范化 URL | ✓ | ✓ | ✗ | ✗ |
| 系列文章 | ✓ (via extras.series) | ✓ (via extras.series) | ✗ | ✗ |
| 反发布支持 | ✗ (未实现) | ✓ | ✗ | ✗ |
| 浏览器回退 | ✗ | ✗ | ✗ | ✗ |
使用示例
通过 MCP 工具发布
// post.publish 工具调用
{
"content": {
"id": "my-article-2026-05-20",
"title": "Building with MCP",
"body_md": "# Introduction\n\nThis article...",
"tags": ["automation", "mcp", "tutorial"],
"canonical_url": "https://myblog.com/mcp-guide",
"author": "Your Name"
},
"variants": [
{
"channel": "hashnode:main",
"title": "Building with MCP: A Complete Guide",
"body": "# Introduction\n\nThis article...",
"tags": ["automation", "mcp", "tutorial", "productivity"],
"canonical_url": "https://myblog.com/mcp-guide",
"extras": {
"series": "mcp-tutorials" // 可选:关联到 Hashnode 系列
}
}
],
"profile_name": "default"
}
获取 Hashnode 渠道提示
// channel.hints 工具调用
{
"channel": "hashnode"
}
// 返回
{
"max_length": 50000,
"supported_md_features": ["bold", "italic", "code_inline", "code_block", "links", "headers", "images", "lists", "tables", "blockquote"],
"tag_vocab": ["ai", "automation", "mcp", "claude", "devops", "tutorial", "productivity", "llm"],
"cta_placement": "bottom",
"canonical_url_supported": true,
"browser_only": false
}
限制与已知问题
v2.2.x 版本的限制
- 反发布功能未实现:当前无法通过 MCP 服务器取消发布 Hashnode 文章
- series 参数仅接受字符串:无法直接通过 API 创建或查询系列 ID,需手动在 Hashnode 后台获取系列 slug
- 标签数量限制:最多发送 5 个标签,超出部分被截断
- 封面图片支持:适配器未实现封面图片上传功能
社区反馈
根据社区 roadmap,Hashnode 适配器的原生 API 支持已完全实现,无需浏览器回退。未来的改进方向可能包括:
- 封面图片上传支持
- 系列文章自动创建
- 增强的反发布功能
参见
- 主文档首页 - 项目整体介绍和快速开始指南
- 适配器概览 - 所有渠道适配器总览
- DEV.to 适配器 - 类似的全功能 API 适配器
- 浏览器回退适配器 - LinkedIn、Medium、Twitter 的回退机制
- 配置指南 - 完整的凭证和环境变量配置说明
Reddit 适配器
Reddit 适配器是 content-distribution-mcp 项目中负责将内容发布到 Reddit 平台的组件。它被归类为 Auto-gated(自动门控) 频道,意味着该适配器需要 Reddit OAuth 凭证才能正常工作,但在内容发布时会受到 Reddit 社区的反垃圾规则的限制。
继续阅读本节完整说明和来源证据。
概述
Reddit 适配器是 content-distribution-mcp 项目中负责将内容发布到 Reddit 平台的组件。它被归类为 Auto-gated(自动门控) 频道,意味着该适配器需要 Reddit OAuth 凭证才能正常工作,但在内容发布时会受到 Reddit 社区的反垃圾规则的限制。
Reddit 适配器的核心职责包括:
- 通过 Reddit API 进行身份认证
- 发布内容到指定的子版块(subreddit)
- 管理每帖子状态的幂等性
- 强制执行子版块级别的冷却期(cooldown)规则
- 处理帖子置顶和精选功能(可选)
Bluesky 适配器
Bluesky 适配器是 content-distribution-mcp 项目中负责将内容发布到 Bluesky 社交平台的组件。Bluesky 是一个基于 AT Protocol(ATP)的去中心化社交网络,适配器通过调用 Bluesky 的官方 API 实现原生发布功能。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
Bluesky 适配器是一个 原生 API 集成 组件,不依赖浏览器模拟器即可完成内容发布。该适配器实现了 ChannelAdapter 接口,提供平台元数据查询、发布和取消发布功能。
| 属性 | 值 |
|---|---|
| 平台标识符 | bluesky |
| API 类型 | 原生 REST API (AT Protocol) |
| 浏览器回退 | 不需要 |
| 发布方式 | AT Protocol com.atproto.server 和 com.atproto.repo |
| 资料来源 | src/adapters/bluesky.ts:1-61 |
核心功能
平台元数据 (Channel Hints)
适配器通过 hints() 方法返回 Bluesky 平台的约束信息:
| 参数 | 值 | 说明 |
|---|---|---|
max_length | 300 | 最大字符限制 |
supported_md_features | ["links"] | 仅支持链接格式 |
cta_placement | bottom | CTA 块放置在底部 |
canonical_url_supported | false | 不支持规范 URL |
browser_only | false | 使用原生 API |
资料来源:src/adapters/bluesky.ts:10-16
发布流程
sequenceDiagram
participant MCP as MCP Server
participant ATP as AT Protocol API
participant Bluesky as Bluesky Server
MCP->>MCP: 验证凭证 (BLUESKY_IDENTIFIER, BLUESKY_PASSWORD)
MCP->>ATP: POST /com.atproto.server.createSession
ATP-->>MCP: { accessJwt, did }
MCP->>ATP: POST /com.atproto.repo.createRecord
Note over MCP: 发送到 app.bsky.feed.post
ATP-->>MCP: { uri }
MCP->>MCP: 解析 uri 生成 webUrl
MCP-->>User: { state: "live", live_url }#### 认证流程
- 调用
com.atproto.server.createSession端点 - 传入
identifier(用户名/邮箱)和password - 获取
accessJwt(访问令牌)和did(去中心化标识符)
#### 发布流程
- 使用
accessJwt作为 Bearer Token 调用com.atproto.repo.createRecord collection设置为app.bsky.feed.postrepo设置为用户的didrecord包含text(最多 300 字符)、$type和createdAt
#### 返回值处理
响应中的 uri 格式为 at://did:plc:xxx/app.bsky.feed.post/rkey,适配器提取最后的 rkey 生成网页 URL:
https://bsky.app/profile/{did}/post/{rkey}
资料来源:src/adapters/bluesky.ts:17-53
取消发布
Bluesky 适配器的 unpublish 方法目前未实现:
async unpublish(_liveUrl: string, _profile: Profile): Promise<[boolean, string]> {
return [false, "Bluesky post deletion not yet implemented — delete via bsky.app"];
}
| 操作 | 状态 | 说明 |
|---|---|---|
| 发布 | ✅ 已实现 | 通过 AT Protocol API |
| 取消发布 | ❌ 未实现 | 需手动在 bsky.app 删除 |
资料来源:src/adapters/bluesky.ts:55-58
适配器注册
在适配器索引文件中,Bluesky 适配器被注册到适配器映射表中:
return {
devto: new DevToAdapter(),
hashnode: new HashnodeAdapter(),
github_discussions: new GitHubDiscussionsAdapter(),
reddit: new RedditAdapter(),
bluesky: new BlueskyAdapter(), // 注册在此
// ...
};
配置要求
环境变量
| 变量名 | 必需 | 说明 |
|---|---|---|
BLUESKY_IDENTIFIER | 是 | Bluesky 用户名或邮箱 |
BLUESKY_PASSWORD | 是 | Bluesky 密码或 App Password |
Profile 配置
在 YAML 后端配置文件中:
profiles:
default:
channels:
- bluesky:me
credentials:
BLUESKY_IDENTIFIER: "your-handle.bsky.social"
BLUESKY_PASSWORD: "app-password-or-password"
资料来源:README.md
使用示例
发布请求
{
"content": {
"id": "bluesky-announce-001",
"title": "新功能发布公告",
"body_md": "我们很高兴宣布 MCP 内容分发服务器现已支持 Bluesky!",
"author": "AutomateLab"
},
"variants": [
{
"channel": "bluesky",
"title": "新功能发布公告",
"body": "我们很高兴宣布 MCP 内容分发服务器现已支持 Bluesky!了解更多: https://automatelab.tech",
"tags": ["mcp", "automation", "bluesky"]
}
],
"profile_name": "default"
}
成功响应
{
"results": [
{
"channel": "bluesky",
"state": "live",
"live_url": "https://bsky.app/profile/did:plc:xxx/app.bsky.feed.post/xyz",
"published_at": "2024-01-15T10:30:00.000Z"
}
]
}
失败响应
{
"results": [
{
"channel": "bluesky",
"state": "failed",
"error": "BLUESKY_IDENTIFIER and BLUESKY_PASSWORD required in profile"
}
]
}
API 端点参考
| 端点 | 方法 | 用途 |
|---|---|---|
https://bsky.social/xrpc/com.atproto.server.createSession | POST | 创建认证会话 |
https://bsky.social/xrpc/com.atproto.repo.createRecord | POST | 创建帖子记录 |
Create Session 请求体
{
"identifier": "user.bsky.social",
"password": "xxxx-xxxx-xxxx"
}
Create Record 请求体
{
"repo": "did:plc:xxx",
"collection": "app.bsky.feed.post",
"record": {
"$type": "app.bsky.feed.post",
"text": "帖子内容(最多300字符)",
"createdAt": "2024-01-15T10:30:00.000Z"
}
}
资料来源:src/adapters/bluesky.ts:22-48
限制与已知问题
| 问题 | 状态 | 说明 |
|---|---|---|
| 取消发布 | ❌ 未实现 | 需手动在平台删除 |
| 图片支持 | ⚠️ 限制 | 当前版本仅支持文本 |
| Markdown 格式 | ⚠️ 限制 | 仅支持链接 |
| 线程支持 | ❌ 未实现 | 暂不支持回复链 |
与其他平台的比较
| 平台 | API 类型 | 字符限制 | Markdown 支持 | 取消发布 |
|---|---|---|---|---|
| Bluesky | 原生 API | 300 | 仅链接 | ❌ 未实现 |
| DEV.to | 原生 API | 100,000 | 完整 | ✅ 已实现 |
| Hashnode | 原生 GraphQL | 50,000 | 完整 | ✅ 已实现 |
| 浏览器回退 | 280 | 仅链接 | ⚠️ 部分支持 | |
| 浏览器回退 | 3,000 | 部分 | ⚠️ 部分支持 |
资料来源:src/adapters/browser.ts 和 src/adapters/devto.ts
相关文件
| 文件 | 说明 |
|---|---|
| src/adapters/bluesky.ts | Bluesky 适配器实现 |
| src/adapters/index.ts | 适配器注册表 |
| src/server.ts | MCP 服务器与工具定义 |
| README.md | 项目整体文档 |
参见
- 发布工具概览 — 了解
post.publish和post.schedule工具 - 平台适配器架构 — 所有支持的平台列表
- GitHub Discussions 适配器 — 类似原生 API 实现参考
- Browser 适配器 — 了解浏览器回退机制
GitHub Discussions 适配器
GitHub Discussions 适配器是 content-distribution-mcp 项目中的核心组件之一,负责将内容分发到 GitHub 仓库的讨论区。该适配器通过 GitHub GraphQL API 与 GitHub Discussions 进行交互,支持创建新讨论、获取讨论分类信息等操作。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
GitHub Discussions 适配器是 content-distribution-mcp 项目中的核心组件之一,负责将内容分发到 GitHub 仓库的讨论区。该适配器通过 GitHub GraphQL API 与 GitHub Discussions 进行交互,支持创建新讨论、获取讨论分类信息等操作。
作为内容分发 MCP 服务器的一部分,该适配器与其他平台适配器(如 DEV.to、Hashnode、Reddit、Bluesky 等)协同工作,为用户提供统一的多平台内容分发体验。适配器遵循通道适配器接口规范,提供只读元数据查询和发布操作两个主要功能。
功能特性
支持的 Markdown 特性
GitHub Discussions 适配器支持丰富的 Markdown 格式,这使得内容创作者可以充分利用 GitHub 的富文本渲染能力。根据源码实现,该适配器支持以下 Markdown 功能:
| 功能类型 | 支持状态 | 说明 |
|---|---|---|
| 粗体 | ✅ 支持 | 使用 text 语法 |
| 斜体 | ✅ 支持 | 使用 *text* 或 _text_ 语法 |
| 行内代码 | ✅ 支持 | 使用 ` code ` 语法 |
| 代码块 | ✅ 支持 | 支持多行代码和语法高亮 |
| 链接 | ✅ 支持 | 自动转换为可点击链接 |
| 标题 | ✅ 支持 | 支持 H1-H6 标题层级 |
| 图片 | ✅ 支持 | 支持嵌入图片 |
| 列表 | ✅ 支持 | 支持有序和无序列表 |
| 表格 | ✅ 支持 | 支持 Markdown 表格 |
| 引用块 | ✅ 支持 | 使用 > 语法的引用 |
资料来源:src/adapters/github-discussions.ts:9-11
内容长度限制
该适配器对发布内容的长度有明确限制,最大支持 65,000 个字符的内容体,这为长篇技术文章、详细的问题解答和深入的教程提供了充足的空间。相比之下,Bluesky 适配器仅支持 300 字符,Twitter 适配器支持 280 字符,LinkedIn 支持 3,000 字符。
资料来源:src/adapters/github-discussions.ts:10
CTA 放置规则
适配器将行动号召(CTA)块放置在页脚位置(footer),这意味着 CTA 块会被追加到讨论正文的末尾。这与某些其他平台(如 DEV.to 和 Hashnode 将 CTA 放在底部,Reddit 放在顶部)有所不同。
资料来源:src/adapters/github-discussions.ts:12
Canonical URL 支持
与其他部分平台不同,GitHub Discussions 适配器不支持 canonical_url 功能。这是因为 GitHub Discussions 本身不提供设置规范链接的 API 选项。内容创作者在使用该适配器时应注意这一点。
资料来源:src/adapters/github-discussions.ts:13
架构设计
适配器注册机制
在 content-distribution-mcp 项目中,所有平台适配器通过统一的适配器注册表进行管理。GitHub Discussions 适配器在适配器映射表中有两个注册键:github_discussions 和 github-discussions(带连字符版本),这提供了更好的命名灵活性。
graph TD
A[适配器索引 index.ts] --> B[buildAdapterMap 函数]
B --> C[GitHubDiscussionsAdapter 实例]
C --> D{channel 参数解析}
D -->|github_discussions| C
D -->|github-discussions| C
style C fill:#90EE90资料来源:src/adapters/index.ts:14-24
发布流程
GitHub Discussions 适配器的发布流程涉及多个步骤,包括身份验证、仓库查询、分类获取和讨论创建。以下是该流程的详细架构:
sequenceDiagram
participant 客户端
participant 适配器 as GitHubDiscussionsAdapter
participant GitHub API as GitHub GraphQL API
客户端->>适配器: publish(variant, profile)
适配器->>适配器: 验证凭证和参数
适配器->>GitHub API: GraphQL 查询仓库信息
GitHub API-->>适配器: 返回仓库 ID 和讨论分类
适配器->>适配器: 确定 categoryId
适配器->>GitHub API: GraphQL mutation 创建讨论
GitHub API-->>适配器: 返回讨论 URL
适配器->>客户端: PublishResult { state: "live", live_url }资料来源:src/adapters/github-discussions.ts:17-53
配置要求
必需凭证
GitHub Discussions 适配器需要以下凭证才能正常工作:
| 凭证名称 | 必需 | 说明 |
|---|---|---|
GITHUB_TOKEN | 必需 | GitHub 个人访问令牌(PAT),需要 repo 和 read:discussion 权限 |
GITHUB_DISCUSSION_REPO | 必需 | 目标仓库,格式为 owner/repo,例如 AutomateLab-tech/content-distribution-mcp |
GITHUB_DISCUSSION_CATEGORY_ID | 可选 | 讨论分类 ID,可通过 API 或仓库设置获取 |
资料来源:src/adapters/github-discussions.ts:22-23
凭证优先级
适配器在确定实际使用的参数值时采用以下优先级规则:
graph LR
A[variant.extras] -->|优先级最高| B[实际使用值]
C[profile.credentials] -->|优先级次之| B
D[默认值] -->|最低优先级| B
style A fill:#FFD700
style C fill:#FFA500
style D fill:#D3D3D3对于 repo 参数:优先使用 variant.extras.repo,其次使用 profile.credentials["GITHUB_DISCUSSION_REPO"]
对于 categoryId 参数:优先使用 variant.extras.category_id,其次使用 profile.credentials["GITHUB_DISCUSSION_CATEGORY_ID"]
资料来源:src/adapters/github-discussions.ts:22-23
Profile 配置示例
在 ~/.distribution-mcp/profiles.yaml 中的配置示例:
default:
credentials:
GITHUB_TOKEN: "ghp_your_personal_access_token_here"
GITHUB_DISCUSSION_REPO: "your-org/your-repo"
GITHUB_DISCUSSION_CATEGORY_ID: "DIC_kwDOHAX7vs4CXXXX"
Variant 参数
必需字段
| 字段名 | 类型 | 必需 | 说明 |
|---|---|---|---|
channel | string | 必需 | 通道标识,格式为 github_discussions 或 github_discussions:account |
title | string | 必需 | 讨论标题,将作为 GitHub Discussion 的标题显示 |
body | string | 必需 | 讨论正文内容,支持 Markdown 格式 |
可选字段
| 字段名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
extras | object | {} | 扩展参数,包含 repo 和 category_id |
canonical_url | string | - | 不支持,将被忽略 |
cta_block | string | - | CTA 内容,追加到正文章节末尾(footer) |
schedule_at | string | - | ISO-8601 格式的调度时间 |
资料来源:src/server.ts:30-40
extras 字段详解
extras 对象支持以下平台特定参数:
{
"extras": {
"repo": "owner/repo",
"category_id": "DIC_kwDxxxxxxxxxxxxx"
}
}
repo: 覆盖 profile 中配置的默认仓库category_id: 指定讨论的分类 ID,如果不提供,适配器会尝试使用 profile 中的配置
发布结果
成功响应
成功发布后,适配器返回以下结构的结果:
| 字段 | 类型 | 说明 |
|---|---|---|
channel | string | 通道标识,与请求中的 channel 一致 |
state | enum | 发布状态,值为 "live" 表示成功 |
live_url | string | GitHub Discussion 的完整 URL |
published_at | string | UTC ISO-8601 格式的发布时间戳 |
draft_path | null | 该适配器不使用,始终为 null |
compose_url | null | 该适配器不使用,始终为 null |
error | null | 成功时为 null |
资料来源:src/server.ts:52-60
失败响应
发布失败时,返回以下结构:
| 字段 | 类型 | 说明 |
|---|---|---|
channel | string | 通道标识 |
state | enum | 值为 "failed" 表示失败 |
live_url | null | 失败时为 null |
draft_path | null | 失败时为 null |
compose_url | null | 失败时为 null |
error | string | 错误消息,描述失败原因 |
published_at | null | 失败时为 null |
错误处理
常见错误类型
适配器实现了完善的错误处理机制,以下是可能遇到的错误情况:
| 错误场景 | 错误消息 | 解决方案 |
|---|---|---|
| 缺少 GITHUB_TOKEN | "GITHUB_TOKEN and GITHUB_DISCUSSION_REPO required in profile" | 在 profile 中配置有效的 GitHub Token |
| 缺少 GITHUB_DISCUSSION_REPO | "GITHUB_TOKEN and GITHUB_DISCUSSION_REPO required in profile" | 在 profile 或 extras 中指定目标仓库 |
| 仓库不存在或无权限 | "state: 'failed' (GraphQL 返回空 data) | 确认仓库存在且 Token 有访问权限 |
| 分类 ID 无效 | "state: 'failed' (未找到对应分类) | 检查分类 ID 是否正确,或省略让适配器自动选择 |
资料来源:src/adapters/github-discussions.ts:26-35
取消发布限制
需要特别注意:GitHub Discussions 适配器尚未实现取消发布功能。调用 unpublish 方法将返回 [false, "GitHub Discussions delete not yet implemented — use the GitHub UI or API directly"]。
资料来源:src/adapters/github-discussions.ts:56-57
与其他适配器的比较
GitHub Discussions 适配器在多个维度上与其他平台适配器存在差异:
| 特性 | GitHub Discussions | DEV.to | Hashnode | Bluesky | Medium | |||
|---|---|---|---|---|---|---|---|---|
| 原生 API | ✅ | ✅ | ✅ | ⚠️ 自动门控 | ✅ | ❌ 浏览器 | ❌ 浏览器 | ❌ 浏览器 |
| 最大长度 | 65,000 | 100,000 | 50,000 | 40,000 | 300 | 100,000 | 3,000 | 280 |
| 支持 Markdown | ✅ 完整 | ✅ 完整 | ✅ 完整 | ❌ 纯文本 | ❌ 仅链接 | ✅ 完整 | ❌ 有限 | ❌ 仅链接 |
| Canonical URL | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| CTA 放置 | footer | bottom | bottom | top | bottom | bottom | bottom | none |
| 取消发布 | ❌ 未实现 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
注:⚠️ 自动门控表示 Reddit 需要额外的自动审核配置
资料来源:src/adapters/github-discussions.ts:8-13、src/adapters/devto.ts:8-13、src/adapters/hashnode.ts:8-13、src/adapters/bluesky.ts:8-12、src/adapters/browser.ts:14-40
路线图与未来计划
根据社区路线图信息,GitHub Discussions 适配器目前没有明确的计划变更。然而,项目正在考虑以下改进方向:
- 原生 API 支持改进:所有标记为"Browser fallback"的平台(Medium、LinkedIn、Twitter/X)都在路线图上,未来可能实现原生 API 支持
- 取消发布功能:当前未实现的
unpublish功能可能在未来版本中添加 - 分类自动发现优化:改进当未指定分类 ID 时的默认分类选择逻辑
资料来源:社区路线图 - Roadmap & feature tracker
使用示例
基础发布调用
{
"content": {
"id": "mcp-intro-2026-05-20",
"title": "Model Context Protocol 入门指南",
"body_md": "# 什么是 MCP\n\nModel Context Protocol (MCP) 是一个...",
"tags": ["mcp", "ai", "automation"],
"author": "YourName"
},
"variants": [
{
"channel": "github_discussions",
"title": "Model Context Protocol 入门指南",
"body": "# 什么是 MCP\n\nModel Context Protocol (MCP) 是一个...",
"extras": {
"repo": "your-org/your-repo",
"category_id": "DIC_kwDxxxxxxxxxxxxx"
}
}
],
"profile_name": "default"
}
带 CTA 的发布
{
"channel": "github_discussions",
"title": "使用 MCP 自动化工作流程",
"body": "# MCP 工作流自动化\n\n本文介绍如何...",
"cta_block": "---\n\n💡 想了解更多信息?访问 [我们的网站](https://example.com)"
}
故障排除
诊断步骤
当 GitHub Discussions 发布失败时,请按以下步骤排查:
- 验证凭证:确认
GITHUB_TOKEN具有repo和read:discussion权限 - 检查仓库访问:确保 Token 可以访问指定的仓库
- 验证分类 ID:确认分类 ID 格式正确且分类存在
- 查看 GraphQL 错误:检查返回的
error字段中的详细错误信息
调试技巧
启用调试模式查看详细的 GraphQL 请求和响应:
DEBUG=* npx -y content-distribution-mcp publish --content-id "your-id"
相关文档
参见
失败模式与踩坑日记
保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。
假设不成立时,用户拿不到承诺的能力。
新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
下游已经要求复核,不能在页面中弱化。
风险会影响是否适合普通用户安装。
Pitfall Log / 踩坑日志
项目:automatelab-tech/content-distribution-mcp
摘要:发现 6 个潜在踩坑项,其中 0 个为 high/blocking;最高优先级:能力坑 - 能力判断依赖假设。
1. 能力坑 · 能力判断依赖假设
- 严重度:medium
- 证据强度:source_linked
- 发现:README/documentation is current enough for a first validation pass.
- 对用户的影响:假设不成立时,用户拿不到承诺的能力。
- 建议检查:将假设转成下游验证清单。
- 防护动作:假设必须转成验证项;没有验证结果前不能写成事实。
- 证据:capability.assumptions | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | README/documentation is current enough for a first validation pass.
2. 维护坑 · 维护活跃度未知
- 严重度:medium
- 证据强度:source_linked
- 发现:未记录 last_activity_observed。
- 对用户的影响:新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
- 建议检查:补 GitHub 最近 commit、release、issue/PR 响应信号。
- 防护动作:维护活跃度未知时,推荐强度不能标为高信任。
- 证据:evidence.maintainer_signals | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | last_activity_observed missing
3. 安全/权限坑 · 下游验证发现风险项
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:下游已经要求复核,不能在页面中弱化。
- 建议检查:进入安全/权限治理复核队列。
- 防护动作:下游风险存在时必须保持 review/recommendation 降级。
- 证据:downstream_validation.risk_items | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | no_demo; severity=medium
4. 安全/权限坑 · 存在评分风险
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:风险会影响是否适合普通用户安装。
- 建议检查:把风险写入边界卡,并确认是否需要人工复核。
- 防护动作:评分风险必须进入边界卡,不能只作为内部分数。
- 证据:risks.scoring_risks | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | no_demo; severity=medium
5. 维护坑 · issue/PR 响应质量未知
- 严重度:low
- 证据强度:source_linked
- 发现:issue_or_pr_quality=unknown。
- 对用户的影响:用户无法判断遇到问题后是否有人维护。
- 建议检查:抽样最近 issue/PR,判断是否长期无人处理。
- 防护动作:issue/PR 响应未知时,必须提示维护风险。
- 证据:evidence.maintainer_signals | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | issue_or_pr_quality=unknown
6. 维护坑 · 发布节奏不明确
- 严重度:low
- 证据强度:source_linked
- 发现:release_recency=unknown。
- 对用户的影响:安装命令和文档可能落后于代码,用户踩坑概率升高。
- 建议检查:确认最近 release/tag 和 README 安装命令是否一致。
- 防护动作:发布节奏未知或过期时,安装说明必须标注可能漂移。
- 证据:evidence.maintainer_signals | mcp_registry:io.github.AutomateLab-tech/content-distribution:2.2.1 | https://registry.modelcontextprotocol.io/v0.1/servers/io.github.AutomateLab-tech%2Fcontent-distribution/versions/2.2.1 | release_recency=unknown
来源:Doramagic 发现、验证与编译记录