News Digest
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@News DigestGet me the latest news from the last 2 hours"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
News Digest
A simple MCP server that fetches news from Telegram and RSS sources. Connect it to your LLM agent of choice and let it doomscroll for you!
Setup
The server speaks MCP over stdio. Run it from a clone, or with npx from the latest GitHub release.
Your choice decides where the config lives.
The config is JSONC — regular JSON plus // comments and trailing commas. The template is
commented throughout, so it doubles as the reference for every option.
With npx
npm fetches a prebuilt bundle from the latest GitHub release, which installs two packages and needs
Node ≥ 20.11. CI builds that bundle on every push to main.
With no checkout, the config has nowhere to sit beside the server, so put it in
~/.config/news-digest/:
mkdir -p ~/.config/news-digest
curl -fsSL https://raw.githubusercontent.com/goldjee/news-digest-mcp/main/sources-template.jsonc -o ~/.config/news-digest/sources.jsoncAdd your Telegram/RSS sources to that file, then register the server:
{
"mcpServers": {
"news-digest": {
"command": "npx",
"args": ["-y", "https://github.com/goldjee/news-digest-mcp/releases/latest/download/news-digest-mcp.tgz"]
}
}
}Keep the -y. Without it, npx prompts on stdin before running a package it hasn't installed, and
stdin carries the MCP protocol, so the handshake hangs.
latest resolves against GitHub on every start, so the server needs network to register, before it
fetches any news. To pin a build, swap latest/download for a specific release, as in
releases/download/build-a1b2c3d/news-digest-mcp.tgz. To drop the per-start fetch, install once with
npm i -g <that URL> and set "command": "news-digest-mcp" with no args.
The artifact is JavaScript rather than TypeScript, because Node refuses to strip types anywhere
under node_modules. That also means Bun is not required: bun and bunx are for working from a
clone.
From a clone
Runs on Bun (any recent version) or Node ≥ 22.6 (≥ 23.6 runs TypeScript natively; 22.6–23.5
needs --experimental-strip-types). No build step either way.
git clone https://github.com/goldjee/news-digest-mcp.git
cd news-digest-mcp
bun install # or: npm install
cp sources-template.jsonc sources.jsonc # then add your Telegram/RSS sourcesRegister it with the absolute path to the runtime (which bun / which node) — GUI apps on macOS
have a stripped PATH.
With Bun:
{
"mcpServers": {
"news-digest": {
"command": "/Users/you/.bun/bin/bun",
"args": ["/path/to/news-digest/server.ts"]
}
}
}With Node (add "--experimental-strip-types" before the path on Node 22.6–23.5):
{
"mcpServers": {
"news-digest": {
"command": "/usr/local/bin/node",
"args": ["/path/to/news-digest/server.ts"]
}
}
}Where the config is read from
The server checks beside its own files first, then ~/.config/news-digest/ ($XDG_CONFIG_HOME is
honoured), taking sources.jsonc or sources.json at either location. $NEWS_DIGEST_CONFIG
overrides both. A clone therefore behaves as it always has; an npx install has nothing writable
beside the code, since npm replaces that cache on every fetch, so it reads from ~/.config. Miss
all three and the error names each path it tried. Some clients prefix tools, e.g.
news-digest_get_news.
Related MCP server: mcp-ai-news-server
Tools
get_news({ page?, lookbackHours?, includeSeen?, maxChars?, runId? })— fresh items in the window, one page at a time (see Paging). Returns only what's new since the last call (state in~/.local/state/news-digest/).includeSeen: true= full pull, no dedup, no state written.list_sources()— configured sources (for debugging).
Both tools declare an outputSchema, so a client can discover the response shape from tools/list
and validate against it. Each result carries the payload twice: as structuredContent (a real JSON
object) and as serialized JSON in a text block, which the spec asks for so clients that don't read
structured output still work.
{
"content": [{ "type": "text", "text": "{\"generatedAt\":\"2026-07-30T…\",…}" }],
"structuredContent": {
"nextPageNeeded": true,
"nextPage": 2,
"generatedAt": "2026-07-30T21:44:03.118Z",
"lookbackHours": 24,
"timezone": "Europe/London",
"stats": { "sources": 5, "newItems": 33, "duplicates": 52, "errors": 1 },
"page": {
"pageNumber": 1,
"totalPages": 2,
"itemsInPage": 16,
"itemsRemaining": 17,
"chars": 79923,
"runId": "2026-07-30t21-44-03-118z-l09e16",
"nextAction": "Call get_news with page=2. 17 of 33 items still to come — …"
},
"sources": [
{
"id": "bbc-uk-rss",
"name": "BBC UK",
"type": "rss",
"items": [
{
"id": "rss:bbc-uk-rss:https://www.bbc.co.uk/news/articles/c78gnj1qqyyo#0",
"title": "…",
"text": "…",
"url": "https://www.bbc.co.uk/news/articles/c78gnj1qqyyo",
"date": "2026-07-30T18:12:00.000Z",
"textSource": "article"
}
]
},
// a source that failed keeps its entry, with `error` set and `items` empty
{ "id": "…", "name": "…", "type": "rss", "items": [], "error": "HTTP 404 Not Found for …" }
]
}
}list_sources returns { "sources": [...] } rather than a bare array — structuredContent has to
be a JSON object, and the text block carries the identical value.
hostEnvelope
One host needs the text block shaped differently. osaurus ignores structuredContent and reads
only content, then re-encodes anything that isn't one of its own envelopes into result.text —
as a JSON string, so every " in the payload becomes \". That fourth encoding is what reaches
the model, nothing unescapes it, and models imitate it: give one 80 kB of escaped JSON and it
starts writing escaped prose back at you.
Set "hostEnvelope": "osaurus" in sources.jsonc and the text block becomes
{"ok":true,"tool":"get_news","result":{…}}, which that host passes through byte-identical. The
default "none" is the spec shape — the serialized payload, nothing added — and is what any other
client should get. structuredContent is the same either way. Measured on a real 5-page digest:
~880 escaped quotes per page under "none", ~450 under "osaurus", and the ones left are real
quotes inside article text.
Duplicates across feeds
Several feeds from one outlet carry the same articles. Measured on a live run, BBC's world, UK and front-page feeds shared 60% of their items: 86 items, 33 unique URLs, with 20 articles appearing in all three and each copy fetching its own full body.
So an article is returned once, under the first source in config order that carried it. The
others are listed on it as alsoIn:
{ "id": "rss:bbc-world-rss:…", "title": "…", "url": "https://www.bbc.co.uk/news/articles/…",
"alsoIn": ["bbc-uk-rss", "bbc-uk-front-page-rss"] }Keeping that list matters: three outlets leading with a story says something about its size, and
the raw duplication was the only place that showed. stats.duplicates counts the copies dropped.
Config order decides attribution, and the digest prints the winning source's name — so if you want UK stories credited to your UK feed rather than a world feed that also carries them, put the UK feed first.
Dedup runs before full-text extraction, so a story three feeds carry costs one article download instead of three. On the run above that was 33 fetches instead of 86, and the payload went from ~427,000 characters to ~43,000.
Paging
MCP hosts cap how much a single tool call may return. osaurus head/tail-truncates anything past 100,000 characters, which quietly removes the middle of a digest — and since the run's ids were already marked seen, those stories never come back. A digest with full article text clears that cap easily: five feeds at 15 kB an article is ~180 kB.
So get_news returns one page at a time, in the shape
Sequential Thinking
uses for the same "keep calling me" problem — a boolean to loop on and an integer to pass back,
no opaque token to transcribe:
Call it with no arguments. It fetches every source once, snapshots the whole run, and returns page 1.
While the result says
"nextPageNeeded": true, callget_newsagain with onlypageset to the result'snextPage. Those calls read the snapshot — no network, no config reload, no state written — and return in milliseconds."nextPageNeeded": falsemeans the digest is complete.
nextPageNeeded and nextPage are the first two keys in the response, so a model reading the
serialized text meets the loop condition before the 80 kB of news. page.totalPages says up
front how many calls it will take, and page.nextAction repeats the next step in plain words —
small local models follow an imperative sentence more reliably than a schema.
The fetch happens once, on the first call, and that is load-bearing rather than an optimisation:
get_news marks the whole run as seen when it snapshots, so a second run would correctly
report the rest of the digest as old news and return nothing. Paging over a snapshot is what
makes "read the whole feed" survive being split across calls. A bare page continues the most
recent run, which is what a paging loop always means; runId can pin a specific one, but you
should not need it.
Sizing is per page, by measured JSON length, not by item count — a page holds as many items as
fit. maxCharsPerCall in sources.jsonc sets the budget (default 80000, clamped to
4000–95000); maxChars overrides it for one call. Prefer few large pages: hosts summarise older
tool results as a conversation grows, so a long paging loop risks losing the early pages.
An item too large for a whole page is returned alone with its body cut to fit and
"truncated": true set, so paging always advances. Snapshots live beside the dedup state and
are kept for 24 hours (10 runs max); asking for a page when no snapshot survives returns an
error telling you to start a fresh run.
Sources
Edit sources.jsonc — re-read on every call, no restart. A source is { id, name, type, url } (type: telegram | rss). A new type = a lib/<type>.ts plus one line in HANDLERS (lib/run.ts).
Full article text
Feeds usually ship a one-line teaser. Add "fullText": true to a source and the server follows each
item's link and replaces the body with the article itself, extracted by Firefox's reader-mode
algorithm (@mozilla/readability). It scores the page's DOM
by text density, so it works site-agnostically — there are no per-domain rules to maintain.
Enriched items carry "textSource": "article"; items whose page couldn't be read keep the feed text
and say "textSource": "feed". Extraction never fails a source. Article bodies obey the same
maxCharsPerItem cap as everything else, so consider lowering maxItems on enriched sources — 25
items × 4000 chars is ~100 kB for the agent to read.
Optional tuning, all with sane defaults:
"fullText": { "concurrency": 4, "timeoutMs": 15000, "minChars": 200, "charThreshold": 500 }concurrency is article pages fetched at once per source, minChars rejects extractions too short
to be worth taking, and charThreshold is Readability's own give-up length.
What you won't get: JavaScript-rendered articles, hard paywalls and consent walls yield the feed text rather than an error, and feeds that link through a redirector (Google News and friends) rarely resolve to a readable page.
Without an agent
Call it from code: import { runDigest } from "./lib/run.ts".
Read entry store
The history of read news entries is stored in ~/.local/state/news-digest/state.json.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceAn MCP server that fetches RSS feeds and shares them with LLMs, allowing AI assistants to access and present the latest news and articles from configured feeds.19
- Flicense-qualityDmaintenanceMCP server that fetches and filters AI-related news from 20+ RSS feeds with scheduled notifications and email digests.1
- Alicense-qualityBmaintenanceA persistent news intelligence MCP server that enables AI agents to fetch, store, deduplicate, embed, and semantically query news from Google News across 141 countries and 41 languages.3MIT
- Flicense-qualityCmaintenanceLocal MCP server that ingests and normalizes recent AI, tech, and research news from multiple sources, exposing tools for media agents to query and retrieve article content on demand.
Related MCP Connectors
Telegram bridge for your MCP-compatible agent. Bidirectional, no LLM in our stack.
World News Feeds MCP.
Real-time curated crypto news for AI agents with sentiment, recaps, and search.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/goldjee/news-digest-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server