Skip to main content
Glama
stature

TheNewsAPI MCP Server

by stature
README.md
# TheNewsAPI MCP Server

An [MCP](https://modelcontextprotocol.io) server that exposes the read endpoints of
**[TheNewsAPI.com](https://www.thenewsapi.com)** as tools, so an AI assistant (Claude
Code, Claude Desktop, or any MCP client) can query news articles from natural-language
prompts.

> This targets **TheNewsAPI.com** (`https://api.thenewsapi.com/v1/`) — *not* the
> unrelated `newsapi.org` service.

npm package: `@trifecta/thenewsapi-mcp-server` (not yet published).

## Tools

| Tool | Endpoint | Purpose |
| --- | --- | --- |
| `search_news` | `GET /v1/news/all` | Full-archive keyword/boolean search across all articles. |
| `get_top_stories` | `GET /v1/news/top` | Current top / trending stories, optionally by `locale`. |
| `get_headlines` | `GET /v1/news/headlines` | Current headlines grouped by category. |
| `get_similar_articles` | `GET /v1/news/similar/{uuid}` | Other coverage of the same story as a given article. |
| `get_article_by_uuid` | `GET /v1/news/uuid/{uuid}` | Fetch one article by its UUID. |
| `list_sources` | `GET /v1/news/sources` | Enumerate indexed publishers and their `source_id`s. |

### Boolean search syntax (`search` param)

`search_news` and `get_top_stories` accept a boolean query:

| Operator | Meaning | Example |
| --- | --- | --- |
| `+` | term is required (AND) | `+bitcoin +etf` |
| `\|` | either term (OR) | `apple \| microsoft` |
| `-` | exclude term | `+tesla -musk` |
| `"..."` | exact phrase | `+"artificial intelligence"` |
| `( )` | group | `(tesla \| rivian) +earnings` |

Bare space-separated words are treated as OR.

## Requirements

- **Node.js 18 or newer** (the MCP SDK and the native `fetch` used for HTTP calls both
  require it). Check with `node --version`; upgrade via [nvm](https://github.com/nvm-sh/nvm)
  (`nvm install 20 && nvm use 20`) or [nodejs.org](https://nodejs.org).
- A free or paid TheNewsAPI account.

## Getting an API token

1. Register at <https://www.thenewsapi.com/register>.
2. Open your dashboard: <https://www.thenewsapi.com/account/dashboard>.
3. Copy the **API Token**.

Free-plan notes: the free tier caps `limit` at 3 articles per request and has a low
monthly quota; some endpoints/parameters are paid-only and will return
`endpoint_access_restricted`.

The token is supplied by each user at runtime via the `NEWS_API_TOKEN` environment
variable. It is never bundled into the package and never logged by the server.

---

## Install & configure — published package (for end users, once on npm)

No clone or build needed. Point your MCP client at the package via `npx`.

### Claude Code

```bash
claude mcp add thenewsapi \
  --env NEWS_API_TOKEN=your_token_here \
  -- npx -y @trifecta/thenewsapi-mcp-server
```

Then run `claude`, check `/mcp` — `thenewsapi` should be connected with 6 tools.

### Claude Desktop

Edit `claude_desktop_config.json`:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "thenewsapi": {
      "command": "npx",
      "args": ["-y", "@trifecta/thenewsapi-mcp-server"],
      "env": { "NEWS_API_TOKEN": "your_token_here" }
    }
  }
}
```

Restart Claude Desktop.

### Global install alternative

```bash
npm install -g @trifecta/thenewsapi-mcp-server
# then use command "thenewsapi-mcp-server" instead of "npx -y @trifecta/..."
```

---

## Local development setup (while unpublished)

```bash
git clone https://github.com/stature/trifecta-thenewsapi-mcp-server.git
cd trifecta-thenewsapi-mcp-server

npm install
cp .env.example .env
# edit .env and set NEWS_API_TOKEN=...

npm run build
```

`.env` is gitignored — keep the real token there, never in `.env.example`.

Quick check that it starts:

```bash
npm start
# -> "[thenewsapi-mcp] server ready (stdio)" on stderr, then Ctrl-C
```

Point Claude Code at your local build:

```bash
claude mcp add thenewsapi \
  --env NEWS_API_TOKEN=your_token_here \
  -- node /absolute/path/to/newsapi-mcp/dist/index.js
```

Or the config JSON directly (`~/.claude.json`, or `.mcp.json` in a project):

```jsonc
{
  "mcpServers": {
    "thenewsapi": {
      "command": "node",
      "args": ["/absolute/path/to/newsapi-mcp/dist/index.js"],
      "env": { "NEWS_API_TOKEN": "your_token_here" }
    }
  }
}
```

---

## Remote deployment — Streamable HTTP (SecureAI / HatzAI and other HTTP MCP clients)

Two entrypoints ship in this package:

| Entrypoint | Transport | Use with |
| --- | --- | --- |
| `dist/index.js` (`npm start`) | stdio | Claude Code / Claude Desktop (client spawns it locally) |
| `dist/http.js` (`npm run start:http`) | Streamable HTTP | SecureAI / HatzAI, or any client that takes a **Server URL** |

The HTTP server keeps `NEWS_API_TOKEN` entirely server-side; remote clients authenticate
with a **separate** shared secret (`MCP_AUTH_TOKEN`) and never see the TheNewsAPI token.

### Configuration (env vars)

| Var | Required | Default | Purpose |
| --- | --- | --- | --- |
| `NEWS_API_TOKEN` | yes | — | TheNewsAPI token (server-side only). |
| `MCP_AUTH_TOKEN` | recommended | _(none)_ | Shared secret clients must send. Unset = **no auth**. |
| `MCP_API_KEY_HEADER` | no | `x-api-key` | Header accepted for "API Key" style auth. |
| `MCP_HTTP_PORT` | no | `3000` | Listen port (`PORT` also honoured). |
| `MCP_HTTP_HOST` | no | `127.0.0.1` | Bind address — keep on loopback behind a TLS proxy. |
| `MCP_HTTP_PATH` | no | `/mcp` | Route the MCP endpoint is served on. |
| `MCP_CORS_ORIGIN` | no | _(none)_ | Set to an origin or `*` to emit CORS headers. |

`GET /healthz` is an unauthenticated liveness probe.

### Point SecureAI / HatzAI at it

In the custom connection form:

| Field | Value |
| --- | --- |
| **Server URL** | `https://your-domain.example/mcp` (must be HTTPS in production) |
| **Transport** | Streamable HTTP |
| **Authentication Method** | `Bearer Token` → token = your `MCP_AUTH_TOKEN` value |
| | _or_ `API Key` → key = your `MCP_AUTH_TOKEN` value, sent as `X-API-Key` |
| | _or_ `None` → only if `MCP_AUTH_TOKEN` is unset and the URL is not public |

### Try it locally first

```bash
cp .env.example .env
# set NEWS_API_TOKEN=... and MCP_AUTH_TOKEN=some-long-random-string
npm run dev:http
# -> [thenewsapi-mcp] Streamable HTTP listening on http://127.0.0.1:3000/mcp (auth: required)
```

```bash
# handshake check
curl -sD- http://127.0.0.1:3000/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'authorization: Bearer some-long-random-string' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
# 200 + an `mcp-session-id` response header + the server capabilities
```

### Deploy on an Oracle Cloud "Always Free" VM + Caddy

The recommended host: a free, always-on VM with Caddy terminating TLS (automatic
Let's Encrypt certs) and reverse-proxying to the Node process managed by systemd.

Config templates live in [`deploy/`](deploy/):

- `deploy/Caddyfile` → `/etc/caddy/Caddyfile`
- `deploy/thenewsapi-mcp.service` → `/etc/systemd/system/`
- `deploy/env.production.example` → `/opt/thenewsapi-mcp/.env`

**Full step-by-step (VM creation, both Oracle firewalls, DNS, TLS, verification):
[`deploy/README.md`](deploy/README.md).** Outline:

1. Create an Ubuntu 22.04 `VM.Standard.A1.Flex` (Ampere) instance; reserve its public IP.
2. Open TCP 80 + 443 in **both** the VCN Security List *and* the instance `iptables`.
3. Install Node 20 (NodeSource) and Caddy (apt).
4. Point a domain's A record at the VM.
5. Clone to `/opt/thenewsapi-mcp`, `npm ci && npm run build`, create `.env` (with an
   `openssl rand -hex 32` value for `MCP_AUTH_TOKEN`), `chmod 600`.
6. Install the systemd unit: `systemctl enable --now thenewsapi-mcp`.
7. Install the Caddyfile (swap in your domain), `systemctl restart caddy`.
8. Verify `https://your-domain/mcp` with the `curl` handshake above, then add it to
   SecureAI.

No domain? `deploy/README.md` Appendix A covers a free DuckDNS hostname.

Scaling note: sessions are held in memory in one process — perfect for a single
always-on VM. Multiple instances would need sticky-session routing.

## Example prompts

- "Search the news for articles about the EU AI Act from the last two weeks, English only."
- "What are the top business stories in the US right now?"
- "Give me a news briefing — headlines across tech, business, and science."
- "Find coverage of `+\"interest rate\" +\"Federal Reserve\" -crypto` sorted by relevance."
- "Get everything from nytimes.com and bbc.com about the Mars sample return mission."
- "Here's an article UUID `abc-123` — find other outlets covering the same story."
- "List the news sources you can access in the `tech` category."
- "Fetch the full article with UUID `abc-123`."

## Error handling

TheNewsAPI's documented error codes are surfaced back to the assistant with guidance
and a retryable flag:

| Code | Retryable | Meaning |
| --- | --- | --- |
| `malformed_parameters` | no | Bad parameter name/format — fix and retry. |
| `invalid_api_token` | no | `NEWS_API_TOKEN` missing/invalid. |
| `usage_limit_reached` | no | Monthly quota exhausted. |
| `endpoint_access_restricted` | no | Not available on current plan. |
| `resource_not_found` | no | UUID / resource does not exist. |
| `rate_limit_reached` | yes | Per-second rate limit — wait and retry. |
| `server_error` | yes | Transient upstream error. |
| `maintenance_mode` | yes | API temporarily down. |

## Project layout

```
src/
  index.ts              stdio entrypoint (Claude Code / Desktop)
  http.ts               Streamable HTTP entrypoint (SecureAI / remote clients) + auth
  server.ts             shared McpServer factory used by both entrypoints
  client.ts             API client wrapper: base URL, auth, query building, error mapping
  errors.ts             NewsApiError + documented error-code translation
  schemas.ts            Shared zod schema fragments (filters, pagination, search help)
  tools/
    shared.ts           safeHandler wrapper + JSON result formatter
    search-news.ts
    get-top-stories.ts
    get-headlines.ts
    get-similar-articles.ts
    get-article-by-uuid.ts
    list-sources.ts
```

Compiled output goes to `dist/`. Only `dist/`, `README.md`, and `LICENSE` are included
in the published package (see `files` in `package.json`).

## Scripts

| Script | Action |
| --- | --- |
| `npm run build` | Compile TypeScript to `dist/`. |
| `npm run watch` | Recompile on change. |
| `npm run typecheck` | Type-check without emitting. |
| `npm run clean` | Remove `dist/`. |
| `npm start` | Run the compiled stdio server. |
| `npm run start:http` | Run the compiled Streamable HTTP server. |
| `npm run dev` | Build then run (stdio). |
| `npm run dev:http` | Build then run (HTTP). |
| `prepublishOnly` | Auto-runs `clean` + `build` before `npm publish`. |

## Publishing

```bash
npm run typecheck
npm publish            # scoped package; publishConfig.access is already "public"
```

Before the first publish: make sure you're a member of the `@trifecta` npm org. Repo:
<https://github.com/stature/trifecta-thenewsapi-mcp-server>.

Versioning: starts at `0.1.0` (pre-1.0 — the tool surface may still change). Follow semver
afterwards.

## Out of scope (this phase)

No database, caching, webhooks, or downstream integrations — just the read endpoints
as MCP tools.

## License

MIT — see [LICENSE](LICENSE).