Skip to main content
Glama

Substack MCP Server

A Model Context Protocol (MCP) server that indexes and enables searching of Substack blog posts. Built for the Fejl 40 publication.

Overview

This MCP server fetches blog posts from a Substack RSS feed and provides tools for searching and querying the content through Claude Desktop. It includes full-text search, in-memory caching, and automatic refresh capabilities.

Related MCP server: substack-article-mcp

Features

  • Full-text Search: Search across post titles, content, and descriptions using FlexSearch

  • Persistent Disk Cache: Saves posts to disk for instant startup (~/.cache/substack-mcp/)

  • Smart Caching: In-memory cache with configurable TTL (default: 30 minutes)

  • Auto-refresh: Automatically updates when cache expires

  • 7 MCP Tools:

    • search_posts - Search by keywords

    • list_recent_posts - Get most recent posts

    • get_post_by_title - Find by title

    • get_post_by_url - Get by URL

    • get_posts_by_date_range - Filter by date range

    • generate_citation - Generate formatted citations (APA, MLA, Chicago, markdown)

    • export_to_markdown - Export posts to markdown files

Installation

Prerequisites

  • Node.js 18 or higher

  • Claude Desktop app

  • npm

Setup

  1. Clone or download this repository:

    git clone https://github.com/andersschaffner/substack-mcp.git
    cd substack-mcp
  2. Install dependencies:

    npm install
  3. Build the project:

    npm run build
  4. Configure Claude Desktop:

    Add this to your Claude Desktop config file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

    • Windows: %APPDATA%/Claude/claude_desktop_config.json

    {
      "mcpServers": {
        "substack-mcp": {
          "command": "node",
          "args": ["/absolute/path/to/substack-mcp/index.js"],
          "env": {
            "SUBSTACK_FEED_URL": "https://fejl40.substack.com/feed"
          }
        }
      }
    }

    Important: Replace /absolute/path/to/substack-mcp/ with the actual path where you cloned the repo.

  5. Restart Claude Desktop

Configuration

Environment Variables

  • SUBSTACK_FEED_URL - The RSS feed URL for your Substack publication (required)

  • CACHE_TTL_MS - Cache time-to-live in milliseconds (default: 1800000 = 30 minutes)

Using with a Different Substack

To use with a different Substack publication, simply change the SUBSTACK_FEED_URL in your Claude Desktop config:

"env": {
  "SUBSTACK_FEED_URL": "https://your-publication.substack.com/feed"
}

Usage

Once installed and configured, you can ask Claude questions about your blog posts:

  • "Search my blog posts about AI"

  • "What have I written about product management?"

  • "Show me my most recent blog posts"

  • "Find my post about [specific topic]"

  • "What did I write in December 2024?"

Claude will automatically use the MCP tools to search and retrieve your blog content.

Available Tools

search_posts

Search blog posts by keywords across title and content.

Parameters:

  • query (string, required) - Search query

  • limit (number, optional) - Maximum results (default: 10)

list_recent_posts

List the most recent blog posts.

Parameters:

  • limit (number, optional) - Number of posts (default: 10)

  • offset (number, optional) - Posts to skip (default: 0)

get_post_by_title

Get a specific post by exact or partial title match.

Parameters:

  • title (string, required) - Post title or partial title

  • exact (boolean, optional) - Require exact match (default: false)

get_post_by_url

Retrieve a specific post by its URL.

Parameters:

  • url (string, required) - Full URL of the Substack post

get_posts_by_date_range

Get posts published within a date range.

Parameters:

  • start_date (string, required) - Start date (ISO 8601 format)

  • end_date (string, required) - End date (ISO 8601 format)

  • limit (number, optional) - Maximum results (default: 100)

generate_citation

Generate a formatted citation for a blog post.

Parameters:

  • url (string, required) - URL of the blog post to cite

  • style (string, optional) - Citation style: 'APA', 'MLA', 'Chicago', or 'markdown' (default: 'markdown')

Example output:

{
  "citation": "[Post Title](https://fejl40.substack.com/...) by Anders Schaffner, December 15, 2024",
  "style": "markdown",
  "post": {
    "title": "Post Title",
    "link": "https://fejl40.substack.com/...",
    "author": "Anders Schaffner",
    "pubDate": "2024-12-15T..."
  }
}

export_to_markdown

Export blog posts to a markdown file.

Parameters:

  • output_path (string, required) - Output file path (e.g., ~/Desktop/posts.md)

  • query (string, optional) - Search query to filter posts

  • start_date (string, optional) - Start date filter (ISO 8601 format)

  • end_date (string, optional) - End date filter (ISO 8601 format)

  • limit (number, optional) - Maximum posts to export (default: 100)

