Skip to main content
Glama

Substack MCP Server

A Model Context Protocol (MCP) Server for Substack enabling LLM clients to interact with Substack's API for automations like creating posts, managing drafts, and more.

Docker Pulls npm downloads

Create and publish posts, work with subscribers and analytics, browse your reader feeds, manage tags and comments, and upload images — 27 tools exposed through one MCP server.

IMPORTANT

Substack does not provide a public API for these operations. This server uses your authenticated web session. Treat the session token exactly like a password: keep it local, never commit it, and never include it or a complete Cookie header in a bug report.

Quick start

The fastest installation uses Node.js 22 or newer and npx.

1. Collect your Substack credentials

Sign in to Substack in your browser and open your publication dashboard. You need three values:

  • Publication URL — the full base URL of your publication, for example https://your-publication.substack.com.

  • Session token — open your browser's developer tools, select Network, filter to Fetch/XHR, and reload the dashboard. Open a successful authenticated request to your publication. Under Request Headers, find the Cookie header and locate a session cookie named substack.sid or connect.sid. Copy its value without the cookie name or the rest of the header. If both names appear with different values, test them separately and locally with the read-only verification in step 3; never paste either value into an issue.

  • User ID — in the same Network panel, search for a successful publication_user request. In its JSON response, copy the numeric id inside the user object.

If the browser UI differs, the illustrated credential guide shows the same requests. If authentication later stops working, sign in again and repeat these steps to obtain the current token.

2. Add the server to your MCP client

For clients that accept MCP JSON configuration, add:

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

Replace the three example values, save the configuration, and restart your MCP client. Consult your client's documentation if it uses a different configuration format.

3. Verify the connection

Ask your client:

List my five most recent Substack drafts.

The client should call list_posts with status: "drafts". If it fails, check the client's MCP logs and the logging section below before opening an issue.

Docker quick start

To use the published Docker image instead of Node.js, add this server configuration:

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

Related MCP server: Substack MCP Server

🛠 Available Tools

Inputs:

  • title (string): Title of the post

  • subtitle (string): Subtitle of the post

  • body (string): Body of the post. Plain text becomes one paragraph per line — Markdown is not interpreted, so ## Heading arrives literally. A JSON string of a Substack document also works and is validated against the same schema set_post_body publishes, so an unrecognised node name is an error rather than a silently mangled post.

Returns: {draft_id, is_published}. Pass draft_id to get_draft to read the draft back.

For anything structured — headings, lists, links, code, images, a paywall — use set_post_body after creating the draft: the schema is published there, so the calling model can read the node vocabulary rather than guess at it.

Exposes the same filtering the Subscribers dashboard offers: 48 columns, 18 operators, free-text search, sorting and pagination.

Inputs:

  • filters (array, optional): conditions combined with AND, each {column, operator, value}

  • search (string, optional): free text matched against subscriber name and email

  • sort_by (string, optional): any filterable column

  • sort_direction (asc | desc, optional): defaults to desc

  • limit (number, optional): 1–100, defaults to 25

  • offset (number, optional): for paging

Which operators a column accepts depends on its type:

Type

Operators

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

The columns cover subscriber identity (name, email, country, state, group membership), subscription (type, start/expiry/cancel dates, revenue, Stripe plan, attribution), email engagement (opens and unique opens over 7d/30d/6mo, links clicked, sections) and site engagement (post views, unique posts seen, comments, shares, days active, activity rating). The full list with types reaches the client in the tool's JSON Schema, so a model does not have to guess names.

Returns: {count, returned, limit, offset, subscribers}. count is the total matching the filters regardless of limit, so a call with limit: 1 is a cheap way to size a segment.

Note: engagement columns can be filtered on here but are not part of the records this tool returns — Substack takes the fields it returns from the publication's saved Display settings and ignores a per-request column list. Use export_subscribers to read their values.

There is no OR and no nesting: anything needing OR has to be issued as separate calls.

The way to actually read the engagement metrics list_subscribers can only filter on: email opens over 7d/30d/6mo, unique emails seen, post views, unique posts seen, comments, shares, links clicked, days active and activity rating.

Inputs:

  • filters (array, optional): the same conditions as list_subscribers, combined with AND

  • search (string, optional): free text matched against subscriber name and email

  • columns (array, optional): which columns to include, defaulting to all of them

  • max_wait_seconds (number, optional): 1–600, defaulting to 120

Returns: {count, columns, missing_columns, unmapped_columns, export_id, subscribers}, where each subscriber is keyed by column name.

Substack generates the file asynchronously, so the tool creates a subscriber set, requests the export, polls until it is ready and downloads it. A small export lands in a few seconds. If the wait budget runs out the tool says so and names the export_id rather than blocking.

Two caveats, both verified against the live API:

  • tag_ids and group_membership cannot be exported. Substack drops them silently rather than failing, so they are reported in missing_columns — asking for all 48 columns returns 46.

  • Values arrive display-formatted, not raw: revenue is "€50.00" here and the number 50 through list_subscribers. Dates are ISO strings.

There is no paging: an export covers the whole matching set.

Inputs:

  • status (drafts | published | scheduled): which list to read

  • search (string, optional): free text matched against title and content

  • limit (number, optional): 1–100, defaults to 25

  • offset (number, optional): for paging

  • sort_direction (asc | desc, optional): drafts and published posts are newest-first, scheduled posts soonest-first

Returns: {status, total, returned, limit, offset, posts}, each post summarised — use get_draft for the full content of an unpublished one.

Inputs:

  • draft_id (number): the id returned by list_posts or create_draft_post

Returns: the draft as Substack stores it, body and audience/email settings included.

The only way to write structured content: headings, lists, links, code blocks, quotes, images, buttons and a paywall. create_draft_post takes plain text; this takes the document Substack actually stores, and its schema is published in tools/list so the calling model can read the node vocabulary instead of guessing.

Inputs:

  • draft_id (number): the id returned by list_posts or create_draft_post

  • body (object): a Substack ProseMirror document — {type: 'doc', content: [...]}

Fifteen node types are accepted: paragraph, heading, bullet_list, ordered_list, list_item, blockquote, highlighted_code_block, code_block, horizontal_rule, captionedImage, button, paywall, youtube2, plus digestPostEmbed, substack_mentions and directMessage passed through unchanged so a document read with get_draft can be written back. Marks: strong, em, code, strikethrough, link.

Returns: {draft_id, nodes}, where nodes counts what was stored by type — so a caller that asked for a paywall can confirm there is one. Validation cannot report a node that was never sent.

Three things worth knowing:

  • An image must already be hosted by Substack. image2.src pointing at an external URL is stored but does not render. Use upload_image to re-host one and get a src that works.

  • A document may contain at most one paywall. Substack accepts two and renders both, leaving it undefined which one cuts the post, so this tool refuses the second.

  • ordered_list numbers from attrs.order, not attrs.start. A list given only start renders from 1 with no error.

Substack's editor uploads images as base64 data URIs to POST /api/v1/image, which answers with a Substack-hosted URL. image2.src in set_post_body and cover_image in update_draft only render such a URL, so this tool is the bridge. Substack itself only re-fetches URLs already in its own storage, so the image is encoded here rather than being handed off.

Inputs — exactly one of url or path:

  • url (string): the http(s) URL of an image to download and re-host

  • path (string): absolute path to an image file on the machine running this server, read straight from disk with no download

  • post_id (number, optional): the post the image belongs to; its effect is unconfirmed

Returns: {id, url, content_type, bytes, width, height} — put url into an image2.src when calling set_post_body, or into cover_image when calling update_draft.

A download is guarded: only http/https, private and loopback hosts are refused after DNS resolution (redirects are re-checked at every hop), the content type must be an image, HEIC is rejected with a note to convert it, and the image may not exceed 10 MB.

A local file is guarded differently, because it has no Content-Type header to trust. The path must be absolute — a relative one would resolve against this server's working directory, not the calling client's — and the type is read from the file's magic bytes rather than its extension, so a non-image with an image extension is caught here instead of at Substack. PNG, JPEG, GIF and WebP are accepted; HEIC and SVG are not. The same 10 MB cap applies, checked against the file size before the file is read. Note that path reads whatever absolute path it is given: if that matters in your setup, do not expose this server to a client you would not trust with your filesystem.

The update is partial: only the fields you pass change, and the body is left alone.

Inputs:

  • draft_id (number): the id returned by list_posts or create_draft_post

  • draft_title (string, optional)

  • draft_subtitle (string, optional)

  • audience (everyone | only_paid | only_free | founding, optional)

  • write_comment_permissions (everyone | subscribers | only_paid | none, optional): who may comment

  • default_comment_sort (best_first | most_recent_first | oldest_first, optional)

  • cover_image (string, optional): the social preview image. A URL already on substack-post-media.s3.amazonaws.com or substackcdn.com is used as-is; anything else is downloaded and re-hosted on Substack first, under the same guards as upload_image

  • social_title (string, optional): the title used when the post is shared elsewhere

  • description (string, optional): the social preview description — not the subtitle

  • search_engine_title (string, optional)

  • search_engine_description (string, optional)

  • slug (string, optional): the post's URL slug

Returns: {draft_id, updated_fields, draft_title, draft_subtitle, audience, is_published, cover_image, cover_image_rehosted_from}. A call with no field to change is refused rather than sent as a no-op. cover_image is the URL that actually landed, which differs from the one passed when it was re-hosted.

