关于 Substack MCP Server
Substack MCP Server 是一个 MCP 服务,让 LLM 客户端通过已登录的网页会话管理 Substack 发布物,包括草稿、文章、订阅者、数据分析、阅读源、评论和图片。它面向需要自动化 Substack 发布流程的作者和运营者。Substack 未提供公开 API,该项目基于会话令牌工作,因此需要妥善保管凭据。
主要功能
- 01共 27 个工具,覆盖发布与订阅者管理
- 02订阅者列表支持多条件筛选与分页
- 03订阅者导出可读取各项互动指标
- 04草稿正文支持结构化文档写入
- 05图片可从 URL 或本地文件上传至 Substack
适用场景
- 01通过 LLM 客户端创建和修改草稿文章
- 02按条件筛选订阅者并导出互动数据
- 03将本地或网络图片上传后插入文章
MCP 相关
该项目是一个 MCP 服务,向 MCP 客户端暴露 Substack 的发布、订阅者、数据和图片相关工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y substack-mcpSUBSTACK_PUBLICATION_URLFull URL of your Substack publication, including https://SUBSTACK_SESSION_TOKEN密钥Value of the substack.sid or connect.sid cookie from your authenticated Substack sessionSUBSTACK_USER_IDNumeric user ID returned by Substack's publication_user request本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
27收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| body | string | 是 | 文章正文。纯文本会按每行转换为一个段落——不会解析 Markdown,因此 `## Heading` 会按原样显示。也可以使用 Substack 文档的 JSON 字符串,并根据 set_post_body 发布时使用的同一组架构进行验证;无法识别的节点名称会报错,而不会生成格式错乱的文章。对于结构化内容,建议使用 set_post_body;该工具会公开架构并显示节点词汇。 |
| title | string | 是 | 要创建的文章标题。 |
| subtitle | string | 是 | 要创建的文章副标题。 |
详细介绍
Substack MCP 服务
一个用于 Substack 的模型上下文协议(MCP)服务,让大模型客户端能够与 Substack 的 API 交互,以自动创建文章、管理草稿等。
创建并发布文章、管理订阅者和分析数据、浏览读者动态、管理标签和评论,以及上传图片——通过一个 MCP 服务提供 27 种工具。
重要
Substack 不为这些操作提供公开 API。此服务使用经过身份验证的网页会话。请像对待密码一样处理会话令牌:将其保留在本地,切勿提交,也不要在错误报告中包含该令牌或完整的 Cookie 标头。
快速开始
最快的安装方式是使用 Node.js 22 或更高版本和 npx。
1. 收集 Substack 凭据
在浏览器中登录 Substack 并打开你的出版物控制面板。你需要三个值:
- 出版物 URL——出版物的完整基础 URL,例如
https://your-publication.substack.com。 - 会话令牌——打开浏览器开发者工具,选择 Network,筛选为
Fetch/XHR,然后重新加载控制面板。打开一个对出版物进行身份验证且成功的请求。在
Request Headers 下,找到
Cookie标头,并找到名为substack.sid或connect.sid的会话 Cookie。复制其值,不要复制 Cookie 名称或标头的其余部分。 如果这两个名称都出现且值不同,请分别在本地使用第 3 步中的只读验证进行测试;切勿在问题中粘贴任一值。 - 用户 ID——在同一个 Network 面板中,搜索一个成功的
publication_user请求。在 其 JSON 响应中,复制user对象中的数字id。
如果浏览器界面有所不同,图文并茂的凭据指南 展示了相同的请求。如果之后身份验证失效,请重新登录并重复这些步骤,以获取当前令牌。
2. 将服务添加到 MCP 客户端
对于接受 MCP JSON 配置的客户端,请添加:
{
"mcpServers": {
"substack": {
"command": "npx",
"args": ["-y", "substack-mcp@latest"],
"env": {
"SUBSTACK_PUBLICATION_URL": "https://your-publication.substack.com",
"SUBSTACK_SESSION_TOKEN": "your-session-token",
"SUBSTACK_USER_ID": "your-user-id"
}
}
}
}
替换三个示例值,保存配置,然后重启 MCP 客户端。如果客户端使用其他配置格式,请查阅其文档。
3. 验证连接
向客户端提问:
列出我最近的五篇 Substack 草稿。
客户端应调用 list_posts,并传入 status: "drafts"。如果失败,请先检查客户端的 MCP
日志和下方的日志部分,然后再提交问题。
Docker 快速开始
如果要使用已发布的 Docker 镜像,而不是 Node.js,请添加以下服务配置:
{
"mcpServers": {
"substack": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "SUBSTACK_PUBLICATION_URL",
"-e", "SUBSTACK_SESSION_TOKEN",
"-e", "SUBSTACK_USER_ID",
"marcomoauro/substack-mcp:latest"
],
"env": {
"SUBSTACK_PUBLICATION_URL": "https://your-publication.substack.com",
"SUBSTACK_SESSION_TOKEN": "your-session-token",
"SUBSTACK_USER_ID": "your-user-id"
}
}
}
}
🛠 可用工具
create_draft_post - 创建草稿文章
输入:
title(字符串):文章标题subtitle(字符串):文章副标题body(字符串):文章正文。纯文本会按每行转换为一个段落——不会解析 Markdown,因此## Heading会按原样显示。也可以使用 Substack 文档的 JSON 字符串,并根据set_post_body发布时使用的同一架构进行验证,因此无法识别的节点名称会报错,而不会悄悄生成格式错误的文章。
返回值:{draft_id, is_published}。将 draft_id 传给 get_draft,即可读取草稿。
对于任何结构化内容——标题、列表、链接、代码、图片、付费墙——请在创建草稿后使用 set_post_body:该工具会公开架构,因此调用模型可以读取节点词汇表,而不必猜测。
list_subscribers - 列出并筛选你的订阅者
提供与“订阅者”仪表板相同的筛选功能:48 个列、18 种运算符、自由文本搜索、排序和分页。输入:
filters(数组,可选):使用 AND 组合条件,每项为{column, operator, value}search(字符串,可选):与订阅者姓名和电子邮件匹配的自由文本sort_by(字符串,可选):任意可筛选列sort_direction(asc|desc,可选):默认为desclimit(数字,可选):1–100,默认为 25offset(数字,可选):用于分页
列可接受的运算符取决于其类型:
| 类型 | 运算符 |
|---|---|
Int | is is_not gt gte lt lte |
String | is is_not is_any_of contains starts_with ends_with includes_none |
DateTime | is_on is_after is_on_or_after is_before is_on_or_before |
Array(tag_ids、emails_enabled) | includes_any includes_all includes_none |
subscription_type、group_membership | is is_not is_any_of |
这些列涵盖订阅者身份信息(姓名、电子邮件、国家/地区、州/省、群组成员身份)、订阅信息(类型、开始/到期/取消日期、收入、Stripe 方案、归因)、电子邮件互动情况(过去 7 天/30 天/6 个月的打开次数和独立打开次数、点击的链接、版块)以及网站互动情况(帖子浏览量、看到的独立帖子数、评论、分享、活跃天数、活跃度评级)。完整列表及其类型会通过此工具的 JSON Schema 传给客户端,因此模型无需猜测列名。
返回:{count, returned, limit, offset, subscribers}。count 是符合筛选条件的总数,不受 limit 影响,因此调用时设置 limit: 1,即可低成本估算某个细分群体的规模。
注意:此处可以筛选互动情况相关列,但它们不会包含在此工具返回的记录中——Substack 会根据出版物已保存的“显示”设置来决定返回哪些字段,并忽略每次请求指定的列列表。使用
export_subscribers读取这些值。
不支持 OR 和嵌套条件:需要 OR 的筛选必须分多次调用。
export_subscribers - 导出包含所有列值的订阅者
此工具用于实际读取互动指标,这些指标只能由 list_subscribers 筛选:过去 7 天/30 天/6 个月的电子邮件打开次数、看到的独立电子邮件数、帖子浏览量、看到的独立帖子数、评论、分享、点击的链接、活跃天数和活跃度评级。
输入:
filters(数组,可选):与list_subscribers相同的条件,使用 AND 组合search(字符串,可选):与订阅者姓名和电子邮件匹配的自由文本columns(数组,可选):要包含的列,默认为全部max_wait_seconds(数字,可选):1–600,默认为 120
返回:{count, columns, missing_columns, unmapped_columns, export_id, subscribers},其中每位订阅者的数据都以列名为键。
Substack 会异步生成文件,因此此工具会创建订阅者集合、请求导出、轮询等待导出就绪,然后下载文件。少量数据的导出只需几秒。如果等待时间超出限制,工具会说明情况并提供 export_id,而不会一直阻塞。
两个注意事项,均已通过实时 API 验证:
- 无法导出
tag_ids和group_membership。Substack 会静默丢弃这些列,而不是报错,因此它们会列在missing_columns中——请求全部 48 列时只会返回 46 列。- 返回的值为格式化后的显示值,而非原始值:此处的收入为
"€50.00",通过list_subscribers返回的则是数字50。日期为 ISO 字符串。
不支持分页:每次导出都会覆盖所有符合条件的数据。
list_posts - 列出草稿、已发布或已安排发布的帖子
**输入**: - `status`(`drafts` | `published` | `scheduled`):要读取的列表 - `search`(字符串,可选):与标题和内容匹配的自由文本 - `limit`(数字,可选):1–100,默认为 25 - `offset`(数字,可选):用于分页 - `sort_direction`(`asc` | `desc`,可选):草稿和已发布文章按时间从新到旧排序, 已安排的文章按时间从近到远排序返回:{status, total, returned, limit, offset, posts},每篇文章均为摘要形式——如需查看未发布文章的完整内容,请使用
get_draft。
get_draft - 完整读取一篇草稿
输入:
draft_id(数字):list_posts或create_draft_post返回的 ID
返回:Substack 存储的草稿,包含正文和受众/电子邮件设置。
set_post_body - 用结构化文档替换草稿正文
这是编写结构化内容的唯一方式:标题、列表、链接、代码块、引用、图片、按钮和付费墙。create_draft_post 接受纯文本;此工具接受 Substack 实际存储的文档,其架构已发布在 tools/list 中,因此调用模型可以读取节点类型,而不是自行猜测。
输入:
draft_id(数字):list_posts或create_draft_post返回的 IDbody(对象):Substack ProseMirror 文档——{type: 'doc', content: [...]}
接受 15 种节点类型:paragraph、heading、bullet_list、ordered_list、list_item、
blockquote、highlighted_code_block、code_block、horizontal_rule、captionedImage、button、
paywall、youtube2,以及原样传递的 digestPostEmbed、substack_mentions 和 directMessage,以便通过 get_draft 读取的文档可以写回。标记:strong、em、code、
strikethrough、link。
返回:{draft_id, nodes},其中 nodes 按类型统计已存储的节点数量——因此,调用方可以确认自己请求的付费墙是否存在。验证无法报告从未发送的节点。
有三点值得注意:
- 图片必须已托管在 Substack 上。
image2.src指向外部 URL 时会被存储,但不会显示。请使用upload_image重新托管图片,以获取可用的src。 - 文档最多只能包含一个
paywall。 Substack 接受两个并都会显示,但无法确定哪一个会截断文章,因此此工具会拒绝第二个。 ordered_list使用attrs.order而非attrs.start作为编号起点。 只提供start的列表会从 1 开始显示,且不会报错。
upload_image - 从 URL 或本地文件将图片托管到 Substack
Substack 的编辑器会将图片作为 base64 数据 URI 上传到 POST /api/v1/image,并由该接口返回 Substack 托管的 URL。set_post_body 中的 image2.src 和 update_draft 中的 cover_image 只能显示此类 URL,因此此工具负责在两者之间搭建桥梁。Substack 只会重新获取已存储在自身的 URL,因此图片会在此处编码,而不是转交给 Substack。
输入——url 和 path 中恰好提供一个:
url(字符串):要下载并重新托管的图片 http(s) URLpath(字符串):运行此服务的机器上图片文件的绝对路径,直接从磁盘读取,不进行下载post_id(数字,可选):图片所属文章;其作用尚未确认
返回:{id, url, content_type, bytes, width, height}——调用 set_post_body 时,将 url 填入 image2.src;调用 update_draft 时,将其填入 cover_image。
下载受安全保护:仅允许 http/https;DNS 解析后会拒绝私有和环回地址的主机(每次重定向都会重新检查);内容类型必须是图片;不接受 HEIC,并会提示进行转换;图片大小不得超过 10 MB。
update_draft - 更改草稿标题、副标题或任意文章设置
更新是部分更新:只更改传入的字段,正文保持不变。
输入:
draft_id(数字):由list_posts或create_draft_post返回的 IDdraft_title(字符串,可选)draft_subtitle(字符串,可选)audience(everyone|only_paid|only_free|founding,可选)write_comment_permissions(everyone|subscribers|only_paid|none,可选):允许哪些人发表评论default_comment_sort(best_first|most_recent_first|oldest_first,可选)cover_image(字符串,可选):社交预览图片。若 URL 已位于substack-post-media.s3.amazonaws.com或substackcdn.com,则直接使用;否则会先下载并重新托管到 Substack,遵循与upload_image相同的限制social_title(字符串,可选):文章在其他地方分享时使用的标题description(字符串,可选):社交预览描述——不是副标题search_engine_title(字符串,可选)search_engine_description(字符串,可选)slug(字符串,可选):文章的 URL 别名
返回:{draft_id, updated_fields, draft_title, draft_subtitle, audience, is_published, cover_image, cover_image_rehosted_from}。
如果没有要更改的字段,调用会被拒绝,而不是作为空操作发送。cover_image 是最终实际使用的 URL;如果图片经过重新托管,它就会与传入的 URL 不同。
publish_draft - 发布草稿
输入:
draft_id(数字):由list_posts或create_draft_post返回的 IDsend(布尔值,可选):是否通过电子邮件向订阅者发送文章。默认为false,与 Substack API 自身的默认值不同——无论如何,文章都会在网站上发布,但电子邮件无法撤回,因此必须明确请求发送。
返回:{status, draft_id, post_id, title, slug, canonical_url, emailed, email_sent_at}。
emailed 表示请求发送的意图;email_sent_at 是服务自身记录的实际发送情况。
发送邮件的意图会在发布前写入草稿的 should_send_email 字段,并同时传入发布调用。仪表板会通过该字段保存发送决定,且其默认值为 true,因此如果只设置其中一处,端点读取草稿而非请求正文时,就可能会向整个订阅者列表发送邮件。
没有取消发布工具:发布后无法通过此服务撤销。
delete_draft - 删除尚未发布的草稿
输入:
draft_id(数字):由list_posts或create_draft_post返回的 ID
返回:{status, draft_id, draft_title}。
Substack 使用同一个端点删除草稿和已发布的文章,因此此工具会先读取目标,若文章已发布则拒绝操作——删除已上线的文章不可撤销,需通过仪表板操作。
get_publication - 查看出版物设置
输入:
full(布尔值,可选):返回全部 111 个字段(约 24 KB),而非精简视图。 默认为false。 返回:默认返回投影结果——名称、子域名、自定义域名、首页宣传文案、版权信息、发件人 名称、徽标、套餐、付款状态和社区/播客标记——以及说明已省略字段数量的_meta。完整载荷主要 包含通知开关,以及欢迎邮件、服务条款页面和隐私页面的原始 HTML。
get_user_profile - 读取此会话对应的账户
输入:
full(布尔值,可选):包含完整的subscriptions数组。默认为false。
返回:{id, name, handle, bio, photo_url, publications, primary_publication_id, subscription_count}。publications 列出此会话拥有某种角色的所有出版物,可据此发现
SUBSTACK_PUBLICATION_URL 并非唯一可指向的出版物。
list_publication_tags - 列出你的出版物中定义的标签
输入:
include_hidden(布尔值,可选):包含导航中未显示的标签。默认为true。
返回:{total, returned, tags},每个标签为 {id, name, slug, hidden}。标签 ID 是 UUID,而非
整数——与此 API 中的其他所有 ID 不同。
get_post_tags - 列出一篇文章的标签
输入:
post_id(数字):来自list_posts的 ID。草稿也适用。
返回:{post_id, count, tags},每个标签为 {post_tag_id, name, slug, hidden, association_id}。
底层端点只返回 UUID,因此此工具会根据出版物的标签列表解析名称。get_draft 和
list_posts 都不包含标签,所以这是唯一能读取标签的方式。
add_tag_to_post - 为文章添加标签
输入:
post_id(数字):来自list_posts的 ID。草稿也适用。tag_name(字符串):与现有标签进行不区分大小写的匹配create_if_missing(布尔值,可选):没有匹配名称时创建标签。默认为true; 设为false时,拼写错误会被报告,而不会被转成新标签。
返回:{status, post_id, tag, tag_created, association_id},其中 status 为 tagged 或
already_tagged——对文章已有的标签重复添加会使上游直接返回 400,因此此工具会先检查。
此工具接受名称而非 ID,因为 ID 是 UUID,调用方通常不可能持有这些 ID。
get_post_comments - 读取你的一篇文章下的评论
输入:
post_id(数字):来自list_posts的 IDlimit(数字,可选):1–100,默认为 50
返回:{post_id, returned, automod_hidden_count, comments}。每条评论包含作者、
纯文本正文、表情回应数和回复数,以及其在线程中的位置(parent_comment_id、
depth)。被 Substack 自动审核隐藏的评论会计入数量,但不会合并到评论列表中——它们会在上游的
单独数组中返回;若静默丢弃,就会把“已拦截”变成“无人评论”。
comment_on_post - 在你的一篇文章下发表评论
输入:
post_id(数字):来自list_posts的 IDbody(字符串):纯文本;Substack 会在服务器端转换
返回:{status, post_id, comment}。
评论会以你的名义发布。请求发出前,完整文本会以 info 级别记录,因为日志是所说内容的唯一
记录。此服务不提供删除功能,但可以在 Substack 界面中删除评论——与 restack 不同,评论有自己的 ID。
以下七个工具读取的是 substack.com,而非你的出版物。它们关注的是作为
读者的账户——订阅了什么、收件箱和动态中有什么——这与上方出版方接口使用的主机和 ID 空间
不同。
list_subscriptions - 列出此账户订阅的内容
**输入**: - `limit`(数字,可选):1–500,默认值为 100 - `active_only`(布尔值,可选):排除已暂停和已过期的订阅。默认值为 `true`。返回值:{returned, pages_fetched, subscriptions},每个订阅都包含方案、membership_state、
is_founding、is_favorite,以及是否已关闭邮件通知。最多在内部请求 20 页;如果达到此上限而停止,则会返回
truncated: true。
不要与 list_subscribers 混淆,后者列出的是订阅了你的内容的人。
list_reader_posts - 读者收件箱
输入:
limit(数字,可选):1–100,默认值为 20after(字符串,可选):上次响应中的next_after。这是一个时间戳,不是不透明游标——此端点自己的cursor字段始终为 null。
返回值:{returned, more, next_after, posts},每篇文章都包含阅读状态摘要(is_read、read_progress、is_saved)。收件箱会发送每篇文章的完整正文;此处会将其丢弃,因此请使用 get_reader_post 阅读单篇文章。
get_reader_post - 完整阅读任意文章
输入:
post_id(数字):来自list_reader_posts或get_reader_feedinclude_body(布尔值,可选):默认值为true
返回值:文章的元数据及 body_html。body_truncated: true 表示正文受付费墙限制,本次会话无法解除限制——preview_text 仍包含预览内容。
正文保持为 HTML:转换正文需要新增依赖,或对标记进行正则处理,而正则 HTML 转换器会在不报错的情况下破坏嵌套列表和嵌入内容。
get_reader_feed - Notes 信息流
输入:
tab(字符串,可选):标签页 id——for-you(默认值)或subscribed。不能使用显示名称:显示名称会本地化。limit(数字,可选):1–50,默认值为 20cursor(字符串,可选):上次响应中的next_cursorinclude_tabs(布尔值,可选):同时返回可用的标签页 id
返回值:{tab, returned, next_cursor, items}。每个项目都是一条 note 或一篇 post。
non_content_items_skipped 统计 Substack 混入数组的“关注推荐”区块数量,这些区块完全不包含内容。
get_profile_feed - 某个账号发布的内容
输入:
user_id(数字,可选):默认为SUBSTACK_USER_ID——你自己的账号type(all|notes|posts,可选):默认值为alllimit(数字,可选):1–50,默认值为 20cursor(字符串,可选)
返回值:{user_id, type, returned, next_cursor, items}。启用筛选时,read_from_profile
会报告该页面实际包含的条目数——否则,“从 20 个已读取条目中读到 3 条 notes”会让人误以为“这个账号只写了 3 条 notes”。
get_comment_thread - 阅读一条 Note 及其回复
输入:
comment_id(数字):不带 Substack 在 URL 中使用的c-前缀include_replies(布尔值,可选):默认值为true
返回值:{comment, branch_count, replies_returned, more_branches, next_cursor, branches}。每个分支包含一条直接回复及其后代回复,并解析出 parent_comment_id 和 depth。
restack_item - Restack 一条 Note
输入:
comment_id(数字):要 Restack 的 Note,来自get_reader_feed或get_profile_feedtab_id(字符串,可选):默认值为for-you
返回值:{status, comment_id, restack_id, note}。
此操作是公开的,并会显示在你的个人资料中,且无法在此处撤销:Restack 没有独立的 id——它会通过 context: comment_restack 显示原始 Note——因此此 MCP 服务无法删除任何内容。请在 Substack 界面中将其移除。
get_publication_stats - 查看关键统计数据
输入:无。
返回:订阅者总数和近期订阅者数、邮件和应用订阅者数、ARR、网站浏览量及近 30 天邮件打开率;Substack 提供变化数据的项目也会一并返回。如果某个底层端点调用失败,其余数据仍会返回,失败的端点会列在 errors 下。
如需更深入的数据,请使用 get_analytics。
get_post_stats - 按 43 项指标为帖子排名
哪篇帖子真正扩大了订阅列表,哪篇收益最高,哪篇导致你流失了订阅者?查看仪表板的“帖子”选项卡,支持排序和分页。
输入:
order_by(字符串,可选):43 项指标中的任意一项,默认为post_dateorder_direction(asc|desc,可选):默认为desclimit(数字,可选):1–100,默认为 25offset(数字,可选):用于翻阅存档
值得关注的指标:
| 组别 | 字段 |
|---|---|
| 转化 | signups subscribes founding_subscribes annual_subscribes monthly_subscribes free_trials free_to_paid_upgrades signups_within_1_day estimated_value |
| 流失 | unsubscribes |
| 阅读 | opens open_rate clicks click_through_rate views subscribers_finished_post |
| 社交 | likes shares restacks engagement_rate unique_engagements |
| 投递 | queued sent delivered dropped |
| 视频 / 播客 | video_views video_minutes_watched downloads downloads_day30 … |
返回:{total, returned, limit, offset, order_by, order_direction, posts}。total 表示整个存档的总数,而非当前页的数量;结果会附带 order_by 和 order_direction,确保查看排名时始终知道排名依据。
两点注意事项,均已验证:
- 没有日期筛选。 此端点会忽略
from_date/to_date,total不会改变,因此架构中不提供这些参数。请改用排序和分页缩小范围。- 按比率(
open_rate、engagement_rate、click_through_rate)降序排名时,没有数据的帖子会排在最前面,因为null的排序位置在数字之前。工具不会将其过滤掉,以免悄然回答了另一个问题。
order_by被有意定义为枚举:对于无法识别的字段,API 仍会返回200和任意顺序,因此拼写错误可能产生看似权威的排名。
get_analytics - 查看 16 种刊物级报告之一
仪表板“统计数据”选项卡背后的全部数据,都由这一个工具提供;它使用 report 枚举,而不是拆成十七个几乎相同的工具。
输入:
report(字符串):要查看的报告——见下表from_date、to_date(字符串,可选):YYYY-MM-DD。仅供涵盖一段时间的报告使用;此类报告默认查看最近 30 天limit(数字,可选):1–100,仅供audience_overlap和subscriber_notes使用
返回:{report, params, ignored_params, data}。params 是实际发送的参数,包含默认值——同一份报告在不同时间范围下的结果可能大不相同,因此不结合时间范围,数字就意义不大。ignored_params 会列出你传入但所选报告不接受的参数,而不是悄悄丢弃它们。
有两个相邻端点是刻意未公开的:
audience_insights/location(订阅者地图)和visitor_sources,即使是 Substack 自己的仪表板调用它们也会返回400,因此问题出在上游,而不是调用方式有误。
🏗 从源代码运行
如果你想修改服务本身,请使用此方式。无需构建步骤——源代码是纯 ESM,可直接运行。
Node.js
git clone https://github.com/marcomoauro/substack-mcp.git
cd substack-mcp
npm ci
然后添加到你的 MCP 配置中:
{
"mcpServers": {
"substack-api": {
"command": "node",
"args": ["<FULL_PATH_TO_PROJECT>/src/index.js"],
"env": {
"SUBSTACK_PUBLICATION_URL": "<YOUR_PUBLICATION_URL>",
"SUBSTACK_SESSION_TOKEN": "<YOUR_SESSION_TOKEN>",
"SUBSTACK_USER_ID": "<YOUR_USER_ID>"
}
}
}
}
Docker(从源代码构建)
git clone https://github.com/marcomoauro/substack-mcp.git
cd substack-mcp
docker build -t substack-mcp .
然后添加到你的 MCP 配置中:
{
"mcpServers": {
"substack-api": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "SUBSTACK_PUBLICATION_URL",
"-e", "SUBSTACK_SESSION_TOKEN",
"-e", "SUBSTACK_USER_ID",
"substack-mcp"
],
"env": {
"SUBSTACK_PUBLICATION_URL": "<YOUR_PUBLICATION_URL>",
"SUBSTACK_SESSION_TOKEN": "<YOUR_SESSION_TOKEN>",
"SUBSTACK_USER_ID": "<YOUR_USER_ID>"
}
}
}
}
🪵 日志
服务会将执行情况按每行一个 JSON 对象的形式记录到 stderr——MCP 客户端会将其收集到自己的日志文件中(在 macOS 上,Claude Desktop 会将日志写入
~/Library/Logs/Claude/mcp-server-substack-api.log)。当调用结果不符合预期时,这是查看 LLM 实际发送内容最快捷的方式:
{"ts":"2026-08-07T10:12:03.114Z","level":"info","msg":"tool.call.start","tool":"create_draft_post","args":{"title":"My title","subtitle":"My subtitle","body":"…"}}
{"ts":"2026-08-07T10:12:03.402Z","level":"info","msg":"substack.response","status":200,"duration_ms":287}
{"ts":"2026-08-07T10:12:03.403Z","level":"info","msg":"create_draft_post.created","draft_id":167712345}
在凭据旁设置可选的 SUBSTACK_MCP_LOG_LEVEL 环境变量,即可更改日志记录的详细程度:
| 值 | 记录内容 |
|---|---|
silent | 不记录任何内容 |
error | 仅记录失败的调用 |
warn | 除上述内容外,还记录客户端收到的每个错误响应——包括运行前因参数错误而被拒绝的调用 |
info (默认) | 除上述内容外,还记录每次工具调用、请求和响应 |
debug | 除上述内容外,还记录完整负载和每条 JSON-RPC 消息 |
任何日志级别都不会记录你的会话令牌。
💻 热门 MCP 客户端
如需查看 MCP 客户端及其功能支持的完整列表,请访问 MCP 官方客户端页面。
| 客户端 | 描述 |
|---|---|
| Claude Desktop | Claude AI 的桌面应用 |
| Cursor | AI 优先的代码编辑器 |
| Cline for VS Code | 提供 AI 辅助功能的 VS Code 扩展 |
| GitHub Copilot MCP | 用于集成 GitHub Copilot MCP 的 VS Code 扩展 |
| Windsurf | AI 驱动的代码编辑器和开发环境 |
🆘 支持
- 如果 MCP 服务遇到问题:在 GitHub 上提交问题
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。