Skip to main content
Glama
dkships

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.

The first MCP server for Substack's official Publisher API. Query post analytics, subscriber counts, and publication data directly from Claude, Cursor, or any MCP client.

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 18+. 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"

  • "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

get_post

Get a specific post by URL slug

urlSlug (required)

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.

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": [
    {
      "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"
}

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.

Missing environment variables warning

Only configure env vars for publications you have keys for. Remove the rest.

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

6 tools
get_postGet PostA
Read-onlyIdempotent

Get detailed information about a specific post by its URL slug. Returns full post metadata including title, subtitle, audience, publish date, and content details.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlSlugYesThe URL slug of the post (from list_posts results or the post URL).
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide readOnlyHint, openWorldHint, idempotentHint. Description adds that it returns full post metadata but no additional behavioral traits like auth needs or rate limits. Adequate but not enhanced beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences, front-loaded with purpose. No filler, every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers main use case well. Missing details about when publication parameter is required versus optional, and no error scenarios. With no output schema, could be more complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers 100% of parameters with descriptions. Description adds 'by its URL slug' and mentions slug origin, but no new meaning beyond schema. Baseline 3 justified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Get detailed information about a specific post by its URL slug', specifying verb and resource. Distinguishes from siblings like 'get_post_stats' (stats) and 'list_posts' (multiple posts).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Mentions using URL slug from list_posts or URL, providing context. Does not explicitly state when not to use or compare with alternatives like get_post_stats, lacking exclusion guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_post_statsGet Post StatsA
Read-onlyIdempotent

Get engagement statistics for a specific post by its URL slug. Returns metrics like opens, clicks, and other engagement data.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlSlugYesThe URL slug of the post (from list_posts results or the post URL).
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint as true. The description adds that it returns metrics like 'opens, clicks', but this is minimal extra value beyond annotations. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with verb and resource, no wasted words. Every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (2 params, no output schema, rich annotations), the description is largely complete. It could optionally mention that the slug comes from list_posts, but this is implied by the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so parameters are already well-documented. The description mentions 'URL slug' and 'publication' implicitly but does not add significant meaning beyond what the schema provides, meriting the baseline score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Get'), the resource ('engagement statistics'), and the method ('by its URL slug'). It distinguishes itself from sibling tools like get_post and get_subscriber by specifically focusing on engagement data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when engagement metrics are needed but lacks explicit when-to-use, when-not-to-use, or alternative tool comparisons. No guidance on prerequisites or context for invocation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_subscriberGet SubscriberA
Read-onlyIdempotent

Look up a specific subscriber by email address. Returns their subscription details including type, status, and social handles.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailYesThe subscriber's email address.
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, idempotentHint. Description adds value by specifying the return fields (type, status, social handles), exceeding the safety profile provided by annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no wasted words. Front-loaded with purpose, followed by output description. Efficient and easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 2 parameters, no output schema, and rich annotations, the description covers the main functionality and return values. Slight gap on publication parameter meaning, but overall sufficient for a lookup tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, baseline 3. Description mentions lookup by email but does not explain the optional publication parameter beyond what the schema provides. No additional semantic clarity added.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the action (look up) and resource (subscriber by email), and lists the returned information (subscription details including type, status, social handles). Distinguishes from sibling tools like get_post and get_subscriber_counts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use vs alternatives. While lookup intent is clear, there are no exclusions or references to sibling tools (e.g., get_subscriber_counts) that perform similar but distinct functions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_subscriber_countsGet Subscriber CountsA
Read-onlyIdempotent

Get daily subscriber counts broken down by subscription type (free, paid, etc.). Useful for tracking growth and churn over time.

ParametersJSON Schema
NameRequiredDescriptionDefault
endDateNoEnd of date range (YYYY-MM-DD).
startDateNoStart of date range (YYYY-MM-DD).
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, so the description adds the behavioral detail of returning daily counts broken by subscription type. This adds some context but is not extensive; no contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core purpose. No redundant words, and the second sentence adds useful context. Highly concise and structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 3 parameters and no output schema, the description explains what the output looks like (daily counts by type) and its utility. It does not mention pagination or limits, but given the openWorldHint and readOnlyHint, this is adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description does not add any meaning beyond what the schema provides for the parameters (startDate, endDate, publication).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Get' and resource 'daily subscriber counts broken down by subscription type', and distinguishes it from sibling tools like get_post and get_post_stats which are focused on posts rather than subscriber analytics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'Useful for tracking growth and churn over time' provides clear context for when to use the tool. While it does not explicitly mention alternatives or when not to use, the context is sufficient to guide the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_postsList PostsA
Read-onlyIdempotent

List posts published by a Substack publication. Returns post metadata including title, URL slug, audience, publish date, and type. Use the urlSlug from results with get_post or get_post_stats for details.

ParametersJSON Schema
NameRequiredDescriptionDefault
nextNoPagination cursor from a previous list_posts response. Pass this to get the next page of results.
typeNoFilter by post type.
sortByNoSort order. Defaults to newest.
endDateNoFilter posts published on or before this date (YYYY-MM-DD).
startDateNoFilter posts published on or after this date (YYYY-MM-DD).
maxResultsNoMaximum number of posts to return. Default 100.
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint. The description adds behavioral context by listing returned metadata (title, URL slug, etc.) and implying pagination via 'next' parameter. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no unnecessary words. First sentence states purpose and return info, second gives actionable guidance. Highly efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description adequately covers return values (title, URL slug, etc.) and hints at pagination. For a list tool with well-documented parameters, this is sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the description does not need to repeat parameter details. It mentions urlSlug in context but does not add new semantic meaning beyond the schema, earning the baseline score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'List posts published by a Substack publication' with a specific verb ('List') and resource ('posts'). It distinguishes from sibling tools like get_post and get_post_stats by implying it returns multiple posts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly recommends using urlSlug from results with get_post or get_post_stats for details, providing clear context for follow-up. However, it does not explicitly state when to use this tool versus alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_publicationsList PublicationsA
Read-onlyIdempotent

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

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint. The description adds no further behavioral details beyond stating it lists names, which is adequate but not extra.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no wasted words, front-loaded with purpose. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list tool with no output schema, the description adequately explains what is returned (publication names) and how to use them, making it complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are no parameters, so baseline 4 applies. The description adds no param info, but none is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists all configured Substack publications and their names, with a specific verb 'list' and resource 'publications'. It distinguishes from sibling tools focused on posts, stats, and subscribers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells agents to use the output names as the 'publication' parameter in other tools, providing clear usage context. It doesn't mention when not to use, but the guidance is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 5 tool updatesv1.1.0
    • Changedget_post1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedget_post_stats1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedget_subscriber3 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / email / format
        Added value: +"email"
      • addedInput schema / properties / email / pattern
        Added value: +"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
    • Changedget_subscriber_counts3 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / endDate / pattern
        Added value: +"^\\d{4}-\\d{2}-\\d{2}$"
      • addedInput schema / properties / startDate / pattern
        Added value: +"^\\d{4}-\\d{2}-\\d{2}$"
    • Changedlist_posts5 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / endDate / pattern
        Added value: +"^\\d{4}-\\d{2}-\\d{2}$"
      • addedInput schema / properties / maxResults / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / maxResults / minimum
        Added value: +1
      • addedInput schema / properties / startDate / pattern
        Added value: +"^\\d{4}-\\d{2}-\\d{2}$"
  2. 6 tool updatesv1.0.0
    • First observedget_post
    • First observedget_post_stats
    • First observedget_subscriber
    • First observedget_subscriber_counts
    • First observedlist_posts
    • First observedlist_publications

TDQS

A4.1/5.0
Disambiguation5/5

Each tool targets a distinct resource or aspect: post metadata vs. stats, individual subscriber vs. counts, and listing posts vs. publications. No overlap.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern (get/list + noun), with singular/plural used appropriately. No mixing of conventions.

Tool Count5/5

6 tools is well-scoped for a read-only Substack publisher tool: covers publications, posts, subscribers, and stats without being overwhelming.

Completeness4/5

Read operations are well-covered (list, get details, get stats, get subscriber, counts), but lacks write operations (create, update, delete) which may be expected for a 'publisher' server. Minor gap.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dkships/substack-publisher-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server