Skip to main content
Glama

news-digest

A small personal news digest: RSS aggregator with optional LLM summarization, exposed as an MCP server for Claude Code / Claude Desktop (plus a FastAPI HTTP layer). Built as a hobby project "delve into Python", focused on asyncio, raw SQL, the Anthropic API and the Model Context Protocol.

README CONTENTS:

What it does

Register the server in Claude Code and ask "fetch the latest articles and give me an overview of today's AI news". The model calls the fetch_latest and get_articles_for_digest tools on its own and writes the digest from the source material.

The app is domain-agnostic: it digests whatever your feeds cover. The bundled defaults are tech feeds, so the examples below are tech - but point feeds.txt at economics, sports or local-news sources and everything downstream (archive, search, digest, topic filter) follows. A sample run with the defaults:

Today's AI news (Aug 11, 2026)

Local and edge inference - today's strongest theme

  • H3-metal (391 pts) - antirez wrote native MiniMax-H3 inference for Apple Silicon in plain C. The biggest AI story of the day on HN.

  • Needle 2 (472 pts) - a 14 MB agentic LLM: tool calls and structured extraction on phones and Raspberry Pi 5 (~500 tok/s).

Business and society

  • As AI eats the web (693 pts, 744 comments) - how AI answers drain the web...

Related MCP server: junk-filter-mcp

Architecture

                    ┌─────────────────────────┐
  RSS/Atom feeds ──▶│  ingest.py              │
  (httpx async,     │  httpx.AsyncClient      │
   N sources        │  + feedparser (sync!)   │
   in parallel)     │  → list[Article]        │
                    └────────────┬────────────┘
                                 │
                                 ▼
                    ┌─────────────────────────┐
                    │  storage.py             │
                    │  SQLite, raw SQL        │
                    │  articles, sources      │
                    │  dedup via UNIQUE(url)  │
                    └────────────┬────────────┘
                                 │
                                 ▼
                    ┌─────────────────────────┐
                    │  llm.py                 │
                    │  Anthropic API:         │
                    │  classify (Haiku)       │
                    │  → rank → summarize     │
                    │  (Opus), structured out │
                    └────────────┬────────────┘
                                 │
                 ┌───────────────┴───────────────┐
                 ▼                                ▼
    ┌─────────────────────────┐     ┌─────────────────────────┐
    │  mcp_server.py          │     │  api.py                 │
    │  MCPServer, stdio       │     │  FastAPI, Pydantic      │
    │  tools: fetch_latest,   │     │  GET /digest/latest     │
    │  search, digest,        │     │  GET /articles          │
    │  source management      │     │                         │
    └─────────────────────────┘     └─────────────────────────┘
         ▲
         │ stdio transport
    Claude Code / Claude Desktop (MCP host)

Design decisions

  • Two phases of intelligence. Phase 1 MCP tools (fetch_latest, search_archive, get_articles_for_digest) return data only - the host model does the synthesis, so the server needs no API key. Phase 2 (make_digest) runs its own pipeline against the Anthropic API: a cheap model (Haiku) classifies every article, a stronger one (Opus) only summarizes the top N. With MCP you have to decide on which side the LLM call runs - both variants live here side by side on purpose.

  • SQLite + raw SQL, no ORM. A local single-user tool: the DB is one file, dedup is UNIQUE(url) + INSERT OR IGNORE, every query is parametrized. On a bigger schema I would reach for SQLAlchemy/SQLModel for the same reasons I use Drizzle in TypeScript.

  • feedparser runs via asyncio.to_thread. No blocking calls inside async code - either the library has an async variant (httpx), or it goes to a worker thread.

  • One dead feed never kills the run. fetch_feed returns None instead of raising; a failing source is skipped and the rest proceed.

Getting started

You will need these tools installed:

  • git - to clone this repo (on macOS, xcode-select --install gets you it)

  • uv - Python package manager (also installs Python itself if you have none)

  • Claude Code - the MCP host you will talk to

No git? Download the repo as a ZIP from the green Code button on GitHub, unpack it, and start from cd news-digest below.

You do not "run" this app directly - you clone it, register it as an MCP server and then talk to it through Claude. In your terminal:

git clone https://github.com/davpu/news-digest
cd news-digest
uv sync
claude mcp add news-digest -- uv run --directory "$(pwd)" python src/mcp_server.py

The registration is scoped to the directory you run claude mcp add from - start your Claude Code sessions there (cd news-digest && claude) to see the server.

