Local-first RSS reader + readability extractor - subscribe to feeds, get clean article text without ad sites, full-text + semantic search.
rss-mcp defines 6 tools with schemas and descriptions, but exhibits significant quality gaps. Three tools (feeds_ops, readability_ops, reader_ops) are mega-tools combining 5-13 operations each via an 'operation' enum parameter, violating the single-responsibility principle. Descriptions are present but generic (194-char baseline met for some, but many lack actionable context). Parameter descriptions exist but are minimal (e.g., 'Feed ID for operations' lacks guidance on how to obtain it). Output schemas are documented in docstrings but not formally in JSON Schema. Error handling is absent, no recovery guidance, no categorization of retryable vs. fatal errors. Tool names like 'feeds_ops' and 'readability_ops' use '_ops' suffix rather than verb_noun convention, making intent less discoverable. No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite destructive operations (delete, mark_read, subscribe). Security: no evidence of input validation, SQL injection guards, or permission checks visible in the sample code.
feeds_ops - Full feed subscription lifecycle. RATIONALE: groups subscribe/unsubscribe/list/refresh/fetch/get/mark/star/remove/opml/health into one discoverable tool. Stateful reader, unlike glance-mcp single-shot fetch. ## Return Format Dictionary with `success` (bool), `message` (str), and operation-specific payload (e.g. `feeds`, `items`, `feed`, `opml`). ## Examples - {"operation": "list_feeds"} -> lists all subscribed feeds. - {"operation": "subscribe", "url": "https://news.ycombinator.com/rss"} -> subscribes to RSS feed. - {"operation": "refresh_all"} -> polls all feeds for new articles. - {"operation": "fetch_items", "limit": 20, "unread_only": true} -> retrieves unread articles.
readability_ops - Clean article text without ad sites. RATIONALE: trafilatura primary plus readability-lxml fallback, cached in SQLite. No paywall bypass: if the page needs login, extraction reports failure honestly. ## Return Format Dictionary with `success` (bool), `message` (str), and article text fields (`title`, `author`, `text`, `html`, `extractor`, `cached`). ## Examples - {"operation": "extract", "item_id": 3} -> downloads article and extracts cleaned text. - {"operation": "get_cached", "item_id": 3} -> retrieves previously cached clean extraction. - {"operation": "list_extractors"} -> lists available readability engines.
reader_ops - Daily reading surface: inbox, stars, search, stats, RAG sweeps. RATIONALE: single entry point for what a human checks every morning. ## Return Format {success, message, items/stats/job/job_id/status/chunks} ## Examples - {"operation": "inbox"} unread items - {"operation": "search_text", "query": "vienna"} FTS search - {"operation": "search_semantic", "query": "rocket engine"} vector search - {"operation": "trigger_sweep", "mode": "incremental", "hours": 4} background index job
Mega-tools violate single-responsibility principle. feeds_ops combines 13 operations (subscribe, unsubscribe, list_feeds, refresh_feed, refresh_all, fetch_items, get_item, mark_read, mark_star, remove_item, opml_import, opml_export, feed_health) into one tool via an 'operation' enum. This forces the LLM to reason about which operation to invoke rather than selecting a tool by name. Should split into separate tools: subscribe_feed, unsubscribe_feed, list_feeds, refresh_feed, refresh_all, fetch_items, get_item, mark_item_read, star_item, remove_item, import_opml, export_opml, check_feed_health.
Tool naming uses '_ops' suffix instead of verb_noun convention. 'feeds_ops', 'readability_ops', 'reader_ops' are vague; LLMs cannot infer action from the name alone. Should be: subscribe_feed, list_feeds, extract_article, search_articles, etc. Verb-first naming (get_, create_, update_, delete_, search_, list_) is the production standard and aids LLM tool selection.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 46 | 2025-06-18+ | v2 |
rss_help - Capability summary and op list. ## Return Format Dictionary with `success` (bool), `message` (str), registered tools, ports, and comparison vs glance-mcp. ## Examples - {} -> summary of reader capabilities and registered tools.
rss_shutdown - Graceful shutdown hook for the daemon. ## Return Format Dictionary with `success` (bool) and acknowledgement `message` (str). ## Examples - {} -> acknowledges shutdown request.
show_reader_prefab_card - Prefab stats card for list/status/stats coverage. ## Return Format ToolResult with PrefabApp structured content plus plain-text fallback. ## Examples - {} -> renders reader stats card.
No error handling or recovery guidance. Tools return {success, message} but do not categorize errors as retryable, user-fixable, or fatal. Example: feeds_ops subscribe returns {'success': False, 'message': 'url is required'}, good. But no guidance on what to do if the feed URL is invalid, unreachable, or already subscribed. Should return: 'Feed URL invalid or unreachable. Verify the URL is a valid RSS feed and try again.' with a retryable classification.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Tools like mark_read, mark_star, remove_item, rss_shutdown are destructive or have side effects, but the schema does not declare this. LLMs cannot distinguish safe (read-only) from dangerous (destructive) operations without explicit hints. Should add destructiveHint=true to mark_read, mark_star, remove_item, subscribe, unsubscribe, rss_shutdown.
Parameter descriptions are minimal and lack actionable context. Example: 'feed_id' described as 'Feed ID for operations', does not explain how to obtain a feed_id (call list_feeds first?), what range is valid, or what happens if the ID does not exist. Should be: 'Feed ID (integer, obtained from list_feeds). If invalid, operation returns success=false with available feed IDs.'