Skip to main content

MCP server for news aggregation, structured reports, and photocard generation. RSS + GDELT, no API keys required.

Project description

news-reporter

PyPI Python License: MIT MCP smithery badge

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. stdio for local, streamable-http for 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:

  1. search_news("AI regulation EU", max_results=8) → 8 articles from BBC, Guardian, NYT, GDELT global sources
  2. generate_report_structure(articles, "EU AI Regulation Update") → fills in synthesis sections from raw evidence
  3. create_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_news budget: 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": "..."} to RSS_FEEDS in src/news_reporter/config.py
  • Cache TTL: edit SEARCH_CACHE_TTL_SECONDS / ARTICLE_CACHE_TTL_SECONDS in config.py
  • Custom font: drop a .ttf named DejaVuSans-Bold.ttf into src/news_reporter/assets/fonts/ (without one, Pillow's bundled default is used)
  • Photocard palette: edit PALETTE in src/news_reporter/tools/photocard.py

Roadmap

  • topic_trends tool — GDELT volume timeseries for a query
  • Optional og:image scrape for richer photocard backgrounds
  • Multi-language UI (GDELT supports sourcelang filter)
  • Auth header support for remote deployments
  • Turso/LibSQL adapter for edge caching

PRs welcome. See DEPLOY.md for publish + hosting workflow.


Contributing

  1. Fork + clone
  2. uv sync --extra dev
  3. Make changes + add tests
  4. uv run pytest -q (must stay green)
  5. Open a PR

Bug reports + feature requests: https://github.com/SaimumIslam/news-reporter/issues


Credits


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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

news_reporter-0.1.0.tar.gz (16.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

news_reporter-0.1.0-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

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

Hashes for news_reporter-0.1.0.tar.gz
Algorithm Hash digest
SHA256 48075914d417046363f700d0a34dc13140b9680ab5a78b82ee873f437a863e87
MD5 91e1c5e26a1fad551288ca650cfa54f3
BLAKE2b-256 98729eab7bbc2f453c1e08572fe5d8961a03b76c037b0bb1077f02fb4753b55a

See more details on using hashes here.

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

Hashes for news_reporter-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 79ecb7d25f96f8555f5d7d10ca067a911df8c27763b62c7fabb06c403a37ef7d
MD5 9854b2d943028661d743bb303637262e
BLAKE2b-256 39506693205e8e278314943db7dbf14d59d02dcd8ecbaad167789cf9cd7ad712

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page