MCP server for the public news API of tagesschau.de, including regional news from ARD broadcasters
Disclaimer: This is a private, unofficial project. It is not an ARD product and is neither operated nor endorsed by ARD, ARD-aktuell, or NDR. It is developed independently of the author's professional employment. "ARD" and "tagesschau" are trademarks of their respective owners and are named here solely to describe the API being accessed.
This project merely connects the public API to an MCP-capable AI assistant. ARD-aktuell is responsible for the API itself, its operation, and its content; this project cannot provide information on any of those. Please raise any concerns about this project, in particular from rights holders, as a GitHub issue. Substantiated concerns will be addressed promptly.
This Model Context Protocol (MCP) server
connects your AI assistant (Claude Desktop and others) to the public news
API of tagesschau.de: current headlines, category and regional news, and
full-text search, locally via stdio, no API key required.
Example: asked "Was sind die aktuellen Schlagzeilen?", the assistant answers with the current top stories from tagesschau.de, each with title, date, summary, and a link to the article.
Language:
This MCP server connects the public news API of tagesschau.de to your AI assistant (Claude, Open Claw, etc.).
Once connected, your AI can answer questions like:
For details on the API and its terms, see Data Source and Terms of Use. No API key required.
This server calls the publicly accessible endpoint
www.tagesschau.de/api2u/, operated by ARD-aktuell. The delivered
content originates from the ARD broadcasters, remains subject to their
rights and to the terms of use of tagesschau.de:
https://www.tagesschau.de/nutzungsbedingungen/
The API is not officially documented. Community documentation is available at bund.dev (https://tagesschau.api.bund.dev). bund.dev is a civil-society documentation project, neither the operator of the API nor a rights holder of the content. Its documentation served as a reference for this project but does not grant any usage rights.
Applicable limits: at most 60 requests per hour, and no republication of the content except for offerings under a CC licence (https://tagesschau.de/creativecommons). Compliance is the responsibility of whoever operates a given instance.
The robots.txt of tagesschau.de additionally declares an express reservation of rights under Section 44b(3) of the German Copyright Act (as of 2026-05-19): text and data mining and the automated use of the content for training or fine-tuning AI models are prohibited without written consent. Expressly exempt is automated access for the sole purpose of retrieval-augmented generation (RAG) or grounding, provided the technical directives of the robots.txt are complied with and the content remains attributed to its original source. This server falls under that exemption: it passes content to the assistant exclusively together with source links. The retrieved content must not be used to train AI models.
| ποΈ Live news | Fetches breaking news, categorised news, and regional news in real time |
| π Full-text search | Search across all available articles |
| πΊ Live streams | List all available channels and HLS stream URLs |
| β±οΈ Rate limiter | Local token bucket honouring the API's 60/h limit |
| π Dual transport | stdio for local Claude Desktop; streamable-http for remote / Docker |
| π³ Docker-ready | Multi-stage image, non-root user, health-check, resource limits |
| β 273 tests | 251 unit tests + 22 live integration tests |
| π οΈ Makefile | make test, make lint, make docker-build and more |
| π No secrets | Public API, no API keys |
german-newsfeed-mcp/
βββ src/
β βββ german_newsfeed_mcp/
β βββ __init__.py # Package metadata
β βββ config.py # Environment-driven configuration
β βββ client.py # Async HTTP client (httpx) + error handling
β βββ rate_limiter.py # Token-bucket rate limiter
β βββ validators.py # Domain constants + validation helpers
β βββ formatters.py # Markdown rendering of news items & channels
β βββ tools.py # MCP tool business logic
β βββ resources.py # MCP resource business logic
β βββ server.py # Composition root: FastMCP + run() entry-point
βββ tests/
β βββ conftest.py # Shared fixtures & mock payloads
β βββ test_client.py # Client unit + live integration tests
β βββ test_rate_limiter.py # Rate-limiter unit tests
β βββ test_validators.py # Validator unit tests (URL & ressort checks)
β βββ test_formatters.py # Formatter unit tests (pure functions)
β βββ test_tools.py # Tool unit + live integration tests
β βββ test_resources.py # Resource unit + live integration tests
βββ main.py # Thin entry-point (calls server.run())
βββ pyproject.toml # Project metadata, deps, pytest & pylint config
βββ uv.lock # Locked dependency graph (commit this!)
βββ Dockerfile # Multi-stage production image
βββ docker-compose.yml # One-command remote deployment
βββ .env.example # Configuration template
βββ Makefile # Developer shortcuts (test, lint, docker, clean)
βββ CHANGELOG.md # Version history
βββ CONTRIBUTING.md # How to contribute
βββ README_de.md # German version of this file
Also 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.
curl -LsSf https://astral.sh/uv/install.sh | shgit clone https://github.com/Jakolo121/german-newsfeed-mcp.git
cd german-newsfeed-mcp
uv sync
uv run python -c "from german_newsfeed_mcp.server import mcp; print('OK!', mcp.name)"
# Expected: OK! German Newsfeed MCP
Open your Claude Desktop config file:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Add the following entry (adjust the path to your clone):
"german-newsfeed": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/german-newsfeed-mcp",
"run",
"german-newsfeed-mcp"
]
},
Quit and reopen the application, or reload its MCP servers, depending on the application.
Ask your assistant:
"Was sind die aktuellen Nachrichten?"
Self-hosting for your own or team-internal use. The recommended default
is stdio (see Quick Start);
the streamable-http transport is an option you choose deliberately.
Security note: The HTTP transport has no authentication. Do not
expose it publicly without an authentication layer in front (e.g. a
reverse proxy). The Compose setup therefore deliberately binds the port
to 127.0.0.1 only. Whoever makes an instance reachable for third
parties becomes the responsible operator under the
terms of use.
Rate limiter limitations: stateless_http=True refers to MCP
sessions, not to the rate limiter. The token bucket is process-local
in-memory state. Two limitations follow:
restart: unless-stopped and a crash loop, this can exceed the
limit. Watch the logs.(The legacy sse transport is still supported.)
cp .env.example .env
# Edit .env if you want a different port or log level
docker compose up --build -d
Or:
make docker-build
make docker-run
The server starts at http://localhost:8000.
docker compose logs german-newsfeed-mcp
docker compose ps
Or:
make docker-logs
"german-newsfeed": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8000/mcp"]
},
For 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.
docker compose down
docker compose up --build -d
All settings are read from environment variables (or a .env file).
| Variable | Default | Description |
|---|---|---|
TRANSPORT | stdio | stdio or streamable-http (sse legacy) |
HOST | 0.0.0.0 | Bind address (HTTP transports only) |
PORT | 4200 | HTTP port (HTTP transports only) |
LOG_LEVEL | INFO | DEBUG, INFO, WARNING, ERROR |
RATE_LIMIT_PER_HOUR | 60 | Local request budget per hour towards the upstream API |
USER_AGENT_CONTACT | β | Optional: contact info in the User-Agent header; omitted when unset |
The defaults apply when running directly (uv run german-newsfeed-mcp).
The Compose setup overrides them: it sets HOST=0.0.0.0 and PORT=8000
(see docker-compose.yml).
These tools are callable by your AI assistant.
get_latest_newsGet the top stories.
Returns the full article text for each item (homepage feed). Every item also carries a π Volltext: link that get_article accepts.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 10 | Max items to return |
get_articleGet the complete text of a single article.
Takes 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | str | β | Article link shown in a news listing |
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.
search_newsresults carry no article link and cannot be passed toget_article. Locate the article viaget_latest_news,get_news_by_ressortorget_regional_newsfirst.
get_news_by_ressortFilter news by category.
Returns 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
ressort | str | β | inland ausland wirtschaft sport video investigativ wissen |
limit | int | 10 | Max items to return |
Ressort strings are automatically normalised to lowercase:
"Inland","INLAND"and"inland"are all equivalent.
get_regional_newsNews from a specific German state.
Returns 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
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 |
ressort | str | None | Optional category filter |
limit | int | 10 | Max items to return |
search_newsFull-text search across all available articles.
Returns 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
search_text | str | β | Search query |
page_size | int | 10 | Results per page (max 30) |
result_page | int | 0 | Page offset (0-based) |
get_channelsList all live channels with stream URLs.
(No parameters)
Resources are addressable URIs that MCP clients can read directly.
| URI | Description |
|---|---|
news://tagesschau/homepage | Homepage top stories |
news://tagesschau/news/{ressort} | News by category |
news://tagesschau/regional/{region_id} | Regional news by state ID |
news://tagesschau/search/{search_text} | Search results |
news://tagesschau/channels | Available channels & streams |
git clone https://github.com/Jakolo121/german-newsfeed-mcp.git
cd german-newsfeed-mcp
uv sync --extra dev
Start the server:
uv run german-newsfeed-mcp
Or:
make run
Each module has exactly one responsibility:
| Module | Responsibility |
|---|---|
config.py | Read & expose env vars |
client.py | HTTP requests + error handling |
rate_limiter.py | Local request budget (token bucket) |
validators.py | Domain constants (VALID_RESSORTS) + input validation |
formatters.py | Turn raw API dicts into Markdown |
tools.py | Validate inputs, call client, call formatter |
resources.py | Same as tools but for MCP resources |
server.py | Composition root: assemble FastMCP, register handlers, start |
tool_<name>() async function in tools.py@mcp.tool() in server.pytests/test_tools.pyuv run pytest -m "not integration" # run all unit tests
uv run pytest -m "not integration" -v # verbose output
uv run pytest tests/test_formatters.py # single file
Or:
make test
uv run pytest -m integration # all live tests
uv run pytest -m integration -v # verbose
uv run pytest
Or:
make test-all
uv run pylint src/german_newsfeed_mcp/
uv run pytest
Or:
make check
251 passed β unit tests (no network)
22 selected β integration tests (live API)
make test # fast unit tests (no network, ~0.3 s)
make test-all # unit + live integration tests
make lint # pylint
make check # lint + unit tests β use as CI gate
make run # start server in stdio mode (Claude Desktop)
make run-http # start server in streamable-http mode
make docker-build # build Docker image
make docker-run # docker compose up -d
make docker-stop # docker compose down
make docker-logs # tail docker compose logs
make clean # remove __pycache__, .pytest_cache, dist, etc.
claude_desktop_config.json path is absoluteuv run python main.py in the terminal, it should start without errorsdocker compose logs german-newsfeed-mcp
Or:
make docker-logs
Common causes: wrong TRANSPORT value (must be streamable-http in Docker), port already in use.
The 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.
If 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.
uv sync --extra dev # ensure dev deps are installed
uv run pytest # always run via uv, not bare pytest
The upstream API is not officially documented and can change without
notice. You can tell by the tools suddenly returning empty lists or
error messages although tagesschau.de is reachable, and by the live
integration tests failing (uv run pytest tests/ -m integration).
All endpoints are defined in a single place: ENDPOINTS in
src/german_newsfeed_mcp/client.py. API changes can be tracked there.
The CI job upstream-check (.github/workflows/ci.yml) runs exactly
these live tests weekly against the real API and fails loudly when the
response format no longer matches.
Apache License 2.0.
The 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.
Thanks to 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.
Source-derived launch command. Check the maintainerβs required arguments and credentials before running:
uvx german-newsfeed-mcpMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-jakolo121-german-newsfeed-mcp": {
"command": "uvx",
"args": [
"german-newsfeed-mcp"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the clientβs tool list, then try a read-only example from its documentation.
Claude Desktop setup referencegerman-newsfeed-mcppypiGerman Newsfeed MCP Server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.