Skip to main content
Glama
thenewrhythm

Substack MCP Server

by thenewrhythm
README.md
# Substack MCP Server

[![Buy Me A Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-support-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/thenewrhythm)

A Model Context Protocol server that lets Claude read from — and optionally draft
to — a Substack publication.

Substack has **no official public API**. This server uses the same unofficial
JSON endpoints the Substack website itself calls, plus the public RSS feed. That
has two consequences worth knowing up front:

- **Read tools work with no login** and are reliable.
- **Writing (drafts) needs your session cookie**, and because it rides on
  undocumented endpoints, treat `create_draft` as experimental — Substack can
  change these endpoints without notice.

## Tools

| Tool | Auth? | What it does |
|------|-------|--------------|
| `get_publication_info` | no | Connection check; publication name, URL, latest post. |
| `list_recent_posts` | no | Recent posts, newest first, with free/paid flag. |
| `search_posts` | no | Keyword search across your posts. |
| `get_post` | no | Full content of one post by slug (API, RSS fallback). |
| `create_draft` | **yes** | Creates a *draft* (never publishes). Attaches your author byline. Needs `SUBSTACK_SID`. |
| `list_drafts` | **yes** | Lists your unpublished drafts with edit URLs. |
| `create_note` | **yes** | Publishes a plain-text Note. **Live/public — no draft step.** |
| `create_note_with_link` | **yes** | Publishes a Note with a link-preview card. **Live/public.** |
| `create_note_with_image` | **yes** | Publishes a Note with an uploaded image. **Live/public.** |

A local `.env` file (next to `server.py`) is loaded automatically, so you can
keep `SUBSTACK_PUBLICATION` / `SUBSTACK_SID` there for local runs instead of
exporting them by hand. Copy `.env.example` to `.env` to start.

## Setup

```bash
cd substack-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
```

Set your publication (subdomain only):

```bash
export SUBSTACK_PUBLICATION=your-subdomain
python server.py   # should start and wait silently on stdio
```

Ctrl-C to stop. If it exits with an error about `SUBSTACK_PUBLICATION`, that
variable isn't set.

## Connect it to Claude Code

From the repo directory:

```bash
claude mcp add substack \
  --scope user \
  --env SUBSTACK_PUBLICATION=your-subdomain \
  -- /absolute/path/to/substack-mcp/.venv/bin/python /absolute/path/to/substack-mcp/server.py
```

Then `claude mcp get substack` should show **✔ Connected**. The write tools read
`SUBSTACK_SID` from your local `.env` (see below), so it never lands in config.

## Connect it to Claude Desktop

Edit your Claude Desktop config file:

- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

Add (use **absolute paths**, and your venv's python if you made one):

```json
{
  "mcpServers": {
    "substack": {
      "command": "/absolute/path/to/substack-mcp/.venv/bin/python",
      "args": ["/absolute/path/to/substack-mcp/server.py"],
      "env": {
        "SUBSTACK_PUBLICATION": "your-subdomain",
        "SUBSTACK_SID": ""
      }
    }
  }
}
```

Fully quit and reopen Claude Desktop. You should see the `substack` tools appear.
Try: *"Use the substack tools to list my 5 most recent posts."*

## Enabling the write tools (optional)

The write tools (`create_draft`, `create_note`, `create_note_with_link`,
`create_note_with_image`, `list_drafts`) act on your behalf, so they need proof
you're logged in. There's no API key, so it uses your browser session cookie:

1. Log into Substack in your browser.
2. Open DevTools → **Application** (Chrome) / **Storage** (Firefox) → Cookies →
   `https://substack.com`.
3. Copy the value of the **`substack.sid`** cookie.
4. Copy `.env.example` to `.env` and paste it after `SUBSTACK_SID=`.

**Security notes:** this cookie is equivalent to your login — anyone with it can
act as you. It lives only in your local `.env` (git-ignored). Don't commit it,
don't paste it into chats, and rotate it (log out / back in) if it leaks. It
expires periodically; when a write tool starts returning auth errors, refresh it.

> ⚠️ **Notes publish immediately.** `create_draft` only ever makes a **draft** you
> publish yourself, but `create_note*` post to your public Notes feed the moment
> they run — there is no draft step. Treat them as a real send.

## Limitations & notes

- Draft and note bodies convert plain paragraphs (blank-line separated) into
  Substack's editor format — no bold/italic/headings yet. Drafts are meant to
  *seed* a post you finish in the editor.
- Paywalled post bodies only come through in full when authenticated; otherwise
  you get the free preview.
- The unofficial endpoints can change without notice. If a read tool starts
  failing, the `/api/v1/archive` shape or `/feed` is the first thing to check.

## Extending it

Natural next tools: `list_comments(slug)`, `delete_note(id)`, scheduling, or
richer Markdown→ProseMirror conversion (bold/italic/links/headings). Each is a
new `@mcp.tool()` function in `server.py`.

## Support

Every tool here started as a mess in my own week — a client problem I kept
solving by hand, until I built something that could run it for me. I ship them
open, MIT, because a tool you can hand to someone else is the only proof the
thinking was real.

A coffee keeps the tools free and maintained, and funds the next one — built in
the open.

[![Buy Me A Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-support-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/thenewrhythm)

## Disclaimer

This project is not affiliated with or endorsed by Substack. It uses
undocumented endpoints that may change or break at any time. Use at your own
risk, and in line with Substack's terms of service.