Inputs:

  • draft_id (number): the id returned by list_posts or create_draft_post

  • send (boolean, optional): email the post to subscribers. Defaults to false, unlike the Substack API's own default — the post goes live on the web either way, but an email cannot be recalled, so it has to be asked for explicitly.

Returns: {status, draft_id, post_id, title, slug, canonical_url, emailed, email_sent_at}. emailed is what was asked for; email_sent_at is the server's own record of whether it mailed.

The email intent is written to the draft's should_send_email before publishing, as well as being passed on the publish call. That field is where the dashboard keeps the decision and it defaults to true, so setting only one of the two would risk mailing the whole list if the endpoint reads the draft rather than the request body.

There is no unpublish tool: publishing cannot be undone from this server.

Inputs:

  • draft_id (number): the id returned by list_posts or create_draft_post

Returns: {status, draft_id, draft_title}.

Substack deletes drafts and published posts through the same endpoint, so this tool reads the target first and refuses if it is published — removing a live post is irreversible and is left to the dashboard.

Inputs:

  • full (boolean, optional): return all 111 fields (~24 KB) instead of the projection. Defaults to false.

Returns: by default a projection — name, subdomain, custom domain, hero text, copyright, sender name, logo, plans, payment state and the community/podcast flags — plus _meta naming how many fields were dropped. The full payload is mostly notification toggles and the raw HTML of the welcome email, terms and privacy pages.

Inputs:

  • full (boolean, optional): include the complete subscriptions array. Defaults to false.

Returns: {id, name, handle, bio, photo_url, publications, primary_publication_id, subscription_count}. publications lists every publication the session has a role on, which is how to discover that SUBSTACK_PUBLICATION_URL is not the only one it could be pointed at.

Inputs:

  • include_hidden (boolean, optional): include tags not shown in the navigation. Defaults to true.

Returns: {total, returned, tags}, each {id, name, slug, hidden}. Tag ids are UUIDs, not integers — unlike every other id in this API.

Inputs:

  • post_id (number): the id from list_posts. Works for drafts too.

Returns: {post_id, count, tags}, each {post_tag_id, name, slug, hidden, association_id}.

The underlying endpoint answers only UUIDs, so this resolves the names against the publication's tag list. Neither get_draft nor list_posts carries tags, so this is the only way to read them back.

Inputs:

  • post_id (number): the id from list_posts. Works for drafts too.

  • tag_name (string): matched case-insensitively against existing tags

  • create_if_missing (boolean, optional): create the tag when no name matches. Defaults to true; set it to false to have a typo reported instead of turned into a new tag.

Returns: {status, post_id, tag, tag_created, association_id} where status is tagged or already_tagged — re-adding a tag the post already has answers a bare 400 upstream, so it is checked first.

Takes a name rather than an id because the ids are UUIDs, which no caller could reasonably hold.

Inputs:

  • post_id (number): the id from list_posts

  • limit (number, optional): 1–100, defaults to 50

Returns: {post_id, returned, automod_hidden_count, comments}. Each comment carries its author, plain-text body, reaction and reply counts, and its position in the thread (parent_comment_id, depth). Comments withheld by Substack's automod are counted, not merged in — they arrive in a separate array upstream, and dropping them silently would turn "held" into "nobody commented".

Inputs:

  • post_id (number): the id from list_posts

  • body (string): plain text; Substack converts it server-side

Returns: {status, post_id, comment}.

This is published under your name. The full text is logged at info before the request, since the log is the only record of what was said. This server does not expose deletion, but the comment can be removed from the Substack UI — unlike a restack, a comment does have an id of its own.

The seven tools below read substack.com, not your publication. They are about the account as a reader — what it subscribes to, what is in its inbox and feed — which is a different host and a different id space from the publisher surface above.

Inputs:

  • limit (number, optional): 1–500, defaults to 100

  • active_only (boolean, optional): exclude paused and expired subscriptions. Defaults to true.

Returns: {returned, pages_fetched, subscriptions}, each with plan, membership_state, is_founding, is_favorite and whether emails are off. Pages internally up to 20 requests and says truncated: true if that bound is what stopped it.

Not to be confused with list_subscribers, which is who subscribes to you.

Inputs:

  • limit (number, optional): 1–100, defaults to 20

  • after (string, optional): the next_after from a previous response. A timestamp, not an opaque cursor — this endpoint's own cursor field is always null.

Returns: {returned, more, next_after, posts}, each post summarised with its reading state (is_read, read_progress, is_saved). The Inbox sends every post's full body; it is dropped here, so use get_reader_post to read one.

Inputs:

  • post_id (number): from list_reader_posts or get_reader_feed

  • include_body (boolean, optional): defaults to true

Returns: the post's metadata plus body_html. body_truncated: true means the body was withheld behind a paywall this session does not clear — preview_text still carries the teaser.

The body stays HTML: converting it would mean a new dependency or a regex pass over markup, and a regex HTML converter mangles nested lists and embeds silently.

Inputs:

  • tab (string, optional): tab idfor-you (default) or subscribed. Never the display name: those are localized.

  • limit (number, optional): 1–50, defaults to 20

  • cursor (string, optional): the next_cursor from a previous response

  • include_tabs (boolean, optional): also return the available tab ids

Returns: {tab, returned, next_cursor, items}. Each item is a note or a post. non_content_items_skipped counts the "people to follow" blocks Substack mixes into the array, which carry no content at all.

Inputs:

  • user_id (number, optional): defaults to SUBSTACK_USER_ID — your own account

  • type (all | notes | posts, optional): defaults to all

  • limit (number, optional): 1–50, defaults to 20

  • cursor (string, optional)

Returns: {user_id, type, returned, next_cursor, items}. When filtering, read_from_profile reports how many entries the page actually held — otherwise "3 notes out of 20 entries read" would look like "this account has written 3 notes".

Inputs:

  • comment_id (number): without the c- prefix Substack uses in urls

  • include_replies (boolean, optional): defaults to true

Returns: {comment, branch_count, replies_returned, more_branches, next_cursor, branches}. Each branch is a direct reply plus its descendants, with parent_comment_id and depth resolved.

Inputs:

  • comment_id (number): the Note to restack, from get_reader_feed or get_profile_feed

  • tab_id (string, optional): defaults to for-you

Returns: {status, comment_id, restack_id, note}.

This is public and appears on your profile, and cannot be undone from here: a restack has no id of its own — it surfaces the original Note with context: comment_restack — so there is nothing for this server to delete. Remove it from the Substack UI.

Notes only. Restacking a post is not offered: that call answers 404 even for a published post on your own publication, so a post_id parameter would produce an error that reads as the post being gone rather than as the tool being wrong.

Inputs: none.

Returns: total and recent subscribers, email and app subscribers, ARR, site views and the 30-day email open rate, each with its change where Substack reports one. If one of the underlying endpoints fails the rest are still returned, and the failure is named under errors.

For anything deeper, use get_analytics.

Which post actually grew the list, which was worth most, which cost you subscribers. The dashboard's "Posts" tab, sortable and paged.

Inputs:

  • order_by (string, optional): any of the 43 metrics, defaulting to post_date

  • order_direction (asc | desc, optional): defaults to desc

  • limit (number, optional): 1–100, defaults to 25

  • offset (number, optional): for paging the archive

The metrics worth reaching for:

group

fields

conversion

signups subscribes founding_subscribes annual_subscribes monthly_subscribes free_trials free_to_paid_upgrades signups_within_1_day estimated_value

churn

unsubscribes

reading

opens open_rate clicks click_through_rate views subscribers_finished_post

social

likes shares restacks engagement_rate unique_engagements

delivery

queued sent delivered dropped

video / podcast

video_views video_minutes_watched downloads downloads_day30

Returns: {total, returned, limit, offset, order_by, order_direction, posts}. total is the whole archive, not the page; order_by and order_direction are echoed so a ranking is never read without knowing what produced it.

Two caveats, both verified:

  • There is no date filter. from_date/to_date are ignored by this endpoint — total does not change — so the schema does not offer them. Narrow by sorting and paging instead.

  • Ranking by a rate (open_rate, engagement_rate, click_through_rate) descending puts posts with no data first, because null sorts before numbers. The tool does not filter them out, since that would silently answer a different question.

order_by is an enum on purpose: the API answers 200 for a field it does not recognise and returns an arbitrary order, so a typo would produce a ranking that looks authoritative.

Everything behind the dashboard's Stats tabs, as one tool with a report enum rather than seventeen near-identical tools.

Inputs:

  • report (string): which report to read — see the table below

  • from_date, to_date (string, optional): YYYY-MM-DD. Used only by the reports covering a period, which default to the last 30 days

  • limit (number, optional): 1–100, used only by audience_overlap and subscriber_notes

report

what it tells you

retention

cohort retention — how much of each signup cohort is still subscribed months later

retention_summary

headline retention at 1, 6 and 12 months

unsubscribes / unsubscribes_timeseries

churn, with the reasons given

growth_sources

where new subscribers came from, ranked

growth_events

the individual growth events in a window

referrals_leaderboard / referrals_summary

who refers most; gifts sent, accepted, converted

audience_overlap

other Substacks whose audience overlaps yours — the collaboration shortlist

audience_locations

how many countries and US states your subscribers span

subscriber_notes

recent Notes written by your subscribers

paid_subscriber_growth

paid growth rate, new subscriptions, expirations

subscribers_timeseries, followers_timeseries, arr_timeseries

counts and revenue over time

network_attribution

what share of subscribers arrived via the Substack network

