substack-saved-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| SUBSTACK_SAVED_DB_PATH | No | Custom path to the SQLite database file | |
| SUBSTACK_SAVED_DATA_DIR | No | Custom data directory for the database |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| extensions | {
"io.modelcontextprotocol/ui": {}
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| search_saved_postsA | Perform full-text FTS5 search across cached saved posts. Searches title, excerpt, publication name, author, and content text. Allows filtering by publication name, audience tier (see list_audiences for cached values, e.g. "everyone", "only_paid"), original post date (published_at), saved date (saved_at), and read_state ('unread', 'in_progress', 'finished', or 'started' — a post is 'finished' once its high-water reading progress crosses a threshold, default 0.95). |
| list_saved_postsA | List cached saved posts with pagination and optional publication/audience filters. sort_by can be 'saved_at', 'published_at', 'read_progress', or 'minutes_remaining'. audience filters by tier (see list_audiences for cached values, e.g. "everyone", "only_paid"). read_state filters by reading progress: 'unread', 'in_progress', 'finished', or 'started' — a post is 'finished' once its high-water reading progress crosses a threshold, default 0.95. |
| get_saved_postA | Retrieve full cached post details, timestamps (published_at and saved_at), and content by URL or local ID. |
| save_postA | Bookmark a Substack post remotely on Substack and save it to the local cache. Requires an active authenticated Substack session (run 'substack-saved-mcp login' if expired). Remote confirmation is best-effort: Substack's bookmark button markup isn't officially documented, so this detects whether the button's rendered state provably changed after clicking. remote_confirmed=False means the post is still cached locally, but the tool could not verify the bookmark was actually created on Substack's side — a subsequent 'sync --force' will correct the local cache if the remote save didn't actually happen. |
| unsave_postA | Unbookmark a Substack post remotely and soft-delete it in local cache. Soft-deletion preserves post history while removing it from active search/list outputs. When the post's Substack ID is known (normally true after a sync), this calls Substack's real unsave endpoint directly and is reliably confirmed; otherwise it falls back to a best-effort DOM click (see save_post). remote_confirmed=False means the post was still soft-deleted locally, but the tool could not verify the unbookmark on Substack's side. |
| get_post_contentA | Fetch a saved post's full content, cleaned and formatted for LLM consumption. Returns the cached content_text if a previous fetch already stored it, unless force_refetch is set. Otherwise fetches the post's page directly, extracts its body_html from Substack's server-rendered window._preloads blob, converts it to plain text (headings, list items, and links kept readable), and caches the result. Requires an active authenticated Substack session. If the content can't be located on the page (e.g. Substack changed how it embeds it, or the post is paywalled beyond this account's access), returns success=False with a message suggesting the caller run 'substack-saved-mcp inspect-network' while opening the post so the real content source can be captured. |
| list_publicationsA | List all publications in local cache with post counts. |
| list_audiencesA | List distinct audience tiers present in local cache with post counts. Discovers actual values in use (e.g. "everyone", "only_paid") rather than a hardcoded enum, since Substack's audience values aren't officially documented and may vary or grow over time. |
| saved_posts_statusA | Return cache statistics, database path, and last sync run status. |
| sync_saved_postsC | Trigger incremental or full resync of saved posts from Substack account into local SQLite cache. Requires an active authenticated Substack session. |
| search_saved_notesA | Perform full-text FTS5 search across cached saved notes. Searches body text, author name/handle, and (for restacks) the attached post's title. Allows filtering by author (name or handle), original note date (posted_at), and saved date (saved_at). Notes lack a bookmark timestamp on Substack's side, so saved_at is typically None. |
| list_saved_notesA | List cached saved notes with pagination and optional author/restack filters. sort_by can be 'saved_at' (when note was bookmarked) or 'posted_at' (when note was posted). restacks_only limits results to notes that restack a post. |
| get_saved_noteA | Retrieve full cached note details (author, body, engagement counts) by URL or local ID. |
| save_noteA | Bookmark a Substack note remotely on Substack and save it to the local cache. Requires an active authenticated Substack session (run 'substack-saved-mcp login' if expired). remote_confirmed=False means the note is still cached locally, but the tool could not verify the bookmark was actually created on Substack's side — a subsequent 'sync --force' will correct the local cache if the remote save didn't actually happen. |
| unsave_noteA | Unbookmark a Substack note remotely and soft-delete it in local cache. Soft-deletion preserves note history while removing it from active search/list outputs. remote_confirmed=False means the note was still soft-deleted locally, but the tool could not verify the unbookmark on Substack's side. |
| get_note_contentA | Fetch a saved note's full content, cleaned and formatted for LLM consumption. Returns the cached body_text if a previous fetch already stored it, unless force_refetch is set. Otherwise fetches the note directly via Substack's reader API (no browser page needed) and caches the result. Requires an active authenticated Substack session. |
| sync_saved_notesA | Trigger incremental or full resync of saved notes from Substack account into local SQLite cache. Requires an active authenticated Substack session. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| get_publications_resource | Resource returning JSON list of cached Substack publications. |
TDQS
Scored across 17 tools
Tools are clearly separated by resource type (posts vs notes) and action (save, unsave, search, list, get, sync). The main ambiguity is between get_saved_post and get_post_content, which both return post content, and search_saved_posts vs list_saved_posts could be confused without reading descriptions closely.
The naming follows a mostly consistent snake_case verb_noun pattern: save_post, unsave_note, list_saved_posts, sync_saved_notes. The main deviation is saved_posts_status, which is a noun phrase rather than verb-first, and get_saved_post/get_post_content are slightly inconsistent in object phrasing.
17 tools is slightly heavy, and the 16-25 range is considered borderline. However, the count is justified by having two parallel content domains (posts and notes) plus cache metadata status, publications, and audiences.
The tool set covers the full lifecycle for both saved posts and notes: save, unsave, search, list, get, content fetch, and sync. Minor gaps include no explicit tool to update read progress or manage cached deletion beyond soft-delete, but these are workable.