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.

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
urlSlugYesThe URL slug of the post (from list_posts results or the post URL).
bodyFormatNoHow to return the body: 'markdown' (default), 'prosemirror' (the raw JSON string from the API), or 'none' to omit it and return metadata only.
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A4/5.0
Behavior4/5

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

Annotations already cover read-only, idempotent, and open-world behavior. The description adds value beyond those by specifying the exact return fields and the default body format (Markdown), which is useful given there is no output schema.

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?

A single sentence that front-loads the action and then provides a compact, useful list of returned fields. No filler or redundant explanation.

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 there is no output schema, spelling out the returned fields is valuable and mostly sufficient. The minor gap is the absence of any mention of the publication disambiguation scenario, though the schema does cover it.

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?

Input schema description coverage is 100%, so the schema already documents urlSlug, bodyFormat, and publication. The description only echoes the URL-slug mechanism and Markdown default, adding no new parameter meaning beyond the schema.

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?

States a specific verb and resource ('Get a post by its URL slug') and clarifies it returns the full body. The enumerated return fields make it easy to distinguish from sibling tools like list_posts, search_posts, and get_post_stats.

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 intended use case is implied by the URL-slug parameter and the nature of the tool, but there is no explicit guidance on when to use it versus alternatives like search_posts or list_posts. No exclusions or alternative routing are mentioned.

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 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.

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.6/5.0
Behavior3/5

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

Annotations already convey read-only, idempotent, and open-world behavior, so the description does not need to restate those. It adds modest context by noting metrics are conditional ('when applicable') and revenue is 'estimated,' but it does not describe response shape or edge cases.

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?

A single sentence front-loads the action and resource, then compactly lists the included metrics with no filler. Every clause adds useful information.

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 simple read-only stats tool with no output schema, the description adequately enumerates the return categories and conditional metrics. The main gaps are usage alternatives and output formatting, which are already addressed in other dimensions.

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?

The input schema already documents both parameters at 100% coverage, including the urlSlug source and publication requirement. The description adds no new parameter-level meaning beyond referring to the slug, so the baseline of 3 applies.

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 a specific verb and resource: 'Get engagement statistics for a post by its URL slug,' and enumerates the exact metrics returned. This clearly separates it from content-focused sibling get_post and from subscriber-count tools.

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 when-to-use guidance is provided. It does not explain when to prefer this over get_post or get_subscriber_counts, nor does it mention that list_posts can supply the needed slug. Usage context is only implicit in the resource type.

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 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.

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

TDQS

A3.7/5.0
Behavior4/5

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

Annotations already establish readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description adds valuable behavioral context beyond annotations: it specifies the 404 error for unknown emails and summarizes the return payload. 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 with no filler: the purpose is front-loaded, followed by a compact list of returned fields and the error behavior. 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 read-only lookup, the description covers the purpose, the key returned fields, and the error case. Parameter details are fully handled by the schema, and the return-field list compensates for the absence of an output 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 the schema already documents both parameters, including the email format and the conditional requirement for publication. The description adds no parameter-level meaning beyond what the schema provides, so baseline 3 is appropriate.

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

Purpose4/5

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

The description uses a specific verb ('Look up') and identifies the resource ('a subscriber by email address'), and it enumerates the returned fields. It is clearly distinct from sibling tools like get_subscriber_counts, though it does not explicitly name or contrast itself with any sibling.

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?

There is no guidance about when to choose this tool over siblings such as get_subscriber_counts, nor any exclusions or alternative routing. The description implies usage through its purpose statement but provides no decision-making context.

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, 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.

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

TDQS

A4.7/5.0
Behavior5/5

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

Beyond the readOnlyHint/idempotentHint annotations, the description discloses sort order, the exact row fields, the derived 'Free subscribers = total minus paid' relationship, and the default date window. This material detail helps an agent predict output even though no output schema is provided.

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?

The description is four short, information-dense sentences with the core purpose and ordering front-loaded. Every sentence adds value: row contents, the derived field relationship, and the default date behavior. There is no redundancy with the schema or annotations.

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?

Given no output schema, the description names all returned fields, explains their relationship, and states the sort order and default range. Combined with schema-described parameters and safety annotations, the definition is complete for a low-complexity read-only tool.

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?