Then just ask, in plain language (examples assume the default tech feeds - with your own sources, ask about your own domain):

  • "fetch the latest articles and give me an overview of today's AI news"

  • "did we have anything about Kubernetes lately?"

or use the built-in prompt template as a one-click action: /mcp__news-digest__daily_digest (arguments: topic, days).

Phase 2 (make_digest, module llm.py) needs ANTHROPIC_API_KEY in .env (see .env.example).

Development

Each module doubles as a smoke test when run directly:

uv run python src/ingest.py      # feed fetching
uv run python src/storage.py     # SQLite layer

Configuration

Everything model- or content-facing lives outside the code, which is also what makes the app domain-agnostic:

  • feeds.txt - what to digest from: one feed URL per line, any domain; while empty, the app runs on bundled defaults (DEFAULT_FEEDS in src/ingest.py). You can also just ask Claude - the list_sources, add_source and remove_source tools manage this file conversationally, and each new feed is downloaded and validated before it is added. The setup_sources prompt template bootstraps a whole new domain in one go ("find me quality economics feeds")

  • prompts/interests.md - default relevance profile for the LLM classification step (phase 2)

  • what to digest: the daily_digest prompt template and the get_articles_for_digest tool both take an optional topic, so the same archive can produce an AI digest, a security digest, or anything else

Repo structure

  • src/ - the code (5 modules, see diagram)

  • prompts/ - model-facing text kept out of code

The MCP server also exposes a daily_digest(topic, days) prompt template (MCP prompts primitive), so hosts can offer the whole flow as a one-click action.

Available Tools

7 tools
add_sourceA

Validate and add a new RSS/Atom feed to feeds.txt. Use when the user wants to follow a new source. The feed is downloaded and parsed first - invalid or dead URLs are rejected, nothing is written.

While feeds.txt is empty the app runs on bundled default feeds. When adding the user's FIRST own feed, ask them once whether to keep the defaults too (then call this with keep_defaults=True) or start fresh with only their own sources. Do not ask again once feeds.txt has entries.

Inputs: url (required), keep_defaults. Returns: result

fetch_latestA

Fetch the latest articles from all configured RSS feeds and store them (duplicates are skipped). Use when the user wants to refresh the archive or asks about the newest articles.

Inputs: no named parameters. Returns: result

get_articles_for_digestA

Return raw article data (title, URL, source, summary) from the last days days as a markdown list. Use when you (the host model) should write the digest yourself - this tool only provides the source material.

topic is an optional keyword pre-filter (whole-word match) - useful for large archives. Omit it to get everything and pick relevant articles yourself, which handles synonyms and related themes better.

Inputs: days, topic. Returns: result

list_sourcesA

List the feeds the digest currently aggregates, with the number of stored articles per source. Reports whether the app runs on bundled default feeds (feeds.txt is empty) or the user's own selection.

Inputs: no named parameters. Returns: result

make_digestA

Build a ready-made markdown digest of the most relevant articles from the last days days using the server's own LLM pipeline. Not implemented yet - use get_articles_for_digest instead.

Inputs: days. Returns: result

remove_sourceA

Remove a feed from feeds.txt. Use when the user no longer wants a source. Already stored articles from that source stay in the archive.

Inputs: url (required). Returns: result

search_archiveA

Search stored articles by keyword (matches title or summary). Use when the user asks about a specific topic from the archive.

Inputs: query (required). Returns: result

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 7 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes: add_source vs remove_source are opposites, fetch_latest vs search_archive vs get_articles_for_digest each target different actions. The only slight ambiguity is between get_articles_for_digest and make_digest, but the latter is explicitly marked as not implemented and directs to the former.

    Naming Consistency5/5

    All tool names follow the same verb_noun pattern in lowercase with underscores (add_source, remove_source, fetch_latest, search_archive, get_articles_for_digest, make_digest, list_sources). This is highly consistent and predictable.

    Tool Count5/5

    With 7 tools, the set is well-scoped for an RSS digest server: source management (add, remove, list), content fetching, archive search, and digest creation. Each tool serves a distinct purpose without unnecessary bloat or missing essentials.

    Completeness4/5

    The core workflows are covered: source CRUD (add/remove/list), fetching latest articles, searching the archive, and retrieving articles for a digest. Minor gaps include lack of an update_source tool and the unimplemented make_digest, but the alternatives provided make the surface functional.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers