About gdelt-mcp-server
gdelt-mcp-server uses GDELT global news and US television transcript data for coverage search and analysis, serving media research and event tracking. It analyzes news over time, by tone, language, or country, and searches TV clips and co-occurring terms.
Key features
- 01Search multilingual global news
- 02Analyze coverage timelines and tone
- 03Break down coverage by language or country
- 04Find GDELT theme identifiers
- 05Search US TV transcripts and clips
- 06List stations and monitoring periods
Use cases
- 01Track how an event spreads across countries
- 02Compare changes in news tone
- 03Find US TV news clips and context
MCP support
As an MCP server, it exposes tools for news and TV transcript search, timelines, tone, geographic breakdowns, and station lookup. It supports STDIO and Streamable HTTP.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://gdelt.caseyjhand.com/mcpclaude mcp add --transport http cyanheads-gdelt https://gdelt.caseyjhand.com/mcp{"mcpServers":{"cyanheads-gdelt":{"type":"http","url":"https://gdelt.caseyjhand.com/mcp"}}}Open in Cursor{"servers":{"cyanheads-gdelt":{"type":"http","url":"https://gdelt.caseyjhand.com/mcp"}}}Open in VS Code{"mcpServers":{"cyanheads-gdelt":{"type":"http","url":"https://gdelt.caseyjhand.com/mcp"}}}{"mcpServers":{"cyanheads-gdelt":{"serverUrl":"https://gdelt.caseyjhand.com/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
9| Parameter | Type | Required | Description |
|---|---|---|---|
| sort | enum | No | Sort order: relevance (default), dateDesc/dateAsc, toneDesc/toneAsc, or hybridRel (GDELT hybrid relevance and recency).relevance | dateDesc | dateAsc | toneDesc | toneAsc | hybridRel |
| query | string | Yes | Search query. Supports GDELT operators: phrases ("bird flu"), boolean OR ((flu OR pandemic)), sourcecountry:china, sourcelang:spanish, domain:who.int, theme:TAX_DISEASE_OUTBREAK (GKG theme identifiers come from gdelt_search_themes), tone<-5, near20:"flu virus", repeat3:"outbreak". |
| timespan | string | No | Time window relative to now, minimum "15min"; other examples: "24h", "7d", "1m". Ignored when startDatetime/endDatetime are set. Maximum is 3 months (the full DOC API window). Defaults to the full 3-month window. |
| maxRecords | integer | No | Maximum number of articles to fetch (1–250; a value up to 2500 runs at 250, and the response says so in limitClamp). The response carries as many of them as fit its 48,000-byte budget. 250 is GDELT's hard per-call ceiling, not a page size — there is no cursor past it, so a query that fills 250 must be split into narrower startDatetime/endDatetime windows instead. |
| endDatetime | string | No | End of date range in GDELT format YYYYMMDDHHMMSS — exactly 14 digits, no separators (e.g. 20240131235959). Must be supplied together with startDatetime; supplying only one of the two is rejected. |
| startDatetime | string | No | Start of date range in GDELT format YYYYMMDDHHMMSS — exactly 14 digits, no separators (e.g. 20240101000000). Must be supplied together with endDatetime; supplying only one of the two is rejected. |
Details
From the project README@cyanheads/gdelt-mcp-server
Search and analyze global news coverage and US television transcripts from the GDELT Project's real-time APIs via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://gdelt.caseyjhand.com/mcp
Overview
News and TV coverage analysis from the GDELT Project: the last 3 months of global news in 65+ languages, and US TV transcripts from 2009 through October 2024 across 150+ stations. Search articles and clips, track coverage spikes, analyze tone, and trace how a story spread across languages and countries.
Tools
| Tool | Description |
|---|---|
gdelt_search_articles | Search the last 3 months of global news coverage (65+ languages) with full-text and filter operators. Fetches up to 250 articles, returns as many as fit a 48,000-byte response, and hands back the date windows to re-query for the rest. |
gdelt_get_coverage_timeline | Retrieve a time series of coverage volume or average tone for a query. volume_with_articles mode includes top articles per spike timestep, with points to get named timesteps' full article lists. Pages by offset within a 48,000-byte response. |
gdelt_get_tone_distribution | Get a tone histogram (bins ~−30 to +30) showing whether coverage is uniformly negative, bimodal, or clustered near neutral. Pages by offset within a 48,000-byte response. |
gdelt_get_coverage_breakdown | Break down coverage volume by source language or source country — a multi-series time series showing geographic propagation. Values are normalized shares of media output, not article counts. Pages by offset within a 48,000-byte response. |
gdelt_search_themes | Find GKG theme identifiers for the theme: operator the DOC tools accept — drought finds NATURAL_DISASTER_DROUGHT, cyberattack finds CYBER_ATTACK — ranked by the count GDELT's theme lookup lists. |
gdelt_search_tv | Search US television news closed captions (2009–Oct 2024) and return per-station airtime time series. |
gdelt_get_tv_clips | Retrieve matching TV clips with transcript excerpts and Internet Archive viewing links — up to 3,000 fetched, as many as fit a 48,000-byte response returned — and the date windows to re-query for the rest. |
gdelt_get_tv_context | Get the most frequent co-occurring words and phrases from GDELT's relevance-ranked sample of TV clips matching a query. |
gdelt_list_tv_stations | List TV stations with market, network, and monitoring date ranges — all of them, or filtered by station ID, network, or market — to verify station availability before querying. |
Capability reference
gdelt_search_articles tool
- Full GDELT query syntax: phrases, boolean OR, exclusion, filter operators (
sourcecountry:,sourcelang:,domain:,theme:,tone</tone>), proximity (near20:) and repetition (repeat3:) —gdelt_search_themesfinds the identifierstheme:takes - Configurable sort (
relevance,dateDesc,dateAsc,toneDesc,toneAsc,hybridRel) and fetch count, up to 250 per call — amaxRecordsup to 2,500 runs at 250 and reports the clamp inlimitClamp; above 2,500 the call is rejected - Returns URL, title, publication date, domain, language, source country, and social image URL
- Responses carry as many articles as fit a 48,000-byte budget; the rest are counted in
withheldCount, andcontinuationWindowsgives date windows to re-query for them (de-duplicate byurl). 250 is a hard ceiling, not a page size, so narrowing the date window is the only way past it - Articles outside an explicit
startDatetime/endDatetimewindow are dropped, and the notice counts them
gdelt_get_coverage_timeline tool
- Three modes:
volume(normalized % per timestep),volume_with_articles(volume plus top articles per spike — signal detection in one call),tone(average sentiment per timestep) - Each timestep carries its first 3 article links beside
referenceCount, the true count;points: ["<date>"]returns only the named timesteps, each with every link - Responses hold as many whole timesteps as fit a 48,000-byte budget; a cut page returns
nextOffsetto pass back asoffset. Each series'pointCountandpeakcover the whole series on every page - Configurable smoothing (0–5 timesteps) and time range (
timespan, or explicitstartDatetime/endDatetime); date resolution (15min/hour/day) is inferred from the returned intervals - A
pointsdate matching no timestep, or anoffsetpast the last timestep, is rejected with the valid values
gdelt_get_tone_distribution tool
- Histogram bins from approximately −30 to +30; each bin carries its first 3 article links beside
referenceCount, andbins: [<bin>]returns only the named bins, each with every link. Abinsvalue matching no bin is rejected with the available bins - Summary fields over the whole histogram:
peakNegativeBin,peakPositiveBin,neutralPct(% of articles in the −2 to +2 range) - Responses hold as many whole bins as fit a 48,000-byte budget; a cut page returns
nextOffsetto pass back asoffset. A snapshot across all matching articles, unlike the tone timeline (gdelt_get_coverage_timelinemodetone)
gdelt_get_coverage_breakdown tool
- Breaks down by
languageorcountryinto a multi-series time series: the top 10 series by summed value, with every other series named inotherSeriesLabels series: ["<label>"]returns only those series, complete, underselectedSeries; a label matching no series is rejected with the available labels- Responses hold as many whole series as fit a 48,000-byte budget; a cut page returns
nextOffsetto pass back asoffset - Values are normalized shares of each source's media output, not article counts, so don't sum them across sources
gdelt_search_themes tool
- Searches the identifiers in GDELT's GKG theme lookup (59,315 themes); each match is the identifier, its listed count, and a paste-ready
operatorsuch astheme:TAX_DISEASE_OUTBREAK - Case-insensitive, with no stemming or synonyms: words match parts of an identifier (
cyberattack→CYBER_ATTACK,plant disease→TAX_PLANTDISEASE). If nothing matches, it retries once with a trailingsdropped (protests→PROTEST) and the notice says so - Ranked by exact match, then listed count (a static prevalence figure, not a live article total), then identifier
- Pages with
offset/limit(1–100, default 25; alimitup to 1,000 runs at 100 and reports the clamp inlimitClamp, and above 1,000 the call is rejected);nextOffsetappears only when more matches remain, and an offset past a non-empty result failsoffset_out_of_range - Does not queue behind the DOC and TV rate limit
gdelt_search_tv tool
- Up to 10 structured
stations(e.g.["CNN", "FOXNEWS"]), or astation:selector embedded in the query — the TV API requires at least one, either way normalizetoggles relative airtime % (default) vs. raw matching 15-second clip counts; optionaldateresaggregation (hour/day/week/month/year). TV operators:station:,network:,market:,show:,context:- Each series carries
station(the ID thestationsinput takes,FOXNEWS) andstationName(GDELT's label,FOX News). If an ID can't be resolved, the series keepsstationNamewithoutstationand the notice says so - Pages at most 500 points per call in date-then-station-name order; pass
nextOffsetwith the same inputs for the next page. Alimitup to 5,000 runs at 500 and reports the clamp inlimitClamp; above 5,000 the call is rejected. The text surface's per-station count, sum, and peak cover only the returned page - Check station date ranges with
gdelt_list_tv_stationsbefore querying recent events
gdelt_get_tv_clips tool
- Up to 3,000 clips fetched per call, sorted by relevance, date descending, or date ascending — a
maxRecordsup to 30,000 runs at 3,000 and reports the clamp inlimitClamp; above 30,000 the call is rejected - Each clip: show name,
stationandstationName(resolved as ingdelt_search_tv, so a series'stationpasses straight intostations), air timestamp, 15-second transcript excerpt, direct Archive.org link, and optional thumbnail - The server requests whole clock hours, so any window width works; clips outside an explicit
startDatetime/endDatetimewindow are dropped and counted in the notice (atimespancall drops nothing) - Responses carry as many clips as fit a 48,000-byte budget; the rest are counted in
withheldCount, andcontinuationWindowsgives date windows to re-query for them (de-duplicate byarchiveUrl). 3,000 is a hard ceiling, not a page size, so narrowing the date window is the only way past it
gdelt_get_tv_context tool
- Returns the most frequent non-stopword terms co-occurring with the query
- Terms and scores come from GDELT's sample of the 200 most relevant matching clips, not all coverage; every non-empty response says so in
notice. Compare windows or stations with a separate, narrower call for each - Relative frequency scores 0–100, where the query term itself scores 100
gdelt_list_tv_stations tool
- Returns every station with market, network, monitoring start date, and end date — or only those matching the optional
stations,network, andmarketfilters - Filters match whole values case-insensitively and combine with AND (
network: "FOX"does not matchFOXNEWS);activeCountandtotalCountcount the returned stations - A filter or station ID that matches nothing is named in the notice;
isActiveis true when the end date is within the last 24 hours - Use to verify a station was active during a target period, or to find valid station IDs for the
stationsparameter on other TV tools
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
GDELT-specific:
- Shared request pacer across the DOC and TV tools: one request in flight at 1 req/5s, with a backoff (5s, doubling to 60s) after any rate-limit response
- Structured
stationsarrays instead of raw TV query syntax - GKG theme lookup held in memory after first download
Agent-friendly output:
- Query echo on every response: the original query and applied timespan, so calls chain without re-deriving parameters
- Typed series
labelfields ("Volume Intensity","Average Tone", language/country names) instead of positional arrays - Station metadata with an
isActiveboolean and ISO 8601 dates - Zero matches return the normal output shape with empty arrays and a
noticeon how to broaden the query
Getting started
Public Hosted Instance
A public instance is available at https://gdelt.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"gdelt-mcp-server": {
"type": "streamable-http",
"url": "https://gdelt.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"gdelt-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/gdelt-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"gdelt-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/gdelt-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"gdelt-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/gdelt-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key required — GDELT is a free public API.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/gdelt-mcp-server.git
- Navigate into the directory:
cd gdelt-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if you need to override defaults
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
GDELT_BASE_URL | Override the GDELT API base URL for both DOC and TV APIs. | https://api.gdeltproject.org/api/v2 |
GDELT_REQUEST_DELAY_MS | Minimum milliseconds between GDELT requests (enforces 1 req/5s limit). | 5300 |
GDELT_REQUEST_TIMEOUT_MS | Deadline for a single GDELT request (a call with retries is bounded at twice this). | 60000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted. | /mcp |
MCP_PUBLIC_URL | Public origin override for TLS-terminating reverse-proxy deployments. | none |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_SESSION_MODE | HTTP session mode: auto, stateful, or stateless. | stateless |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in Bun-only forced-GC loop interval in ms. | 0 (disabled) |
LOGS_DIR | Directory for log files (Node.js only). | <app-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security audit bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t gdelt-mcp-server .
docker run --rm -p 3010:3010 gdelt-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/gdelt-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Nine tools across the DOC and TV APIs and the GKG theme lookup. |
src/services/gdelt | GdeltDocService and GdeltTvService wrapping the DOC and TV APIs, the shared outbound pacer every DOC and TV call queues behind, and GdeltThemeService for the GKG theme lookup. |
tests/ | Unit and integration tests mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools via the barrels in
src/mcp-server/tools/definitions/index.ts - Wrap GDELT API calls: validate raw JSON → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.