Example usage:

  • Export all posts: export_to_markdown({ output_path: "~/Desktop/all-posts.md" })

  • Export AI-related posts: export_to_markdown({ output_path: "~/Desktop/ai-posts.md", query: "AI" })

  • Export 2024 posts: export_to_markdown({ output_path: "~/Desktop/2024.md", start_date: "2024-01-01", end_date: "2024-12-31" })

Development

Scripts

  • npm run build - Build the TypeScript project

  • npm run dev - Build in watch mode

  • npm start - Run the compiled server

Project Structure

.
├── src/
│   ├── index.ts          # Main MCP server
│   ├── post-cache.ts     # Caching and search logic with disk persistence
│   ├── rss-fetcher.ts    # RSS feed parser
│   ├── citation.ts       # Citation formatting (APA, MLA, Chicago, markdown)
│   ├── export.ts         # Export to markdown/JSON
│   ├── types.ts          # TypeScript interfaces
│   └── utils.ts          # Helper functions
├── build.js              # esbuild configuration
├── package.json
├── tsconfig.json
└── index.js              # Compiled output

How It Works

  1. Server Startup: When Claude Desktop launches, it starts the MCP server

  2. Disk Cache Load: Server attempts to load posts from disk cache (~/.cache/substack-mcp/posts.json)

  3. RSS Fetch: If disk cache is missing or stale, fetches posts from the Substack RSS feed

  4. Indexing: Posts are indexed with FlexSearch for fast searching

  5. Disk Persistence: Posts are saved to disk cache for instant startup next time

  6. Memory Caching: Posts are cached in memory with a 30-minute TTL

  7. Auto-refresh: Cache automatically refreshes when it expires

  8. Tool Calls: Claude calls the MCP tools when you ask about your blog

Performance: With disk cache, startup is nearly instant (< 1 second). Without disk cache, first startup takes 3-5 seconds to fetch RSS.

Troubleshooting

"Command not found" or server won't start

  • Ensure the path in claude_desktop_config.json is absolute, not relative

  • Verify Node.js is installed: node --version

  • Check that index.js exists after running npm run build

No posts found

  • Verify the RSS feed URL is correct and accessible

  • Check Claude's MCP logs: ~/Library/Logs/Claude/mcp*.log

  • Ensure the RSS feed has posts

JSON parse errors

  • Make sure you're using the latest build

  • All logging should go to stderr, not stdout

Technical Details

  • Language: TypeScript

  • Runtime: Node.js 18+

  • Build Tool: esbuild

  • Search Engine: FlexSearch

  • RSS Parser: rss-parser

  • MCP SDK: @modelcontextprotocol/sdk v1.25+

License

ISC

Author

Anders Schaffner

Contributing

Feel free to open issues or submit pull requests for improvements!

Available Tools

7 tools
export_to_markdownC

Export blog posts to a markdown file

Inputs: output_path (required), limit, query, end_date, +1 more.

generate_citationB

Generate a formatted citation for a blog post

Inputs: url (required), style.

get_post_by_titleC

Get a specific post by exact or partial title match

Inputs: title (required), exact.

get_post_by_urlC

Retrieve a specific post by its URL

Inputs: url (required).

get_posts_by_date_rangeC

Get posts published within a date range

Inputs: end_date (required), start_date (required), limit.

list_recent_postsC

List the most recent blog posts

Inputs: limit, offset.

search_postsB

Search blog posts by keywords across title, content, and description

Inputs: query (required), limit.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    B3.4/5.0

    Scored across 7 tools

    Disambiguation4/5

    The five retrieval tools differ by access pattern (keyword, recency, title, URL, date range), which is mostly clear. However, get_post_by_title and search_posts can overlap when a title is used as a keyword, and generate_citation vs export_to_markdown both consume posts, creating mild boundary blur.

    Naming Consistency5/5

    All tools follow a consistent snake_case verb_noun pattern (search_posts, list_recent_posts, get_post_by_title, export_to_markdown). The convention is predictable and readable throughout.

    Tool Count5/5

    Seven tools is well-scoped for a blog reading/search server, with each tool earning its place by covering a distinct retrieval or output mode.

    Completeness4/5

    Core read workflows (search, recency, by title, by URL, by date, cite, export) are well covered. Minor gaps exist around author/subscription or comment retrieval, but these are workable given the reading-focused scope.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers