# https://github.com/AutomateLab-tech/content-distribution-mcp 项目说明书

生成时间：2026-05-27 16:37:37 UTC

## 目录

- [首页](#page-home)
- [核心功能](#page-features)
- [系统架构](#page-architecture)
- [数据流与状态管理](#page-data-flow)
- [适配器概述](#page-adapters-overview)
- [DEV.to 适配器](#page-adapter-devto)
- [Hashnode 适配器](#page-adapter-hashnode)
- [Reddit 适配器](#page-adapter-reddit)
- [Bluesky 适配器](#page-adapter-bluesky)
- [GitHub Discussions 适配器](#page-adapter-github-discussions)

<a id='page-home'></a>

## 首页

### 相关页面

相关主题：[系统架构](#page-architecture), [核心功能](#page-features)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
- [package.json](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/package.json)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)
- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)

</details>

# content-distribution-mcp

**多渠道内容分发 MCP 服务器** | [GitHub 仓库](https://github.com/AutomateLab-tech/content-distribution-mcp)

## 项目概述

content-distribution-mcp 是一个基于 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 的服务器，用于将单一内容自动分发到 **8+ 个平台**（DEV.to、Hashnode、GitHub Discussions、Reddit、Bluesky、LinkedIn、Medium、Twitter/X），支持**平台自适应**、**幂等发布**、**社区反垃圾规则**和**集中式状态管理**。

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### 它解决的问题

内容规模化发布存在以下痛点：

| 痛点 | 描述 |
|------|------|
| **格式差异** | Reddit 清除格式、Twitter 有字符限制、DEV.to 支持嵌入和富媒体 |
| **平台规则** | 子版块强制冷却期和标签要求、社区有发帖模式和自动审核 |
| **状态混乱** | 哪些帖子发布成功？失败后重试是否会重复发布？ |

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### 工作原理

```mermaid
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
```

资料来源：[src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

---

## 核心概念

### 关键术语

| 术语 | 定义 |
|------|------|
| **Content (内容)** | 核心内容对象，包含 `id`、`title`、`body_md`、`tags` 等 |
| **Variant (变体)** | 针对特定平台的适配版本，包含平台特定的标题、正文、标签 |
| **Channel (渠道)** | 目标平台标识符，格式为 `platform` 或 `platform:account`，如 `devto:main`、`reddit:ClaudeAI` |
| **Profile (配置文件)** | 一组凭证和环境配置，对应 YAML 后端中的配置文件 |
| **State Backend (状态后端)** | 存储发布状态的存储后端，默认使用 YAML 文件 |
| **Idempotency (幂等性)** | 相同 `content.id` + `channel` 的重复发布返回已有 `live_url`，不重复调用平台 API |

资料来源：[src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

---

## 系统架构

### 架构组件

```mermaid
graph TB
    subgraph "MCP Server Layer"
        S[src/server.ts<br/>MCP Server]
    end
    
    subgraph "Adapter Layer"
        D[DevToAdapter]
        H[HashnodeAdapter]
        G[GitHubDiscussionsAdapter]
        R[RedditAdapter]
        B[BlueskyAdapter]
        BR[makeBrowserAdapter<br/>Medium/LinkedIn/Twitter]
    end
    
    subgraph "Backend Layer"
        Y[YAML Backend<br/>~/.distribution-mcp/]
    end
    
    S --> D
    S --> H
    S --> G
    S --> R
    S --> B
    S --> BR
    
    D --> Y
    H --> Y
    G --> Y
    R --> Y
    B --> Y
    BR --> Y
```

资料来源：[src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

### 适配器设计

所有适配器实现统一的 `ChannelAdapter` 接口：

```typescript
interface ChannelAdapter {
  hints(): ChannelHints;                              // 返回平台元数据
  publish(variant: Variant, profile: Profile): Promise<PublishResult>;
  unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string | undefined]>;
}
```

资料来源：[src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

---

## 支持的平台

### 平台对比表

| 渠道 Key | 类型 | API 方式 | Markdown 支持 | 字符限制 | 规范化 URL |
|----------|------|----------|---------------|----------|------------|
| `devto` | 自动 | REST API | ✅ 完整 | 100,000 | ✅ |
| `hashnode` | 自动 | GraphQL | ✅ 完整 | 50,000 | ✅ |
| `github_discussions` | 自动 | GraphQL | ✅ 部分 | - | ❌ |
| `reddit` | 自动-受限 | REST API | ❌ 纯文本 | - | ❌ |
| `bluesky` | 自动 | AT Protocol | ❌ 仅链接 | 300 | ❌ |
| `medium` | 浏览器回退 | - | ✅ 完整 | 100,000 | ✅ |
| `linkedin` | 浏览器回退 | - | ✅ 部分 | 3,000 | ❌ |
| `twitter` / `x` | 浏览器回退 | - | ❌ 仅链接 | 280 | ❌ |

资料来源：[src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts) | [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

### 浏览器回退模式

对于尚无原生 API 的平台，适配器返回 `needs_browser` 状态和 `compose_url`，用户需手动完成发布：

```typescript
{
  channel: "linkedin",
  state: "needs_browser",
  compose_url: "https://www.linkedin.com/post/new"
}
```

资料来源：[src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)

---

## MCP 工具

### 工具列表

| 工具名 | 功能 | 副作用 | 幂等性 |
|--------|------|--------|--------|
| `post.publish` | 立即发布内容变体 | 发布到平台、写入状态后端 | ✅ `(content.id, channel)` |
| `post.schedule` | 队列变体供定时发布 | 写入 `scheduled.yaml` | ✅ |
| `post.drain` | 触发所有到期定时任务 | 执行定时发布 | ✅ |
| `post.status` | 查询内容在各平台的发布状态 | 只读 | ✅ |
| `post.unpublish` | 尝试取消发布 | 调用平台删除接口 | ❌ |
| `channel.hints` | 获取平台元数据（字符限制、标签词汇等） | 只读，无外部调用 | ✅ |
| `profile.list` | 列出配置的发布配置文件 | 只读 | ✅ |
| `subreddit.list` | 列出子版块目录（冷却期、标签等） | 只读 | ✅ |

资料来源：[src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### post.publish 完整示例

```jsonc
{
  "content": {
    "id": "n8n-webhook-setup@2026-05-20",
    "title": "How to set up an n8n webhook",
    "body_md": "...",
    "tags": ["automation", "n8n", "tutorial"],
    "canonical_url": "https://yourblog.com/n8n-webhook-setup",
    "author": "You"
  },
  "variants": [
    {
      "channel": "devto:main",
      "title": "How to set up an n8n webhook",
      "body": "...",
      "tags": ["automation", "n8n", "tutorial", "devops"],
      "canonical_url": "https://yourblog.com/n8n-webhook-setup",
      "extras": {}
    },
    {
      "channel": "reddit:ClaudeAI",
      "title": "Built a webhook automation with n8n",
      "body": "Here's how I set it up...",
      "tags": [],
      "extras": { "flair": "Project" }
    }
  ],
  "profile_name": "default"
}
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### post.publish 返回值

```jsonc
{
  "results": [
    {
      "channel": "devto:main",
      "state": "live",           // "live" | "queued" | "needs_browser" | "failed"
      "live_url": "https://dev.to/...",  // null when not live
      "draft_path": null,        // 浏览器回退时的本地草稿路径
      "compose_url": null,       // 浏览器回退时的平台编辑 URL
      "error": null,             // null on success
      "published_at": "2026-05-20T10:30:00.000Z"
    }
  ]
}
```

资料来源：[src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

---

## 配置与凭证

### 环境变量

| 变量 | 默认值 | 用途 |
|------|--------|------|
| `DISTRIBUTION_BACKEND` | `yaml` | 状态后端类型（当前仅支持 `yaml`） |
| `DISTRIBUTION_DATA_DIR` | `~/.distribution-mcp/` | 数据目录路径 |

资料来源：[src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 平台凭证配置

凭证存储在 YAML 配置文件中，按配置文件分组：

| 平台 | 必需凭证 |
|------|----------|
| DEV.to | `DEV_TO_API_KEY` |
| Hashnode | `HASHNODE_TOKEN` + `HASHNODE_PUBLICATION_ID` |
| GitHub Discussions | `GITHUB_TOKEN` + `GITHUB_DISCUSSION_REPO` |
| Reddit | `REDDIT_CLIENT_ID` + `REDDIT_CLIENT_SECRET` + `REDDIT_USERNAME` + `REDDIT_PASSWORD` |
| Bluesky | `BLUESKY_IDENTIFIER` + `BLUESKY_PASSWORD` |
| LinkedIn | 无（浏览器回退） |
| Medium | 无（浏览器回退） |
| Twitter/X | 无（浏览器回退） |

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### 安装与运行

```bash
# 全局安装
npm install -g @automatelab/content-distribution-mcp

# 直接运行
npx @automatelab/content-distribution-mcp

# 发布定时任务
npx @automatelab/content-distribution-mcp drain
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md) | [package.json](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/package.json)

---

## 使用模式

### 幂等发布

重复使用相同的 `content.id` + `channel` 调用 `post.publish` 时，直接返回已存在的 `live_url`，不会重复调用平台 API：

```mermaid
sequenceDiagram
    participant Agent
    participant MCP
    participant Backend
    
    Agent->>MCP: post.publish(content.id="abc", channel="devto:main")
    MCP->>Backend: 查询状态
    Backend-->>MCP: 未找到记录
    MCP->>DEV.to: POST /articles
    DEV.to-->>MCP: 201 Created
    MCP->>Backend: 写入状态
    MCP-->>Agent: { state: "live", live_url: "..." }
    
    Agent->>MCP: post.publish(content.id="abc", channel="devto:main")
    MCP->>Backend: 查询状态
    Backend-->>MCP: 找到记录 live_url
    MCP-->>Agent: { state: "live", live_url: "..." }
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### 定时发布

带有 `schedule_at`（ISO-8601 时区时间）的变体会被存入 `scheduled.yaml`，在 `post.drain` 调用时触发：

```jsonc
{
  "channel": "devto:main",
  "schedule_at": "2026-05-21T09:00:00+01:00",
  // ...
}
```

建议通过 cron 定期执行 drain：

```bash
# 每 5 分钟检查并发布到期任务
*/5 * * * * npx -y @automatelab/content-distribution-mcp drain
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

---

## channel.hints 平台元数据

调用 `channel.hints` 获取平台的约束信息，用于生成合规的变体内容：

| 平台 | max_length | cta_placement | canonical_url_supported | browser_only |
|------|------------|---------------|------------------------|--------------|
| devto | 100,000 | bottom | ✅ | ❌ |
| hashnode | 50,000 | bottom | ✅ | ❌ |
| github_discussions | - | footer | ❌ | ❌ |
| reddit | - | none | ❌ | ❌ |
| bluesky | 300 | bottom | ❌ | ❌ |
| medium | 100,000 | bottom | ✅ | ✅ |
| linkedin | 3,000 | bottom | ❌ | ✅ |
| twitter | 280 | none | ❌ | ✅ |

资料来源：[src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts) | [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

---

## 常见故障与排查

### 故障模式

| 状态 | 含义 | 排查步骤 |
|------|------|----------|
| `failed` | 发布失败 | 检查返回的 `error` 字段、凭证配置、API 配额 |
| `needs_browser` | 需浏览器操作 | 打开 `compose_url`，手动完成发布 |
| `live` | 发布成功 | 验证 `live_url` 可访问 |

### 平台特定问题

**DEV.to 发布失败**
- 检查 `DEV_TO_API_KEY` 是否有效
- 验证标签数量 ≤ 4 个
- 确认 `canonical_url` 格式正确

**Reddit 冷却期**
- 使用 `subreddit.list` 查看各子版块的最后发帖时间
- 遵守 `cooldown_days` 规定的最小间隔

**Bluesky 认证失败**
- 确认 `BLUESKY_IDENTIFIER`（通常是邮箱或句柄）
- 确认 `BLUESKY_PASSWORD` 未过期

资料来源：[src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts) | [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

---

## 路线图与未来计划

根据社区反馈，以下功能正在开发中：

| 功能 | 当前状态 | 目标 |
|------|----------|------|
| Twitter/X 原生 API 支持 | 浏览器回退 | 使用官方 API |
| LinkedIn 原生 API 支持 | 浏览器回退 | 使用官方 API |
| Medium 原生 API 支持 | 浏览器回退 | 使用官方 API |
| 平台级配置覆盖 | 计划中 | 支持变体级别的 `extras` 扩展 |

资料来源：[社区 Issue：Roadmap & feature tracker](https://github.com/AutomateLab-tech/content-distribution-mcp/issues)

> ⚠️ **v2.2.0 破坏性变更**：工具名称从扁平命名（如 `publish`）改为点号命名（如 `post.publish`）。请更新任何引用旧名称的提示词、代理技能或 n8n 节点。

---

## 平台适配器详解

### DEV.to 适配器

通过 REST API 直接发布，支持完整的 Markdown：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)

### Hashnode 适配器

通过 GraphQL API 发布，支持系列（series）功能：

```typescript
const query = `
  mutation PublishPost($input: PublishPostInput!) {
    publishPost(input: $input) { post { url } }
  }
`;
```

资料来源：[src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)

### Bluesky 适配器

使用 AT Protocol 的 `com.atproto.repo.createRecord` 端点：

```typescript
await fetch(`${BSKY}/com.atproto.repo.createRecord`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${session.accessJwt}` },
  body: JSON.stringify({
    repo: session.did,
    collection: "app.bsky.feed.post",
    record: { $type: "app.bsky.feed.post", text: variant.body.slice(0, 300) },
  }),
});
```

资料来源：[src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

---

## 技术规格

| 项目 | 值 |
|------|-----|
| 包名 | `@automatelab/content-distribution-mcp` |
| 最新版本 | 2.2.1 |
| Node.js 要求 | ≥18 |
| 主要依赖 | `@modelcontextprotocol/sdk` ^1.12.0, `zod` ^3.23.0, `js-yaml` ^4.1.0 |
| 许可证 | MIT |
| 仓库 | [github.com/AutomateLab-tech/content-distribution-mcp](https://github.com/AutomateLab-tech/content-distribution-mcp) |

资料来源：[package.json](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/package.json)

---

## 另请参阅

- [官方文档首页](https://automatelab.tech/products/mcp/content-distribution-mcp/)
- [完整变更日志](https://github.com/AutomateLab-tech/content-distribution-mcp/commits/v2.1.0)
- [MCP 协议规范](https://modelcontextprotocol.io/)
- [DEV.to API 文档](https://developers.forem.com/api/v1)
- [Bluesky AT Protocol](https://atproto.com/)
- [Hashnode GraphQL API](https://api.hashnode.com/)

---

<a id='page-features'></a>

## 核心功能

### 相关页面

相关主题：[系统架构](#page-architecture)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)
- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)
- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# 核心功能

content-distribution-mcp 是一个基于 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 的多平台内容分发服务器。它通过单一的内容输入，自动将内容适配并发布到 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）系统 |

## 系统架构

### 整体架构图

```mermaid
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` 接口：

```mermaid
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)*

## MCP 工具接口

本服务器暴露 8 个 MCP 工具，采用点号命名空间组织：

| 工具名称 | 功能描述 | 只读 | 幂等 |
|---------|---------|-----|-----|
| `post.publish` | 立即发布内容到指定渠道 | ❌ | ✅ |
| `post.schedule` | 调度内容发布（定时立即执行） | ❌ | ✅ |
| `post.drain` | 触发所有到期定时任务 | ❌ | ❌ |
| `post.status` | 查询内容发布状态 | ✅ | ✅ |
| `post.unpublish` | 取消发布（最佳努力） | ❌ | ❌ |
| `channel.hints` | 获取平台元数据（字符限制、Markdown 支持等） | ✅ | ✅ |
| `profile.list` | 列出配置的分布配置文件 | ✅ | ✅ |
| `subreddit.list` | 子版块目录查询（冷却期、标签词汇） | ✅ | ✅ |

*资料来源：[src/server.ts - 工具注册部分](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/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 等） |

**输出结构：**

```typescript
{
  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 在生成变体时遵守平台约束：

```mermaid
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）

```typescript
// 资料来源：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**，使用浏览器回退模式：

| 平台 | 状态 | 说明 |
|-----|------|------|
| LinkedIn | `needs_browser` | 返回 `compose_url` 指向 `linkedin.com/post/new` |
| Medium | `needs_browser` | 返回 `compose_url` 指向 `medium.com/new-story` |
| Twitter/X | `needs_browser` | 返回预填充文本的推文编辑 URL |

发布结果示例：
```json
{
  "channel": "twitter",
  "state": "needs_browser",
  "compose_url": "https://twitter.com/compose/tweet?text=..."
}
```

> **社区规划**：社区正在追踪 Twitter/X、LinkedIn 和 Medium 的原生 API 支持（当前为浏览器回退模式）。

## 幂等性保证

核心设计原则：**同一 `(content.id, channel)` 组合不会产生重复发布**。

### 幂等性流程

```mermaid
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`：

```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` 可实现定时发布：

```json
{
  "variants": [
    {
      "channel": "devto:main",
      "title": "定时发布文章",
      "body": "...",
      "schedule_at": "2026-05-21T09:00:00+01:00"
    }
  ]
}
```

### drain 命令

定时任务通过 `post.drain` 工具触发：

```bash
# 每 5 分钟检查并执行到期任务
*/5 * * * * npx -y content-distribution-mcp drain
```

调度数据存储在 `~/.distribution-mcp/scheduled.yaml`。

## 凭证管理

### 分布配置文件

凭证通过 YAML 配置文件管理（默认位置：`~/.distribution-mcp/profiles.yaml`）：

```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` |
| Reddit | OAuth | `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET`, `REDDIT_USERNAME`, `REDDIT_PASSWORD` |
| Bluesky | App Password | `BLUESKY_IDENTIFIER`, `BLUESKY_PASSWORD` |
| LinkedIn | 浏览器回退 | 无 |
| Medium | 浏览器回退 | 无 |
| Twitter/X | 浏览器回退 | 无 |

*资料来源：[README.md - 配置凭证部分](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)*

## 常见失败模式

### API 认证失败

```json
{
  "channel": "devto",
  "state": "failed",
  "error": "DEV_TO_API_KEY not set in profile"
}
```

**解决**: 确保 `profiles.yaml` 中包含对应平台的凭证。

### 平台速率限制

Reddit 和部分平台有严格的速率限制。适配器会返回 429 状态码：

```json
{
  "channel": "reddit",
  "state": "failed", 
  "error": "reddit 429: Too Many Requests"
}
```

**解决**: 等待冷却期后重试，或使用 `subreddit.list` 查询子版块的 `cooldown_days`。

### 浏览器回退平台

LinkedIn、Medium、Twitter 当前只能返回 `needs_browser` 状态：

```json
{
  "channel": "twitter",
  "state": "needs_browser",
  "compose_url": "https://twitter.com/compose/tweet?text=..."
}
```

**说明**: 用户需要手动在浏览器中完成发布操作。

## 核心数据类型

### ContentInput（内容输入）

```typescript
{
  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（渠道变体）

```typescript
{
  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（平台提示）

```typescript
{
  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://automatelab.tech/products/mcp/content-distribution-mcp/)
- [GitHub 仓库](https://github.com/AutomateLab-tech/content-distribution-mcp)
- [最新版本 (v2.2.1)](https://github.com/AutomateLab-tech/content-distribution-mcp/releases)
- [变更日志](https://github.com/AutomateLab-tech/content-distribution-mcp/commits/main)

---

<a id='page-architecture'></a>

## 系统架构

### 相关页面

相关主题：[适配器概述](#page-adapters-overview), [数据流与状态管理](#page-data-flow)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)
- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)
- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [src/backends/base.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/backends/base.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# 系统架构

## 概览

content-distribution-mcp 是一个基于 Model Context Protocol (MCP) 的多渠道内容分发服务器。其核心设计目标是：**将单一内容自动适配并发布到 8+ 个不同平台**，同时保证幂等性、状态追踪和平台规则合规性。

该系统不包含 LLM 调用，仅负责内容分发逻辑的执行。内容创作和平台适配决策由外部 Agent 完成，系统提供各平台约束提示（字符限制、标签词汇表、排版规则等），由调用方根据提示生成平台化内容变体。

资料来源：[README.md:1-15](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 架构设计原则

| 原则 | 描述 |
|------|------|
| **适配器模式** | 每个平台封装为独立 Adapter，解耦平台差异 |
| **幂等性发布** | 相同 `content.id` + `channel` 组合不会重复发布 |
| **状态后端抽象** | 状态存储支持 YAML 文件（默认）和可扩展的其他后端 |
| **工具化接口** | 所有功能通过 MCP 工具暴露，支持类型检查和注解 |
| **无 LLM 依赖** | 服务器纯执行分发逻辑，不含任何 AI 调用 |

资料来源：[README.md:30-35](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 核心组件

### 系统组件概览

```mermaid
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 服务器是系统的中央调度器，负责：

1. **工具注册** — 向 MCP Host 暴露 8 个标准化工具
2. **请求路由** — 将工具调用分发给对应的适配器
3. **响应格式化** — 统一返回结构化 JSON 响应

```mermaid
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 工具接口设计

系统定义了 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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 适配器系统架构

#### 适配器接口定义

所有平台适配器实现统一的 `ChannelAdapter` 接口：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

#### 适配器注册表

适配器通过 `buildAdapterMap()` 构建，支持平台别名和变体：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

#### 平台分类

| 平台 | 适配器类型 | 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` |
| Reddit | `RedditAdapter` | REST API | OAuth (client_id/secret/username/password) |
| Bluesky | `BlueskyAdapter` | AT Protocol | `BLUESKY_IDENTIFIER` + `BLUESKY_PASSWORD` |
| Medium | `BrowserAdapter` | 浏览器回退 | 无 (返回 compose URL) |
| LinkedIn | `BrowserAdapter` | 浏览器回退 | 无 (返回 compose URL) |
| Twitter/X | `BrowserAdapter` | 浏览器回退 | 无 (返回 compose URL) |

资料来源：[README.md:80-90](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 适配器实现详解

### API 适配器

#### DEV.to 适配器

```mermaid
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)

#### Hashnode 适配器

使用 GraphQL mutation `publishPost`，标签限制 5 个，支持：
- Markdown 完整格式
- `canonical_url` SEO 设置
- `repo` 和 `series` 额外字段

```typescript
const query = `
  mutation PublishPost($input: PublishPostInput!) {
    publishPost(input: $input) { post { url } }
  }
`;
```

资料来源：[src/adapters/hashnode.ts:1-50](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)

#### Bluesky 适配器

使用 AT Protocol (atproto)：

1. 创建会话获取 `accessJwt` 和 `did`
2. 调用 `com.atproto.repo.createRecord` 发布帖子

```typescript
const sessionRes = await fetch(`${BSKY}/com.atproto.server.createSession`, {
  method: "POST",
  body: JSON.stringify({ identifier, password }),
});
```

资料来源：[src/adapters/bluesky.ts:1-50](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

### 浏览器回退适配器

对于没有公开 API 或认证复杂的平台（Medium、LinkedIn、Twitter），系统采用**浏览器回退策略**：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)

> **路线图提示**：根据社区反馈，Twitter/X、LinkedIn 和 Medium 的原生 API 支持已在路线图中规划。
> 
> 资料来源：[社区 Issue #1](https://github.com/AutomateLab-tech/content-distribution-mcp/issues/1)

## 状态后端设计

### 后端抽象

状态后端通过 `StateBackend` 接口抽象，支持可插拔实现：

```typescript
interface StateBackend {
  read(): Promise<DistributionState>;
  write(state: DistributionState): Promise<void>;
  readScheduled(): Promise<ScheduledItem[]>;
  writeScheduled(items: ScheduledItem[]): Promise<void>;
}
```

资料来源：[src/backends/base.ts:1-20](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/backends/base.ts)

### 默认实现：YAML 后端

默认使用 YAML 文件存储，文件位置：

| 文件 | 用途 |
|------|------|
| `~/.distribution-mcp/state.yaml` | 发布状态追踪 |
| `~/.distribution-mcp/scheduled.yaml` | 排程任务队列 |
| `~/.distribution-mcp/profiles.yaml` | 分发配置和凭证 |

```typescript
function buildBackend(): StateBackend {
  const name = (process.env.DISTRIBUTION_BACKEND ?? "yaml").toLowerCase();
  // 支持扩展其他后端实现
}
```

资料来源：[src/server.ts:180-190](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 发布结果数据结构

```typescript
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`：

```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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

### 环境变量配置

| 变量 | 默认值 | 用途 |
|------|--------|------|
| `DISTRIBUTION_BACKEND` | `yaml` | 状态后端类型 |
| `DISTRIBUTION_PROFILES_PATH` | `~/.distribution-mcp/profiles.yaml` | 配置文件路径 |

## 内容变体模型

### Variant 数据结构

```mermaid
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` 工具获取平台约束：

```typescript
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 | 否 |
| Reddit | 40,000 | 无 | none | 否 |
| Bluesky | 300 | 仅链接 | bottom | 否 |
| Medium | 100,000 | 完整 | bottom | 是 |
| LinkedIn | 3,000 | 粗体/斜体/链接 | bottom | 是 |
| Twitter/X | 280 | 仅链接 | none | 是 |

## 发布流程

### 完整发布时序

```mermaid
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 完成后续工作流
```

### 排程发布流程

```mermaid
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 触发：

```bash
# 每 5 分钟检查并发布到期任务
*/5 * * * * npx -y content-distribution-mcp drain
```

## 安全性设计

### 凭证隔离

- 凭证存储在本地 `profiles.yaml`，不进入源码
- 每个 profile 独立配置，支持多账号
- 通过环境变量或配置文件注入，无硬编码密钥

### 平台合规

| 平台 | 合规机制 |
|------|----------|
| Reddit | 子版块冷却期 (cooldown_days)、标签词汇表、最后发布时间追踪 |
| 所有平台 | 幂等性防止重复发布、速率限制感知 |

资料来源：[src/server.ts:150-180](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

## 扩展架构

### 添加新平台适配器

1. 在 `src/adapters/` 创建新文件，实现 `ChannelAdapter` 接口
2. 在 `src/adapters/index.ts` 的 `buildAdapterMap()` 中注册
3. 在 `profiles.yaml` 添加对应凭证配置

```typescript
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()` 工厂函数中注册：

```typescript
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
```

## 参见

- [快速开始指南](https://github.com/AutomateLab-tech/content-distribution-mcp#install) — 安装和配置
- [工具参考](https://github.com/AutomateLab-tech/content-distribution-mcp#mcp-tool-surface) — 所有 MCP 工具详细文档
- [路线图](https://github.com/AutomateLab-tech/content-distribution-mcp/issues/1) — 原生 API 支持计划
- [官方文档](https://automatelab.tech/products/mcp/content-distribution-mcp/) — AutomateLab 产品页

---

<a id='page-data-flow'></a>

## 数据流与状态管理

### 相关页面

相关主题：[系统架构](#page-architecture)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/models.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# 数据流与状态管理

## 概述

Content Distribution MCP 的核心职责之一是管理多平台内容发布的状态与数据流。系统需要解决以下关键问题：

1. **幂等性保障**：同一内容在相同渠道的重复发布应返回已有结果，而非重复创建
2. **状态追踪**：记录每个渠道的发布状态（live、queued、needs_browser、failed）
3. **去重机制**：通过 `content.id` + `channel` 组合键实现发布去重
4. **调度管理**：支持定时发布，将未来时间的发布请求入队待执行

资料来源：[src/models.ts:1-45](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)

## 核心数据模型

### Content 内容模型

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)

### Variant 变体模型

每个渠道的适配内容称为变体，包含渠道特定的调整：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)

### PublishState 发布状态枚举

| 状态值 | 含义 | 触发场景 |
|--------|------|----------|
| `live` | 已成功发布 | 平台 API 返回成功响应 |
| `queued` | 已加入调度队列 | 设置了未来时间的 `schedule_at` |
| `needs_browser` | 需要浏览器操作 | LinkedIn、Medium、Twitter 等无 API 的渠道 |
| `failed` | 发布失败 | API 调用失败、认证缺失、平台限流等 |

资料来源：[src/models.ts:30](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)

### PublishResult 发布结果

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)

## 架构组件

### 适配器架构

系统采用适配器模式，每种发布渠道对应一个适配器实现：

```mermaid
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

### 适配器接口定义

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

## 状态后端架构

### 后端类型

系统支持可插拔的状态后端，通过环境变量 `DISTRIBUTION_BACKEND` 配置：

| 后端类型 | 默认值 | 说明 |
|----------|--------|------|
| `yaml` | ✅ | 使用 YAML 文件存储状态（`~/.distribution-mcp/`） |

```typescript
function buildBackend(): StateBackend {
  const name = (process.env.DISTRIBUTION_BACKEND ?? "yaml").toLowerCase();
  // 根据配置构建对应后端实例
}
```

资料来源：[src/server.ts:180-185](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 状态存储结构

状态后端管理以下数据：

```mermaid
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`：
```yaml
"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** 存储待调度任务：
```yaml
- channel: reddit:ClaudeAI
  schedule_at: "2026-05-21T09:00:00+01:00"
  content: {...}
  variant: {...}
```

资料来源：[README.md:60-80](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 幂等性机制

### 幂等性原理

系统的幂等性基于 `(content.id, channel)` 组合键实现：

```mermaid
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 调度工作流

### 调度发布流程

使用 `post.schedule` 时，带有 `schedule_at` 的变体会被加入调度队列：

```mermaid
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` 工具检查所有待调度的发布，触发已到期的任务：

```bash
# 建议通过 cron 每 5 分钟执行一次
*/5 * * * * npx -y content-distribution-mcp drain
```

调度格式要求：
- 必须包含时区偏移量（如 `+08:00`）
- 正确格式：`2026-05-21T09:00:00+01:00`
- 缺少时区会导致不可预期行为

资料来源：[README.md:56-70](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 渠道适配器详解

### API 直连渠道

以下渠道通过原生 API 发布：

| 渠道 | 适配器 | 认证要求 |
|------|--------|----------|
| DEV.to | `DevToAdapter` | `DEV_TO_API_KEY` |
| Hashnode | `HashnodeAdapter` | `HASHNODE_TOKEN` + `HASHNODE_PUBLICATION_ID` |
| GitHub Discussions | `GitHubDiscussionsAdapter` | `GITHUB_TOKEN` + `GITHUB_DISCUSSION_REPO` |
| Reddit | `RedditAdapter` | `REDDIT_CLIENT_ID/SECRET/USERNAME/PASSWORD` |
| Bluesky | `BlueskyAdapter` | `BLUESKY_IDENTIFIER` + `BLUESKY_PASSWORD` |

#### DEV.to 发布流程示例

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)

#### Bluesky 发布流程示例

```typescript
// 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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

### 浏览器回退渠道

以下渠道无公开 API，需要浏览器操作：

| 渠道 | 返回状态 | 返回字段 |
|------|----------|----------|
| Medium | `needs_browser` | `compose_url` → `https://medium.com/new-story` |
| LinkedIn | `needs_browser` | `compose_url` → `https://www.linkedin.com/post/new` |
| Twitter/X | `needs_browser` | `compose_url` → 预填充推文链接 |

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)

> **社区关注**：根据路线图规划，Twitter/X、LinkedIn、Medium 的原生 API 支持正在计划中。资料来源：[社区上下文 - Roadmap & feature tracker](#top-community-issues)

## 工具接口

### MCP 工具列表

| 工具 | 用途 | 副作用 |
|------|------|--------|
| `post.publish` | 立即发布所有变体 | 幂等写操作 |
| `post.schedule` | 队列定时发布 + 立即发布 | 写 scheduled.yaml + 幂等写操作 |
| `post.drain` | 触发所有到期调度 | 执行 queued → live |
| `post.status` | 查询内容/渠道状态 | 只读 |
| `post.unpublish` | 尝试取消发布 | 平台删除操作 |
| `channel.hints` | 渠道静态元数据 | 只读，无 HTTP |
| `profile.list` | 列出分发配置 | 只读 |
| `subreddit.list` | Reddit 子版块目录 | 只读 |

### 发布结果输出模式

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

## 常见故障模式

### 认证失败

| 渠道 | 缺失凭证 | 错误表现 |
|------|----------|----------|
| 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 状态码时：

```json
{
  "channel": "devto:main",
  "state": "failed",
  "error": "devto 429: Rate limit exceeded"
}
```

### Reddit 反垃圾机制

Reddit 渠道具有自动限流检测：
- 子版块冷却期（cooldown_days）
- 发帖频率超过限制时返回 `needs_browser`
- 可通过 `subreddit.list` 查看上次成功发帖时间

### 浏览器回退渠道状态

对于 `needs_browser` 状态的应用流程：

```mermaid
graph TD
    A[post.publish 返回 needs_browser] --> B[获取 compose_url]
    B --> C[用户在浏览器中手动发布]
    D[可选: 手动记录 live_url] --> E[后续请求可识别为 live]
```

资料来源：[src/adapters/browser.ts:15-30](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)

## 最佳实践

### 1. 设计稳定的 content.id

推荐格式：`{slug}@{date}`，例如：
```json
{
  "id": "n8n-webhook-setup@2026-05-20"
}
```

### 2. 合理使用调度

- 避免过密的调度间隔
- 建议 Reddit 渠道间隔至少 24-48 小时
- 时区必须明确指定

### 3. 渠道特定调整

发布前使用 `channel.hints` 获取约束：

```json
{
  "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. 错误处理策略

```typescript
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}`);
}
```

## 相关文档

- [安装与快速开始](../getting-started) — 环境配置与凭证设置
- [渠道适配器参考](../adapters) — 各平台详细配置
- [调度系统配置](../scheduling) — Cron 与 drain 配置
- [故障排除指南](../troubleshooting) — 常见问题排查

## 参见

- [官方文档首页](https://github.com/AutomateLab-tech/content-distribution-mcp)
- [版本更新日志](https://github.com/AutomateLab-tech/content-distribution-mcp/commits)
- [社区路线图](https://github.com/AutomateLab-tech/content-distribution-mcp/issues)

---

<a id='page-adapters-overview'></a>

## 适配器概述

### 相关页面

相关主题：[DEV.to 适配器](#page-adapter-devto), [Reddit 适配器](#page-adapter-reddit)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)
- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)
- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
</details>

# 适配器概述

## 概述

适配器（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()` 函数构建所有适配器的映射表，并将其存储在内存中供工具处理器调用：

```typescript
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` 字段选择对应的适配器：

```mermaid
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` |
| Reddit | `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，采用浏览器回退机制：

| 平台 | 状态 | 返回值 | 用户操作 |
|------|------|--------|----------|
| LinkedIn | 浏览器回退 | `needs_browser` + `compose_url` | 手动在浏览器中完成发布 |
| Medium | 浏览器回退 | `needs_browser` + `compose_url` | 手动在浏览器中完成发布 |
| Twitter/X | 浏览器回退 | `needs_browser` + `compose_url` | 手动在浏览器中完成发布 |

资料来源：[src/adapters/browser.ts:1-45]()

> **社区规划**：[路线图与功能追踪](https://github.com/AutomateLab-tech/content-distribution-mcp/issues) 显示社区正在计划为这些平台添加原生 API 支持。

## 平台 hints 元数据

每个适配器的 `hints()` 方法返回平台静态元数据，帮助调用方了解平台约束：

### ChannelHints 数据结构

```typescript
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 | ✗ | ✗ |
| Reddit | 40,000 | bold, italic, code_inline, links | bottom | ✗ | ✗ |
| Bluesky | 300 | links | bottom | ✗ | ✗ |
| LinkedIn | 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 认证：

```typescript
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 认证流程获取访问令牌：

```typescript
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 发布内容：

```typescript
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()` 工厂函数创建浏览器回退适配器：

```typescript
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` 字段：

1. 当 `schedule_at` 存在时，服务器将变体存入 `scheduled.yaml`
2. 定时时间到达后，`post.drain` 工具读取队列并调用适配器 `publish()`
3. 适配器无需感知定时机制

## 常见失败模式

### 认证失败

| 错误信息 | 原因 | 解决方案 |
|----------|------|----------|
| `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()`：

1. 在 `src/adapters/` 目录创建新文件（如 `newplatform.ts`）
2. 实现 `ChannelAdapter` 接口的三个方法
3. 在 `src/adapters/index.ts` 中导入并添加到返回对象
4. 在 `src/server.ts` 的工具处理逻辑中添加对应的工具名处理

## 相关文档

- [内容发布工具](../tools/post-tools.md) - `post.publish`、`post.schedule`、`post.drain` 工具详解
- [凭证配置](../configuration/credentials.md) - 各平台凭证配置指南
- [发行说明](https://github.com/AutomateLab-tech/content-distribution-mcp/releases) - 版本更新与迁移指南

---

<a id='page-adapter-devto'></a>

## DEV.to 适配器

### 相关页面

相关主题：[适配器概述](#page-adapters-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/models.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# DEV.to 适配器

## 概述

DEV.to 适配器是 content-distribution-mcp 项目中负责将内容发布到 [DEV.to](https://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]()

## 技术架构

### 适配器定位

```mermaid
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:#90EE90
```

DEV.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()` 方法返回以下平台约束信息：

```mermaid
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]()

## 发布流程

### 发布状态流转

```mermaid
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 message
```

### publish 方法详解

```typescript
async publish(variant: Variant, profile: Profile): Promise<PublishResult>
```

**认证要求：**

DEV.to API 需要 `DEV_TO_API_KEY` 凭证，该凭证存储在分发配置文件的 `credentials` 字段中：

```yaml
# ~/.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`: 解析得到的文章 URL
  - `published_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 方法详解

```typescript
async unpublish(liveUrl: string, profile: Profile): Promise<[boolean, string | undefined]>
```

DEV.to 平台不支持直接删除文章，适配器采用**设为草稿**的方式实现下线：

```mermaid
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
```

**下线步骤：**

1. 使用 `DEV_TO_API_KEY` 调用 `GET /articles/me/published?per_page=100` 获取当前用户已发布的文章列表
2. 在列表中匹配 `liveUrl` 中包含的文章 slug
3. 若找到匹配项，调用 `PUT /articles/{id}` 将 `published` 设为 `false`
4. 返回 `[true, undefined]` 表示成功，或 `[false, errorMessage]` 表示失败

资料来源：[src/adapters/devto.ts:54-76]()

## 配置指南

### 环境变量与凭证配置

| 配置项 | 说明 | 必需 |
|--------|------|------|
| `DEV_TO_API_KEY` | DEV.to 个人 API Key | 是 |

获取 API Key 的步骤：
1. 登录 [DEV.to](https://dev.to)
2. 进入 **Settings** → **Extensions** → **DEV API Keys**
3. 创建一个新的 API Key

### profile.yaml 配置示例

```yaml
# ~/.distribution-mcp/profiles/my-profile.yaml
credentials:
  DEV_TO_API_KEY: "ABCdefGHIjklMNOpqrsTUVwxyz123456"
```

### Variant 配置示例

```json
{
  "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](url)` | ✅ | |
| 标题 `# ## ###` | ✅ | 支持 h1-h6 |
| 图片 `![alt](url)` | ✅ | |
| 列表 `- 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"`

**解决方案：**
1. 确认 `profile.yaml` 中 `credentials.DEV_TO_API_KEY` 已正确配置
2. 确认 API Key 未过期，可前往 DEV.to 设置页面重新生成
3. 确认使用的 profile 名称与配置一致

### 文章未找到（下线失败）

**症状：** `unpublish` 返回 `[false, "article not found in published list"]`

**原因：**
- 传入的 `liveUrl` 中的 slug 与已发布列表中的任何文章都不匹配
- 文章已被平台手动删除

**解决方案：**
- 手动在 DEV.to 网站上将文章设为草稿

### API 限流

**症状：** 返回 `"devto 429: ..."`

**解决方案：**
- DEV.to API 有请求频率限制
- 等待一段时间后重试
- 避免短时间内发布大量文章

## 与其他适配器的对比

| 特性 | DEV.to | Hashnode | GitHub Discussions | Reddit | 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 支持（目前使用浏览器回退）

资料来源：[社区路线图](https://github.com/AutomateLab-tech/content-distribution-mcp/issues)

## 相关页面

- [内容分发 MCP 概览](../README)
- [快速开始指南](./快速开始)
- [通道适配器架构](./通道适配器)
- [post.publish 工具](./post.publish)
- [channel.hints 工具](./channel.hints)
- [分发配置文件](./分发配置)

---

<a id='page-adapter-hashnode'></a>

## Hashnode 适配器

### 相关页面

相关主题：[适配器概述](#page-adapters-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/hashnode.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/hashnode.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [src/models.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/models.ts) (类型定义)
- [src/backends/base.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/backends/base.ts) (Profile 类型)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# Hashnode 适配器

## 概述

Hashnode 适配器是 content-distribution-mcp 项目中用于与 Hashnode 博客平台进行集成的核心组件。它负责将内容变体（Variant）发布到 Hashnode 出版物（Publication），并提供平台特定的功能支持，包括标签规范化、系列文章关联和规范化 URL 设置。

Hashnode 是 content-distribution-mcp 支持的 **8+ 渠道**之一，属于**自动发布渠道**类别，即该渠道拥有公开 API，无需浏览器回退即可完成发布操作。资料来源：[README.md]()

## 架构设计

### 适配器角色

Hashnode 适配器实现了 `ChannelAdapter` 接口，该接口定义了所有渠道适配器必须实现的标准方法。适配器作为 MCP 服务器与 Hashnode GraphQL API 之间的桥梁，负责：

1. **转换**：将通用的内容模型转换为 Hashnode API 所需的 GraphQL 变异格式
2. **验证**：确保必填凭证存在且有效
3. **发布**：通过 GraphQL 变异将内容发布到 Hashnode 出版物
4. **反解析**：提供渠道特定的元数据（ChannelHints）

```mermaid
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]()

### 与其他渠道的对比

```mermaid
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` 方法遵循以下执行流程：

```mermaid
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。核心变异语句结构如下：

```graphql
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 的标签格式要求与通用格式不同，需要进行转换：

```typescript
tags: variant.tags.slice(0, 5).map(t => ({
  slug: t.toLowerCase().replace(/\s+/g, "-"),  // 转小写，空格替换为连字符
  name: t                                        // 保留原始名称
}))
```

### 发布结果

成功发布后，适配器返回以下结构：

```typescript
{
  channel: variant.channel,           // 渠道标识，如 "hashnode:main"
  state: "live",                      // 状态：已上线
  live_url: json.data.publishPost.post.url,  // Hashnode 文章 URL
  published_at: new Date().toISOString()      // UTC 时间戳
}
```

## 反发布支持

Hashnode 适配器实现了 `unpublish` 方法，但当前版本存在限制：

```typescript
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 中获取 |

### 配置示例

```yaml
# ~/.distribution-mcp/profiles.yaml
default:
  credentials:
    HASHNODE_TOKEN: "your-hashnode-token"
    HASHNODE_PUBLICATION_ID: "your-publication-id"
  subreddits: []
```

### 凭证验证失败处理

当凭证缺失时，适配器返回：

```typescript
{
  channel: variant.channel,
  state: "failed",
  error: "HASHNODE_TOKEN or HASHNODE_PUBLICATION_ID not set in profile"
}
```

## 适配器注册与路由

### 适配器映射表

Hashnode 适配器通过 `buildAdapterMap()` 函数注册到主适配器映射中：

```typescript
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 服务器中，渠道标识符通过冒号分隔的平台前缀进行解析：

```typescript
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 工具发布

```jsonc
// 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 渠道提示

```jsonc
// 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 版本的限制

1. **反发布功能未实现**：当前无法通过 MCP 服务器取消发布 Hashnode 文章
2. **series 参数仅接受字符串**：无法直接通过 API 创建或查询系列 ID，需手动在 Hashnode 后台获取系列 slug
3. **标签数量限制**：最多发送 5 个标签，超出部分被截断
4. **封面图片支持**：适配器未实现封面图片上传功能

### 社区反馈

根据社区 roadmap，Hashnode 适配器的原生 API 支持已完全实现，无需浏览器回退。未来的改进方向可能包括：

- 封面图片上传支持
- 系列文章自动创建
- 增强的反发布功能

## 参见

- [主文档首页](../README.md) - 项目整体介绍和快速开始指南
- [适配器概览](../adapters/index.md) - 所有渠道适配器总览
- [DEV.to 适配器](../adapters/devto.md) - 类似的全功能 API 适配器
- [浏览器回退适配器](../adapters/browser.md) - LinkedIn、Medium、Twitter 的回退机制
- [配置指南](../configuration.md) - 完整的凭证和环境变量配置说明

---

<a id='page-adapter-reddit'></a>

## Reddit 适配器

### 相关页面

相关主题：[适配器概述](#page-adapters-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/reddit.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/reddit.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# Reddit 适配器

## 概述

Reddit 适配器是 `content-distribution-mcp` 项目中负责将内容发布到 Reddit 平台的组件。它被归类为 **Auto-gated（自动门控）** 频道，意味着该适配器需要 Reddit OAuth 凭证才能正常工作，但在内容发布时会受到 Reddit 社区的反垃圾规则的限制。

Reddit 适配器的核心职责包括：
- 通过 Reddit API 进行身份认证
- 发布内容到指定的子版块（subreddit）
- 管理每帖子状态的幂等性
- 强制执行子版块级别的冷却期（cooldown）规则
- 处理帖子置顶和精选功能（可选）

资料来源：[src/adapters/index.ts:14](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

---

## 架构设计

### 适配器定位

Reddit 适配器作为 `ChannelAdapter` 接口的实现，被注册到适配器映射表中。与其他平台适配器（如 DEV.to、Hashnode、Bluesky）一样，它实现了统一的 `hints()`、`publish()` 和 `unpublish()` 方法。

```mermaid
graph TD
    A[post.publish / post.schedule] --> B[Server Tool Handler]
    B --> C[adapters/index.ts]
    C --> D[RedditAdapter]
    D --> E[Reddit OAuth API]
    E --> F[子版块发布]
    
    G[subreddit.list] --> H[State Backend<br/>~/.distribution-mcp/state.yaml]
    H --> D
```

### 与其他适配器的比较

| 特性 | Reddit | DEV.to | Hashnode | Bluesky | LinkedIn |
|------|--------|--------|----------|---------|----------|
| 认证方式 | OAuth 2.0 | API Key | GraphQL Token | App Password | 浏览器回退 |
| 最大字符数 | 40,000 | 100,000 | 50,000 | 300 | 3,000 |
| Markdown 支持 | 有限 | 完整 | 完整 | 无 | 有限 |
| 原生 API | ✅ | ✅ | ✅ | ✅ | ❌ |
| 社区规则强制 | ✅ | ❌ | ❌ | ❌ | ❌ |

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

---

## 核心功能

### 1. 频道元数据查询

通过 `channel.hints` 工具可以获取 Reddit 频道的平台约束信息：

```typescript
hints(): ChannelHints {
  return {
    max_length: 40_000,
    supported_md_features: ["bold", "italic", "links"],
    cta_placement: "none",  // Reddit 不支持自动添加 CTA
    canonical_url_supported: false,
    browser_only: false,
  };
}
```

**关键限制**：
- Reddit 对 Markdown 的支持非常有限，仅支持粗体、斜体和链接
- 不支持 `canonical_url`，所有链接将被转换为纯文本
- CTA 块（Call-to-Action）无法自动添加

### 2. 内容发布流程

Reddit 适配器的发布流程包含以下步骤：

```mermaid
sequenceDiagram
    participant Agent as AI Agent
    participant Server as MCP Server
    participant Reddit as Reddit API
    participant Backend as State Backend
    
    Agent->>Server: post.publish with reddit:subreddit
    Server->>Backend: Check existing state
    Backend-->>Server: No existing post found
    Server->>Server: Validate cooldown rules
    Server->>Reddit: Create OAuth session
    Reddit-->>Server: Access token
    Server->>Reddit: POST /api/submit
    Reddit-->>Server: Post result
    Server->>Backend: Save publish state
    Server-->>Agent: Return live_url
```

#### 发布状态返回值

| 状态 | 含义 | 何时出现 |
|------|------|----------|
| `live` | 发布成功 | 帖子成功发布到子版块 |
| `queued` | 排队中 | 帖子等待调度执行 |
| `needs_browser` | 需要浏览器 | 不适用于 Reddit（原生 API） |
| `failed` | 发布失败 | 认证失败或违反社区规则 |

资料来源：[src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)

### 3. 社区反垃圾规则

Reddit 适配器最重要的特性是**强制执行子版块级别的冷却期（cooldown）规则**。

```typescript
const subredditEntryShape = {
  subreddit: z.string().describe("子版块名称，不含 'r/' 前缀"),
  cooldown_days: z.number().optional().describe("两次发帖之间的最小天数间隔"),
  flair_vocab: z.array(z.string()).optional().describe("允许使用的帖子标签 ID 或标签文本"),
  last_posted_at: z.string().nullable().optional().describe("上次成功发帖的 UTC ISO-8601 时间戳"),
  notes: z.string().optional(),
};
```

#### 冷却期工作原理

当尝试向某个子版块发布内容时，适配器会：
1. 从状态后端读取该子版块的 `last_posted_at` 时间戳
2. 计算当前时间与上次发帖时间的间隔
3. 若间隔小于 `cooldown_days`，则拒绝发布并返回错误

```mermaid
graph TD
    A[收到发布请求] --> B{查询 last_posted_at}
    B --> C{已超过 cooldown_days?}
    C -->|是| D[允许发布]
    C -->|否| E[拒绝发布<br/>返回 cooldown 错误]
    D --> F[更新 last_posted_at]
    E --> G[返回失败状态]
```

### 4. 帖子标签（Flair）

Reddit 适配器支持通过 `extras.flair` 参数指定帖子标签：

```json
{
  "channel": "reddit:programming",
  "title": "...",
  "body": "...",
  "extras": {
    "flair": "Discussion"
  }
}
```

可通过 `subreddit.list` 工具获取特定子版块允许的标签词汇：

```json
{
  "filter": "programming"
}
// 返回:
// {
//   "subreddits": [
//     {
//       "subreddit": "programming",
//       "cooldown_days": 3,
//       "flair_vocab": ["Question", "Discussion", "Show DEV"]
//     }
//   ]
// }
```

---

## 配置与凭证

### 环境变量配置

Reddit 适配器需要以下环境变量（通常配置在 distribution profile 中）：

| 变量名 | 必需 | 说明 |
|--------|------|------|
| `REDDIT_CLIENT_ID` | ✅ | Reddit 应用客户端 ID |
| `REDDIT_CLIENT_SECRET` | ✅ | Reddit 应用客户端密钥 |
| `REDDIT_USERNAME` | ✅ | Reddit 账户用户名 |
| `REDDIT_PASSWORD` | ✅ | Reddit 账户密码 |

### 配置文件示例

在 `~/.distribution-mcp/profiles/default.yaml` 中配置：

```yaml
profiles:
  default:
    channels:
      reddit:
        credentials:
          REDDIT_CLIENT_ID: "your_client_id"
          REDDIT_CLIENT_SECRET: "your_client_secret"
          REDDIT_USERNAME: "your_username"
          REDDIT_PASSWORD: "your_password"
    subreddits:
      - name: "programming"
        cooldown_days: 3
        flair_vocab:
          - "Question"
          - "Discussion"
      - name: "webdev"
        cooldown_days: 1
        flair_vocab:
          - "Help"
          - "Tutorial"
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

---

## 工具链集成

### 使用的 MCP 工具

| 工具名称 | 用途 | Reddit 适配器关联 |
|----------|------|-------------------|
| `post.publish` | 立即发布内容 | 将 variant 传递给 RedditAdapter |
| `post.schedule` | 调度内容发布 | 支持 schedule_at 参数 |
| `post.drain` | 执行所有待调度的帖子 | 触发 Reddit 发布操作 |
| `post.status` | 查询发布状态 | 返回 per-channel state |
| `post.unpublish` | 撤回帖子 | 删除 Reddit 帖子 |
| `subreddit.list` | 列出子版块目录 | 返回 cooldown 和 flair 信息 |
| `channel.hints` | 获取平台约束 | 返回 Reddit 的 max_length 等 |

### 完整使用示例

```json
{
  "name": "post.publish",
  "arguments": {
    "content": {
      "id": "ai-mcp-guide-2026-05-20",
      "title": "A Complete Guide to MCP Servers",
      "body_md": "# Introduction\n\nModel Context Protocol (MCP) is...",
      "tags": ["mcp", "ai", "tutorial"],
      "author": "TechWriter"
    },
    "variants": [
      {
        "channel": "reddit:programming",
        "title": "Built a complete MCP server - here's what I learned",
        "body": "After building several MCP servers, I want to share...",
        "tags": [],
        "extras": {
          "flair": "Tutorial"
        }
      },
      {
        "channel": "reddit:JavaScript",
        "title": "MCP servers in JavaScript - full tutorial",
        "body": "Step-by-step guide to...",
        "tags": [],
        "extras": {
          "flair": "Help"
        }
      }
    ],
    "profile_name": "default"
  }
}
```

---

## 已知限制

### 技术限制

1. **Reddit API 的速率限制**：Reddit 对 API 调用有严格的速率限制（每分钟请求数限制），大量发布可能触发限制
2. **子版块版主权限**：某些子版块可能设置了自动审核规则，帖子可能被自动移除
3. **冷却期强制执行**：仅在 MCP 状态后端中强制执行，跨实例部署可能出现不一致

### 与原生 API 的差距

根据社区路线图，以下功能仍在规划中：

- [ ] **原生 API 增强支持**：Reddit 的某些高级功能（如评论管理）尚未集成
- [ ] **更好的错误诊断**：当前错误消息可能不够详细，难以定位问题根源

资料来源：[README.md - Roadmap](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

---

## 故障排查

### 常见错误及解决方案

| 错误信息 | 原因 | 解决方案 |
|----------|------|----------|
| `REDDIT_CLIENT_ID not set` | 凭证未配置 | 检查 profile YAML 中的 credentials |
| `cooldown not elapsed` | 未达到冷却期 | 等待 cooldown_days 指定的时间 |
| `Invalid subreddit` | 子版块名称错误 | 使用正确的子版块名称（无 r/ 前缀） |
| `flair not allowed` | 标签不在允许列表中 | 使用 `subreddit.list` 查看允许的标签 |
| `Rate limit exceeded` | API 速率限制 | 减少发布频率，或使用调度功能错峰发布 |

### 调试步骤

1. **验证凭证**：
   ```bash
   curl -X POST "https://www.reddit.com/api/v1/access_token" \
     -d "grant_type=password" \
     -d "username=$REDDIT_USERNAME" \
     -d "password=$REDDIT_PASSWORD" \
     -H "Authorization: Basic $(echo -n $REDDIT_CLIENT_ID:$REDDIT_CLIENT_SECRET | base64)"
   ```

2. **检查子版块配置**：
   ```json
   {
     "name": "subreddit.list",
     "arguments": { "filter": "programming" }
   }
   ```

3. **查看发布状态**：
   ```json
   {
     "name": "post.status",
     "arguments": {
       "content_id": "your-content-id",
       "channel": "reddit:programming"
     }
   }
   ```

---

## 与其他频道的对比

Reddit 在整个内容分发生态中的定位：

```mermaid
graph LR
    A[content-distribution-mcp] --> B[自动发布频道]
    A --> C[浏览器回退频道]
    A --> D[自动门控频道]
    
    B --> B1[DEV.to]
    B --> B2[Hashnode]
    B --> B3[GitHub Discussions]
    B --> B4[Bluesky]
    
    C --> C1[LinkedIn]
    C --> C2[Medium]
    C --> C3[Twitter/X]
    
    D --> D1[Reddit]
    
    style D1 fill:#ffcc00
```

---

## 参见

- [总览](../index.md) — content-distribution-mcp 项目总览
- [DEV.to 适配器](./devto-adapter.md) — 完整 Markdown 支持的平台
- [调度系统](./scheduling.md) — Reddit 冷却期与调度集成
- [子版块管理](./subreddit-management.md) — 社区规则与目录管理
- [凭证管理](./credentials.md) — 安全存储 Reddit OAuth 凭证

---

<a id='page-adapter-bluesky'></a>

## Bluesky 适配器

### 相关页面

相关主题：[适配器概述](#page-adapters-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
- [package.json](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/package.json)
</details>

# 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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts) |

## 核心功能

### 平台元数据 (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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

### 发布流程

```mermaid
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 }
```

#### 认证流程

1. 调用 `com.atproto.server.createSession` 端点
2. 传入 `identifier`（用户名/邮箱）和 `password`
3. 获取 `accessJwt`（访问令牌）和 `did`（去中心化标识符）

#### 发布流程

1. 使用 `accessJwt` 作为 Bearer Token 调用 `com.atproto.repo.createRecord`
2. `collection` 设置为 `app.bsky.feed.post`
3. `repo` 设置为用户的 `did`
4. `record` 包含 `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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

### 取消发布

Bluesky 适配器的 `unpublish` 方法目前**未实现**：

```typescript
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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

## 适配器注册

在适配器索引文件中，Bluesky 适配器被注册到适配器映射表中：

```typescript
return {
  devto: new DevToAdapter(),
  hashnode: new HashnodeAdapter(),
  github_discussions: new GitHubDiscussionsAdapter(),
  reddit: new RedditAdapter(),
  bluesky: new BlueskyAdapter(),  // 注册在此
  // ...
};
```

资料来源：[src/adapters/index.ts:19](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)

## 配置要求

### 环境变量

| 变量名 | 必需 | 说明 |
|--------|------|------|
| `BLUESKY_IDENTIFIER` | 是 | Bluesky 用户名或邮箱 |
| `BLUESKY_PASSWORD` | 是 | Bluesky 密码或 App Password |

### Profile 配置

在 YAML 后端配置文件中：

```yaml
profiles:
  default:
    channels:
      - bluesky:me
    credentials:
      BLUESKY_IDENTIFIER: "your-handle.bsky.social"
      BLUESKY_PASSWORD: "app-password-or-password"
```

资料来源：[README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)

## 使用示例

### 发布请求

```json
{
  "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"
}
```

### 成功响应

```json
{
  "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"
    }
  ]
}
```

### 失败响应

```json
{
  "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 请求体

```json
{
  "identifier": "user.bsky.social",
  "password": "xxxx-xxxx-xxxx"
}
```

### Create Record 请求体

```json
{
  "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](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts)

## 限制与已知问题

| 问题 | 状态 | 说明 |
|------|------|------|
| 取消发布 | ❌ 未实现 | 需手动在平台删除 |
| 图片支持 | ⚠️ 限制 | 当前版本仅支持文本 |
| Markdown 格式 | ⚠️ 限制 | 仅支持链接 |
| 线程支持 | ❌ 未实现 | 暂不支持回复链 |

## 与其他平台的比较

| 平台 | API 类型 | 字符限制 | Markdown 支持 | 取消发布 |
|------|----------|----------|---------------|----------|
| **Bluesky** | 原生 API | 300 | 仅链接 | ❌ 未实现 |
| DEV.to | 原生 API | 100,000 | 完整 | ✅ 已实现 |
| Hashnode | 原生 GraphQL | 50,000 | 完整 | ✅ 已实现 |
| Twitter | 浏览器回退 | 280 | 仅链接 | ⚠️ 部分支持 |
| LinkedIn | 浏览器回退 | 3,000 | 部分 | ⚠️ 部分支持 |

资料来源：[src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts) 和 [src/adapters/devto.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/devto.ts)

## 相关文件

| 文件 | 说明 |
|------|------|
| [src/adapters/bluesky.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/bluesky.ts) | Bluesky 适配器实现 |
| [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts) | 适配器注册表 |
| [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts) | MCP 服务器与工具定义 |
| [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md) | 项目整体文档 |

## 参见

- [发布工具概览](README.md) — 了解 `post.publish` 和 `post.schedule` 工具
- [平台适配器架构](src/adapters/index.ts) — 所有支持的平台列表
- [GitHub Discussions 适配器](src/adapters/github-discussions.ts) — 类似原生 API 实现参考
- [Browser 适配器](src/adapters/browser.ts) — 了解浏览器回退机制

---

<a id='page-adapter-github-discussions'></a>

## GitHub Discussions 适配器

### 相关页面

相关主题：[适配器概述](#page-adapters-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/adapters/github-discussions.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/github-discussions.ts)
- [src/adapters/index.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/index.ts)
- [src/adapters/browser.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/adapters/browser.ts)
- [src/server.ts](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/src/server.ts)
- [README.md](https://github.com/AutomateLab-tech/content-distribution-mcp/blob/main/README.md)
</details>

# 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`（带连字符版本），这提供了更好的命名灵活性。

```mermaid
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 适配器的发布流程涉及多个步骤，包括身份验证、仓库查询、分类获取和讨论创建。以下是该流程的详细架构：

```mermaid
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]()

### 凭证优先级

适配器在确定实际使用的参数值时采用以下优先级规则：

```mermaid
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` 中的配置示例：

```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` 对象支持以下平台特定参数：

```json
{
  "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 | Reddit | Bluesky | Medium | LinkedIn | Twitter |
|------|-------------------|--------|----------|--------|---------|--------|----------|---------|
| 原生 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]()

## 使用示例

### 基础发布调用

```json
{
  "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 的发布

```json
{
  "channel": "github_discussions",
  "title": "使用 MCP 自动化工作流程",
  "body": "# MCP 工作流自动化\n\n本文介绍如何...",
  "cta_block": "---\n\n💡 想了解更多信息？访问 [我们的网站](https://example.com)"
}
```

## 故障排除

### 诊断步骤

当 GitHub Discussions 发布失败时，请按以下步骤排查：

1. **验证凭证**：确认 `GITHUB_TOKEN` 具有 `repo` 和 `read:discussion` 权限
2. **检查仓库访问**：确保 Token 可以访问指定的仓库
3. **验证分类 ID**：确认分类 ID 格式正确且分类存在
4. **查看 GraphQL 错误**：检查返回的 `error` 字段中的详细错误信息

### 调试技巧

启用调试模式查看详细的 GraphQL 请求和响应：

```bash
DEBUG=* npx -y content-distribution-mcp publish --content-id "your-id"
```

## 相关文档

- [主项目文档](../README.md)
- [适配器架构概述](./adapters-overview.md)
- [内容 Variant 规范](./variant-specification.md)
- [Profile 配置指南](./profile-configuration.md)

## 参见

- [content-distribution-mcp 主页](https://github.com/AutomateLab-tech/content-distribution-mcp)
- [GitHub GraphQL API 文档](https://docs.github.com/en/graphql)
- [GitHub Discussions 文档](https://docs.github.com/en/discussions)

---

<!-- evidence_injected: true -->

---

## Doramagic 踩坑日志

项目：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

<!-- canonical_name: automatelab-tech/content-distribution-mcp; human_manual_source: deepwiki_human_wiki -->
