{
  "markdown": "# substack-mcp\n\n\n<!-- mycelium-badges:start -->\n\n<p>\n  <a href=\"https://github.com/adelaidasofia/substack-mcp/blob/main/LICENSE\"><img alt=\"License\" src=\"https://img.shields.io/github/license/adelaidasofia/substack-mcp?color=blue\"></a>\n  <a href=\"https://github.com/adelaidasofia/substack-mcp/stargazers\"><img alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/adelaidasofia/substack-mcp?color=eab308\"></a>\n  <a href=\"https://github.com/adelaidasofia/substack-mcp/commits/main\"><img alt=\"Last commit\" src=\"https://img.shields.io/github/last-commit/adelaidasofia/substack-mcp\"></a>\n  <a href=\"https://github.com/adelaidasofia/substack-mcp/issues\"><img alt=\"Open issues\" src=\"https://img.shields.io/github/issues/adelaidasofia/substack-mcp\"></a>\n  <a href=\"https://pypi.org/project/adelaidasofia-substack-mcp/\"><img alt=\"PyPI version\" src=\"https://img.shields.io/pypi/v/adelaidasofia-substack-mcp?color=blue&label=pypi\"></a>\n  <a href=\"https://pypi.org/project/adelaidasofia-substack-mcp/\"><img alt=\"PyPI downloads\" src=\"https://img.shields.io/pypi/dm/adelaidasofia-substack-mcp?color=blue&label=downloads\"></a>\n  <a href=\"https://myceliumai.co\"><img alt=\"Built by Mycelium AI\" src=\"https://img.shields.io/badge/built_by-Mycelium_AI-15B89A\"></a>\n</p>\n\n<!-- mycelium-badges:end -->\n\nFastMCP server for Substack: publish Notes and posts, pull analytics, manage drafts, bridge Obsidian vault drafts to Substack, and generate visual cards with pluggable image generators.\n\n## Quick reference\n\n```\nsubstack__test_connection             -- verify auth (start here)\nsubstack__publish_note text=...       -- publish a Note immediately\nsubstack__list_vault_drafts           -- show drafts from your vault file\nsubstack__publish_vault_draft index=N -- publish one vault draft as a Note\nsubstack__create_draft title=... body=... -- create a post draft\nsubstack__get_dashboard days=30       -- subscriber + view KPIs\nsubstack__capture_analytics_to_vault  -- snapshot analytics to vault markdown\n```\n\n## Tool inventory (30)\n\n### Notes + vault pipeline\n\n- `test_connection(publication?)` -- verify auth, return your profile\n- `list_publications()` -- show configured publications and which is default\n- `publish_note(text, publication?, attachment_ids?)` -- publish a Note immediately; markdown supported\n- `create_note_attachment(image_path?, image_url?, link_url?, publication?)` -- upload image or register link; returns attachment UUID for use with publish_note\n- `list_my_notes(limit?, publication?)` -- your recent Notes with reaction + comment counts\n- `reply_to_note(note_id, text, publication?)` -- reply to a Note by ID\n- `list_vault_drafts()` -- parse vault drafts file, return index + preview for each draft\n- `publish_vault_draft(index, publication?, move_to_published?)` -- publish one vault draft as a Note\n- `batch_publish_vault_drafts(indices, publication?)` -- publish multiple vault drafts in sequence\n\n### Post management\n\n- `create_draft(title, body, subtitle?, audience?, publication?, section_id?)` -- create a post draft; body is markdown\n- `update_draft(draft_id, title?, body?, subtitle?, audience?, publication?, section_id?)` -- edit an existing draft\n- `publish_post(draft_id, send_email?, audience?, publication?, section_id?)` -- publish a draft live to subscribers\n- `schedule_post(draft_id, publish_at, publication?, section_id?)` -- schedule a draft for future publication (ISO 8601)\n- `list_sections(publication?)` -- list configured sections for a publication; required to publish on publications with sections\n- `list_drafts(limit?, publication?)` -- list unpublished drafts\n- `list_published(limit?, publication?)` -- list published posts with basic stats\n- `get_post(identifier, publication?)` -- get full post by slug or numeric ID\n- `upload_image(image_path, publication?)` -- upload an image to Substack CDN; returns CDN URL\n- `react(post_id, publication?)` -- heart a post\n- `restack(post_id, publication?)` -- restack a post\n- `comment(post_id, body, publication?)` -- comment on a post\n- `get_feed(limit?, publication?)` -- your reader feed (posts from publications you follow)\n\n### Analytics\n\n- `get_dashboard(days?, publication?)` -- KPIs: total/paid subscribers, views, growth, ARR\n- `get_post_stats(post_id, publication?)` -- views, opens, clicks, shares, conversions for one post\n- `get_subscriber_growth(publication?)` -- subscriber count over time\n- `get_growth_sources(publication?)` -- subscriber growth by source (search, recommendations, direct, social)\n- `get_top_posts(limit?, publication?)` -- posts ranked by engagement\n- `get_earnings(publication?)` -- revenue data for paid publications\n- `get_recommendation_stats(direction?, publication?)` -- recommendation network performance\n- `capture_analytics_to_vault(publication?)` -- write a weekly analytics snapshot to your vault\n\n## Install\n\nOpen Claude Code, paste:\n\n    /plugin marketplace add adelaidasofia/substack-mcp\n    /plugin install substack-mcp@substack-mcp\n\n<details><summary>Legacy install</summary>\n\n```bash\ngit clone https://github.com/adelaidasofia/substack-mcp\ncd substack-mcp\npip3 install -r requirements.txt\npython3 -c \"import server; print('OK')\"\n```\n\n</details>\n\n## Configuration\n\nCopy `config.example.json` to `config.json` and fill in your values:\n\n```bash\ncp config.example.json config.json\n```\n\n`config.json` is gitignored. Key fields:\n\n| Field | Description |\n|---|---|\n| `publications[].name` | Internal name used in tool calls |\n| `publications[].subdomain` | Your Substack subdomain (e.g. `yourname`) |\n| `publications[].cookie` | Session cookie (see below) |\n| `default_publication` | Which publication to use when `publication` arg is omitted |\n| `vault_drafts_path` | Path to your vault drafts markdown file |\n| `image_generator.default` | Active image adapter: `pillow_local` or `canva` |\n\n## Session cookie setup\n\nSubstack does not have a public API with OAuth. Auth uses your browser session cookie.\n\n1. Open Chrome and log in to Substack.\n2. Open DevTools (F12) and go to the Application tab.\n3. Under Cookies, find `substack.com`.\n4. Copy the value of `substack.sid`.\n5. Paste it into `config.json` under the matching publication's `cookie` field.\n\nThe raw value works; the server accepts both `abc123` and `substack.sid=abc123`.\n\nSession cookies expire. If tools return `{\"error\": \"Auth failed\"}`, re-extract the cookie.\n\n## Register in Claude Code\n\nAdd to your `.mcp.json` (project-scoped) or via `claude mcp add -s user`:\n\n```json\n{\n  \"mcpServers\": {\n    \"substack\": {\n      \"command\": \"python3\",\n      \"args\": [\"/path/to/substack-mcp/server.py\"]\n    }\n  }\n}\n```\n\nRestart Claude Code after editing `.mcp.json`. Verify with `claude mcp list`.\n\n## Vault integration\n\nSet `vault_drafts_path` in `config.json` to a markdown file in your Obsidian vault.\nFormat each draft as a section separated by `---`. Sections under `## Ready to Post`\nare surfaced first by `list_vault_drafts`. Sections under `## Essay Seeds` come next.\n\nAfter publishing, `publish_vault_draft` moves the draft to a `## Published` section\nand appends the Substack URL and timestamp.\n\n## Pluggable image generators\n\nVisual card generation is handled by the adapter set in `config.json` under\n`image_generator.default`.\n\n### Shipped adapters\n\n**`pillow_local`** (default) -- pure Pillow, no external API. Renders 1080x1080 PNG\ncards locally using fonts from the `fonts/` directory. Three pillar templates: warm\nmustard with optional figure (P1), deep burgundy bold quote (P2), mustard with section\ntag (P3). Use with `render_card.py` as a standalone CLI.\n\n**`canva`** -- Canva MCP choreography. Returns a `steps` list describing the Canva MCP\ntool calls needed to clone a template, replace text, replace the figure, and export PNG.\nThe Claude session executing the playbook follows these steps. Requires Canva MCP\nconnected in Claude Code and design IDs filled in under `image_generator.canva.pillars`.\n\n### Stubbed adapters (contributions welcome)\n\n**`nano_banana`** -- Gemini 3 Pro Image via the nano-banana skill. Stub in\n`image_generators/nano_banana.py`. Implement `generate()` by calling the skill's\nimage endpoint with a prompt built from the card spec.\n\n**`midjourney`** -- Midjourney API (or proxy). Stub in `image_generators/midjourney.py`.\nImplement `generate()` by submitting a prompt, polling for completion, and downloading\nthe result.\n\n**`dalle`** -- OpenAI DALL-E. Stub in `image_generators/dalle.py`. Implement `generate()`\nusing `openai.images.generate`.\n\n### Writing your own adapter\n\nSubclass `ImageGenerator` from `image_generators.base`:\n\n```python\nfrom image_generators.base import ImageGenerator\n\nclass MyAdapter(ImageGenerator):\n    @property\n    def name(self) -> str:\n        return \"my_adapter\"\n\n    def generate(self, spec: dict) -> dict:\n        # spec keys: pillar, quote, handle, figure_path, output_path\n        # return: {status: \"ok\", adapter: ..., image_path: ..., width: ..., height: ...}\n        ...\n```\n\nRegister it in `image_generators/__init__.py` and add a config block under\n`image_generator.my_adapter` in `config.json`.\n\n## Visual publishing pipeline\n\nThe `visual_helper.py` script handles the deterministic side of the visual queue\npipeline: parsing a Review Queue markdown file, rotating through figures, and\nmarking entries published. See `visual_playbook.md` for the full end-to-end flow\nincluding image generation, upload, and publishing steps.\n\n```bash\npython3 visual_helper.py peek --lang es        # get next approved visual item\npython3 visual_helper.py mark --lang es ...    # mark published + update log\npython3 visual_helper.py rotate-figure --pillar P1\n```\n\n## Known gotchas\n\n1. **Cookie expiry.** `substack.sid` cookies expire after a few weeks. Re-extract from\n   Chrome DevTools when `test_connection` returns auth errors.\n\n2. **`draft_bylines` is required but undocumented.** `create_draft` will 400 without it.\n   The server fetches your `user_id` from `/user/profile/self` automatically and injects\n   it into every draft POST.\n\n3. **Note attachment endpoint requires trailing slash.** `POST /comment/attachment/` (with\n   slash) works. Without slash it returns 404. The server handles this correctly.\n\n4. **Publication-scoped vs global endpoints.** Notes use `substack.com/api/v1/comment/feed/`.\n   Post drafts use `{subdomain}.substack.com/api/v1/drafts`. Mixing them returns 404 or 403.\n\n5. **`pledgedArr` in the dashboard is a projection, not real pledges.** Use\n   `get_earnings` for actual revenue data.\n\n6. **Multi-publication support.** Pass `publication=\"name\"` to any tool to target a\n   specific publication. Omit it to use `default_publication` from config.\n\n7. **Sections are required to publish on publications that have them.** Substack\n   returns `HTTP 400: Please choose a section` from `publish_post` / `schedule_post`\n   when the draft hasn't been filed under a section. Look up section IDs with\n   `list_sections(publication=\"main\")`, then pass `section_id=<id>` to either the\n   draft tool or the publish/schedule tool:\n\n   ```\n   list_sections                             # → [{id: 12345, name: \"Essays\", slug: \"essays\", ...}, ...]\n   create_draft title=... body=... section_id=12345\n   # or, for an existing draft:\n   publish_post draft_id=987654 section_id=12345\n   ```\n\n   `publish_post` / `schedule_post` patch the draft before publishing, so a single\n   call is enough.\n\n## Related MCPs\n\nSame author, same architecture pattern (FastMCP, draft+confirm on writes where applicable, vault auto-export, MIT):\n\n- [slack-mcp](https://github.com/adelaidasofia/slack-mcp) — multi-workspace Slack\n- [imessage-mcp](https://github.com/adelaidasofia/imessage-mcp) — macOS iMessage\n- [whatsapp-mcp](https://github.com/adelaidasofia/whatsapp-mcp) — WhatsApp via whatsmeow\n- [google-workspace-mcp](https://github.com/adelaidasofia/google-workspace-mcp) — Gmail / Calendar / Drive / Docs / Sheets\n- [apollo-mcp](https://github.com/adelaidasofia/apollo-mcp) — Apollo.io CRM + sequences\n- [luma-mcp](https://github.com/adelaidasofia/luma-mcp) — lu.ma events\n- [parse-mcp](https://github.com/adelaidasofia/parse-mcp) — markitdown / Docling / LlamaParse router\n- [rescuetime-mcp](https://github.com/adelaidasofia/rescuetime-mcp) — RescueTime productivity data\n- [graph-query-mcp](https://github.com/adelaidasofia/graph-query-mcp) — vault knowledge graph queries\n- [graph-autotagger-mcp](https://github.com/adelaidasofia/graph-autotagger-mcp) — wikilink suggestions from the graph\n- [investor-relations-mcp](https://github.com/adelaidasofia/investor-relations-mcp) — seed-raise pipeline tracker\n- [vault-sync-mcp](https://github.com/adelaidasofia/vault-sync-mcp) — bidirectional vault sync\n\n\n## Telemetry\n\nThis plugin sends a single anonymous install signal to `myceliumai.co` the first time it loads in a Claude Code session on a given machine.\n\n**What is sent:**\n- Plugin name (e.g. `slack-mcp`)\n- Plugin version (e.g. `0.1.0`)\n\n**What is NOT sent:**\n- No user identifiers, names, emails, tokens, or API keys\n- No file paths, message content, or anything from your work\n- No IP address is stored after dedup processing\n\n**Why:** Helps the maintainer know which plugins people actually install, so attention goes to the ones that get used.\n\n**Opt out:** Set the environment variable `MYCELIUM_NO_PING=1` before launching Claude Code. The hook will skip the network call entirely. Already-pinged installs leave a sentinel at `~/.mycelium/onboarded-<plugin>` — delete it if you want to reset state.\n\n## License\n\nMIT\n\n---\n\nBuilt by [Mycelium AI](https://myceliumai.co). Full install or team version at [diazroa.com](https://diazroa.com).\n",
  "bytes": 13610,
  "sha": "e3078df0991c323f8afe7e12c8208d0f58858006935bb6014f7b92204a183c1a",
  "repo_slug": "adelaidasofia/substack-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_adelaidasofia_substack_mcp_a917969b/readme"
}