substack-saved-mcp
This server provides a local, stdio-based MCP interface for managing saved Substack posts, with offline search and authenticated remote actions.
Search and List: Full-text search across titles, excerpts, authors, publications, and cached content, with filters by publication, audience tier, and date ranges. List posts with pagination and sorting.
Retrieve Posts: Get cached metadata for a specific post by URL or ID. Fetch and cache the full cleaned content, optimized for LLM consumption.
Save and Unsave: Bookmark or unbookmark posts remotely (requires authentication), updating both your Substack account and local cache.
Sync: Trigger incremental or full resync of saved posts from Substack into the local SQLite cache.
Cache Management: View statistics like total posts, publications, audience tiers, and last sync status. List all publications and audience tiers with post counts.
Authentication: Required for remote operations such as saving, unsaving, content fetching, and syncing.
Manages saved/bookmarked Substack posts, including syncing, searching, retrieving full content, and toggling save/unsave status.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@substack-saved-mcpshow me my saved posts about AI"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Substack Saved Posts & Notes MCP & CLI
A local, stdio-based Model Context Protocol (MCP) server and sync engine for your saved/bookmarked Substack posts and notes.
Features
Read & Search: Full-text search (SQLite FTS5) across saved post titles, excerpts, authors, and publications. Filter by publication, audience tier (e.g.
everyone,only_paid), reading progress (--read-state unread/in_progress/finished/started), and date ranges (published_atvssaved_at). Search also covers a post's full body text, but only for posts whose content has already been fetched once viaget-content/ theget_post_contenttool — a normalsyncstores metadata and excerpts, not full bodies, so posts you haven't opened yet are matched on their title/excerpt/metadata only, not their full text.Reading Progress: Substack tracks how far you've read each saved post (visible in its mobile apps, not the web UI) — this tool surfaces it. Each post reports
is_fully_readandminutes_remaining, derived from the storedmax_read_progresshigh-water mark at a configurable threshold (default 0.95,SUBSTACK_SAVED_FULLY_READ_THRESHOLD). Filter with--read-state, or sort a list byread_progress/minutes_remainingto find something short to finish. Progress refreshes on every sync a post is touched by; runsync --forceto refresh it for your whole backlog.Saved Notes, too: Substack's short-form notes are synced, searched, and cached separately from posts (they carry an author and body rather than a title or publication tier). Full-text search covers note bodies, authors, and restacked-post titles. Notes never require a browser at all — every notes operation (sync, save, unsave, full-content fetch) is a plain authenticated API call.
Full Content for LLMs: Fetch a saved post's or note's full content and get it back cleaned and formatted (headings, lists, links) for feeding directly to an LLM, with the result cached locally for next time.
Save & Unsave: Bookmark new Substack posts and notes, or unbookmark existing ones. Posts go through an authenticated browser session; notes are API-only.
Offline First: Fast, offline queries directly from local SQLite cache.
Privacy & Security: Keeps session credentials local, redacting tokens from logs.
FastMCP Protocol: Stdio MCP interface with rich tool suite and resources for both posts and notes.
Related MCP server: substack-article-mcp
Installation with uv
uv is the recommended fast Python package manager for installing and running substack-saved-mcp.
Option A: Install from PyPI as a System-wide Tool (uv tool install)
Install the published package from PyPI:
# Install system-wide into an isolated uv environment
uv tool install substack-saved-mcpTo install directly from a local repository folder instead:
# Navigate to the repository
cd /path/to/substack-saved-mcp
# Install system-wide into an isolated uv environment
uv tool install .
# Or install directly from a remote Git repository:
# uv tool install git+https://github.com/your-username/substack-saved-mcp.gitAfter installation, substack-saved-mcp is immediately available in your PATH:
# Verify installation
substack-saved-mcp --helpTo update or uninstall:
# Upgrade installed tool
uv tool upgrade substack-saved-mcp
# Uninstall tool
uv tool uninstall substack-saved-mcpOption B: Local Development / Development Environment (uv sync)
If you are developing or modifying the codebase:
# Clone and enter directory
cd substack-saved-mcp
# Install dependencies and dev tools (pytest)
uv sync --extra dev
# Run CLI commands using uv run
uv run substack-saved-mcp --help
# Run tests
uv run pytestQuick Start
# 1. Initialize local database
substack-saved-mcp init
# 2. Authenticate with Substack (opens interactive browser window once)
substack-saved-mcp login
# 3. Sync saved posts AND notes into local cache (both entities by default)
substack-saved-mcp sync
# 3b. Or sync just one entity
substack-saved-mcp sync --only posts
substack-saved-mcp sync --only notes
# 4. Search saved posts via CLI
substack-saved-mcp search "artificial intelligence"
# 4b. Filter by publication or audience tier (see which tiers are cached with `audiences`)
substack-saved-mcp audiences
substack-saved-mcp list --audience only_paid
substack-saved-mcp search "artificial intelligence" --audience everyone
# 4c. Filter or sort by reading progress
substack-saved-mcp list --read-state finished
substack-saved-mcp list --read-state in_progress --sort-by minutes_remaining
substack-saved-mcp search "artificial intelligence" --read-state unread
# 5. Save or unsave a post
substack-saved-mcp save "https://example.substack.com/p/post-slug"
substack-saved-mcp unsave "https://example.substack.com/p/post-slug"
# 6. Get a saved post's full content, cleaned up and ready for an LLM
substack-saved-mcp get-content "https://example.substack.com/p/post-slug"
# 7. Work with saved notes the same way
substack-saved-mcp list-notes --limit 10
substack-saved-mcp search-notes "kubernetes" --author alice
substack-saved-mcp note-authors
substack-saved-mcp save-note "https://substack.com/@handle/note/c-123456"
substack-saved-mcp unsave-note "https://substack.com/@handle/note/c-123456"
substack-saved-mcp get-note "https://substack.com/@handle/note/c-123456"
# 8. Check combined status (posts and notes counts, last sync per entity)
substack-saved-mcp status
# 9. Launch stdio MCP server
substack-saved-mcp serveConfiguring MCP Clients (Claude Desktop, Goose, Cursor, etc.)
Add substack-saved-mcp to your MCP client's configuration file (e.g. claude_desktop_config.json).
Using System-Wide Installed Tool (uv tool or global binary)
{
"mcpServers": {
"substack-saved": {
"command": "substack-saved-mcp",
"args": ["serve"]
}
}
}Using uv directly from the Repository Path
If you prefer running directly from your repository path without installing system-wide:
{
"mcpServers": {
"substack-saved": {
"command": "uv",
"args": [
"--directory",
"/path/to/substack-saved-mcp",
"run",
"substack-saved-mcp",
"serve"
]
}
}
}Frequently Asked Questions (FAQ)
Where is the database saved?
By default, the SQLite database is saved in your OS application data directory:
Linux / macOS:
~/.local/share/substack-saved-mcp/saved_posts.sqlite
(or$XDG_DATA_HOME/substack-saved-mcp/saved_posts.sqliteifXDG_DATA_HOMEis set)
You can specify a custom database path or directory using environment variables:
export SUBSTACK_SAVED_DB_PATH="/path/to/my/custom_database.sqlite"
# or
export SUBSTACK_SAVED_DATA_DIR="/path/to/my/data_dir"Which Substack API does syncing saved posts use?
Saved posts are fetched from Substack's newer unified reader API by default, with automatic fallback to an older, posts-only API and finally to headless browser scraping if needed — you shouldn't normally need to think about this. If you ever want to force a specific source (e.g. while troubleshooting), set:
export SUBSTACK_SAVED_POSTS_SOURCE="unified" # or "legacy" or "dom"Leave it unset (or "auto") for the default, self-healing behavior.
What does a "partial" sync status mean?
If Substack rate-limits (HTTP 429) a sync so heavily that a page of results
can't be fetched even after retrying, the sync keeps whatever it already
fetched rather than failing outright, and reports status: partial (instead
of success) in substack-saved-mcp status or the sync tool's response.
When this happens on a sync --force, reconciliation (soft-deleting posts/notes
no longer in the remote list) is automatically skipped for that run, so a post
or note that merely couldn't be fetched is never mistaken for one you actually
unsaved on Substack. Just run sync again later — a subsequent successful run
picks up anything that was missed.
How is "fully read" determined, and how fresh is it?
Substack reports a max_read_progress high-water mark (0.0–1.0) per saved post; a
post counts as fully read once that crosses a threshold (default 0.95 — real
posts top out around 0.98–0.9999 rather than an exact 1.0). Override it with:
export SUBSTACK_SAVED_FULLY_READ_THRESHOLD="0.90"Progress is refreshed whenever a post is re-fetched during sync. An incremental
sync only touches recently-saved posts, so progress on older posts in your
backlog can go stale between reads; run substack-saved-mcp sync --force to
refresh it for everything. Notes have no reading-progress concept.
Will a browser window pop up when running as an MCP server?
No, a visible browser window will not open during normal MCP operations.
Read & Search Tools (
search_saved_posts,list_saved_posts,get_saved_post,search_saved_notes,list_saved_notes,get_saved_note,list_publications,list_audiences,saved_posts_status):
Operate 100% offline using the local SQLite database. Zero browser activity.Post Sync & Write Tools (
sync_saved_posts,save_post,unsave_post,get_post_content):
Run in headless background mode using the pre-authenticated session stored instorage_state.json.Note Sync & Write Tools (
sync_saved_notes,save_note,unsave_note,get_note_content):
Never open a browser page at all, headless or otherwise — Substack's notes endpoints are plain authenticated HTTP calls, so these tools only ever make direct API requests usingstorage_state.json.Interactive Login:
A visible browser window opens only when you manually runsubstack-saved-mcp loginfrom your terminal. If your session expires while using an MCP client, the tool will return a clear error message instructing you to re-authenticate viasubstack-saved-mcp logininstead of popping open a browser window unexpectedly.
What if I get a Playwright "Executable doesn't exist" error?
If you encounter an error like BrowserType.launch: Executable doesn't exist when running commands (especially login), it means Playwright hasn't installed its required browsers in the isolated environment.
To fix this, you need to run the playwright install command inside the environment where the tool is installed.
For a system-wide tool installation (via uv tool install), run:
~/.local/share/uv/tools/substack-saved-mcp/bin/playwright installIf you are using a local development environment (via uv sync), run:
uv run playwright installI edited the source code, but the installed substack-saved-mcp command still behaves like the old version. Why?
uv tool install copies the package into its own isolated environment at install time — it does not track your working tree. If you edited files under src/ (or pulled new commits) after installing the tool system-wide, the globally installed copy is stale and keeps running the old code, even though uv run substack-saved-mcp ... from the repo would use the latest source.
Reinstall from your current working tree to pick up the changes:
uv tool install . --no-cache --force--forcereplaces the existing installed version instead of skipping the install because a version is already present.--no-cacheensures a fresh build rather than reusing a cached wheel/build artifact from before your edits.
Do this any time after modifying the codebase and before relying on the globally installed substack-saved-mcp binary (as opposed to uv run substack-saved-mcp, which always reflects the working tree).
Available Tools
17 toolsget_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.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes | ||
| force_refetch | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It transparently discloses caching behavior, the use of an API instead of a browser, and the requirement for an authenticated session. This covers key behavioral aspects, though it does not mention error handling or response details beyond the output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four concise sentences, front-loaded with the primary purpose, and each sentence adds relevant detail without redundancy. It avoids jargon and is well-structured for quick consumption by an AI agent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with only two parameters and an output schema, the description covers essential context: caching, API access, authentication, and the cleaning/formating. It is complete enough for most use cases, though it omits potential edge cases like failure modes or idempotency guarantees, which are not critical here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, requiring the description to compensate. It explains force_refetch's effect on caching but does not clarify what url_or_id actually accepts beyond the schema's generic string type. This partial compensation leaves a gap for the primary parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'Fetch' and the resource 'a saved note's full content', with additional value of 'cleaned and formatted for LLM consumption'. This distinguishes it from sibling tools like get_post_content or get_saved_note by explicitly focusing on notes and formatting for LLMs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
While it mentions the caching mechanism and the API-based fetch, it does not explicitly contrast with alternatives or state when to prefer this over get_saved_note or get_post_content. It implies usage for fetching note content but lacks explicit when-not-to-use guidance or alternative recommendations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes | ||
| force_refetch | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully carries the transparency burden. It discloses caching behavior, the fetch source (window._preloads blob), text conversion process, auth requirements, and failure mode with a suggested remediation. This is exceptional behavioral disclosure for a read tool, covering side effects and edge cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact yet detailed, with three sentences covering purpose, behavior, and failure handling. The second sentence is long and clause-heavy, but every piece of information earns its place given the tool's complexity. It is front-loaded with the core purpose and avoids unnecessary repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, no annotations, and an output schema present, the description is remarkably complete. It covers caching, extraction method, auth prerequisites, and failure handling with a user- actionable next step. Nothing critical is omitted for an agent to invoke this tool safely and effectively.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has zero descriptions, but the tool description explicitly explains force_refetch semantics (overrides cached content) and implies url_or_id identifies the saved post. While the exact format for url_or_id isn't specified, the parameter name and context are sufficiently self-explanatory for an agent. The description compensates well for the schema's lack of detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool fetches a saved post's full content, cleaned and formatted for LLM consumption. This specific verb+resource combination distinguishes it from siblings like list_saved_posts or get_saved_post, which likely handle metadata or listings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description establishes clear context: use when you need the full content of a saved post, with prerequisites (authenticated session) and caching behavior. It does not explicitly name alternative tools for different use cases, but the purpose is unambiguous and the troubleshooting tip for failed fetches adds practical guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_saved_noteA
Retrieve full cached note details (author, body, engagement counts) by URL or local ID.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description clearly identifies that this is a read operation returning cached data, but with no annotations present, more context is needed—such as staleness implications, error handling, or authentication requirements. It is not misleading but stays surface-level.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence that communicates action, resource, and details without redundancy. Every clause adds value and no space is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with an output schema, the description sufficiently covers purpose and parameter semantics. It does not address edge cases like missing or invalid IDs, but overall it gives an adequate mental model for an agent to select and use the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The parameter `url_or_id` is given meaningful explanation: it can be a URL or a local identifier. Since the schema description coverage is 0%, this is essential additional context. A concrete example or format hint would have been useful but is not required for the one-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Retrieve'), specific resource ('full cached note details'), and enumerates returned fields ('author, body, engagement counts'). This clarifies the tool's scope and distinguishes it from sibling tools like get_note_content or search_saved_notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance about when to choose this tool over alternatives such as get_note_content or sync_saved_notes. It specifies the lookup mechanism (by URL or local ID) but does not explain contexts, exclusions, or constraints.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_saved_postA
Retrieve full cached post details, timestamps (published_at and saved_at), and content by URL or local ID.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations available, the description carries the burden of behavioral disclosure and does convey that this is a read-only retrieval of cached data, not a mutation. However, it omits useful behavioral context such as what happens if the post is not found, whether the cache can be stale, or whether any fetch/refresh is triggered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The entire description is a single, well-ordered sentence that front-loads the action and then packs only useful qualifiers: full, cached, timestamps, content, URL/local ID. There is no filler, redundancy, or unnecessary elaboration.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter getter with an output schema already present, this description provides the essential information: what resource is being retrieved, what identifying data is accepted, and what major fields the result includes. It could be more complete by explaining how it relates to the sibling tools, but for this low-complexity tool the essentials are not missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides only an unannotated string property 'url_or_id' with zero description coverage. The tool description compensates by explicitly stating that either a URL or a local ID can be supplied, which is the essential semantic needed to populate the single required parameter. It does not specify edge-case formats, but the coverage for a one-parameter tool is adequate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a concrete verb 'retrieve' and clearly identifies the resource: full cached post details, including timestamps and content. The clarification that lookup can be by URL or local ID makes the tool's job unambiguous and distinguishes it from sibling tools like list_saved_posts or get_post_content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit guidance is provided about when to use this tool versus alternatives such as search_saved_posts, list_saved_posts, or get_post_content. The description implies a retrieval use case but does not state conditions, exclusions, or preferred scenarios.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden of behavioral disclosure. It explains that values are discovered from local cache and are dynamic, but it does not explicitly state safety traits (e.g., read-only, no side effects) or mention whether the cache might be stale or require syncing beforehand.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, with the core function stated first and the rationale in the second sentence. Every sentence provides value and there is no redundancy or unnecessary detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity (0 params) and presence of an output schema, the description is mostly complete. It covers what the tool does, where it reads from, and why it uses live values. It could mention prerequisites like cache syncing, but that is a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and the schema coverage is 100%, so the baseline is 4. The description adds context about why there are no parameters (it scans actual values in use) which is helpful, though not strictly necessary.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it lists distinct audience tiers from local cache with post counts. It differentiates itself from sibling tools by focusing on audience tiers specifically, and the context about discovering actual values rather than hardcoded enums adds clarity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for discovering dynamic audience values rather than relying on a static enum, but it does not explicitly state when to use this tool versus alternatives or mention any exclusions. No sibling tool is referenced for comparison.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_publicationsA
List all publications in local cache with post counts.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are absent, so the description must disclose behavioral traits. The description only mentions listing local cache with post counts, but does not state whether the operation is read-only, requires prerequisites, or has side effects. It also does not clarify what 'local cache' means operationally.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured sentence that front-loads the core purpose and includes relevant detail ('with post counts'). No redundancy or unnecessary text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Though the tool is simple with no parameters and an output schema, the description lacks critical contextual details such as whether it is a read-only operation, how 'local cache' is populated, and when to choose this over sibling tools. Given the importance of an agent knowing side effects and usage, the description is minimally viable but has clear gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the input schema is inherently fully covered. The description correctly omits parameter details, warranting the baseline score of 4 for no-parameter tools.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists all publications in local cache and includes post counts, specifying the exact resource and scope. It is distinct from sibling tools like list_saved_posts, which focuses on saved posts rather than publications.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided regarding when to use this tool instead of alternatives like search_saved_posts or sync_saved_posts. There are no explicit context cues, prerequisites, or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| author | No | ||
| offset | No | ||
| sort_by | No | saved_at | |
| restacks_only | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the transparency burden. It discloses that notes are 'cached' (potential staleness) and explains sorting/filters, but does not state side effects (e.g., read-only implication) or any limitations beyond caching. It adds useful context but lacks explicit safety or behavioral notes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact, two sentences, front-loaded with the main purpose, and includes necessary filter details without redundancy. Every sentence adds value, and the structure is easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema, the description needn't explain return values. It covers pagination, filters, sorting, and the 'cached' nuance, which is sufficient for a list tool. However, it could mention author filtering behavior or note that results are limited to user's saved notes, though not essential.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the description explains two non-obvious parameters (sort_by and restacks_only) with examples. It omits limit, author, and offset, which are intuitive but still deserve mention. The partial coverage does not fully compensate for the zero schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'List cached saved notes' with specific filters and pagination. It distinguishes from siblings like search_saved_notes (search vs list) and sync_saved_notes (sync vs list) by naming the core action and resource.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for listing saved notes and mentions filters but does not explicitly contrast with alternatives (e.g., when to use search_saved_notes instead). There is no explicit 'use this when' or 'don't use when' guidance, though the functionality is evident.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No | ||
| sort_by | No | saved_at | |
| audience | No | ||
| read_state | No | ||
| publication | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must carry the burden of behavioral disclosure. It explains the nuanced behavior of read_state, including the 'finished' threshold concept, and the source of audience values (cached, from list_audiences). It provides insight into how sorting works (allowed values for sort_by). However, it does not describe the output format, pagination behavior (beyond having limit/offset), or error cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-organized: the first sentence gives the overview, and the following lines explain each filter clearly. Front-loads the core purpose and then details the filter semantics. The structure is efficient with no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Has an output schema, so return type explanation is not needed. The description covers the main behavioral aspects: sorting options, audience filter, read_state filter with its threshold logic, and implicitly pagination. Manual page size and limit are inferable. The main missing piece is a note on how the response is shaped or how pagination actually works (e.g., whether offset is counts of posts), but that is likely covered by the output schema. Given the complexity, it is close to complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must fully compensate. It explains the semantics of sort_by (enumerating options), audience (referencing list_audiences for valid values), and read_state (explaining each value and the threshold concept). It also implies semantics for pagination (limit/offset). The description adds substantial meaning beyond the raw schema, which only shows parameter names and types.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('List'), the resource ('cached saved posts'), and the key features (pagination, optional filters). It distinguishes itself from siblings like search_saved_posts by focusing on cached data listing with structured filters rather than search. The specific behaviors around read_state and sorting are unique to this tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage by listing the available filters (audience, read_state, publication). It references list_audiences for cached values and explains the semantics of read_state clearly (including the threshold concept). However, it does not explicitly state when to prefer this over siblings like search_saved_posts or get_saved_post, nor does it provide explicit exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
saved_posts_statusA
Return cache statistics, database path, and last sync run status.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| posts_unread | No | |
| database_path | Yes | |
| last_sync_status | No | |
| posts_fully_read | No | |
| posts_in_progress | No | |
| total_saved_notes | No | |
| total_saved_posts | Yes | |
| total_publications | Yes | |
| total_unsaved_notes | No | |
| total_unsaved_posts | Yes | |
| last_successful_sync | No | |
| last_note_sync_status | No | |
| minutes_remaining_total | No | |
| last_successful_note_sync | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It discloses the tool is read-only in nature (returns statistics and status) but doesn't state whether it triggers any side effects or has any rate limits. It doesn't describe what 'last sync run status' entails (e.g., success/failure details, timestamps) or what the database path refers to. The description is adequate but not rich in behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence that states the exact content of the return. It is appropriately sized for a zero-parameter tool that retrieves status information, with no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool is simple with no parameters and the output schema exists (though not detailed here), the description covers what the tool does and what it returns. It doesn't explain the format of the output statistics or status, but the output schema presumably provides that. For a status-check tool of this simplicity, the description is mostly complete, though it could benefit from clarifying whether this is a read-only operation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the input schema is empty (schema coverage 100% by default). The description correctly doesn't mention parameters since there are none. Per the rubric, 0 params gives a baseline 4, and the description appropriately focuses on what the tool returns rather than inputs. It adds value by specifying the exact output categories.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns three specific pieces of information: cache statistics, database path, and last sync run status. This distinguishes it from the sibling tools that operate on posts or notes directly, since this tool is about system status rather than content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies a diagnostic or monitoring use case ('Return cache statistics, database path, and last sync run status'), but does not explicitly state when to use this tool instead of other sync-related tools like sync_saved_posts or sync_saved_notes. There are no explicit exclusions or alternatives mentioned, leaving the agent to infer that this is for checking sync status without triggering a sync.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full responsibility. It discloses the remote bookmark action, local caching, the authentication prerequisite, the remote_confirmed=False failure state, and the corrective sync behavior—this is substantial and actionable behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with the purpose, and efficiently uses three sentences to cover action, prerequisite, and failure behavior. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with an output schema, the description fully covers the essential context: authentication requirement, what the tool does, what failure looks like, and how to recover. Return-value details are unnecessary because an output schema exists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 0% description coverage and only lists 'url' as a required string. The tool description implies 'url' is a Substack note URL, which adds limited meaning, but it doesn't explain URL format, validation, or any constraints beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb ('Bookmark') and resource ('Substack note'), and clarifies the dual action of remote bookmarking and local caching. This clearly distinguishes it from siblings like save_post, unsave_note, and sync_saved_notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear usage context: it requires an active authenticated Substack session, mentions what to do if expired, and explains the recovery path when remote confirmation fails. It does not explicitly name alternative tools or say when not to use it, but the intended use is well implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. It discloses the auth requirement, the uncertainty of remote confirmation, the meaning of remote_confirmed=False, and the corrective role of sync --force. This is thorough and honest.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and then provides two short, information-dense paragraphs about prerequisites and caveats. Every sentence adds value, and there is no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema, return values do not need further explanation. The description covers prerequisites, failure modes, local caching behavior, and recovery steps, making it fully complete for a remote save operation with uncertain confirmation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has one required url parameter with zero description coverage. The description clarifies that the URL should refer to a Substack post, adding some semantics beyond the parameter name, but it does not specify accepted URL formats, edge cases, or example values.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Bookmark a Substack post remotely on Substack and save it to the local cache.' This uses specific verbs and resources, and it distinguishes the tool from siblings like unsave_post and list_saved_posts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides clear context about requiring an authenticated session and explains the best-effort nature of remote confirmation. However, it does not explicitly compare with alternatives like unsave_post or sync_saved_posts, though the usage is heavily implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| author | No | ||
| saved_after | No | ||
| posted_after | No | ||
| saved_before | No | ||
| posted_before | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses that the search operates on 'cached' notes (implying synced data, not real-time), explains the FTS5 technology, and explicitly warns that saved_at is typically None due to Substack lacking a bookmark timestamp. These are meaningful behavioral traits beyond the schema. It does not state read-only status, but for a search tool that is implied. The caveats add solid transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is about 80 words, front-loaded with the primary purpose, then elaborates on search scope and filters, and ends with a caveat. It is dense with information but avoids redundant phrasing. Each sentence contributes to understanding, and there is no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (7 params, 0% schema coverage), the description covers the key aspects: what is searched, available filters, and the saved_at caveat. An output schema exists, so return values are likely covered there. Missing details like date format and sorting behavior are minor and could be inferred or documented in the schema. Overall, it is sufficiently complete for effective use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 0% description coverage, so the description must compensate. It explains that author filtering accepts name or handle, and clarifies the distinction between posted_at (original note date) and saved_at (saved date), even noting the saved_at limitation. However, it does not explain the format for date parameters (e.g., ISO 8601) or the meaning of the limit parameter, leaving some semantics to be inferred.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool performs 'full-text FTS5 search across cached saved notes', specifies the exact resources searched (body text, author name/handle, attached post title for restacks), and explicitly differentiates from sibling search_saved_posts by focusing on notes. It also lists filtering capabilities, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context: it is for searching saved notes with content filtering, and explicitly differentiates from listing tools by highlighting full-text search. It also notes a key limitation (saved_at typically None) that affects usage. However, it does not explicitly state when not to use this tool versus alternatives like list_saved_notes or search_saved_posts, but the intent is clear enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| audience | No | ||
| read_state | No | ||
| publication | No | ||
| saved_after | No | ||
| saved_before | No | ||
| published_after | No | ||
| published_before | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It does disclose what fields are searched (title, excerpt, publication, author, content), the exact semantics of read_state including the threshold definition (0.95), and that it operates on cached saved posts. This goes beyond a generic 'search' and gives the agent insight into how results are classified. It does not mention pagination or rate limits, but for a search tool this is acceptable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and information-dense. The main action is stated in the first sentence, and subsequent sentences add only necessary filtering details. No filler or redundancy exists; every sentence provides value. The structure is logical: what it does, then what it searches, then how to filter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that an output schema exists (covering return values), the description does not need to repeat that. It covers the core behavior, searchable fields, and all non-obvious filter semantics (especially read_state). It might benefit from clarifying the date format or limit behavior, but these are relatively minor and inferable from schema names. The description is sufficiently complete for an agent to invoke the tool correctly in most scenarios.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It clarifies the meaning of query (full-text on multiple fields), audience (with example values and a reference to list_audiences), read_state (with explicit enumerated values and the 'finished' threshold), and publication (filter by name). It also mentions date filtering (published_at, saved_at) but does not explain that these are range parameters or their format. It entirely omits the 'limit' parameter. This partial coverage earns a 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb-resource pair: 'Perform full-text FTS5 search across cached saved posts.' This distinguishes it from sibling search_saved_notes (search notes) and from list_saved_posts (listing vs. content search). The use of 'FTS5' and 'cached' adds specificity that leaves no ambiguity about what the tool operates on.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains the filtering capabilities (publication, audience, dates, read_state), which implicitly tells the agent when this tool is suitable (when a full-text search or filtering is needed). However, it does not explicitly state when to prefer list_saved_posts over this tool (e.g., 'for a simple list without text search'), and it does not mention any exclusions or alternatives. The usage context is clear but not contrasted with siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_saved_notesA
Trigger incremental or full resync of saved notes from Substack account into local SQLite cache.
Requires an active authenticated Substack session.
| Name | Required | Description | Default |
|---|---|---|---|
| force | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| entity | No | |
| status | Yes | |
| sync_mode | No | |
| started_at | Yes | |
| completed_at | No | |
| error_message | No | |
| fetched_count | No | |
| upserted_count | No | |
| reconciled_count | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It discloses that the tool resyncs to a local cache and requires authentication, both important. However, it does not explain what force=true does, whether the cache is replaced or merged, or any potential rate limits/performance impacts.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no fluff, and the key information is front-loaded. The auth requirement is relevant and succinctly stated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's basic purpose and an essential prerequisite. However, for a sync operation with side effects and a parameter that changes behavior, it lacks the force parameter mapping and details on what happens during a resync. The output schema likely covers return values, so that gap is acceptable, but the parameter gap is significant.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has one boolean parameter 'force' with 0% description coverage. The description mentions 'incremental or full' resync but never explicitly connects force=true to full resync, leaving the parameter's effect ambiguous. The description should compensate for the sparse schema but does not.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly identifies the action ('Trigger... resync'), the resource ('saved notes from Substack account into local SQLite cache'), and the mode ('incremental or full'). Distinguishes it from sibling tools like sync_saved_posts by specifying notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a useful prerequisite ('Requires an active authenticated Substack session') but does not explicitly say when to choose this over alternatives like sync_saved_posts or when to use incremental vs full. Usage context is implied but not fully elaborated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_saved_postsC
Trigger incremental or full resync of saved posts from Substack account into local SQLite cache.
Requires an active authenticated Substack session.
| Name | Required | Description | Default |
|---|---|---|---|
| force | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| entity | No | |
| status | Yes | |
| sync_mode | No | |
| started_at | Yes | |
| completed_at | No | |
| error_message | No | |
| fetched_count | No | |
| upserted_count | No | |
| reconciled_count | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must disclose side effects. It mentions writing to a local cache but does not clarify whether the sync is destructive, whether it overwrites or merges data, or if it has any other side effects. The behavior of 'incremental' vs 'full' is vague.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences with no redundant words or filler. It efficiently conveys the core purpose and prerequisite.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description leaves significant gaps: no explanation of the 'force' parameter, no detail on the sync behavior (incremental/full) or its implications, and no mention of output. Given the tool's side effects (writing to cache), this is incomplete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter 'force' is not explained anywhere. The description gives no indication of what 'force' does (e.g., force full resync regardless of state). This is a critical omission for correct usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('sync'), the resource ('saved posts'), the source ('Substack'), and the destination ('local SQLite cache'). It distinguishes from the sibling tool 'sync_saved_notes' by explicitly targeting saved posts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description only mentions a prerequisite (authenticated session) but does not indicate when this tool should be used over alternatives, when incremental vs. full sync is appropriate, or any scenario-specific guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure, and it delivers: it explains the soft-delete side effect, what's preserved (note history), what's affected (active search/list outputs), and the meaning of remote_confirmed=False as a partial-failure state. It could add idempotency behavior or error handling, preventing a 5, but this is well above the minimum.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded core purpose, with each subsequent sentence adding meaningful behavior about soft-deletion and the remote_confirmed flag. Every word earns its place; no fluff or redundancy. This is textbook conciseness without under-specification.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter, low-complexity tool with an output schema, the description covers the essentials: remote action, local cache behavior, and a partial-failure state. Since an output schema exists, the description needn't explain return values, though it could have addressed edge cases like calling on an already-unsaved note or transient network errors.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and the description never explicitly explains the url_or_id parameter format or accepted values. However, the parameter name is largely self-descriptive, and the description's mention of remote_confirmed indirectly hints at the identifier's role in the unbookmark operation. A bit more explicit guidance (URL formats, prefix expectations) would justify a higher score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb+resource+scope: 'Unbookmark a Substack note remotely and soft-delete it in local cache.' It clearly distinguishes from sibling unsave_post by scoping to notes and explicitly describes the dual remote/local action. This matches the 'specific verb+resource+scope' high bar.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is clearly implied (use when you want to remove a bookmark from a note), but no explicit when/when-not rules or named alternatives are given. Unlike the calibration example that says 'use search_calls_extensive instead,' this description doesn't guide the agent to an alternative sibling, so it earns a 3 for implied usage without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url_or_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully carries the behavioral disclosure burden. It explains soft-deletion semantics, that remote_confirmed=False indicates local deletion without remote verification, and describes the direct endpoint vs. best-effort DOM click fallback. This is transparent and detailed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is information-dense with no filler. The main action is front-loaded, and each subsequent sentence adds essential detail about soft-deletion, remote confirmation, and fallback behavior. The length is justified by the tool's nuanced behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having only one parameter and an output schema, the description covers the tool's core behavior, failure mode, and local side effects. It explains the remote_confirmed field and points to save_post for the fallback, making the description sufficiently complete for an agent to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides only a parameter name 'url_or_id' with no description, and the description does not explicitly define accepted formats. However, the description does add useful context about using the Substack ID when known and the fallback behavior, which partially compensates for the 0% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Unbookmark a Substack post remotely and soft-delete it in local cache.' It clearly distinguishes this from sibling tools like save_post and list_saved_posts by describing both the remote action and local cache behavior.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context on when to use the tool—such as when the post's Substack ID is known after a sync—and references save_post for the fallback DOM-click behavior. However, it does not explicitly state when not to use this tool or compare it to alternatives like delete or list operations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
4 tool updates
v0.3.1- Changed
get_saved_post1 field changed- changed
Output schema / properties / result / anyOfPrevious value: -[ - { - "description": "Represents a Substack post record stored in SQLite.", - "properties": { - "audience": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "author_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "content_text": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "created_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "excerpt": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null - }, - "image_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "is_paywalled": { - "default": 0, - "type": "integer" - }, - "is_saved": { - "default": 1, - "type": "integer" - }, - "publication_name": { - "type": "string" - }, - "publication_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "published_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "reading_time_minutes": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null - }, - "saved_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "substack_post_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "title": { - "type": "string" - }, - "unsaved_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "updated_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null - }, - "url": { - "type": "string" - }, - "word_count": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null - } - }, - "required": [ - "url", - "title", - "publication_name" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "description": "Represents a Substack post record stored in SQLite.", + "properties": { + "audience": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "author_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "content_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "created_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "excerpt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null + }, + "image_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "is_fully_read": { + "readOnly": true, + "type": "boolean" + }, + "is_paywalled": { + "default": 0, + "type": "integer" + }, + "is_saved": { + "default": 1, + "type": "integer" + }, + "is_viewed": { + "default": 0, + "type": "integer" + }, + "max_read_progress": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null + }, + "minutes_remaining": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "readOnly": true + }, + "publication_name": { + "type": "string" + }, + "publication_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "published_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "read_progress": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null + }, + "reading_time_minutes": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null + }, + "saved_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "substack_post_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "title": { + "type": "string" + }, + "unsaved_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "updated_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null + }, + "url": { + "type": "string" + }, + "word_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "url", + "title", + "publication_name", + "is_fully_read", + "minutes_remaining" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
list_saved_posts7 fields changed- added
Input schema / properties / read_stateAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / result / items / properties / is_fully_readAdded value: +{ + "readOnly": true, + "type": "boolean" +} - added
Output schema / properties / result / items / properties / is_viewedAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / result / items / properties / max_read_progressAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / result / items / properties / minutes_remainingAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "readOnly": true +} - added
Output schema / properties / result / items / properties / read_progressAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null +} - changed
Output schema / properties / result / items / requiredPrevious value: -[ - "url", - "title", - "publication_name" -]New value: +[ + "url", + "title", + "publication_name", + "is_fully_read", + "minutes_remaining" +]
- Changed
saved_posts_status4 fields changed- added
Output schema / properties / minutes_remaining_totalAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / posts_fully_readAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / posts_in_progressAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / posts_unreadAdded value: +{ + "default": 0, + "type": "integer" +}
- Changed
search_saved_posts7 fields changed- added
Input schema / properties / read_stateAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / result / items / properties / is_fully_readAdded value: +{ + "readOnly": true, + "type": "boolean" +} - added
Output schema / properties / result / items / properties / is_viewedAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / result / items / properties / max_read_progressAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / result / items / properties / minutes_remainingAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "readOnly": true +} - added
Output schema / properties / result / items / properties / read_progressAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null +} - changed
Output schema / properties / result / items / requiredPrevious value: -[ - "url", - "title", - "publication_name" -]New value: +[ + "url", + "title", + "publication_name", + "is_fully_read", + "minutes_remaining" +]
9 tool updates
v0.3.0- Added
get_note_content - Added
get_saved_note - Added
list_saved_notes - Added
save_note - Changed
saved_posts_status4 fields changed- added
Output schema / properties / last_note_sync_statusAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / last_successful_note_syncAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null +} - added
Output schema / properties / total_saved_notesAdded value: +{ + "default": 0, + "type": "integer" +} - added
Output schema / properties / total_unsaved_notesAdded value: +{ + "default": 0, + "type": "integer" +}
- Added
search_saved_notes - Added
sync_saved_notes - Changed
sync_saved_posts1 field changed- added
Output schema / properties / entityAdded value: +{ + "default": "post", + "type": "string" +}
- Added
unsave_note
10 tool updates
v0.1.0- First observed
get_post_content - First observed
get_saved_post - First observed
list_audiences - First observed
list_publications - First observed
list_saved_posts - First observed
save_post - First observed
saved_posts_status - First observed
search_saved_posts - First observed
sync_saved_posts - First observed
unsave_post
TDQS
Tools are generally distinct in action (list, get, search, save, unsave, sync), but overlap exists between similar operations for posts and notes. Names like get_saved_post vs get_post_content may confuse agents without reading descriptions.
Inconsistent use of 'saved' prefix (e.g., get_saved_post vs get_post_content), and lack of parallel structures like saved_notes_status. Mixed verb patterns and inconsistent inclusion of 'saved' in names reduce predictability.
17 tools is on the higher end but justified by supporting two content types (posts and notes) with duplicate operations. Still slightly above typical range, but not excessive for the feature set.
The toolset covers listing, searching, retrieving, saving, unsaving, syncing, and auxiliary metadata (publications, audiences). No obvious missing operations for the bookmarking domain.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Search Hacker News, Bluesky, and Substack from a single MCP interface
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
Personal knowledge base MCP server with semantic search, auto-categorization, metadata extraction
Related MCP Servers
- AlicenseAqualityDmaintenanceStandalone Substack CLI + 26-tool MCP server. Your IDE drafts the replies. Zero AI API keys.261MIT
- AlicenseNot gradedqualityCmaintenanceMCP server for Substack that enables reading articles, comments, feed, and subscriptions from AI clients like Cursor and Claude, with optional authentication for paid content.353MIT
- AlicenseBqualityCmaintenanceAn MCP server that gives Copilot access to Substack posts from your authenticated reader account, including paid subscriptions, enabling daily summary generation by syncing, caching, and summarizing posts.13MIT
- AlicenseNot gradedqualityBmaintenancePrivate MCP server for validating, previewing, creating, and updating Substack newsletter drafts through an MCP-compatible client.9391Apache 2.0
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/toniher/substack-saved-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server