MCP server for news aggregation, structured reports, and photocard generation. RSS + GDELT, no API keys required.
Project description
news-reporter
A free, zero-API-key Model Context Protocol server that lets any LLM search global news, build structured reports, and generate social-ready photocards.
Built with FastMCP. Aggregates from RSS feeds + the GDELT 2.0 DOC API. SQLite cache cuts repeat upstream calls.
Why use this
- Free forever. No NewsAPI key. No DALL-E. No Cloudinary. RSS + GDELT only.
- Three tools, one server. Search → structure → visualize, no chaining services.
- Cached. SQLite layer dedupes articles and skips redundant fetches inside a 1-hour window.
- Drop-in for Claude Desktop, Cursor, Cline, Continue, Zed, or any MCP client.
- Both transports.
stdiofor local,streamable-httpfor remote hosting.
Tools
| Tool | What it does | Returns |
|---|---|---|
search_news(query, max_results, date_from, date_to) |
Searches RSS + GDELT in parallel, dedupes, caches | List of {url, source, headline, published_at, snippet} |
generate_report_structure(articles, topic) |
Builds a Markdown skeleton with timeline + raw evidence sections for the LLM to fill in | Markdown string |
create_photocard(headline, subtitle, output_path) |
Renders a 1080×1080 PNG with gradient background + auto-fitted headline | {path, base64, width, height, mime_type} |
Install
Option A — Local (stdio)
# Run directly without installing (recommended):
uvx news-reporter
# Or install globally:
pipx install news-reporter
Then add to your MCP client config:
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)
{
"mcpServers": {
"news-reporter": {
"command": "uvx",
"args": ["news-reporter"]
}
}
}
Restart Claude Desktop. Three tools appear in the tool picker.
Cursor / Cline / Continue / Zed
Same JSON, in their respective MCP config files. See https://modelcontextprotocol.io/clients.
Option B — Hosted (HTTP)
Use the Smithery-hosted version:
{
"mcpServers": {
"news-reporter": {
"url": "https://server.smithery.ai/@SaimumIslam/news-reporter/mcp"
}
}
}
No install. Updates automatically.
Option C — Self-host with Docker
docker run -d -p 8000:8000 -v news-data:/data ghcr.io/saimumislam/news-reporter:latest
Or build from source:
git clone https://github.com/SaimumIslam/news-reporter
cd news-reporter
docker build -t news-reporter .
docker run -d -p 8000:8000 -v "$PWD/data:/data" news-reporter
The container runs streamable-http on port 8000 with /data for the SQLite cache.
Example workflow
User asks Claude: "Get me the latest on EU AI regulation and make a photocard."
Claude calls:
search_news("AI regulation EU", max_results=8)→ 8 articles from BBC, Guardian, NYT, GDELT global sourcesgenerate_report_structure(articles, "EU AI Regulation Update")→ fills in synthesis sections from raw evidencecreate_photocard("EU Passes Sweeping AI Regulation", subtitle="May 2026")→ 1080×1080 PNG ready for Twitter/Instagram
Total: ~10 seconds, $0 in API costs.
Tool reference
search_news
search_news(
query: str,
max_results: int = 10,
date_from: str | None = None, # "YYYY-MM-DD"
date_to: str | None = None,
) -> dict
Returns:
{
"query": "AI regulation EU",
"count": 5,
"articles": [
{
"url": "https://www.bbc.com/news/...",
"source": "BBC World",
"headline": "EU passes new AI regulation framework",
"published_at": "2026-05-10T09:30:00+00:00",
"snippet": "The European Union approved..."
}
],
"partial": false,
"errors": [],
"cache_hit": false
}
partial: true means at least one source failed (rate-limited, timeout, etc) but others succeeded. The tool never raises.
generate_report_structure
Returns Markdown with five sections:
# Report: <topic>
_Generated: <timestamp> | Sources: <N> articles from <M> outlets_
## Executive Summary
<!-- LLM: synthesize 3-5 sentences -->
## Key Events / Timeline
- 2026-05-10 — Headline (Source) — URL
...
## Conflicting Perspectives
<!-- LLM: identify divergent framings -->
## Impact Analysis
<!-- LLM: short, medium, long-term impacts -->
## Source Articles (raw)
1. Headline — Source — Published — URL — snippet
...
The HTML-comment placeholders are written for the consumer LLM to expand using the structured raw evidence below.
create_photocard
- Canvas: 1080×1080 (Instagram-ready)
- Background: vertical gradient seeded by
sha256(headline)→ same topic always renders same colors - Headline: bundled DejaVuSans Bold, auto-wrapped, auto-fit shrink loop (84px → 36px, max 4 lines)
- Subtitle: optional second line
- Watermark: bottom-right "news-reporter"
- Output: writes PNG + returns base64 — caller can embed without filesystem access
Configuration
All optional. Set via env or .env file.
| Variable | Default | Purpose |
|---|---|---|
MCP_TRANSPORT |
stdio |
stdio, streamable-http, or sse |
HOST |
0.0.0.0 |
Bind host (HTTP transport) |
PORT |
8000 |
Bind port (HTTP transport) |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR |
NEWS_DB_PATH |
./news.db |
SQLite cache path |
PHOTOCARD_OUTPUT_DIR |
./photocards |
PNG output directory |
Architecture
src/news_reporter/
├── server.py FastMCP entrypoint, registers 3 tools, transport switch
├── config.py paths, RSS feed list, TTL constants
├── db.py SQLite cache (auto-init on first connect)
├── models.py Article + SearchResult dataclasses
├── sources/
│ ├── rss.py feedparser + httpx parallel fetch
│ ├── gdelt.py GDELT 2.0 DOC API client
│ └── scrape.py BeautifulSoup full-text extractor
├── tools/
│ ├── search.py search_news impl
│ ├── report.py generate_report_structure impl
│ └── photocard.py create_photocard impl (Pillow)
└── assets/fonts/ drop a TTF here to override the default font
Cache schema
articles(url PK, source, headline, published_at, snippet, full_text, fetched_at)
search_cache(query_hash PK, query, article_urls_json, cached_at)
query_hash = sha256(lower(query) + date_from + date_to). Search TTL 1h. Article TTL 24h.
Reliability
- Per-source HTTP timeout: 8s
- Total
search_newsbudget: 15s - GDELT 429 → exponential backoff (3 attempts, 1/2/4s)
- Any source failure →
partial: true+ per-source error in response; other sources still returned - Tools never raise to the MCP layer
Development
git clone https://github.com/SaimumIslam/news-reporter
cd news-reporter
brew install uv # if not installed
uv sync --extra dev
uv run pytest -q # 36 tests, ~0.5s
Running locally
# stdio (for MCP clients):
uv run news-reporter
# HTTP (for hosted use):
MCP_TRANSPORT=streamable-http PORT=8000 uv run news-reporter
# Live debugger:
npx @modelcontextprotocol/inspector uv run news-reporter
Customization
- Add an RSS source: append
{"name": "...", "url": "..."}toRSS_FEEDSinsrc/news_reporter/config.py - Cache TTL: edit
SEARCH_CACHE_TTL_SECONDS/ARTICLE_CACHE_TTL_SECONDSinconfig.py - Custom font: drop a
.ttfnamedDejaVuSans-Bold.ttfintosrc/news_reporter/assets/fonts/(without one, Pillow's bundled default is used) - Photocard palette: edit
PALETTEinsrc/news_reporter/tools/photocard.py
Roadmap
-
topic_trendstool — GDELT volume timeseries for a query - Optional
og:imagescrape for richer photocard backgrounds - Multi-language UI (GDELT supports
sourcelangfilter) - Auth header support for remote deployments
- Turso/LibSQL adapter for edge caching
PRs welcome. See DEPLOY.md for publish + hosting workflow.
Contributing
- Fork + clone
uv sync --extra dev- Make changes + add tests
uv run pytest -q(must stay green)- Open a PR
Bug reports + feature requests: https://github.com/SaimumIslam/news-reporter/issues
Credits
- Model Context Protocol by Anthropic
- FastMCP Python SDK
- GDELT Project — global news event database
- DejaVu Fonts — bundled with Pillow
- RSS publishers: BBC, Guardian, Al Jazeera, NPR, DW, NYT, Hacker News
License
MIT — see LICENSE. Use it, fork it, ship it.
If this project saves you time, a ⭐ on GitHub helps others discover it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file news_reporter-0.1.0.tar.gz.
File metadata
- Download URL: news_reporter-0.1.0.tar.gz
- Upload date:
- Size: 16.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48075914d417046363f700d0a34dc13140b9680ab5a78b82ee873f437a863e87
|
|
| MD5 |
91e1c5e26a1fad551288ca650cfa54f3
|
|
| BLAKE2b-256 |
98729eab7bbc2f453c1e08572fe5d8961a03b76c037b0bb1077f02fb4753b55a
|
File details
Details for the file news_reporter-0.1.0-py3-none-any.whl.
File metadata
- Download URL: news_reporter-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79ecb7d25f96f8555f5d7d10ca067a911df8c27763b62c7fabb06c403a37ef7d
|
|
| MD5 |
9854b2d943028661d743bb303637262e
|
|
| BLAKE2b-256 |
39506693205e8e278314943db7dbf14d59d02dcd8ecbaad167789cf9cd7ad712
|