Skip to main content

news-reporter

PyPI Python License: MIT MCP

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.
  • Zero config. stdio transport, works out of the box with any MCP client.

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.


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
LOG_LEVEL INFO DEBUG, INFO, WARNING, ERROR
NEWS_DB_PATH ./news.db SQLite cache path
PHOTOCARD_OUTPUT_DIR ./photocards PNG output directory

Architecture

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.6s

Running locally

# stdio (for MCP clients):
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 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 news_reporter/assets/fonts/ (without one, Pillow's bundled default is used)
  • Photocard palette: edit PALETTE in 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)

PRs welcome.


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.

Metadata

Release files for news-reporter 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for news-reporter 0.1.1
File Size Uploaded
news_reporter-0.1.1.tar.gz 15.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for news-reporter 0.1.1
File Interpreter ABI Platform
news_reporter-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 37.5 kB

Release files / news_reporter-0.1.1.tar.gz

Download URL news_reporter-0.1.1.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6b50d1ff932ece7740aa5e0dee7ad90998bc6193ab54667960b33807da09e119
BLAKE2b-256 checksum
How to use checksums
33ce8ecb0a3a5d94a02524669319efa80bc451d9b94e060559afd6554c2bae7d
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / news_reporter-0.1.1-py3-none-any.whl

Download URL news_reporter-0.1.1-py3-none-any.whl
Size 21.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e4bfdd4045281906cf91c54e124eb6cb7036c5a59eec59ead45c11d4c8744877
BLAKE2b-256 checksum
How to use checksums
9c3c5b36cb3ae47287a465134222819f21f17af2387c06f77f57441f1a478002
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page