Skip to main content
Glama
danmarce

news-mcp

by danmarce
README.md
# news-mcp

Curated RSS/Atom/RDF news → SQLite (rolling ~14-day window) → structured MCP tools.
Items are stored in their **original language**; the LLM translates, summarizes and compares framing at query time.
Design rationale: [CLAUDE.md](CLAUDE.md).

## Tools
| tool | use |
|---|---|
| `latest_news(source?, lang?, country?, category?, hours=24, n=15)` | newest headlines, filterable |
| `news_by_topic(query, days=7, lang?, n=20)` | keyword search; spaces = AND, `\|` = OR (`"Noboa \| ノボア"`) — case/accent-insensitive |
| `compare_coverage(topic, days=7, per_source=3)` | matches grouped by outlet + outlets with no match → framing comparison |
| `sources()` | curated feeds, item counts, freshness / last status |

## Run
```bash
uv sync
uv run news-mcp refresh                      # pull all feeds once (conditional GET), upsert, prune
uv run news-mcp serve                        # stdio (Claude Desktop / dev)
NEWS_MCP_TOKEN=secret uv run news-mcp serve --transport http --host 0.0.0.0 --port 8000   # streamable-http at /mcp
uv run pytest
```

| env | default | |
|---|---|---|
| `NEWS_DB` | `data/news.db` | SQLite path (WAL; refresh and server can share it) |
| `NEWS_FEEDS` | packaged `src/news_mcp/feeds.toml` | curated feed list |
| `NEWS_RETENTION_DAYS` | `14` | rolling window |
| `NEWS_REFRESH_MINUTES` | `0` (off) | >0: `serve` also refreshes in the background (no timer needed) |
| `NEWS_MCP_TOKEN` | unset | bearer token required on HTTP (`/healthz` stays open) |
| `NEWS_USER_AGENT`, `NEWS_FETCH_TIMEOUT` | polite UA, `20` | |

## Claude Desktop
`%APPDATA%\Claude\claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "news": {
      "command": "uv",
      "args": ["--directory", "C:\\Code\\news-mcp", "run", "news-mcp", "serve"],
      "env": { "NEWS_DB": "C:\\Code\\news-mcp\\data\\news.db", "NEWS_REFRESH_MINUTES": "45" }
    }
  }
}
```

## Docker Compose (e.g. mcpo → Open WebUI)
Build the image with `docker build -t news-mcp .`. By default it serves HTTP on :8000 at `/mcp`, with an open
`/healthz` endpoint for the built-in healthcheck.
```yaml
services:
  news-mcp:
    image: news-mcp:latest
    restart: unless-stopped
    environment:
      NEWS_MCP_TOKEN: ${NEWS_MCP_TOKEN}      # put it in .env; clients send "Authorization: Bearer <token>"
      NEWS_REFRESH_MINUTES: "45"             # refresh at startup, then every 45 min (no timer needed)
      NEWS_USER_AGENT: "news-mcp/0.1 (+https://example.org/your-contact)"   # identify your deployment
      # NEWS_FEEDS: /config/feeds.toml       # use your own curated list
    volumes:
      - news-data:/data                      # SQLite; local disk, not NFS/SMB
      # - ./feeds.toml:/config/feeds.toml:ro
    # ports: ["8000:8000"]                   # only if clients live outside this compose network
volumes:
  news-data:
```
If you'd rather use a host timer, leave `NEWS_REFRESH_MINUTES` unset and have the timer run
`docker compose run --rm news-mcp refresh`. It uses the same `/data` volume.

mcpo entry (from a container on the same network):
```json
{ "mcpServers": { "news": { "type": "streamable-http", "url": "http://news-mcp:8000/mcp",
  "headers": { "Authorization": "Bearer ${NEWS_MCP_TOKEN}" } } } }
```

## Auth and exposure
The server is deliberately simple about auth: stdio, or streamable-HTTP with a single bearer token
(`NEWS_MCP_TOKEN`) for a private network, e.g. behind mcpo. It has **no OAuth, no users and no audit log**, by
design. To publish it more widely (at work or on the internet), put a management layer in front of it: an MCP
gateway or auth proxy that handles OAuth, users and audit for all your servers at once.

Every tool declares a `title` and all four MCP hints. They are all read-only, non-destructive, idempotent and
closed-world, because tools only query the local SQLite store and only the refresh job touches the network.
Hints are advisory for clients and directories; they don't replace the bearer token or a gateway.

## AI assistance
news-mcp is developed openly with the help of Claude (Anthropic). We state this plainly: commits
Claude helped write carry a `Co-Authored-By: Claude` trailer. The code and design are open source so the
work can be inspected, reused, and given back.

## License
Code: [MPL-2.0](LICENSE). The repo ships only feed *URLs*; the news content it fetches belongs to each publisher and
stays in your local database. Respect each feed's terms, keep the refresh interval polite (≥30 min), and set
`NEWS_USER_AGENT` to identify your own deployment.

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: listing feeds, browsing latest news with metadata filters, keyword search, and cross-outlet comparison. Although latest_news and news_by_topic both return news, their descriptions clearly separate browsing from searching, and compare_coverage is uniquely analytical.

Naming Consistency3/5

The names mix a bare noun (sources), a noun phrase (latest_news), a prepositional phrase (news_by_topic), and a verb_noun pattern (compare_coverage). While all are readable, there is no consistent convention across the set.

Tool Count5/5

With only 4 tools, the set is well-scoped and each tool earns its place. There is no redundancy, and the count fits the narrow domain of news retrieval and comparison.

Completeness4/5

The surface covers listing sources, browsing, searching, and comparing coverage, which are the core operations for a news server. Minor gaps exist, such as fetching full article content or paginating results beyond the n parameter, but agents can work around these.

Maintenance

ActivityMaintained
ResponsivenessNo issues