Skip to main content
Glama
substack-publisher-mcp

substack-publisher-mcp

by dkships

substack-publisher-mcp

MCP server for Substack's official Publisher API

License: MIT Node.js MCP

Note: This is an unofficial, community-developed tool and is not affiliated with, endorsed by, or supported by Substack, Inc.

An MCP server for Substack's official Publisher API. Search and read posts, pull post analytics and subscriber counts, and look up subscribers from Claude, Cursor, or any MCP client. All tools are read-only.

Demo of substack-publisher-mcp in Claude Code

Why this server?

substack-publisher-mcp

Other Substack MCP servers

API

Official Publisher API

Unofficial internal API

Auth

API key (stable)

Browser cookies (fragile)

Stability

Official, documented API

Breaks when Substack changes internals

Multi-publication

Built-in support

Not available

Related MCP server: substack-mcp

Prerequisites

  • Node.js 22+. Check with node --version; install from nodejs.org if missing.

  • Substack Publisher API key. Generate one from your publication's Substack dashboard. If you don't see a Publisher API option there, it may not be enabled for your publication yet; see the Publisher API docs for availability.

Quick Start

1. Install

git clone https://github.com/dkships/substack-publisher-mcp.git
cd substack-publisher-mcp
npm install && npm run build

2. Configure your MCP client

Add to your client's MCP config file (create the file if it doesn't exist):

Client

Config file

Claude Desktop (macOS)

~/Library/Application Support/Claude/claude_desktop_config.json

Claude Desktop (Windows)

%APPDATA%\Claude\claude_desktop_config.json

Claude Code

.mcp.json in your project directory

Cursor

.cursor/mcp.json

{
  "mcpServers": {
    "substack": {
      "command": "node",
      "args": ["/path/to/substack-publisher-mcp/dist/index.js"],
      "env": {
        "SUBSTACK_API_KEY": "your-api-key-here"
      }
    }
  }
}

Claude Code users: Add "type": "stdio" to the server config.

Restart your MCP client after editing the config — servers load at startup.

3. Start using it

Ask Claude (or your MCP client):

  • "Which Substack publications do I have configured?"

  • "Show me my posts from the last month"

  • "Find my posts about pricing"

  • "Pull up my post with the slug my-latest-post"

  • "How many opens and clicks did my latest post get?"

  • "What are my subscriber counts for the last 30 days?"

  • "Look up subscriber jane@example.com"

Installing through an AI agent or registry? See llms-install.md for a condensed, machine-readable setup guide.

Tools

Tool

Description

Key Parameters

list_publications

List configured publications

None

list_posts

List published posts

startDate, endDate, sortBy, type, maxResults, next

search_posts

Full-text search across published posts

query (required), maxResults (1-100)

get_post

Get a post and its body by URL slug

urlSlug (required), bodyFormat

get_post_stats

Get engagement stats for a post

urlSlug (required)

get_subscriber_counts

Get daily subscriber counts by type

startDate, endDate

get_subscriber

Look up a subscriber by email

email (required)

All tools except list_publications accept an optional publication parameter when multiple publications are configured.

get_post returns the post body as Markdown by default. Substack sends it as a JSON-encoded ProseMirror document, typically about twice the size. Pass bodyFormat: "prosemirror" for the raw document or "none" for metadata only.

Date filters take YYYY-MM-DD. In list_posts, endDate is exclusive; in get_subscriber_counts, it is inclusive.

Example responses

[
  {
    "date": "2025-01-15",
    "total_email_subscribers": 25000,
    "paid_subscribers": 500,
    "free_trial_subscribers": 10,
    "comp_subscribers": 50,
    "gift_subscribers": 15,
    "lifetime_subscribers": 0,
    "founding_subscribers": 25
  }
]
{
  "clicks": 320,
  "opens": 5400,
  "post_id": 12345678,
  "recipients": 10000,
  "views": 6100,
  "new_free_subscriptions": 80,
  "new_paid_subscriptions": 5,
  "estimated_revenue_increase": 400
}
{
  "posts": [
    {
      "post_id": 12345678,
      "title": "My Latest Post",
      "audience": "only_paid",
      "subtitle": "A deep dive into the topic",
      "postDate": "2025-01-15T12:00:00.000Z",
      "urlSlug": "my-latest-post",
      "coverImage": "https://substackcdn.com/image/..."
    }
  ],
  "next": "abc123cursor"
}

next is null on the last page.

Multiple publications