Returns: {report, params, ignored_params, data}. params is what was actually sent, defaults included — the same report answers very differently over a different window, so the numbers mean little without it. ignored_params names anything you passed that the chosen report does not accept, rather than dropping it silently.

Two neighbouring endpoints are deliberately not exposed: audience_insights/location (the subscriber map) and visitor_sources answer 400 even for Substack's own dashboard, so they are broken upstream rather than mis-called.

🏗 Running from Source

Use this if you want to hack on the server itself. There is no build step — the sources are plain ESM and run as they are.

Node.js

git clone https://github.com/marcomoauro/substack-mcp.git
cd substack-mcp
npm ci

Then add to your MCP config:

{
  "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 (build from source)

git clone https://github.com/marcomoauro/substack-mcp.git
cd substack-mcp
docker build -t substack-mcp .

Then add to your MCP config:

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

🪵 Logs

The server logs what it does as one JSON object per line, on stderr — MCP clients collect it into their own log file (on macOS, Claude Desktop writes it to ~/Library/Logs/Claude/mcp-server-substack-api.log). It is the fastest way to see what your LLM actually sent when a call does not do what you expected:

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

Set the optional SUBSTACK_MCP_LOG_LEVEL env var alongside your credentials to change how much is written:

Value

What you get

silent

nothing

error

failed calls only

warn

the above, plus every answer the client received as an error — including calls rejected for bad arguments before they ran

info (default)

the above, plus every tool call, request and response

debug

the above, plus full payloads and every JSON-RPC message

Your session token is never written to the log, at any level.

For a complete list of MCP clients and their feature support, visit the official MCP clients page.

Client

Description

Claude Desktop

Desktop application for Claude AI

Cursor

AI-first code editor

Cline for VS Code

VS Code extension for AI assistance

GitHub Copilot MCP

VS Code extension for GitHub Copilot MCP integration

Windsurf

AI-powered code editor and development environment

🆘 Support

  • For issues with this MCP Server: Open an issue on GitHub

Available Tools

27 tools
add_tag_to_postA

Add a tag to a post, by tag name, creating the tag on the publication if it does not exist yet. Works on drafts as well as published posts. Reports already_tagged rather than failing when the post already carries the tag.

ParametersJSON Schema
NameRequiredDescriptionDefault
post_idYesThe numeric id of the post to tag, from list_posts. Works for drafts too.
tag_nameYesThe tag to add, by name. Matched case-insensitively against existing tags.
create_if_missingNoCreate the tag on the publication when no tag by that name exists. Set to false to fail instead — useful when a typo should be reported rather than turned into a new tag.

TDQS

A4.2/5.0
Behavior4/5

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

No annotations are provided, so the description carries the full burden. It discloses key behaviors: creating the tag if it doesn't exist, working on drafts and published posts, and returning 'already_tagged' instead of failing. It doesn't mention auth/rate limits or the return payload, but covers the most important behavioral traits for a tag-adding operation.

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?

Three sentences, each adding distinct information: the core action, scope (drafts/published), and idempotent behavior. No redundant phrasing, front-loaded with the primary purpose.

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 simplicity of the tool and full schema coverage, the description is largely complete. It covers edge cases (already tagged, missing tag creation) and scope. However, with no output schema, it doesn't state the return format, which would be helpful but not critical for this straightforward mutation.

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-described. The description adds little beyond the schema: it mentions 'by tag name' but the schema already explains tag_name matching and create_if_missing. 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?

The description clearly states the verb and resource: 'Add a tag to a post, by tag name'. It also distinguishes from siblings by noting it works on drafts and published posts and creates the tag if missing, which differentiates it from read-only tools like get_post_tags.

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 implies when to use the tool: for adding tags to posts, including drafts. It also specifies behavior for already-tagged posts and tag creation, but doesn't explicitly mention alternatives or when not to use it. However, sibling tools are clearly read-only, so the usage context is reasonably clear.

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

comment_on_postA

Post a public comment on one of your posts, as you. This is published under your name and this server offers no way to delete it.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyYesThe comment text, as plain text. Substack converts it to its own document format server-side.
post_idYesThe numeric id of one of your posts, from list_posts.

TDQS

A4.5/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden. It explicitly discloses that the comment is public, published under your name, and that the server offers no way to delete it—critical behavioral traits that inform the agent of irreversibility and visibility.

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 action and resource, and the second sentence adds a crucial caveat about public visibility and non-deletion. No unnecessary words.

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 write tool with two parameters, no annotations, and no output schema, the description covers all essential context: the action, the target (own posts), identity ('as you'), public visibility, and irreversibility. It is complete enough for an agent to invoke correctly.

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%, with rich descriptions for both parameters: body clarifies plain text conversion to Substack's format, and post_id references list_posts. The description itself adds no extra param semantics beyond what the schema already provides, consistent with the baseline.

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 action ('Post a public comment'), the resource ('one of your posts'), and its nature ('public', 'as you'). It distinguishes itself from read-only comment tools like get_post_comments by specifying it creates a public comment on your own post.

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 gives clear context: use this when you want to post a public comment on your own post, under your identity. However, it does not explicitly mention alternatives or when not to use it, such as pointing to get_post_comments for reading comments.

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

create_draft_postC

create a draft post on your Substack account.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyYesThe body of the post. Plain text becomes one paragraph per line — Markdown is NOT interpreted, so `## Heading` would appear literally. A JSON string of a Substack document also works and is validated against the same schema set_post_body publishes; an unrecognised node name is an error rather than a mangled post. For structured content prefer set_post_body, where that schema is published and the node vocabulary is visible.
titleYesThe title of the post to be created.
subtitleYesThe subtitle of the post to be created.

TDQS

C2.9/5.0
Behavior2/5

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

With no annotations, the description carries the full burden of behavioral disclosure, but it only states that it creates a draft post. It does not disclose return values, side effects, permissions, or the relationship to publishing, making it insufficient for transparent behavior.

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

Conciseness4/5

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

The description is a single, concise sentence with no wasted words. It is front-loaded with the action and resource, matching the requirement for efficiency, though it could have been slightly more informative without losing brevity.

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

Completeness2/5

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

The description is too minimal for the tool's context. It lacks alternative guidance, return values, and fails to mention that the tool creates only a draft (not publishing). While the schema compensates for parameters, the overall description is not complete enough for an agent to use the tool correctly without external knowledge.

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 baseline is 3. The tool description adds no parameter information, but the input schema descriptively covers all three parameters, with particularly rich semantics for 'body' regarding Markdown handling and JSON validation.

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 clearly states the verb 'create' and the resource 'draft post' on Substack, making the core purpose unambiguous. However, it does not differentiate from siblings like update_draft or set_post_body, so it falls short of a 5.

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?

The description provides no guidance on when to use this tool versus alternatives. It does not mention prerequisites, exclusions, or that set_post_body is preferred for structured content (which the schema does mention, but the tool description itself lacks this context).

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

delete_draftA

Delete an unpublished draft. Refuses if the id belongs to a published post: Substack deletes both through the same endpoint, and removing a live post is irreversible, so that is left to the dashboard.

ParametersJSON Schema
NameRequiredDescriptionDefault
draft_idYesThe numeric id of the draft to delete, as returned by list_posts (`id`) or create_draft_post (`draft_id`).

TDQS

A4.4/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden. It discloses the refusal behavior for published posts, explains the shared endpoint, and highlights irreversibility for live posts. However, it does not explicitly state whether draft deletion is permanent, though 'delete' implies it.

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, action-first, with the caveat clearly stated. Every word earns its place; no redundancy or filler.

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 one-parameter delete tool with no output schema, the description covers purpose, usage boundaries, and a critical behavioral caveat. It omits return values, but that is arguably secondary for a destructive operation; the essentials are well covered.

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 schema already provides a detailed description for draft_id (100% coverage), including provenance from list_posts or create_draft_post. The tool description adds no additional parameter meaning beyond referencing the id's association with published posts, which is behavior rather than parameter semantics.

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 ('Delete') and resource ('unpublished draft'), clearly distinguishing it from sibling tools like publish_draft or get_draft. It also adds scope ('unpublished') and explicitly states what it refuses to do, making the tool's purpose unambiguous.

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

Usage Guidelines5/5

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

The description explicitly says when to use the tool (delete unpublished drafts) and when not to (refuses published posts). It also provides an alternative (the dashboard) and explains the rationale, giving clear guidance for tool selection.

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

export_subscribersA

Export subscribers with their full column values, including the engagement metrics list_subscribers can filter on but not return: email opens over 7d/30d/6mo, unique emails seen, post views, unique posts seen, comments, shares, links clicked, days active and activity rating. Takes the same filters as list_subscribers and covers the whole matching set — there is no paging. Substack generates the file asynchronously, so this waits for it and returns the parsed records. Two columns cannot be exported and are reported in missing_columns rather than failing: tag_ids and group_membership.

ParametersJSON Schema
NameRequiredDescriptionDefault
searchNoFree-text search over subscriber name and email.
columnsNoWhich columns to include. Defaults to every column. Available: user_name = Name; user_email_address = Email; country = Country; state = State/Province; group_membership = Group membership; subscription_type = Type; subscription_created_at = Start date; subscription_expires_at = Expiration date; first_payment_at = First paid date; last_subscribed_at = Paid upgrade date; unsubscribed_at = Cancel date; subscription_interval = Subscription interval; stripe_plan_name = Stripe plan; free_attribution = Subscription source (free); paid_attribution = Subscription source (paid); is_subscribed = Can see paid content; bestseller_tier = Bestseller; total_revenue_generated = Revenue; num_subs_gifted = Subscriptions gifted; bundle_id = Bundle; is_bundle_parent = Bundle origin; num_emails_received = Emails received (6mo); num_emails_dropped = Emails dropped (6mo); num_email_opens = Emails opened (6mo); num_email_opens_last_7d = Emails opened (7d); num_email_opens_last_30d = Emails opened (30d); num_unique_email_posts_seen = Unique emails seen (6mo); num_unique_email_posts_seen_last_7d = Unique emails seen (7d); num_unique_email_posts_seen_last_30d = Unique emails seen (30d); last_opened_at = Last email open; links_clicked = Links clicked; last_clicked_at = Last clicked at; emails_enabled = Sections; num_web_post_views = Post views; num_web_post_views_last_7d = Post views (7d); num_web_post_views_last_30d = Post views (30d); num_unique_web_posts_seen = Unique posts seen; num_unique_web_posts_seen_last_7d = Unique posts seen (7d); num_unique_web_posts_seen_last_30d = Unique posts seen (30d); num_comments = Comments; num_comments_last_7d = Comments (7d); num_comments_last_30d = Comments (30d); num_shares = Shares; num_shares_last_7d = Shares (7d); num_shares_last_30d = Shares (30d); days_active_last_30d = Days active (30d); activity_rating = Activity; tag_ids = Tags
filtersNoWhich subscribers to export, using the same conditions as list_subscribers, combined with AND. Omit to export everyone.
max_wait_secondsNoHow long to wait for Substack to generate the file, 1-600, defaulting to 120. A small export is ready in a few seconds.

TDQS

A4.5/5.0
Behavior5/5

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

With no annotations provided, the description fully owns behavioral disclosure. It details the asynchronous generation and waiting process, the absence of paging, and the special handling of unexportable columns via `missing_columns` rather than failure. This goes beyond typical descriptions by explaining edge cases and operational behavior, giving the agent critical context for invocation and error handling.

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

Conciseness4/5

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

The description is dense but every sentence contributes meaningful information. It front-loads the primary purpose, then adds differentiators and operational details. The enumeration of engagement metrics is long but serves to differentiate from list_subscribers. It is not overly verbose for the complexity it covers.

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 no output schema, the description provides essential context about return behavior: it waits asynchronously, returns parsed records, and reports missing columns. It also clarifies the full matching set and the relationship to list_subscribers. While it doesn't detail error handling or the exact record structure, the provided information is sufficient for most invocation decisions.

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?

The schema already provides 100% coverage with detailed descriptions for each parameter, including the full column enum with human-readable labels. The description adds value by clarifying that filters are the same as list_subscribers, the export covers the whole matching set with no paging, and the async wait behavior explains the max_wait_seconds parameter. This enhances the parameter semantics beyond the schema alone.

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 a specific action ('Export subscribers') with the full column values and distinguishes itself from the sibling list_subscribers by highlighting that it returns engagement metrics list_subscribers cannot return. The phrase 'covers the whole matching set — there is no paging' further clarifies its scope, making it unambiguous what this tool does.

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 provides strong usage context by explicitly referencing list_subscribers ('Takes the same filters as list_subscribers') and contrasting the return capabilities ('engagement metrics list_subscribers can filter on but not return'). This implies when to use this tool over the alternative, though it does not explicitly state exclusions or when to prefer list_subscribers. The lack of paging and asynchronous behavior also guide usage expectations.

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

get_analyticsA

Read one publication-level analytics report, covering the dashboard's Stats tabs: cohort retention, unsubscribes and their reasons, growth sources, referrals, audience overlap with other Substacks, subscriber Notes, paid growth, and timeseries for subscribers, followers and ARR. For per-post numbers use get_post_stats instead. Pick a report with report; the ones covering a period accept from_date and to_date and otherwise default to the last 30 days.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many rows to return. Only used by audience_overlap and subscriber_notes, which default to 6 and 8.
reportYesWhich report to read. unsubscribes — Unsubscribes in a window, broken down by the reason given. unsubscribes_timeseries — Unsubscribes over time. retention — Cohort retention: how much of each signup cohort is still subscribed months later. retention_summary — Headline retention rates at 1, 6 and 12 months. referrals_leaderboard — Which subscribers have referred the most readers. referrals_summary — Gifts sent, accepted and converted. audience_overlap — Other Substacks whose audience overlaps yours, with the overlap percentage — the publications worth collaborating with. audience_locations — How many distinct countries and US states your subscribers span. subscriber_notes — Recent Notes written by your subscribers. paid_subscriber_growth — Paid growth rate for the period, with new subscriptions and expirations. arr_timeseries — Annual recurring revenue over time. followers_timeseries — Follower count over time. subscribers_timeseries — Subscriber count over time. growth_sources — Where new subscribers came from in a window, ranked by how many each source brought. growth_events — The individual growth events in a window. network_attribution — What share of your subscribers arrived through the Substack network rather than your own channels.
to_dateNoEnd of the window, as YYYY-MM-DD. Defaults to today.
from_dateNoStart of the window, as YYYY-MM-DD. Only used by the reports that cover a period; defaults to 30 days ago, or a year ago for retention.

TDQS

A4.5/5.0
Behavior4/5

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

With no annotations, the description carries the disclosure burden. It states the tool is read-only ('Read'), explains report selection, and explicitly mentions date defaults (e.g., 'default to the last 30 days'). However, it does not detail edge cases like rate limits or result format, which is acceptable given the simple read nature.

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 two sentences, front-loaded with the core purpose, and includes only essential usage instructions and an alternative reference. Every clause earns its place without redundancy.

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 the tool's moderate complexity (4 params, 1 enum, no output schema), the description covers purpose, usage, report selection, date defaults, and an alternative path. It is sufficiently complete for an agent to invoke the tool correctly without additional context.

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 baseline is 3. The description provides a high-level summary of parameter behavior ('the ones covering a period accept from_date and to_date') but does not add per-parameter meaning beyond what the schema already specifies, such as the enum values or the limit usage details.

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's function: 'Read one publication-level analytics report,' with an explicit scope covering the dashboard's Stats tabs and a list of report types. It also distinguishes itself from a sibling by directing per-post queries to get_post_stats, ensuring no ambiguity.

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

Usage Guidelines5/5

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

The description tells the agent when to use this tool versus an alternative: 'For per-post numbers use get_post_stats instead.' It also explains how to select a report and the default date behavior, providing clear context for usage.

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

get_comment_threadA

Read one Note or comment together with the replies beneath it, each with its author and its position in the thread. Take the id from get_reader_feed or get_profile_feed.

ParametersJSON Schema
NameRequiredDescriptionDefault
comment_idYesThe numeric id of a Note or comment, from get_reader_feed or get_profile_feed. Without the `c-` prefix Substack uses in its urls.
include_repliesNoAlso fetch the reply branches beneath it. Defaults to true.

TDQS

A4/5.0
Behavior3/5

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

The description states it 'reads' a thread, implying a non-mutating operation, and describes what is returned (author, position). No annotations are provided, so the description carries the burden for behavioral disclosure. It doesn't mention permissions, edge cases, or limitations, but the read-only nature is clear.

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. The first states the action and expected result; the second provides sourcing guidance. No filler or redundancy.

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?

The description covers the core functionality and return content (author, position). With no output schema, it gives enough context for an agent to understand the tool's purpose. It omits details like pagination or failure modes, but for a simple single-thread read, it is reasonably 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 coverage is 100% and both parameter descriptions are rich: comment_id includes 'from get_reader_feed... Without the c- prefix' and include_replies has a clear default and purpose. The description text itself doesn't add parameter details, but the schema already provides full semantics, so 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?

The description uses the specific verb 'Read' and clearly identifies the resource as 'one Note or comment together with the replies beneath it', explicitly mentioning author and position. It also tells the user where to get the id (get_reader_feed or get_profile_feed), which distinguishes it from 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 says 'Take the id from get_reader_feed or get_profile_feed', providing a clear context for when this tool is used. However, it doesn't explicitly name alternatives or when not to use this tool, so it lacks explicit exclusions.

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

get_draftA

Read one draft post of your Substack publication in full, including its body and its audience and email settings. Take the id from list_posts or create_draft_post.

ParametersJSON Schema
NameRequiredDescriptionDefault
draft_idYesThe numeric id of the draft, as returned by list_posts (`id`) or create_draft_post (`draft_id`).

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations provided, the description carries the burden of disclosing behavioral traits. It explicitly says 'Read' and describes the return scope (full body, audience and email settings), clarifying that this is a non-destructive operation. It adds context about the id source, but doesn't discuss error handling or auth, which is acceptable for a straightforward read operation.

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 two concise sentences. The first states the core function and scope, the second gives actionable guidance on sourcing the id. No wasted words.

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?

The tool has one parameter, no output schema, and no annotations, but the description adequately covers the read operation by explaining what is returned (full content including body and settings) and how to obtain the id. It doesn't cover authentication or error scenarios, but given the simplicity, it's complete enough.

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 schema already provides 100% coverage for the single parameter, including its source via list_posts and create_draft_post. The description repeats this guidance in prose, adding no novel semantic value. The baseline of 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?

The description clearly states the purpose: reading one draft post in full, including body and audience/email settings. It distinguishes from sibling tools like list_posts (lists drafts) and update_draft (modifies), making the tool's role unambiguous.

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?

It explicitly directs the user to obtain the id from list_posts or create_draft_post, indicating the appropriate workflow. It implies this is for fetching full draft details rather than modifying or deleting, but doesn't explicitly state when not to use it. This is adequate guidance for a simple read tool.

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

get_post_commentsA

Read the comments on one of your posts, each with its author, text, reaction and reply counts, and its position in the thread. Comments withheld by Substack's automod are counted separately under automod_hidden_count rather than mixed in.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many comments to return. 1–100, defaults to 50.
post_idYesThe numeric id of one of your posts, from list_posts.

TDQS

A4.1/5.0
Behavior4/5

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

With no annotations provided, the description carries the full transparency burden. It discloses that comments withheld by automod are reported separately under automod_hidden_count, and enumerates the per-comment fields (author, text, reaction and reply counts, position). It does not mention sorting or pagination, but the schema covers the limit parameter, and the disclosed details go beyond a generic 'read comments' statement.

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 two sentences long, front-loads the primary purpose, and each sentence adds substantial value: the first defines the action and return contents, the second clarifies a non-obvious behavioral nuance (automod handling). No wasted words.

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?

There is no output schema, so the description must explain what is returned. It does so thoroughly by listing the fields for each comment and the automod_hidden_count. Combined with the schema's parameter documentation, this gives a complete picture for a read-only tool with two parameters.

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 has 100% coverage: both 'limit' and 'post_id' have clear descriptions with ranges and defaults. The description adds no additional parameter-specific semantics beyond what the schema already provides, so the baseline score of 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?

The description uses a specific verb ('Read') and resource ('comments on one of your posts'), clearly stating what the tool does. It also distinguishes itself from siblings like get_comment_thread and comment_on_post by focusing on the comments of a specific post, including thread position and automod handling.

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 for one's own posts ('on one of your posts') and notes that automod-hidden comments are counted separately, which is useful context. However, it does not explicitly state when to prefer this over sibling tools like get_comment_thread, nor does it provide exclusion criteria, leaving room for ambiguity.

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

get_post_statsA

Rank the posts of your Substack publication by any of 43 per-post metrics. This is how to tell which post actually grew the list (signups, subscribes, free_to_paid_upgrades), which was worth most (estimated_value), which cost you subscribers (unsubscribes), and which people read to the end (subscribers_finished_post) — alongside delivery, opens, clicks, views and restacks. Covers the whole archive with paging. There is no date filter: the endpoint ignores one, so narrow by sorting and paging instead.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many posts to return, 1-100, defaulting to 25.
offsetNoHow many posts to skip, for paging through the archive.
order_byNoWhich metric to rank by, defaulting to post_date. Use signups or subscribes for the posts that grew the list, estimated_value for the most valuable, unsubscribes for the ones that cost subscribers, subscribers_finished_post for the ones people actually read to the end.
order_directionNoSort direction, defaulting to desc — the highest first.

TDQS

A4.3/5.0
Behavior4/5

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

With no annotations, the description takes on the full burden. It discloses key behavioral traits: covers the whole archive, supports paging, and ignores any date filter. It also implies read-only behavior by 'Rank the posts'. It does not mention permissions, rate limits, or the exact response shape, but the core behaviors are well covered.

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

Conciseness4/5

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

The description is front-loaded with the verb and resource, then provides concrete usage examples. It is composed of two information-dense sentences that each earn their place. It is slightly long, but not padded, and does not waste words.

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 has no output schema and a moderately complex behavior (43 metrics, paging, no date filter), the description is quite complete. It explains primary use cases, the no-date-filter limitation, and the paging approach. Minor gaps like draft inclusion or exact return fields are not critical for selection and invocation.

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?

The input schema already documents all four parameters with 100% coverage, so the baseline is 3. The description adds meaningful semantic context, especially for order_by, by giving concrete examples like 'signups' for list growth and 'estimated_value' for worth, which goes beyond the schema's generic descriptions.

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 a specific action ('Rank the posts') with a clear object ('of your Substack publication') and a well-defined scope (43 per-post metrics). The focus on per-post metrics distinguishes it from siblings like get_publication_stats and get_analytics, which likely report aggregate stats.

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 provides explicit context for when to use the tool, such as identifying which posts grew the list or cost subscribers, and also gives practical guidance on the absence of a date filter, recommending sorting and paging instead. However, it doesn't explicitly name alternative tools for aggregate stats, so it stops short of a full when-to-use vs. alternatives.

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

get_post_tagsA

List the tags on one post, by name. The underlying endpoint returns only UUIDs, so this resolves them against the publication's tag list — it is the only way to read back what add_tag_to_post did, since neither get_draft nor list_posts carries tags.

ParametersJSON Schema
NameRequiredDescriptionDefault
post_idYesThe numeric id of the post, from list_posts. Works for drafts as well as published posts.

TDQS

A4.5/5.0
Behavior4/5

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

With no annotations, the description carries the full burden of behavioral disclosure. It reveals that the endpoint returns UUIDs and this tool resolves them against the publication's tag list, adding meaningful context beyond a simple 'list' operation. It does not explicitly state read-only behavior, but 'List' implies it.

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 two sentences with no fluff. The first sentence states the core functionality, and the second provides essential context about UUID resolution and why this tool is needed. Every word 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 one-parameter tool with no output schema, the description is complete. It explains what the tool does, why it exists (resolving UUIDs), and how it fits with sibling tools. The parameter is well-documented in the schema, and the output (tag names) is implicitly clear from 'by name.'

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 has 100% coverage with a clear description for post_id, so the baseline is 3. The tool description does not add extra parameter semantics beyond mentioning 'one post,' but the schema sufficiently documents the single parameter.

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 opens with 'List the tags on one post, by name,' which is a specific verb-resource pairing. It further distinguishes itself from siblings by stating it is the only way to read back what add_tag_to_post did, since neither get_draft nor list_posts carries tags.

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

Usage Guidelines5/5

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

The description explicitly explains when to use this tool: after using add_tag_to_post, because the alternatives (get_draft, list_posts) do not include tags. This clearly guides the agent on appropriate usage and exclusions.

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

get_profile_feedA

Read what one account has published — its Notes, its posts, or both. Defaults to your own account, so this is how to list the Notes you have written; pass user_id for anyone else.

ParametersJSON Schema
NameRequiredDescriptionDefault
typeNoFilter what the profile returns: 'notes' for the Notes they wrote, 'posts' for what they published, 'all' (default) for both.all
limitNoHow many entries to return. 1–50, defaults to 20.
cursorNoResume from an earlier page: pass the `next_cursor` from a previous response.
user_idNoWhose profile to read. Defaults to SUBSTACK_USER_ID — your own. Take another user's id from `author_user_id` on any Note returned by get_reader_feed.

TDQS

A4/5.0
Behavior3/5

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

With no annotations, the description must disclose behavioral traits. It mentions the default-to-own-account behavior and the type filter, but omits pagination details, authentication requirements, and any safety confirmation beyond the word 'Read'. This leaves some gaps in behavioral transparency.

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 main purpose and key behaviors. Every clause adds value: 'Read...', 'Defaults to your own account...', and 'pass user_id for anyone else.' No fluff.

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?

The description covers the core purpose, default behavior, and content-type filter, which is solid for a read-only feed tool. While it doesn't outline the response shape or pagination, the schema supplies parameter details, and the description is reasonably complete given its simplicity.

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% with detailed parameter descriptions. The description adds conceptual framing for 'notes'/'posts' and clarifies the default user, but does not materially exceed what the schema already provides. Baseline 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 clearly states the tool reads one account's published content (Notes, posts, or both), using the specific verb 'Read' and identifying the resource. It also distinguishes from sibling tools by clarifying the profile scope and defaulting to the agent's own account.

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 provides concrete usage context: defaulting to your own account for listing your Notes, and passing a user_id for anyone else. It doesn't explicitly name alternatives, but the guidance is sufficient to decide when to use this tool versus a general feed.

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

get_publicationA

Read the settings and identity of your Substack publication: name, subdomain, custom domain, hero text, copyright, sender name, logo, plans and payment state. Returns a projection by default; pass full: true for all 111 fields.

ParametersJSON Schema
NameRequiredDescriptionDefault
fullNoReturn the complete payload — all 111 fields, ~24 KB, including every notification toggle and the full HTML of the welcome email, terms and privacy pages. Defaults to false, which returns identity and the settings that are usually the point.

TDQS

A4.4/5.0
Behavior4/5

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

With no annotations, the description carries the full burden. It discloses the key behavioral trait: by default it returns a projection of fields, and passing full: true returns all 111 fields. This goes beyond the schema by explaining the 'projection' concept and the payload implications (~24 KB, including every notification toggle and HTML content). Since it's a read-only operation, no side-effect disclosure is needed.

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 a single, information-dense sentence that states the action, the resource, enumerates key fields, and explains the default vs. full behavior. Every element earns its place, and the most important information is front-loaded. There is zero fluff.

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 read tool with one optional parameter and no output schema, the description adequately covers the purpose, return content, and the difference between projection and full payload. It omits error handling or authentication details, but these are less critical for a simple read operation. The description is complete enough for an agent to decide on and invoke the tool correctly.

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 coverage is 100% with a detailed description of the 'full' parameter, so the baseline is 3. The tool description adds the 'projection' concept and clarifies the default behavior, which supplements the schema and gives the agent a better mental model of the parameter's effect.

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 the specific verb 'Read' and names the exact resource: 'settings and identity of your Substack publication'. It enumerates concrete fields (name, subdomain, custom domain, hero text, copyright, sender name, logo, plans and payment state), which clearly distinguishes it from the sibling tools that focus on drafts, posts, comments, or 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 description clearly implies when to use it: whenever you need the publication's settings or identity. It doesn't explicitly state exclusions or alternatives, but no sibling tool serves the same purpose, so the guidance is sufficient. A 4 reflects this clear context without explicit when-not-to-use conditions.

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

get_publication_statsA

Read the headline stats of your Substack publication: total and recent subscribers, ARR, site views, and the 30-day email open rate. Takes no arguments. For anything deeper — retention, churn, growth sources, referrals — use get_analytics.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden. It clearly states this is a read-only operation ('Read') and lists the exact metrics covered. It also notes it takes no arguments. It doesn't discuss possible rate limits or time windows for 'recent,' but for a stat-read tool this is reasonably transparent. A 5 would require more detail on data freshness or pagination, but the core behavior is clear.

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 exceptionally concise: two sentences, front-loaded with the action/scope, and every word adds value. It lists the key metrics and then directs users to the alternative without any fluff.

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 zero-parameter read-only stats tool, the description is complete. It tells exactly what data is returned, that no arguments are needed, and where to go for deeper metrics. No output schema exists, but for this simple tool, the description sufficiently covers what the agent needs to know.

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?

The tool has zero parameters, so the schema coverage is vacuously 100%. The baseline for 0 params is 4; the description reinforces this by explicitly stating 'Takes no arguments,' which is helpful but adds little beyond the schema. No deduction 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 uses a specific verb ('Read') and clearly identifies the resource ('your Substack publication') and the exact metrics returned (subscribers, ARR, site views, open rate). It also explicitly contrasts with the deeper get_analytics tool, distinguishing it from the most likely sibling.

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

Usage Guidelines5/5

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

The description gives clear when-to-use guidance for headline stats and explicitly states when not to use it: 'For anything deeper — retention, churn, growth sources, referrals — use get_analytics.' This provides an explicit alternative, satisfying the highest bar for usage guidance.

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

get_reader_feedA

Read the Substack Notes feed: Notes and posts surfaced to this account. Pick the feed with tab ('for-you' or 'subscribed'), and set include_tabs to discover the ids available.

ParametersJSON Schema
NameRequiredDescriptionDefault
tabNoWhich feed to read, by tab **id** — 'for-you' (default) or 'subscribed'. Set include_tabs to see the ids available. Use the id, never the display name: the names are localized.for-you
limitNoHow many entries to return. 1–50, defaults to 20.
cursorNoResume from an earlier page: pass the `next_cursor` from a previous response.
include_tabsNoAlso return the list of available feed tabs, with their ids. Defaults to false.

TDQS

A3.9/5.0
Behavior3/5

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

With no annotations, the description carries the behavioral disclosure burden. It adds useful context about account scoping and feed selection, but it does not mention pagination, response format, or failure behavior. This is a read operation, so the risk is lower, but the transparency is limited.

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 only two sentences, front-loads the core purpose, and every sentence contributes useful information. There is no repetition or filler.

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

Completeness3/5

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

The description explains what the tool returns and how to select feeds, but without an output schema or annotations, it leaves the response structure and pagination behavior implicit. The schema's cursor parameter hints at `next_cursor`, but the description itself is not fully complete for an agent that needs to understand the full call context.

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 schema already provides 100% coverage for all four parameters with descriptions. The description adds a small amount of value by explaining how `tab` and `include_tabs` interact, but it does not expand on `limit` or `cursor` beyond what the schema states.

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 ('Read') with a clear resource ('Substack Notes feed') and scopes it to 'this account,' distinguishing it from sibling tools like get_profile_feed and list_reader_posts. It immediately tells the agent what the tool does and what domain it operates in.

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 gives concrete guidance on how to use the tool ('Pick the feed with `tab`') and how to discover available options ('set include_tabs to discover the ids available'). It does not explicitly mention when to use this tool over alternatives, but the context is clear enough for an agent.

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

get_reader_postA

Read one post in full, from any publication — not just your own. Returns the body as HTML along with its author, stats and audience. This is the only way to read the text of someone else's post.

ParametersJSON Schema
NameRequiredDescriptionDefault
post_idYesThe numeric id of the post, from list_reader_posts or get_reader_feed. Works for posts from any publication, not only your own.
include_bodyNoInclude the post body as HTML. Defaults to true — reading the post is usually the point — but a full body runs to tens of KB, so set it to false when only the metadata is wanted.

TDQS

A4.3/5.0
Behavior4/5

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

With no annotations, the description carries the full burden. It discloses the return payload (HTML body, author, stats, audience) and warns about the size trade-off with include_body. The verb 'read' implies a safe, non-mutating operation, though it does not explicitly state side effects.

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?

Three sentences, front-loaded with the main purpose, and every sentence adds value: scope, return content, and unique usage. No wasted words.

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 tool with two parameters and no output schema, the description is complete. It covers what the tool does, when to use it, what it returns, and the trade-offs of the optional parameter.

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 new parameter-level details beyond the schema, but it reinforces that the body is HTML and that include_body controls body inclusion, matching the schema's explanation.

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's function: 'Read one post in full, from any publication — not just your own.' It uses a specific verb-resource combination and distinguishes itself from siblings by noting it is 'the only way to read the text of someone else's post.'

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 provides clear context for when to use the tool, emphasizing that it works for any publication and is the only way to view others' content. It does not explicitly name alternatives for reading your own posts, but the unique scope makes usage clear.

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

get_user_profileA

Read the account behind the session: id, handle, name, bio, and every publication you have a role on. This is how to discover which publications the session can reach, beyond the one SUBSTACK_PUBLICATION_URL points at.

ParametersJSON Schema
NameRequiredDescriptionDefault
fullNoReturn the complete payload, including the full `subscriptions` array. Defaults to false, which returns identity plus the publications you have a role on.

TDQS

A4.3/5.0
Behavior4/5

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

With no annotations, the description carries the full burden. It clearly states the operation is a read ('Read the account'), lists returned fields, and explains the relationship to SUBSTACK_PUBLICATION_URL. This gives the agent a transparent view of safety and outcome, though it could go further with pagination or error behavior.

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, zero fluff. The first sentence states purpose and payload, the second gives the distinguishing use case. Perfectly concise and front-loaded.

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 tool with one optional parameter and no output schema, the description covers purpose, return contents, and a specific use case (publication discovery). Nothing critical is missing; it is complete for this complexity level.

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% with a clear description of the 'full' parameter. The tool description does not explain the parameter, but the schema does. Baseline 3 is appropriate since the schema does the heavy lifting and the description adds no extra parameter context.

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 opens with a clear verb and resource: 'Read the account behind the session'. It specifies the exact data returned (id, handle, name, bio, and publications) and distinguishes this tool's purpose from any sibling by noting it reveals all reachable publications, not just the one from SUBSTACK_PUBLICATION_URL.

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?

Provides a concrete use case: discover which publications the session can reach beyond the single publication URL. This implies when to use the tool, though it does not explicitly mention alternatives or exclusions. The guidance is sufficient for context.

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

list_postsA

List the posts of your Substack publication: drafts, published posts or scheduled posts. Supports free-text search, pagination and sort direction. Each post is returned as a summary; use get_draft for the full content of an unpublished one.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many posts to return, 1-100, defaulting to 25.
offsetNoHow many posts to skip, for paging through the archive.
searchNoFree-text search over the posts, matching title and content.
statusYesWhich list to read: 'drafts' for unpublished work in progress, 'published' for posts already out, 'scheduled' for posts queued to go out later.
sort_directionNoOverrides the default order. Drafts and published posts are newest-first; scheduled posts are soonest-first. The column sorted on is fixed per status: draft_updated_at, post_date and trigger_at respectively.

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations, the description carries the full burden. It discloses that each post is returned as a summary and that unpublished posts require get_draft for full content, which is useful behavioral context. It also implies read-only behavior by using 'list' and does not mention side effects.

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 two concise sentences, front-loaded with the main purpose and followed by a useful note about output and an alternative tool. No unnecessary words.

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?

The tool is straightforward, and the description covers its key features (statuses, search, pagination, sort) and mentions the output format (summary). With no output schema, it gives a useful hint about what is returned. It sufficiently addresses the tool's purpose within the context of the sibling tools.

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 fully documents all parameters. The description adds some context by summarizing the statuses and mentioning search/pagination/sort, but it doesn't significantly supplement the parameter descriptions beyond what the schema provides.

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 action (list), the resource (posts of your Substack publication), and the scope (drafts, published, scheduled). It also mentions supporting search, pagination, and sorting, and explicitly distinguishes from get_draft for full content.

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 provides clear context for when to use the tool (listing posts by status) and explicitly points to get_draft for full content of unpublished posts, serving as an alternative. It doesn't explicitly state when not to use it, but the context is sufficient.

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

list_publication_tagsA

List every tag defined on your Substack publication, with its name, slug and whether it is hidden. Tag ids are UUIDs, so this is how to find one — though add_tag_to_post takes a name and does not need it.

ParametersJSON Schema
NameRequiredDescriptionDefault
include_hiddenNoInclude tags flagged `hidden` — defined on the publication but not shown in its navigation. Defaults to true, since a hidden tag is still usable on a post.

TDQS

A4.3/5.0
Behavior4/5

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

No annotations are provided, so the description must carry the full burden of disclosure. It accurately describes what the tool returns (name, slug, hidden status) and adds valuable context about UUIDs and the lack of need for a tag id in add_tag_to_post. It does not mention pagination or authentication, but for a simple list operation this is acceptable.

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 exceptionally concise: two sentences that front-load the purpose and output, followed by a useful clarification about UUIDs and the related tool. Every word earns its place, and there is no redundancy or fluff.

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 straightforward list operation with one optional parameter and no output schema, the description provides all essential context: the exact output fields, the significance of tag ids, and a pointer to a related tool. It leaves no critical gaps that would prevent an agent from using it correctly.

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 fully documents the only parameter, include_hidden, including its default value and rationale. The description adds no additional parameter-level detail, which is appropriate given the schema coverage is 100%. Baseline 3 is correct.

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's purpose: 'List every tag defined on your Substack publication, with its name, slug and whether it is hidden.' It specifies the resource (publication tags), the verb (list), and the output fields, making it distinct from sibling tools like get_post_tags and add_tag_to_post.

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 provides a concrete use case: finding a tag id (since ids are UUIDs). It also notes that add_tag_to_post takes a name and does not need the id, offering a clear when-not-to-use scenario. However, it does not explicitly compare against get_post_tags, leaving some ambiguity as to when one would choose this over that similar-sounding tool.

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

list_reader_postsA

List recent posts from the publications this account subscribes to — the reader Inbox — with read state and reading progress. Each post is summarised; use get_reader_post to read one.

ParametersJSON Schema
NameRequiredDescriptionDefault
afterNoResume from an earlier page: pass the `next_after` value from a previous response. This is a timestamp, not an opaque cursor.
limitNoHow many posts to return. 1–100, defaults to 20.

TDQS

A4/5.0
Behavior3/5

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

No annotations are provided, so the description carries the burden of behavioral disclosure. It reveals that each post is 'summarised' and includes 'read state and reading progress,' which goes beyond the tool's name. However, it does not disclose pagination behavior, error conditions, or permissions—though it is clearly a read operation.

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 two sentences, front-loaded with the core purpose, and ends with an actionable pointer to the sibling tool. Every word earns its place with no redundancy.

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 list tool with no output schema, the description adequately explains what is returned (summaries, read state, progress) and points to the tool for full content. It could mention explicit ordering (e.g., 'newest first'), but 'recent' implies recency, making this largely 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?

The input schema provides 100% coverage for both parameters (after and limit) with clear descriptions. The tool description adds no parameter-specific detail beyond what the schema already conveys, so the baseline of 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?

The description clearly states the tool's function with a specific verb ('List'), a resource ('posts'), and a scoped context ('publications this account subscribes to — the reader Inbox'). It also distinguishes itself from sibling tools like list_posts and get_reader_feed by referencing the reader Inbox and explicitly naming get_reader_post as the tool for reading a single post.

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 provides clear context for use (reader Inbox) and explicitly mentions an alternative ('use get_reader_post to read one'). It does not explicitly exclude other sibling tools like get_reader_feed, but the reader Inbox framing makes the appropriate usage clear.

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

list_subscribersA

List and filter the subscribers of your Substack publication. Supports the same 48 columns and operators as the Subscribers dashboard, combined with AND, plus free-text search, sorting and pagination. Returns count, the total matching the filters regardless of limit, so a call with limit 1 is a cheap way to size a segment. Engagement columns (email opens, post views, comments, shares, activity rating) can be filtered on here but are not part of the records this tool returns — use export_subscribers to read their values.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many subscribers to return, 1-100, defaulting to 25.
offsetNoHow many subscribers to skip, for paging through a large segment.
searchNoFree-text search over subscriber name and email.
filtersNoConditions to apply, combined with AND. The API supports no OR and no nesting, so anything needing OR has to be issued as separate calls. Omit to match every subscriber.
sort_byNoColumn to sort by. Any filterable column works.
sort_directionNoSort direction, defaulting to desc.

TDQS

A4.6/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden and does well by disclosing non-obvious behaviors: the count is independent of limit (making limit=1 a segment-size probe), engagement columns can be filtered on but are not part of returned records, and filters combine with AND. It lacks explicit read-only/permission notes, but the read-style action is implied and the disclosed quirks are highly valuable.

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?

Three tight sentences: purpose, feature set, count behavior, and an alternative-tool pointer. Each sentence carries distinct information with no filler or repetition. The structure front-loads the core purpose and then adds operational details in a logical order.

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 complexity (6 params, nested filters with 48 columns) and the very rich schema, the description supplies enough high-level context: it explains the AND limitation, the count behavior, and the division of labor with export_subscribers. It does not enumerate the returned record fields, but the schema and the explicit exclusions make the tool usable without that detail.

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?

The schema already covers all parameters with descriptions (100% coverage), so the baseline is 3. The description adds meaningful extra semantics: the limit parameter's use for cheap segment sizing via count, and the clarification that the filters parameter can include engagement columns even though they are not returned. These enrich the parameter understanding 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?

The description opens with a clear verb-resource pair: 'List and filter the subscribers of your Substack publication.' It further distinguishes itself from sibling tools, especially export_subscribers, by explicitly noting that engagement columns are filterable here but returned only via export_subscribers. This makes the tool's role unambiguous.

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

Usage Guidelines5/5

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

The description gives explicit when-to-use context: it supports dashboard-like filtering, AND-only combination, and a cheap count-based segment sizing pattern. It also provides an explicit alternative: 'use export_subscribers to read their values' for engagement metrics, clearly directing the agent away from this tool for that need.

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

list_subscriptionsA

List the Substack publications this account subscribes to, with the plan, membership state and whether emails are on. Excludes paused and expired subscriptions unless asked otherwise. This is what the account reads, not who reads it — for your own subscribers use list_subscribers.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many subscriptions to return. 1–500, defaults to 100.
active_onlyNoExclude paused subscriptions and ones whose paid term has expired. Defaults to true.

TDQS

A4.4/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral burden. It discloses output fields, default filtering of paused/expired, and directionality. It does not detail auth or return structure, but core behavior is transparent.

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 primary action, then the exclusion and differentiation. No redundant 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 list tool, the description covers purpose, default filters, and alternatives. Return structure is only partially specified, but given the schema covers parameters, this is adequate. A perfect score would require explicit mention of pagination or return format.

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% with detailed descriptions for both limit and active_only. The description adds context tying active_only to the exclusion behavior, but does not materially expand parameter semantics 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?

The description uses the specific verb 'List' with a clear resource ('Substack publications this account subscribes to') and explicitly distinguishes itself from list_subscribers by clarifying directionality. This makes the purpose unmistakable.

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

Usage Guidelines5/5

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

It explicitly states when to use this tool ('This is what the account reads') and names the alternative ('for your own subscribers use list_subscribers'). It also describes default behavior and the condition to change it.

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

publish_draftA

Publish a draft. The post goes live on the web; send additionally emails it to subscribers and defaults to false, because an email cannot be recalled. Publishing cannot be undone from this server — there is no unpublish tool.

ParametersJSON Schema
NameRequiredDescriptionDefault
sendNoWhether to email the post to subscribers. Defaults to false: the post goes live on the web either way, but an email cannot be unsent, so this must be asked for explicitly.
draft_idYesThe numeric id of the draft to publish, as returned by list_posts (`id`) or create_draft_post (`draft_id`).

TDQS

A4.4/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden. It discloses the key side effects: the post goes live, it may email subscribers, and the action is irreversible. This goes beyond the schema and gives the agent essential behavioral context.

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?

Three sentences with no wasted words. The purpose is front-loaded, and each sentence adds a distinct piece of information: action, side effects, irreversibility.

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 two-parameter tool with no output schema, the description covers the main behavioral aspects, including irreversible consequences. It could mention the return value, but given the lack of output schema and the straightforward nature of the operation, it is sufficiently 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?

The input schema already has complete descriptions for both parameters (100% coverage), so the baseline is 3. The description reiterates the email risk but does not add new parameter-specific details beyond what the schema provides.

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 publishes a draft, making it live on the web, and distinguishes the optional email behavior. It also contrasts with siblings like update_draft and delete_draft by noting publishing is irreversible.

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?

It provides context on when to use: to make a draft public, with a caution that publishing cannot be undone. It also explains that `send` should only be true when email is intended, since email cannot be recalled. This is good guidance, though it doesn't explicitly compare to alternatives beyond noting the absence of an unpublish tool.

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

restack_itemA

Restack a Note to your own followers. This is public, appears on your profile, and cannot be undone from here — a restack has no id of its own, so there is nothing to delete. Notes only: restacking a post is not supported, because that endpoint 404s even on a valid post id.

ParametersJSON Schema
NameRequiredDescriptionDefault
tab_idNoThe feed tab the restack is attributed to. Defaults to 'for-you'.for-you
comment_idYesThe numeric id of the Note to restack, from get_reader_feed or get_profile_feed. Without the `c-` prefix Substack uses in its urls.

TDQS

A4.5/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions public visibility, appearance on profile, irreversibility, lack of deletion handle, and 404 errors for posts—comprehensive for a mutation tool.

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 main action, followed by essential caveats. No wasted words or repetition.

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 2-parameter mutation with no output schema, the description covers purpose, public side effects, irreversibility, and input restrictions. It is complete and well-suited to the tool's complexity.

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%, and the schema already provides detailed semantics for both parameters (comment_id source/format, tab_id default). The description adds no parameter-specific information, so baseline 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 opens with 'Restack a Note to your own followers', clearly stating the action (restack), the resource (Note), and the audience. It distinguishes from sibling tools by using 'restack' which appears nowhere else, and adds scope with 'Notes only' to exclude 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?

Provides explicit when-not guidance: 'Notes only: restacking a post is not supported' and warns about irreversibility ('cannot be undone from here'). It does not name alternative tools, but the context strongly implies when to use it.

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

set_post_bodyA

replace the body of a draft with a Substack document. This is the only way to write structured content — headings, lists, links, code blocks, images, buttons and a paywall. Create the draft first with create_draft_post, then call this with its id. The result reports how many nodes of each type were stored, so a caller can confirm that what it asked for is there. An image must already be hosted by Substack: this cannot upload one.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyYesThe post body as a Substack ProseMirror document.
draft_idYesThe numeric id of the draft to write, as returned by list_posts (`id`) or create_draft_post (`draft_id`).

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations, the description carries the transparency burden. It discloses the replacing/destructive nature, the result verification behavior ('reports how many nodes of each type were stored'), and a key constraint ('An image must already be hosted by Substack'). It does not mention permissions, rate limits, or what happens to an existing body, but those are not expected in every description.

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?

Five sentences, each carrying distinct value: purpose, uniqueness + content types, prerequisite workflow, result verification, and image constraint. No filler, front-loaded with the core action.

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 a very complex nested ProseMirror schema and no output schema, the description covers the essential selection and invocation context: what it replaces, why it is unique, the prerequisite, the verification result, and a critical input constraint. It omits explicit error/return details, but given the schema's exhaustive structure, this is a strong, complete description.

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 baseline is 3. The description adds the image-hosting constraint and the create_draft_post prerequisite, but these largely echo or lightly augment the schema's own notes (e.g., draft_id 'as returned by list_posts or create_draft_post'). It does not add substantial meaning beyond the highly detailed 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?

Uses a specific verb-resource pair: 'replace the body of a draft with a Substack document.' It explicitly differentiates from siblings by stating it is 'the only way to write structured content' and enumerates the supported node types (headings, lists, links, code blocks, images, buttons, paywall).

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?

Gives an explicit workflow: 'Create the draft first with create_draft_post, then call this with its id.' The phrase 'the only way to write structured content' signals when to prefer it over alternatives like update_draft. However, it does not explicitly name update_draft as the alternative for non-structured edits, so it falls just short of a 5.

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

update_draftA

Change an existing draft's title, subtitle or any of its Post settings: audience, who may comment, comment order, cover image, social preview title and description, SEO title and description, and URL slug. The update is partial: only the fields you pass change, everything else — including the body — is left alone. A cover_image not already hosted by Substack is downloaded and re-hosted first. Take the id from list_posts or create_draft_post.

ParametersJSON Schema
NameRequiredDescriptionDefault
slugNoThe post's URL slug, the last segment of its public address. Changing it changes the URL the post will be published at. Omit to leave it unchanged.
audienceNoWho the post is for. `founding` is accepted by the API although the editor does not offer it. Omit to leave it unchanged.
draft_idYesThe numeric id of the draft to update, as returned by list_posts (`id`) or create_draft_post (`draft_id`).
cover_imageNoThe post's cover image, used for the social preview. A URL already on substack-post-media.s3.amazonaws.com or substackcdn.com is used as-is; any other URL is downloaded and re-hosted on Substack first, because Substack server-fetches only its own bucket. Private, loopback and link-local hosts are refused. Max 10 MB. HEIC is not accepted. Omit to leave it unchanged.
descriptionNoThe description shown in the social preview. This is NOT the subtitle — draft_subtitle is the subtitle. Omit to leave it unchanged.
draft_titleNoNew title. Omit to leave it unchanged.
social_titleNoThe title shown when the post is shared on other platforms. Distinct from draft_title, which is the title on the post itself. Omit to leave it unchanged.
draft_subtitleNoNew subtitle. Omit to leave it unchanged.
search_engine_titleNoThe SEO title. Substack recommends under 60 characters. Omit to leave it unchanged.
default_comment_sortNoThe order comments are shown in. Omit to leave it unchanged.
search_engine_descriptionNoThe SEO description. Substack recommends 50-160 characters. Omit to leave it unchanged.
write_comment_permissionsNoWho may comment. `subscribers` means free or paid; `none` disables comments. Omit to leave it unchanged.

TDQS

A4.4/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden, and it delivers meaningful disclosure: partial-update semantics, the body being untouched, and the side effect that a cover_image not already hosted by Substack is downloaded and re-hosted first. It does not cover auth/rate-limit/error behavior, but the disclosed traits go well beyond a bare mutation description.

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?

Four sentences, front-loaded with purpose, followed by the partial-update rule, the cover_image re-hosting behavior, and id sourcing. Every sentence earns its place; no redundancy with the schema.

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 12-parameter mutation tool with no annotations and no output schema, the description covers the essentials: scope of what changes, the safety-relevant partial-update behavior, a non-obvious side effect (re-hosting), and parameter provenance. The schema fills in all parameter details. The only gap is the absence of return-value/error information, which the description could have mentioned since there is no output schema.

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 description coverage is 100%, so the baseline is 3. The description adds genuine value beyond the per-parameter schema by stating the partial-update rule — 'only the fields you pass change' — which contextualizes all 11 optional parameters as omit-to-keep semantics. This is a meaningful addition, justifying a 4.

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 opens with a specific verb and resource: 'Change an existing draft's title, subtitle or any of its Post settings', followed by an explicit field list. This clearly differentiates update_draft from siblings like create_draft_post, set_post_body, publish_draft, and delete_draft.

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 gives clear context on when to use the tool: it is a partial update ('only the fields you pass change'), and explicitly notes the body is left alone, which implies set_post_body is the alternative for body edits. It also tells the agent where to source the required id ('Take the id from list_posts or create_draft_post'). It stops short of explicitly naming when-not-to-use alternatives, so it earns a 4 rather than a 5.

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

upload_imageA

Host an image on your Substack publication and get back a Substack URL — the one thing image2.src in set_post_body and cover_image in update_draft will actually render. The source is either an http(s) URL, which the server downloads and re-hosts, or path, an absolute path to a local file on the machine running this server, which is read straight from disk: use that for an image you generated or edited locally, with no need to publish it anywhere first. Private and loopback hosts are refused, HEIC is not accepted, max 10 MB.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNoThe http(s) URL of an image to upload. The server downloads it and re-hosts it on Substack. Private, loopback and link-local hosts are refused. Max 10 MB. HEIC is not accepted. Provide exactly one of `url` or `path`.
pathNoAbsolute path to an image file on the machine running this server, read directly from disk with no download. Use this for a locally generated or edited image. The path must be absolute — a relative one would resolve against the server's working directory, not the caller's. The type is detected from the file's contents, not its extension: PNG, JPEG, GIF and WebP are accepted, HEIC is not. Max 10 MB. Provide exactly one of `url` or `path`.
post_idNoOptional id of the post the image belongs to. Its effect is unconfirmed.

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations, the description carries the full burden and does so admirably. It discloses key behaviors: server-side download for URL, direct disk read for path, refusal of private/loopback/link-local hosts, rejection of HEIC, max 10 MB, and content-based type detection. These are non-obvious and crucial for a caller to avoid errors.

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 a single, dense paragraph that front-loads the main action and then provides the two variants and constraints. Every sentence adds critical information; there is no filler. It is appropriately sized given the number of constraints and the need to distinguish the two input modes.

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?

No output schema exists, yet the description clearly states the return value ('get back a Substack URL'). It covers the two input modes, constraints, and the relationship to callers. The tool's complexity is moderate, but the description leaves no critical unknowns for a caller to invoke it correctly.

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 baseline is 3. The tool description largely restates what the schema already says for the url and path parameters (refusal constraints, max size, HEIC). It adds the 'will actually render' tie-in, which aids purpose, but does not materially extend parameter semantics 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?

The description opens with a specific verb and resource: 'Host an image on your Substack publication and get back a Substack URL.' It clearly distinguishes this tool from siblings by noting the returned URL is exactly what image2.src in set_post_body and cover_image in update_draft will render. This is unambiguous and contextually differentiated.

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

Usage Guidelines5/5

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

Explicitly explains when to use each input mode: 'url' for remote images the server downloads and re-hosts, 'path' for locally generated or edited images read directly from disk. It also ties the tool to the consuming tools (set_post_body, update_draft), making the use case evident. No alternatives are listed among siblings because none exist; the guidance is fully sufficient.

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

TDQS

A3.6/5.0
Disambiguation4/5

Most tools have clearly distinct purposes, but a few pairs overlap, such as list_subscribers/export_subscribers and get_publication_stats/get_analytics. However, descriptions clarify these boundaries, making misselection unlikely.

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern (e.g., create_draft_post, list_subscribers, set_post_body). No mixed casing or irregular conventions are present, making the naming highly predictable.

Tool Count2/5

With 27 tools, the server exceeds the guideline of 3-15 for a well-scoped set and falls into the 'too many' category. While the domain is broad, several tools could be consolidated, such as the analytics and stats tools.

Completeness2/5

The server covers core draft and publishing workflows, but significant gaps exist: no way to edit or delete published posts, no tag removal, no scheduling, and no subscriber management. These omissions create dead ends after publishing.

Maintenance

ActivityMaintained
ResponsivenessSyncing

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/marcomoauro/substack-mcp'

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