Substack MCP Server について
Substack MCP Server は、LLM クライアントから Substack の発行物を操作できる MCP サーバーです。下書き、記事、購読者、分析、リーダーフィード、コメント、画像を管理できます。Substack には公開 API がないため、認証済みのウェブセッションを利用し、セッショントークンを秘匿して扱う必要があります。
主な機能
- 01出版と購読者管理をカバーする 27 のツール
- 02複数条件のフィルターとページングに対応した購読者一覧
- 03エンゲージメント指標を含む購読者のエクスポート
- 04下書き本文への構造化ドキュメントの書き込み
- 05URL またはローカルファイルからの Substack への画像アップロード
活用シーン
- 01LLM クライアントから下書き記事を作成・編集する
- 02セグメントで購読者を絞り込み、エンゲージメントデータを書き出す
- 03画像をアップロードして記事に挿入する
MCP 対応
本プロジェクトは、Substack の発行、購読者、分析、画像に関するツールを MCP クライアントに公開する MCP サーバーです。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの 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 用の Model Context Protocol(MCP)サーバーです。LLM クライアントから Substack の API を利用し、投稿の作成や下書きの管理などを自動化できます。
投稿の作成と公開、購読者や分析データの管理、読者フィードの閲覧、タグやコメントの管理、画像のアップロードができます。1 つの MCP サーバーで 27 個のツールを提供します。
重要
Substack は、これらの操作向けの公開 API を提供していません。このサーバーは、認証済みのウェブセッションを使用します。セッショントークンはパスワードと同様に扱ってください。ローカルに保管し、コミットせず、バグ報告にトークンや Cookie ヘッダー全体を含めないでください。
クイックスタート
最も簡単なインストール方法は、Node.js 22 以降と npx を使用することです。
1. Substack の認証情報を取得する
ブラウザーで Substack にサインインし、出版物のダッシュボードを開きます。次の 3 つの値が必要です。
- 出版物の URL — 出版物の完全なベース URL。例:
https://your-publication.substack.com。 - セッショントークン — ブラウザーの開発者ツールを開き、Network を選択して Fetch/XHR で絞り込み、ダッシュボードを再読み込みします。出版物への認証済みリクエストを開きます。Request Headers で
Cookieヘッダーを見つけ、substack.sidまたはconnect.sidという名前のセッションクッキーを探します。クッキー名やヘッダーの残りの部分を含めず、値だけをコピーします。両方の名前が異なる値で表示される場合は、それぞれを個別に試してください。ステップ 3 の読み取り専用の方法で、ローカル環境で確認できます。どちらの値も課題に貼り付けないでください。 - ユーザー ID — 同じ Network パネルで、成功した
publication_userリクエストを検索します。その JSON レスポンスのuserオブジェクト内にある数値のidをコピーします。
ブラウザーの UI が異なる場合は、図解付きの認証情報ガイドに同じリクエストが掲載されています。認証が機能しなくなった場合は、再度サインインしてこれらの手順を繰り返し、最新のトークンを取得してください。
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"
}
}
}
}
3 つのサンプル値を置き換えて設定を保存し、MCP クライアントを再起動します。クライアントで異なる設定形式を使用している場合は、クライアントのドキュメントを参照してください。
3. 接続を確認する
クライアントに次のように尋ねてください。
Substack の最新の下書きを 5 件表示してください。
クライアントは status: "drafts" を指定して list_posts を呼び出すはずです。失敗した場合は、課題を報告する前に、クライアントの MCP ログと以下のログのセクションを確認してください。
Docker クイックスタート
Node.js の代わりに公開済みの Docker イメージを使用するには、次のサーバー設定を追加してください。
{
"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(文字列):投稿の本文。プレーンテキストは 1 行につき 1 つの段落になります。Markdown は解釈されないため、## Headingはそのまま文字列として扱われます。Substack ドキュメントの JSON 文字列も使用できます。この場合、set_post_bodyが公開するものと同じスキーマに照らして検証されます。そのため、認識されないノード名は、投稿が意図せず改変されるのではなく、エラーになります。
戻り値:{draft_id, is_published}。下書きを読み返すには、draft_id を get_draft に渡してください。
見出し、リスト、リンク、コード、画像、ペイウォールなど、構造化された内容を扱う場合は、下書きを作成した後に set_post_body を使用してください。スキーマはそこで公開されているため、呼び出し元のモデルはノードの種類を推測せず、語彙を確認できます。
list_subscribers - 購読者の一覧表示と絞り込み
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 を示し、処理をブロックしません。
注意点が 2 つあります。どちらも実際の 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 - 下書き1件の全文を読み込む
入力:
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に加え、get_draftで読み込んだドキュメントを書き戻せるよう、digestPostEmbed、substack_mentions、directMessageは変更せずに渡されます。マーク:strong、em、code、strikethrough、link。
戻り値:{draft_id, nodes}。nodesは種類ごとに保存されたノードの数を示すため、ペイウォールを指定した呼び出し元は、実際に1つあることを確認できます。送信されていないノードについては、検証で報告できません。
知っておくべきことが3つあります:
- 画像は事前にSubstack上でホストされている必要があります。 外部URLを指定した
image2.srcは保存されますが、表示されません。upload_imageを使ってホストし直し、使用可能なsrcを取得してください。 - 1つのドキュメントに含められる
paywallは最大1つです。 Substackは2つでも受け付けて両方を表示しますが、どちらが投稿を区切るのか不定になるため、このツールは2つ目を拒否します。 ordered_listはattrs.startではなくattrs.orderに基づいて番号を付けます。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しか再取得しないため、画像は引き渡すのではなく、ここでエンコードされます。
入力 — 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以下でなければなりません。
ローカルファイルは、信頼できる Content-Type ヘッダーがないため、別の方法で保護されます。パスは絶対パスでなければなりません。相対パスは呼び出し元のクライアントではなく、このサーバーの作業ディレクトリを基準に解決されるためです。また、拡張子ではなくファイルのマジックバイトから種類を判定するため、画像拡張子が付いた画像以外のファイルは Substack に渡る前にここで検出されます。PNG、JPEG、GIF、WebP は使用できますが、HEIC と SVG は使用できません。同じ 10 MB の上限が適用され、ファイルを読み込む前にサイズが確認されます。path は指定された絶対パスのファイルを読み込みます。この点が環境にとって問題になる場合は、ファイルシステムを信頼できないクライアントにこのサーバーを公開しないでください。
update_draft - 下書きのタイトル、サブタイトル、または投稿設定を変更します
更新は部分的です。渡したフィールドだけが変更され、本文はそのまま残ります。
入力:
draft_id(number):list_postsまたはcreate_draft_postが返す IDdraft_title(string、任意)draft_subtitle(string、任意)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(string、任意):ソーシャルプレビュー画像。substack-post-media.s3.amazonaws.comまたはsubstackcdn.com上にある URL はそのまま使用されます。それ以外はダウンロードされ、upload_imageと同じ保護措置のもとで、まず Substack に再ホストされますsocial_title(string、任意):投稿を別の場所で共有するときに使われるタイトルdescription(string、任意):ソーシャルプレビューの説明文。サブタイトルではありませんsearch_engine_title(string、任意)search_engine_description(string、任意)slug(string、任意):投稿の 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(number):list_postsまたはcreate_draft_postが返す IDsend(boolean、任意):投稿を購読者にメールで送信します。デフォルトは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(number):list_postsまたはcreate_draft_postが返す ID
戻り値:{status, draft_id, draft_title}。
Substack は下書きと公開済み投稿を同じエンドポイントで削除するため、このツールは対象を先に確認し、公開済みの場合は拒否します。公開中の投稿の削除は取り消せないため、ダッシュボードで行ってください。
get_publication - パブリケーションの設定を読み込みます
入力:
full(boolean、任意):項目を絞った結果ではなく、111 個すべてのフィールド(約 24 KB)を返します。 デフォルトはfalseです。 戻り値:デフォルトでは、name、subdomain、custom domain、hero text、copyright、sender name、logo、plans、payment state、community/podcast フラグの投影に加え、除外されたフィールド数を示す_metaを返します。完全なペイロードの大部分は、通知の切り替え設定とウェルカムメール、利用規約ページ、プライバシーページの生の HTML です。
get_user_profile - セッションの背後にあるアカウントを読み取る
入力:
full(boolean, optional):完全なsubscriptions配列を含めます。デフォルトはfalseです。
戻り値:{id, name, handle, bio, photo_url, publications, primary_publication_id, subscription_count}。publications には、セッションが何らかのロールを持つすべての publication が含まれます。これにより、SUBSTACK_PUBLICATION_URL が接続先になり得る唯一の publication ではないことを確認できます。
list_publication_tags - publication に定義されているタグを一覧表示する
入力:
include_hidden(boolean, optional):ナビゲーションに表示されないタグも含めます。デフォルトはtrueです。
戻り値:{total, returned, tags}。各タグは {id, name, slug, hidden} です。タグ ID は整数ではなく UUID です。この API のほかのすべての ID とは異なります。
get_post_tags - 1 件の投稿のタグを一覧表示する
入力:
post_id(number):list_postsの ID。下書きでも機能します。
戻り値:{post_id, count, tags}。各タグは {post_tag_id, name, slug, hidden, association_id} です。
基盤となるエンドポイントが返すのは UUID のみなので、publication のタグ一覧と照合して名前を解決します。get_draft も list_posts もタグを含まないため、タグを読み取る方法はこれだけです。
add_tag_to_post - 投稿にタグを付ける
入力:
post_id(number):list_postsの ID。下書きでも機能します。tag_name(string):既存のタグと大文字・小文字を区別せずに照合します。create_if_missing(boolean, optional):名前が一致するタグがない場合にタグを作成します。デフォルトはtrueです。入力ミスを新しいタグにせず、エラーとして報告するにはfalseに設定します。
戻り値:{status, post_id, tag, tag_created, association_id}。status は tagged または
already_tagged です。投稿にすでに付いているタグを再追加すると、上流では単に 400 が返されるため、事前に確認します。
ID は UUID で、呼び出し側が合理的に保持できるものではないため、ID ではなく名前を指定します。
get_post_comments - 自分の投稿に付いたコメントを読み取る
入力:
post_id(number):list_postsの IDlimit(number, optional):1~100。デフォルトは 50 です。
戻り値:{post_id, returned, automod_hidden_count, comments}。各コメントには、投稿者、プレーンテキストの本文、リアクション数と返信数、スレッド内の位置(parent_comment_id、depth)が含まれます。Substack の自動モデレーションによって保留されたコメントは、数としてカウントされますが、統合はされません。上流では別の配列で届くため、黙って除外すると「保留中」が「コメントなし」になってしまいます。
comment_on_post - 自分の投稿にコメントする
入力:
post_id(number):list_postsの IDbody(string):プレーンテキスト。Substack がサーバー側で変換します。
戻り値:{status, post_id, comment}。
コメントはあなたの名前で公開されます。何が書き込まれたかを記録する唯一の手段がログであるため、リクエスト前に全文を info に記録します。このサーバーでは削除機能を提供していませんが、Substack UI からコメントを削除できます。リスタックとは異なり、コメントにはそれ自体の ID があります。
以下の 7 つのツールは、publication ではなく substack.com を読み取ります。アカウントの読者としての情報、つまり何を購読しているか、受信トレイやフィードに何があるかを扱います。これは、上記の publisher 向けインターフェイスとはホストも 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_feedから取得include_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件」という表示が、「このアカウントが書いたノートは3件」と誤解される可能性があります。
get_comment_thread - ノートとその返信を読む
入力:
comment_id(数値):SubstackのURLで使われるc-プレフィックスを除いたものinclude_replies(ブール値、任意):デフォルトはtrue
戻り値:{comment, branch_count, replies_returned, more_branches, next_cursor, branches}。各ブランチには直接の返信とその子孫が含まれ、parent_comment_idとdepthが解決されています。
restack_item - ノートをリスタックする
入力:
comment_id(数値):リスタックするノート。get_reader_feedまたはget_profile_feedから取得tab_id(文字列、任意):デフォルトはfor-you
戻り値:{status, comment_id, restack_id, note}。
これは公開され、あなたのプロフィールに表示されます。また、ここから元に戻すことはできません。リスタック自体のIDはなく、元のノートがcontext: comment_restackとともに表示されるため、このサーバーで削除する対象がありません。SubstackのUIから削除してください。
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も返されるため、どの条件で作成された順位なのかが分からないまま順位を読むことはありません。
注意点が2つあります。どちらも検証済みです。
- **日付フィルターはありません。**このエンドポイントでは
from_date/to_dateは無視され、totalも変わらないため、スキーマにはこれらを用意していません。代わりに、並べ替えとページ分割で範囲を絞り込んでください。- 率(
open_rate、engagement_rate、click_through_rate)で降順に並べ替えると、データのない投稿が先頭に表示されます。nullは数値より前に並ぶためです。別の質問に暗黙のうちに答えることにならないよう、ツールではこれらを除外しません。
order_byを列挙型にしているのは意図的です。APIは認識できないフィールドに対しても200を返し、任意の順序で結果を返すため、タイプミスがあると、信頼できそうに見える順位が生成されてしまいます。
get_analytics - Publicationレベルの16種類のレポートから1つを確認する
ダッシュボードの「統計」タブにあるすべての内容を、互いによく似た17個のツールではなく、report列挙型を使う1つのツールで確認できます。
入力:
report(文字列):確認するレポート。下の表を参照してくださいfrom_date、to_date(文字列、任意):YYYY-MM-DD。期間を対象とするレポートでのみ使用され、デフォルトでは過去30日間が対象ですlimit(数値、任意):1~100。audience_overlapとsubscriber_notesでのみ使用されます | レポート | 内容 | |---|---| |retention| コホート別の継続率。各登録コホートが数か月後も購読を続けている割合です | |retention_summary| 1、6、12 か月時点の主要な継続率 | |unsubscribes/unsubscribes_timeseries| 解約数と、解約時に記された理由 | |growth_sources| 新規購読者の流入元を順位付けして表示します | |growth_events| 指定期間内に発生した個々の成長イベント | |referrals_leaderboard/referrals_summary| 紹介数の多いユーザー、送信・承認・成約したギフト | |audience_overlap| オーディエンスが重複する他の Substack。コラボレーション候補の一覧です | |audience_locations| 購読者がいる国と米国の州の数 | |subscriber_notes| 購読者が最近書いた Notes | |paid_subscriber_growth| 有料購読者の成長率、新規購読、期限切れ | |subscribers_timeseries、followers_timeseries、arr_timeseries| 時系列での購読者数、フォロワー数、収益 | |network_attribution| Substack ネットワーク経由で登録した購読者の割合 |
戻り値:{report, params, ignored_params, data}。params には、デフォルト値を含め、実際に送信されたパラメーターが入ります。同じレポートでも対象期間が異なると結果は大きく変わるため、期間の情報なしでは数値の意味を把握しにくくなります。ignored_params には、指定したレポートが受け付けないパラメーターが記載され、黙って破棄されることはありません。
隣接するエンドポイントのうち、意図的に公開していないものが2つあります。
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>"
}
}
}
}
🪵 ログ
サーバーは、実行内容を1行につき1つの 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 サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。