Schema descriptions already cover all three parameters, so the schema carries the baseline documentation burden. The description adds meaning by clarifying that date parameters are optional and that omitting them yields roughly the last year, which is not stated in the schema.

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 a specific verb ('Get'), a precise resource ('daily subscriber counts'), and an ordering ('newest first'). It clearly distinguishes itself from sibling tools like get_subscriber or get_post_stats by describing time-series subscriber count rows rather than post stats or an individual subscriber.

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 makes it clear that this tool is for retrieving daily subscriber counts over time, and it adds useful default behavior: 'Without dates, returns roughly the last year.' It does not explicitly name alternatives or when-not-to-use conditions, but the context is clear enough for an agent to route correctly.

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 { 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.

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 before this date (YYYY-MM-DD, exclusive). For a single day D, use startDate D and endDate D+1.
startDateNoFilter posts published on or after this date (YYYY-MM-DD).
maxResultsNoMaximum number of posts per page. Default 100. Prefer paginating with `next` over large values.
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 provide readOnlyHint, openWorldHint, and idempotentHint, so the safety profile is covered. The description adds useful behavioral details: the response shape, the pagination cursor contract, and the fact that `next` is null on the last page.

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?

The description is three sentences with no filler. It front-loads the operation, then provides the return shape and pagination semantics, all of which are essential for correct use.

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?

Since there is no output schema, the description appropriately explains the return object and its fields. It leaves parameter details to the schema, which is acceptable given the high schema coverage. Minor details like sort defaults are handled by the schema descriptions.

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 all parameters are already well documented. The description adds marginal reinforcement around `next` and urlSlug but does not need to explain filters or sorting because the schema covers them.

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 uses a specific verb ('List') and resource ('posts published by a Substack publication') and describes the return shape, making the tool's purpose immediately clear. It also mentions using urlSlug with get_post or get_post_stats, which helps distinguish it from the single-post sibling tools.

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 establishes clear context: this tool lists posts and supports pagination via the `next` cursor. It does not explicitly contrast with search_posts or state when not to use this tool, but the purpose is specific enough that an agent can infer when listing is appropriate.

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.

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYesSearch terms.
maxResultsNoMaximum number of posts to return (1-100). Default 20.
publicationNoPublication name (e.g., 'ny', 'la'). Required if multiple publications are configured.

TDQS

A4.1/5.0
Behavior4/5

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

Annotations already establish that this is read-only, open-world, and idempotent, so the description doesn't need to restate safety. It adds valuable behavior beyond those hints: search is scoped to published posts, results are relevance-ordered, and the response has no pagination. This is solid context without redundancy.

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 tight sentences with zero filler. The core behavior is front-loaded, and the return-shape reference and pagination note each earn their place by clarifying expectations for the caller.

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 low-complexity search tool with full schema coverage and safety annotations, everything needed is present: scope, ordering, return shape, and pagination behavior. Pointing to list_posts for field definitions is sufficient even without an output 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 the parameters are already fully documented. The description adds no param-specific semantics beyond what the schema provides, but it also doesn't need to because the schema carries the burden. Baseline 3 is appropriate.

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?

States a specific verb ('search'), a clear resource ('a publication's published posts'), and a distinctive behavioral detail (ordered by relevance). It also names the sibling list_posts as the source of the return shape, which helps differentiate it from the other read tools.

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 full-text search purpose implicitly tells the agent when to choose this tool over list_posts or get_post, but it never explicitly says 'use this when you need full-text search' or names an alternative to avoid. The reference to list_posts is helpful but not a direct usage-route.

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.

  1. 6 tool updatesv1.2.0
    • Changedget_post2 fields changed
      • addedInput schema / properties / bodyFormat
        Added value: +{
        +  "description": "How to return the body: 'markdown' (default), 'prosemirror' (the raw JSON string from the API), or 'none' to omit it and return metadata only.",
        +  "enum": [
        +    "markdown",
        +    "prosemirror",
        +    "none"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / urlSlug / minLength
        Added value: +1
    • Changedget_post_stats1 field changed
      • addedInput schema / properties / urlSlug / minLength
        Added value: +1
    • Changedget_subscriber1 field changed
      • changedInput schema / properties / email / pattern
        Previous value: -"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"New value: +"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
    • Changedget_subscriber_counts6 fields changed
      • changedInput schema / properties / endDate / description
        Previous value: -"End of date range (YYYY-MM-DD)."New value: +"End of date range (YYYY-MM-DD, inclusive)."
      • addedInput schema / properties / endDate / format
        Added value: +"date"
      • changedInput schema / properties / endDate / pattern
        Previous value: -"^\\d{4}-\\d{2}-\\d{2}$"New value: +"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
      • changedInput schema / properties / startDate / description
        Previous value: -"Start of date range (YYYY-MM-DD)."New value: +"Start of date range (YYYY-MM-DD, inclusive)."
      • addedInput schema / properties / startDate / format
        Added value: +"date"
      • changedInput schema / properties / startDate / pattern
        Previous value: -"^\\d{4}-\\d{2}-\\d{2}$"New value: +"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
    • Changedlist_posts6 fields changed
      • changedInput schema / properties / endDate / description
        Previous value: -"Filter posts published on or before this date (YYYY-MM-DD)."New value: +"Filter posts published before this date (YYYY-MM-DD, exclusive). For a single day D, use startDate D and endDate D+1."
      • addedInput schema / properties / endDate / format
        Added value: +"date"
      • changedInput schema / properties / endDate / pattern
        Previous value: -"^\\d{4}-\\d{2}-\\d{2}$"New value: +"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
      • changedInput schema / properties / maxResults / description
        Previous value: -"Maximum number of posts to return. Default 100."New value: +"Maximum number of posts per page. Default 100. Prefer paginating with `next` over large values."
      • addedInput schema / properties / startDate / format
        Added value: +"date"
      • changedInput schema / properties / startDate / pattern
        Previous value: -"^\\d{4}-\\d{2}-\\d{2}$"New value: +"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
    • Addedsearch_posts
  2. 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}$"
  3. 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.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 issues

Related MCP Connectors

Related MCP Servers