A news MCP server, built by the people who run the index
Eighteen tools covering search, story grouping and expansion, headlines, article lookup, related-article discovery, entity resolution and relationships, time series and watches. Same filters and same authentication as the REST API, so behaviour does not diverge between the path your backend takes and the path an agent takes.
What changed on chip export controls this week?
list_stories q: "chip export controls", mode: "hybrid", from: "2026-09-08" 12 stories · top: 34 outlets, 3 languages · credits_charged: 1 Twelve distinct stories. The largest, on export licence rules for lithography tools, ran in 34 outlets across English, German and Japanese, first seen at 07:31.
Why this is worth having as a first-party thing
Search the web for a news MCP server today and you will find community wrappers around other vendors' REST APIs — GitHub projects, a package on PyPI, a tutorial or two. Several are well made. All of them share the same structural problem: they are a translation layer maintained by someone who does not control either side of it. When the upstream API adds a filter, the wrapper does not have it. When the wrapper's author moves on, you inherit it.
A first-party server does not have that seam. The tool definitions are generated from the same query surface the REST API exposes, so a filter that exists in one exists in the other, and a query you debugged with curl behaves identically when a model issues it. There is no version of this where the tool layer drifts from the API it describes, because there is no separate tool layer to drift.
The tools
| Tool | What it does | When to reach for it |
|---|---|---|
search_news | Search articles by keyword, meaning or both, with time-range, language and outlet filters. | The default tool. Hybrid mode is usually the right choice when a model wrote the query, because models paraphrase. |
list_stories | The same search, collapsed so one event returns once with the outlets that covered it. | Use this whenever results go into a context window. It is the single biggest token saving available here. |
get_story | Expand one story: every article, every outlet, first and last seen, in one call. | For following up on a story from list_stories without re-running a search. |
top_headlines | The most recent articles, optionally narrowed to a language or an outlet. | For "what is happening right now" questions, where recency matters more than relevance. |
get_article | Fetch one article record by id. | For following up on a specific result the model has already seen, without re-running a search. |
find_related | Find articles similar to a given one. | For "what else has been written about this" without the model having to invent a new query. |
resolve_entity | Find the spellings the index holds for an organization, person, place or topic, grouped with counts. | Filters are exact matches; resolve the name first so a spelling the index does not hold is not mistaken for no coverage. |
aggregate_news | Counts per hour, day, week or month: articles, and on request distinct stories, outlets and signal percentiles. | For attention over time, in one call instead of one search per day. |
related_entities | The organizations, people, places and topics most often reported alongside one entity, strongest first, with co-mention counts and share, and for the strongest the newest shared articles, distinct sources and stories. | For "who shows up alongside X". Co-mention is association in reporting, not partnership. |
connection_path | How two entities connect in the reporting: the articles naming both, and up to five paths through one shared neighbour. | For "how are X and Y linked". Bounded to two hops. |
entity_network | One entity's neighbours grouped by type; with a comparison window, which neighbours are new or gone and which ties strengthened or weakened. | For "what changed around X since last month". |
shared_exposures | What up to five entities have in common: shared articles, stories, sources, topics, countries and signal intensities, with the newest shared articles as evidence. | For a watchlist: the reporting that names all of them at once. |
create_watch | Save a story query the index runs on an interval, recording new and growing stories. | For monitoring without a polling loop. Each run costs its query's credits. |
list_watches | List the account's watches, their status and the plan's cap. | To find a watch paused for credits or a revoked key. |
get_watch | Read one watch: its query, schedule, status (active, paused_key, paused_credits or failing) and when it last ran. | To see why one watch went quiet without listing them all. |
watch_events | Read a watch's new and growing stories, newest first. | When no webhook is set, or to catch up after a gap. Events are kept seven days. |
delete_watch | Delete a watch, its state and its events. | Each run costs credits, so remove the ones nobody reads. |
account_status | Report the current plan, credits and usage. Free. | Lets an agent check its own remaining budget instead of discovering the limit by hitting it. |
Connecting it
The server is hosted, and every MCP client connects to it the same way: a URL and your API key. Nothing to install, and the same key works for the REST API.
{
"mcpServers": {
"unzoi": {
"url": "https://api.unzoi.com/mcp",
"headers": { "x-api-key": "your-key" }
}
}
} The transport is Streamable HTTP, and the protocol version is negotiated — offer yours and the server answers with the one it picked. The endpoint is stateless, so there is no session to keep alive and no reconnect logic to write.
Client-specific walkthroughs, each covering what that client does well and the mistake people actually hit with it:
- Claude — Remote MCP URL — a hosted server, added as a custom connector.
- ChatGPT — Remote MCP endpoint registered as a connector.
- Cursor — Remote MCP URL declared in the editor MCP settings.
- LangChain — MCP tool adapter, or a thin wrapper over the REST API.
The technical docs carry one for every client — including the bridge for clients that only speak stdio.
MCP access is on every plan, including the free one.
What kind of MCP server this is
MCP servers differ in ways that decide how much work integration is, so here is the shape of this one before you wire anything up. It is remote and hosted, so there is no package to install and no process to supervise. The transport is Streamable HTTP — a single endpoint answering POST — rather than the retired two-endpoint SSE arrangement that older servers and older client versions expect.
The protocol version is negotiated per connection rather than pinned, so a client offering an older revision gets that revision back instead of a refusal. The endpoint is stateless: no session identifier is issued, nothing expires, and every request carries its own authorisation. For a client author that removes reconnect logic, keepalives and session recovery from the list of things to build. It also means a request that fails is just a request that failed, rather than a session that has to be re-established before anything else can proceed.
Credits are shared with the REST API rather than metered separately, so a tool call costs
exactly what the equivalent request costs, and every result reports it as
credits_charged. The handshake, tools/list and
account_status are free, and estimate prices a call without
running it. Refusals that concern your account — credits spent on a key that stops there, a
lapsed subscription — arrive as tool errors
on a working connection rather than as transport failures, which is the difference
between an agent that can tell you what is wrong and one that reports the server as
unreachable.
Designing tool calls that do not waste context
The most common mistake when wiring news into an agent is reaching for article search when you wanted story grouping. A busy news day means one event has been covered by dozens of outlets, and article search will faithfully return all of them. The model then reads the same fact many times, and — because repetition reads as importance — weights it accordingly.
Prefer list_stories whenever the output is going into a context window and
search_news when a human will scan the results. Bound every call with a time
range, because "recent" means something different to a model than it does to you, and an
unbounded query over a deep archive is both slower and less useful than a bounded one.
If the agent is doing research rather than answering a single question, let it call
find_related on the best result instead of issuing another search, and
get_story on a story it has already seen instead of searching again with
the identifier. Models
reformulate queries badly under uncertainty, and each reformulation costs a request; a
similarity lookup from a result it already trusts is cheaper and usually better.
For questions about attention over time, aggregate_news returns counts per
hour, day, week or month in one call, where a loop would issue one search per day.
For a place, pass near or bbox rather than a list of city
names; for a kind of event, event_type takes one of seventeen classes.
For "who is connected to X", call related_entities on a resolved id and
have the model present the answer as a reporting pattern: two companies named together
in forty articles were reported together, and the evidence says whether that was a
contract or a lawsuit.
Watches
create_watch saves a story query that the index runs for you on an
interval. It records new_story when a story first reaches the number of
outlets you set, and story_growth as it keeps gaining them. An agent reads
those events with watch_events; a server can receive them at an https
webhook instead, each signed with HMAC-SHA256.
Each run costs its query's credits, and each webhook delivery attempt 1, charged to the key that created the watch. Plans cap how many watches an account keeps and the shortest interval: Free 2 at 60 minutes, Build 25 at 15, Scale 200 at 5, Archive 1,000 at 1.
What the tools return
Metadata, a snippet and a link: headline, outlet, timestamp, language, and a story identifier where one applies. Article bodies are not redistributed, which keeps the publishers who did the reporting in the loop and keeps you clear of the licensing questions that arrive the moment a pipeline starts storing full text it does not own.
Every result arrives twice: as structuredContent, typed by the tool's
outputSchema, and as a text block carrying the same JSON for clients that
predate structured results. A schema-aware host validates the first; an older one reads
the second. Nothing is lost either way.
Pass view: "compact" when the result feeds a model. Each hit keeps its id,
title, url, source, publication time, language, story identifier and score, and the full
record is one get_article call away. A model deciding what to read next
needs to compare candidates, not consume all of them, and a compact result set lets it
consider far more options within the same budget. When it does need the full text, the
link is right there and the publisher gets the visit.
Every response says whether it is complete. partial is true when part of
the index did not answer, coverage says why, and from_clamped
says the time range was shortened to the plan's archive depth. An agent that reads
those three fields can tell "nothing was written" from "part of the index did not
answer", and every search-shaped tool tells it to retry, narrow the range or check
account_status rather than conclude absence.
A misspelled argument is rejected with a suggestion rather than ignored.
organiztion comes back as an invalid-arguments error naming
organization; it does not quietly search everything. Every tool declares
additionalProperties: false, so a client that validates arguments before
sending them catches the same mistake locally.