{
  "markdown": "# German Newsfeed MCP Server\n\n<!-- mcp-name: io.github.Jakolo121/german-newsfeed-mcp -->\n\n![pylint](https://img.shields.io/badge/pylint-10.00%2F10-brightgreen)\n![tests](https://img.shields.io/badge/tests-273%20passed-brightgreen)\n![python](https://img.shields.io/badge/python-3.12%2B-blue)\n![license](https://img.shields.io/badge/license-Apache%202.0-blue)\n\n> **Disclaimer:** This is a private, unofficial project. It is not an ARD\n> product and is neither operated nor endorsed by ARD,\n> ARD-aktuell, or NDR. It is developed independently of the author's\n> professional employment. \"ARD\" and \"tagesschau\" are trademarks of their\n> respective owners and are named here solely to describe the API being\n> accessed.\n\n> This project merely connects the public API to an MCP-capable AI\n> assistant. ARD-aktuell is responsible for the API itself, its\n> operation, and its content; this project cannot provide information\n> on any of those. Please raise any concerns about this project, in\n> particular from rights holders, as a\n> [GitHub issue](https://github.com/Jakolo121/german-newsfeed-mcp/issues).\n> Substantiated concerns will be addressed promptly.\n\n## In thirty seconds\n\nThis [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server\nconnects your AI assistant (Claude Desktop and others) to the public news\nAPI of tagesschau.de: current headlines, category and regional news, and\nfull-text search, locally via `stdio`, no API key required.\n\nExample: asked _\"Was sind die aktuellen Schlagzeilen?\"_, the assistant\nanswers with the current top stories from tagesschau.de, each with title,\ndate, summary, and a link to the article.\n\nLanguage:\n\n- 🇩🇪 [Deutsch](README_de.md)\n- 🇬🇧 English\n\n---\n\n<details>\n<summary><strong>Table of Contents</strong></summary>\n\n1. [What is this?](#what-is-this)\n2. [Data Source and Terms of Use](#data-source-and-terms-of-use)\n3. [Features](#features)\n4. [Project Structure](#project-structure)\n5. [Quick Start Local (Claude Desktop)](#quick-start-local-claude-desktop)\n6. [Remote / Docker Deployment](#remote--docker-deployment)\n7. [Configuration Reference](#configuration-reference)\n8. [Available Tools](#available-tools)\n9. [Available Resources](#available-resources)\n10. [Development Guide](#development-guide)\n11. [Running the Tests](#running-the-tests)\n12. [Makefile Reference](#makefile-reference)\n13. [Troubleshooting](#troubleshooting)\n14. [When it stops working](#when-it-stops-working)\n15. [License & Acknowledgements](#license--acknowledgements)\n\n</details>\n\n---\n\n## What is this?\n\nThis MCP server connects the public news API of tagesschau.de to your AI assistant (Claude, Open Claw, etc.).\n\nOnce connected, your AI can answer questions like:\n\n- _\"Was sind die aktuellen Schlagzeilen?\"_\n- _\"Zeig mir die neuesten Wirtschaftsnachrichten.\"_\n- _\"Suche nach Artikeln über Ukraine.\"_\n- _\"Welche Regionalnachrichten gibt es aus Bayern?\"_\n\nFor details on the API and its terms, see [Data Source and Terms of Use](#data-source-and-terms-of-use). No API key required.\n\n---\n\n## Data Source and Terms of Use\n\nThis server calls the publicly accessible endpoint\n`www.tagesschau.de/api2u/`, operated by ARD-aktuell. The delivered\ncontent originates from the ARD broadcasters, remains subject to their\nrights and to the terms of use of tagesschau.de:\nhttps://www.tagesschau.de/nutzungsbedingungen/\n\nThe API is not officially documented. Community documentation is\navailable at bund.dev (https://tagesschau.api.bund.dev). bund.dev is a\ncivil-society documentation project, neither the operator of the API\nnor a rights holder of the content. Its documentation served as a\nreference for this project but does not grant any usage rights.\n\nApplicable limits: at most 60 requests per hour, and no republication\nof the content except for offerings under a CC licence\n(https://tagesschau.de/creativecommons). Compliance is the\nresponsibility of whoever operates a given instance.\n\nThe robots.txt of tagesschau.de additionally declares an express\nreservation of rights under Section 44b(3) of the German Copyright Act\n(as of 2026-05-19): text and data mining and the automated use of the\ncontent for training or fine-tuning AI models are prohibited without\nwritten consent. Expressly exempt is automated access for the sole\npurpose of retrieval-augmented generation (RAG) or grounding, provided\nthe technical directives of the robots.txt are complied with and the\ncontent remains attributed to its original source. This server falls\nunder that exemption: it passes content to the assistant exclusively\ntogether with source links. The retrieved content must not be used to\ntrain AI models.\n\n---\n\n## Features\n\n|                         |                                                                         |\n| ----------------------- | ----------------------------------------------------------------------- |\n| 🗞️ **Live news**        | Fetches breaking news, categorised news, and regional news in real time |\n| 🔍 **Full-text search** | Search across all available articles                                    |\n| 📺 **Live streams**     | List all available channels and HLS stream URLs                         |\n| ⏱️ **Rate limiter**     | Local token bucket honouring the API's 60/h limit                       |\n| 🚀 **Dual transport**   | `stdio` for local Claude Desktop; `streamable-http` for remote / Docker |\n| 🐳 **Docker-ready**     | Multi-stage image, non-root user, health-check, resource limits         |\n| ✅ **273 tests**        | 251 unit tests + 22 live integration tests                              |\n| 🛠️ **Makefile**         | `make test`, `make lint`, `make docker-build` and more                  |\n| 🔒 **No secrets**       | Public API, no API keys                                                 |\n\n---\n\n## Project Structure\n\n```\ngerman-newsfeed-mcp/\n├── src/\n│   └── german_newsfeed_mcp/\n│       ├── __init__.py      # Package metadata\n│       ├── config.py        # Environment-driven configuration\n│       ├── client.py        # Async HTTP client (httpx) + error handling\n│       ├── rate_limiter.py  # Token-bucket rate limiter\n│       ├── validators.py    # Domain constants + validation helpers\n│       ├── formatters.py    # Markdown rendering of news items & channels\n│       ├── tools.py         # MCP tool business logic\n│       ├── resources.py     # MCP resource business logic\n│       └── server.py        # Composition root: FastMCP + run() entry-point\n├── tests/\n│   ├── conftest.py          # Shared fixtures & mock payloads\n│   ├── test_client.py       # Client unit + live integration tests\n│   ├── test_rate_limiter.py # Rate-limiter unit tests\n│   ├── test_validators.py   # Validator unit tests (URL & ressort checks)\n│   ├── test_formatters.py   # Formatter unit tests (pure functions)\n│   ├── test_tools.py        # Tool unit + live integration tests\n│   └── test_resources.py    # Resource unit + live integration tests\n├── main.py                  # Thin entry-point (calls server.run())\n├── pyproject.toml           # Project metadata, deps, pytest & pylint config\n├── uv.lock                  # Locked dependency graph (commit this!)\n├── Dockerfile               # Multi-stage production image\n├── docker-compose.yml       # One-command remote deployment\n├── .env.example             # Configuration template\n├── Makefile                 # Developer shortcuts (test, lint, docker, clean)\n├── CHANGELOG.md             # Version history\n├── CONTRIBUTING.md          # How to contribute\n└── README_de.md             # German version of this file\n```\n\n---\n\n## Quick Start Local (Claude Desktop)\n\nAlso applicable to other AI assistants, edit their respective config instead. This mode uses `stdio` transport; the server is launched as a child process. No port is needed.\n\n### Prerequisites\n\n- macOS / Linux / Windows (WSL2)\n- [Python 3.12+](https://www.python.org/downloads/)\n- [uv](https://docs.astral.sh/uv/getting-started/installation/): `curl -LsSf https://astral.sh/uv/install.sh | sh`\n- [Claude Desktop](https://claude.ai/download)\n\n### Step 1: Clone and install\n\n```bash\ngit clone https://github.com/Jakolo121/german-newsfeed-mcp.git\ncd german-newsfeed-mcp\nuv sync\n```\n\n### Step 2: Verify it works\n\n```bash\nuv run python -c \"from german_newsfeed_mcp.server import mcp; print('OK!', mcp.name)\"\n# Expected: OK! German Newsfeed MCP\n```\n\n### Step 3: Connect Claude Desktop\n\nOpen your Claude Desktop config file:\n\n| OS      | Path                                                              |\n| ------- | ----------------------------------------------------------------- |\n| macOS   | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Windows | `%APPDATA%\\Claude\\claude_desktop_config.json`                     |\n| Linux   | `~/.config/Claude/claude_desktop_config.json`                     |\n\nAdd the following entry (adjust the path to your clone):\n\n```json\n    \"german-newsfeed\": {\n      \"command\": \"uv\",\n      \"args\": [\n        \"--directory\",\n        \"/absolute/path/to/german-newsfeed-mcp\",\n        \"run\",\n        \"german-newsfeed-mcp\"\n      ]\n    },\n```\n\n### Step 4: Restart Claude Desktop\n\nQuit and reopen the application, or reload its MCP servers, depending on the application.\n\n### Step 5: Try it!\n\nAsk your assistant:\n\n> _\"Was sind die aktuellen Nachrichten?\"_\n\n---\n\n## Remote / Docker Deployment\n\nSelf-hosting for your own or team-internal use. The recommended default\nis `stdio` (see [Quick Start](#quick-start-local-claude-desktop));\nthe `streamable-http` transport is an option you choose deliberately.\n\n**Security note:** The HTTP transport has no authentication. Do not\nexpose it publicly without an authentication layer in front (e.g. a\nreverse proxy). The Compose setup therefore deliberately binds the port\nto `127.0.0.1` only. Whoever makes an instance reachable for third\nparties becomes the responsible operator under the\n[terms of use](#data-source-and-terms-of-use).\n\n**Rate limiter limitations:** `stateless_http=True` refers to MCP\nsessions, not to the rate limiter. The token bucket is process-local\nin-memory state. Two limitations follow:\n\n1. Multiple replicas against the same upstream API multiply the request\n   budget.\n2. A container restart resets the bucket to full. Combined with\n   `restart: unless-stopped` and a crash loop, this can exceed the\n   limit. Watch the logs.\n\n(The legacy `sse` transport is still supported.)\n\n### Prerequisites\n\n- [Docker](https://docs.docker.com/get-docker/) 24+\n- [Docker Compose](https://docs.docker.com/compose/install/) v2+\n\n### Step 1: Create your .env file\n\n```bash\ncp .env.example .env\n# Edit .env if you want a different port or log level\n```\n\n### Step 2: Build and start\n\n```bash\ndocker compose up --build -d\n```\n\nOr:\n\n```bash\nmake docker-build\nmake docker-run\n```\n\nThe server starts at `http://localhost:8000`.\n\n### Step 3: Verify health\n\n```bash\ndocker compose logs german-newsfeed-mcp\ndocker compose ps\n```\n\nOr:\n\n```bash\nmake docker-logs\n```\n\n### Step 4: Connect Claude Desktop (Streamable HTTP)\n\n```json\n    \"german-newsfeed\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"http://localhost:8000/mcp\"]\n    },\n```\n\nFor external servers: put an authentication layer in front first (see the security note above), then adjust the loopback binding in `docker-compose.yml` and replace `localhost` with your proxy's address.\n\n### Stop / Update\n\n```bash\ndocker compose down\ndocker compose up --build -d\n```\n\n---\n\n## Configuration Reference\n\nAll settings are read from environment variables (or a `.env` file).\n\n| Variable              | Default   | Description                                                         |\n| --------------------- | --------- | ------------------------------------------------------------------- |\n| `TRANSPORT`           | `stdio`   | `stdio` or `streamable-http` (`sse` legacy)                         |\n| `HOST`                | `0.0.0.0` | Bind address (HTTP transports only)                                 |\n| `PORT`                | `4200`    | HTTP port (HTTP transports only)                                    |\n| `LOG_LEVEL`           | `INFO`    | DEBUG, INFO, WARNING, ERROR                                         |\n| `RATE_LIMIT_PER_HOUR` | `60`      | Local request budget per hour towards the upstream API              |\n| `USER_AGENT_CONTACT`  | —         | Optional: contact info in the User-Agent header; omitted when unset |\n\nThe defaults apply when running directly (`uv run german-newsfeed-mcp`).\nThe Compose setup overrides them: it sets `HOST=0.0.0.0` and `PORT=8000`\n(see `docker-compose.yml`).\n\n---\n\n## Available Tools\n\nThese tools are callable by your AI assistant.\n\n### `get_latest_news`\n\nGet the top stories.\n\nReturns the full article text for each item (homepage feed). Every item also carries a `🔗 Volltext:` link that `get_article` accepts.\n\n| Parameter | Type | Default | Description         |\n| --------- | ---- | ------- | ------------------- |\n| `limit`   | int  | 10      | Max items to return |\n\n---\n\n### `get_article`\n\nGet the complete text of a single article.\n\nTakes the `🔗 Volltext:` link printed with every item by `get_latest_news`, `get_news_by_ressort` and `get_regional_news`; a plain `https://www.tagesschau.de/<path>.html` article link works too.\n\n| Parameter | Type | Default | Description                          |\n| --------- | ---- | ------- | ------------------------------------ |\n| `url`     | str  | —       | Article link shown in a news listing |\n\n> Costs exactly one upstream request per call out of the 60/h budget — call it for the articles that matter, not for every headline in a listing.\n\n> `search_news` results carry no article link and cannot be passed to `get_article`. Locate the article via `get_latest_news`, `get_news_by_ressort` or `get_regional_news` first.\n\n---\n\n### `get_news_by_ressort`\n\nFilter news by category.\n\nReturns metadata only — title, topline, date and a teaser sentence — no full article text. Every item carries a `🔗 Volltext:` link that `get_article` resolves into the full text.\n\n| Parameter | Type | Default | Description                                                             |\n| --------- | ---- | ------- | ----------------------------------------------------------------------- |\n| `ressort` | str  | —       | `inland` `ausland` `wirtschaft` `sport` `video` `investigativ` `wissen` |\n| `limit`   | int  | 10      | Max items to return                                                     |\n\n> Ressort strings are automatically normalised to lowercase: `\"Inland\"`, `\"INLAND\"` and `\"inland\"` are all equivalent.\n\n---\n\n### `get_regional_news`\n\nNews from a specific German state.\n\nReturns metadata only — title, topline, date and a teaser sentence — no full article text. Every item carries a `🔗 Volltext:` link that `get_article` resolves into the full text; because regional items originate at an ARD state broadcaster, they additionally carry a `📰 Quelle:` line naming that broadcaster.\n\n| Parameter   | Type | Default | Description                                                                                                          |\n| ----------- | ---- | ------- | -------------------------------------------------------------------------------------------------------------------- |\n| `region_id` | int  | —       | 1=BW · 2=BY · 3=BE · 4=BB · 5=HB · 6=HH · 7=HE · 8=MV · 9=NI · 10=NW · 11=RP · 12=SL · 13=SN · 14=ST · 15=SH · 16=TH |\n| `ressort`   | str  | None    | Optional category filter                                                                                             |\n| `limit`     | int  | 10      | Max items to return                                                                                                  |\n\n---\n\n### `search_news`\n\nFull-text search across all available articles.\n\nReturns metadata only — title, date and article type — no full article text. Search results carry no article link and therefore cannot be passed to `get_article`.\n\n| Parameter     | Type | Default | Description               |\n| ------------- | ---- | ------- | ------------------------- |\n| `search_text` | str  | —       | Search query              |\n| `page_size`   | int  | 10      | Results per page (max 30) |\n| `result_page` | int  | 0       | Page offset (0-based)     |\n\n---\n\n### `get_channels`\n\nList all live channels with stream URLs.\n\n_(No parameters)_\n\n---\n\n## Available Resources\n\nResources are addressable URIs that MCP clients can read directly.\n\n| URI                                      | Description                  |\n| ---------------------------------------- | ---------------------------- |\n| `news://tagesschau/homepage`             | Homepage top stories         |\n| `news://tagesschau/news/{ressort}`       | News by category             |\n| `news://tagesschau/regional/{region_id}` | Regional news by state ID    |\n| `news://tagesschau/search/{search_text}` | Search results               |\n| `news://tagesschau/channels`             | Available channels & streams |\n\n---\n\n## Development Guide\n\n### Setup\n\n```bash\ngit clone https://github.com/Jakolo121/german-newsfeed-mcp.git\ncd german-newsfeed-mcp\nuv sync --extra dev\n```\n\nStart the server:\n\n```bash\nuv run german-newsfeed-mcp\n```\n\nOr:\n\n```bash\nmake run\n```\n\n### Code organisation (SOLID)\n\nEach module has exactly one responsibility:\n\n| Module            | Responsibility                                               |\n| ----------------- | ------------------------------------------------------------ |\n| `config.py`       | Read & expose env vars                                       |\n| `client.py`       | HTTP requests + error handling                               |\n| `rate_limiter.py` | Local request budget (token bucket)                          |\n| `validators.py`   | Domain constants (`VALID_RESSORTS`) + input validation       |\n| `formatters.py`   | Turn raw API dicts into Markdown                             |\n| `tools.py`        | Validate inputs, call client, call formatter                 |\n| `resources.py`    | Same as tools but for MCP resources                          |\n| `server.py`       | Composition root: assemble FastMCP, register handlers, start |\n\n### Adding a new tool\n\n1. Add a `tool_<name>()` async function in `tools.py`\n2. Register it with `@mcp.tool()` in `server.py`\n3. Write unit + integration tests in `tests/test_tools.py`\n\n---\n\n## Running the Tests\n\n### Unit tests (no internet required, fast)\n\n```bash\nuv run pytest -m \"not integration\"     # run all unit tests\nuv run pytest -m \"not integration\" -v  # verbose output\nuv run pytest tests/test_formatters.py # single file\n```\n\nOr:\n\n```bash\nmake test\n```\n\n### Live integration tests (requires internet)\n\n```bash\nuv run pytest -m integration           # all live tests\nuv run pytest -m integration -v        # verbose\n```\n\n### Full suite\n\n```bash\nuv run pytest\n```\n\nOr:\n\n```bash\nmake test-all\n```\n\n### Quality gate (lint + tests)\n\n```bash\nuv run pylint src/german_newsfeed_mcp/\nuv run pytest\n```\n\nOr:\n\n```bash\nmake check\n```\n\n### Expected results\n\n```\n251 passed            ← unit tests (no network)\n 22 selected          ← integration tests (live API)\n```\n\n---\n\n## Makefile Reference\n\n```bash\nmake test          # fast unit tests (no network, ~0.3 s)\nmake test-all      # unit + live integration tests\nmake lint          # pylint\nmake check         # lint + unit tests — use as CI gate\nmake run           # start server in stdio mode (Claude Desktop)\nmake run-http      # start server in streamable-http mode\nmake docker-build  # build Docker image\nmake docker-run    # docker compose up -d\nmake docker-stop   # docker compose down\nmake docker-logs   # tail docker compose logs\nmake clean         # remove __pycache__, .pytest_cache, dist, etc.\n```\n\n---\n\n## Troubleshooting\n\n### Claude Desktop shows no MCP tools\n\n1. Check that the `claude_desktop_config.json` path is **absolute**\n2. Run `uv run python main.py` in the terminal, it should start without errors\n3. Fully quit and reopen Claude Desktop (Cmd+Q, not just close window)\n\n### Docker container exits immediately\n\n```bash\ndocker compose logs german-newsfeed-mcp\n```\n\nOr:\n\n```bash\nmake docker-logs\n```\n\nCommon causes: wrong `TRANSPORT` value (must be `streamable-http` in Docker), port already in use.\n\n### API timeouts\n\nThe upstream API occasionally rate-limits certain endpoints. This is normal, the server returns a descriptive error message rather than crashing. Retry after a few seconds.\n\n### Rate-limit errors\n\nIf the server reports \"Rate limit exceeded\", the local request budget (`RATE_LIMIT_PER_HOUR`, default 60/h) is exhausted. No request was sent upstream. Try again later.\n\n### Import errors in tests\n\n```bash\nuv sync --extra dev     # ensure dev deps are installed\nuv run pytest           # always run via uv, not bare pytest\n```\n\n---\n\n## When it stops working\n\nThe upstream API is not officially documented and can change without\nnotice. You can tell by the tools suddenly returning empty lists or\nerror messages although tagesschau.de is reachable, and by the live\nintegration tests failing (`uv run pytest tests/ -m integration`).\n\nAll endpoints are defined in a single place: `ENDPOINTS` in\n`src/german_newsfeed_mcp/client.py`. API changes can be tracked there.\n\nThe CI job `upstream-check` (`.github/workflows/ci.yml`) runs exactly\nthese live tests weekly against the real API and fails loudly when the\nresponse format no longer matches.\n\n---\n\n## License & Acknowledgements\n\nApache License 2.0.\n\nThe delivered news items are content of the ARD broadcasters, subject to their rights and the terms of use of tagesschau.de, see [Data Source and Terms of Use](#data-source-and-terms-of-use).\n\nThanks to **[AndreasFischer1985](https://github.com/AndreasFischer1985)**, the bund.dev community for documenting the API and above all to the journalists at the ARD broadcasters, whose work this project merely passes along.\n",
  "bytes": 21825,
  "sha": "2495f878d5024f54c0be1a5a145e6f3419c59ed4d140cf749e481e80d25eed41",
  "repo_slug": "jakolo121/german-newsfeed-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_jakolo121_german_newsfeed_mcp_459d0186/readme"
}