If you manage multiple Substack publications, configure a separate API key for each using the SUBSTACK_API_KEY_<NAME> pattern:

{
  "mcpServers": {
    "substack": {
      "command": "node",
      "args": ["/path/to/substack-publisher-mcp/dist/index.js"],
      "env": {
        "SUBSTACK_API_KEY_MAIN": "your-main-blog-key",
        "SUBSTACK_API_KEY_TECH": "your-tech-newsletter-key",
        "SUBSTACK_API_KEY_COMPANY": "your-company-updates-key"
      }
    }
  }
}

Then specify which publication to query:

"Show me subscriber counts for main" "List recent posts from the tech publication"

Use list_publications to see all configured publication names.

Troubleshooting

Issue

Solution

Unauthorized error

Verify your API key is correct. The key goes directly in the authorization header with no Bearer prefix.

... duplicates publication ... or ... ignoring it on startup

Two env vars map to the same publication name (names are case-insensitive, and SUBSTACK_API_KEY is default), or a key is malformed. Rename or remove the extra variable.

Server won't start

Make sure you ran npm run build after cloning. The server runs from dist/, not src/.

No API keys configured

Set SUBSTACK_API_KEY or SUBSTACK_API_KEY_<NAME> in your MCP client config.

Server doesn't appear in your client

Check the config file is valid JSON (no trailing commas), then restart the client.

command not found / spawn node ENOENT

Node.js isn't installed or isn't on your PATH. Check node --version.

Still stuck

Check your client's MCP logs. Claude Desktop on macOS: ~/Library/Logs/Claude/mcp*.log.

API Reference

This server wraps the Substack Publisher API. See Substack's documentation for details on available data and rate limits.

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT License. See LICENSE for details.


Substack is a trademark of Substack, Inc. This project is not affiliated with Substack, Inc. Use of the Substack name is for descriptive purposes only.

Available Tools

7 tools
get_postGet PostA
Read-onlyIdempotent

Get a post by its URL slug, including its full body. Returns post_id, title, subtitle, audience, postDate, urlSlug, coverImage, authors, and body (Markdown by default).

Inputs: urlSlug (required), bodyFormat, publication.

get_post_statsGet Post StatsA
Read-onlyIdempotent

Get engagement statistics for a post by its URL slug: recipients, opens, clicks, views, new free and paid subscriptions, and estimated revenue increase, plus podcast and video metrics when applicable.

Inputs: urlSlug (required), publication.

get_subscriberGet SubscriberA
Read-onlyIdempotent

Look up a subscriber by email address. Returns membershipType (paying, comp, free, gift), expiry, firstPaymentAt, nextChargeDate, subscription plan details, hasBounce, and social handles. Errors with 404 if the address is not a subscriber.

Inputs: email (required), publication.

get_subscriber_countsGet Subscriber CountsA
Read-onlyIdempotent

Get daily subscriber counts, newest first. Each row has date, total_email_subscribers (free + paid), paid_subscribers, and free_trial, comp, gift, lifetime, and founding counts. Free subscribers = total minus paid. Without dates, returns roughly the last year.

Inputs: endDate, startDate, publication.

list_postsList PostsA
Read-onlyIdempotent

List posts published by a Substack publication. Returns { posts, next }: each post has post_id, title, subtitle, audience, postDate, urlSlug, and coverImage. next is a cursor for the following page, or null on the last page. Use urlSlug with get_post or get_post_stats.

Inputs: next, type, sortBy, endDate, +3 more.

list_publicationsList PublicationsA
Read-onlyIdempotent

List all configured Substack publications and their names. Use these names as the 'publication' parameter in other tools.

Inputs: no named parameters.

search_postsSearch PostsA
Read-onlyIdempotent

Full-text search across a publication's published posts, ordered by relevance. Returns { posts } with the same fields as list_posts (no pagination).

Inputs: query (required), maxResults, publication.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 7 tools

    Disambiguation5/5

    Each tool has a clear, distinct purpose: search vs. list posts, get post vs. get stats, and subscriber counts vs. individual lookup. No tools overlap ambiguously.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern (e.g., search_posts, list_publications, get_post) with uniform snake_case. No deviations.

    Tool Count5/5

    Seven tools is well-scoped for a read-only Substack publisher API, covering post retrieval and subscriber metrics without unnecessary bloat.

    Completeness4/5

    The surface covers core read operations for posts and subscribers, but lacks bulk subscriber listing or publication-level analytics, which are minor gaps given the server's likely purpose.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers