A single-tenant hosted MCP server that lets you ask cross-publication questions about your Substack subscriptions inside Claude Desktop.
"What are my subs saying about X this week?" — the question generic ChatGPT can't answer because it doesn't have your subs. This is the missing connector.
- Single-tenant. Deployed to your Cloudflare account. You're the only user. No accounts, no signups, no UI.
- RSS-only. Polls the public
/feedendpoint of every Substack you list. Works for free posts; paid-only posts return only public teasers in v0.1 (paid content support → v2 via email forwarding). - MCP-native. Exposes a JSON-RPC 2.0 endpoint at
/mcp. Drop the URL + bearer token into Claude Desktop's config; ask questions in chat. - Cheap. Cloudflare Workers + D1. Free tier comfortably covers a single user with ~50 subs.
| Tool | Purpose |
|---|---|
search_subscriptions |
FTS5 keyword search across all your indexed articles. The main one. |
list_publications |
List indexed pubs with article counts and latest post date. |
get_recent_posts |
N most recent posts from one pub (by slug). |
get_article |
Full extracted text of one article by URL. |
| Route | Purpose |
|---|---|
POST /admin/poll |
Manually trigger the RSS poller now. Same Authorization: Bearer as /mcp. Useful after editing SUBSTACK_FEEDS and redeploying — saves 5 minutes. |
npm install
cp wrangler.toml.example wrangler.tomlwrangler.toml is gitignored — that's where your real database_id and SUBSTACK_FEEDS list will live. The .example template is the version that ships in the repo.
You need a Cloudflare account (free tier is fine). Then:
npx wrangler loginnpx wrangler d1 create substack-mcpCopy the database_id from the output and paste it into wrangler.toml (replacing REPLACE_WITH_YOUR_D1_DATABASE_ID).
npm run migrate:remote(For local dev you can use npm run migrate:local against Miniflare's local D1.)
Generate a random token (any string >32 chars) and set it as a Worker secret:
# Generate one if you don't have a preferred method:
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
# Then set it:
npx wrangler secret put MCP_BEARER
# Paste the token when promptedSave the token — you'll paste it into Claude Desktop's config below.
Edit wrangler.toml. Set SUBSTACK_FEEDS to a comma-separated list:
[vars]
SUBSTACK_FEEDS = "https://www.astralcodexten.com/feed,https://stratechery.com/feed,https://acme.substack.com"You can paste either bare hosts (stratechery.com) or full feed URLs (https://stratechery.com/feed). The poller normalizes both.
npm run deployWrangler prints the deployed URL, e.g. https://substack-mcp.your-subdomain.workers.dev.
The cron runs every 5 minutes. The first run will populate publications from your SUBSTACK_FEEDS list and start fetching articles. Within ~10 minutes you should have content.
You can verify by curling:
curl https://substack-mcp.your-subdomain.workers.dev/health
# → {"status":"ok"}
curl -X POST https://substack-mcp.your-subdomain.workers.dev/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_publications","arguments":{}}}'Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"substack-mcp": {
"url": "https://substack-mcp.your-subdomain.workers.dev/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}Note: Claude Desktop's remote-MCP config keys may vary by version. If your version doesn't support
url + headersdirectly, usemcp-remoteas a stdio adapter:{ "mcpServers": { "substack-mcp": { "command": "npx", "args": ["-y", "mcp-remote", "https://substack-mcp.your-subdomain.workers.dev/mcp", "--header", "Authorization: Bearer YOUR_TOKEN"] } } }
Restart Claude Desktop. You should see substack-mcp in the MCP servers list.
Open Claude Desktop and ask:
"What are my subs saying about [topic] this week?"
Claude will call search_subscriptions with your query and synthesize an answer with citations.
npm run dev # local Wrangler dev server with Miniflare
npm test # run unit tests
npm run typecheck # tsc --noEmit┌─────────────────────────────────────────────────────────────────┐
│ │
│ Cron (every 5 min) ──► poller.ts ──► fetches up to 25 │
│ eligible /feed URLs │
│ ▼ │
│ rss.ts (parse XML) │
│ ▼ │
│ extract.ts (clean HTML) │
│ ▼ │
│ articles.ts (upsert) │
│ ▼ │
│ ┌──────────┐ │
│ │ D1 SQLite│ │
│ │ + FTS5 │ │
│ └──────────┘ │
│ ▲ │
│ │ │
│ POST /mcp ──► index.ts (auth) ──► mcp.ts ────┘ │
│ ▲ │
│ │ │
│ Claude Desktop │
│ │
└─────────────────────────────────────────────────────────────────┘
These are deferred to v0.2+ (gated on you actually using v0.1 daily for 2+ weeks):
- Multi-tenancy (users, signups, magic-link, JWT, multi-user MCP tokens)
- Web UI (landing page, settings page)
- Email-forwarding ingestion (paid Substack content)
- ChatGPT support (needs OAuth 2.1 PKCE)
- Vector search
If you want to ship v0.2 for "Brian" or anyone else, that's a separate ~7-day sprint. v0.1 first; signal first.
- Free Substack content only — paid posts return public teasers only via RSS.
- No historical backfill — only articles published after a sub is added are indexed.
- Claude Desktop only — ChatGPT integration is v1.1 work.
- No UI for managing subs — edit
wrangler.tomland redeploy.
MIT.