Skip to main content
Glama
crzyc0d3r

hacker-news

by crzyc0d3r
README.md
# hacker-news-mcp-server

A small, production-shaped [Model Context Protocol](https://modelcontextprotocol.io)
server that gives any MCP client — Claude Desktop, Cursor, an agent
framework — read access to Hacker News through four tools:

| Tool | What it returns |
|---|---|
| `fetch_top_stories(limit=10)` | the current front page, in ranking order |
| `fetch_best_stories(limit=10)` | the highest-voted recent stories |
| `fetch_new_stories(limit=10)` | the newest submissions |
| `get_story(id)` | one item by id — story, comment, job or poll — with text and child-comment ids |

Every story comes back as a compact record (`title`, `url`, `hn_url`,
`score`, `by`, `comments`, `posted_at`, …) so the model gets what it needs
without the raw Firebase payload. No API key is required: the
[Hacker News API](https://github.com/HackerNews/API) is public.

## Why

Wrapping an existing API as an MCP server is mostly plumbing — reading
the API docs, turning endpoints into typed tools with good descriptions,
wiring a transport, and handing the client a config snippet. This repo is
that plumbing done once for Hacker News, in ~150 lines, with the parts
that matter in practice: concurrent item fetches (the HN API is one
request per item), a short-lived cache, input clamping, proper tool errors,
stdio *and* HTTP transports, and an offline test-suite.

## How it fits together

```mermaid
flowchart LR
    C[MCP client<br/>Claude Desktop · Cursor · agent] -- "stdio (spawns server.py)<br/>or streamable-http" --> S

    subgraph S["server.py · MCP server 'hacker-news'"]
        T1[fetch_top_stories]
        T2[fetch_best_stories]
        T3[fetch_new_stories]
        T4[get_story]
    end

    T1 & T2 & T3 & T4 --> H[hn_client.py<br/>HackerNewsClient<br/>cache · thread pool · summaries]
    H -- "GET /v0/{top,best,new}stories.json<br/>GET /v0/item/{id}.json" --> API[(hacker-news.firebaseio.com)]
```

`server.py` registers the four tools on an `MCPServer` (`FastMCP` on
mcp 1.x — both SDK generations are supported) and picks the transport.
`hn_client.py` does the HTTP work: story-list endpoints return up to 500
ids, so the client fetches the first `limit` items on a thread pool,
memoises responses for `HN_CACHE_TTL` seconds, skips deleted items and
projects each item onto the summary shape.

## Quick start

```bash
git clone <this repo> && cd hacker-news-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

python -m unittest discover -s tests -t .   # offline: fixtures, no network
python smoke_client.py                      # spawn the server over stdio and list its tools
python smoke_client.py --call fetch_top_stories --limit 3
python smoke_client.py --call get_story --id 8863
```

Run it for a remote client instead of stdio:

```bash
python server.py --transport streamable-http --port 8000
```

## Connect it to Claude Desktop

Claude Desktop → *Settings → Developer → Edit Config* opens
`claude_desktop_config.json`. Add the server with absolute paths (run
`which python` inside the virtualenv to get the interpreter path, the
equivalent of `which node` for a Node server):

```json
{
  "mcpServers": {
    "hacker-news": {
      "command": "/absolute/path/to/hacker-news-mcp-server/.venv/bin/python",
      "args": ["/absolute/path/to/hacker-news-mcp-server/server.py"]
    }
  }
}
```

With [uv](https://github.com/astral-sh/uv) instead:

```json
{
  "mcpServers": {
    "hacker-news": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/hacker-news-mcp-server", "run", "server.py"]
    }
  }
}
```

Restart Claude Desktop; the four tools appear under the tools icon and
prompts like *"what's on the Hacker News front page right now?"* or
*"summarize the discussion on story 8863"* route to them. Cursor and
other clients use the same `command`/`args` shape.

`claude_desktop_config.example.json` in this repo is a ready-to-edit copy.

## Configuration

Optional environment variables (see `.env.example`; nothing is required):

| Variable | Default | Meaning |
|---|---|---|
| `HN_BASE_URL` | `https://hacker-news.firebaseio.com/v0` | API root (point at a mirror or a recording proxy) |
| `HN_CACHE_TTL` | `60` | seconds to memoise list and item responses |
| `HN_MAX_WORKERS` | `8` | concurrent item fetches per tool call |

`limit` is clamped to 1–100 per call.

## Code map

```
hacker-news-mcp-server/
├── server.py                           MCP server: tool registration, descriptions, transports, ToolError handling
├── hn_client.py                        HackerNewsClient (cache, thread-pool item fetches, summarize_item, clamp_limit)
├── smoke_client.py                     stdio client that spawns server.py, lists tools, optionally calls one
├── tests/test_server.py                fixture-backed client tests + in-process tool tests (no network)
├── claude_desktop_config.example.json  config snippet for Claude Desktop / Cursor
├── .env.example                        optional settings
├── requirements.txt                    mcp
└── .gitignore
```

## Notes

- Tool errors (unknown id, bad limit) are raised as MCP `ToolError`s, so
  the client sees a proper error result instead of a crashed server.
- The HN API has no rate-limit documentation but is shared infrastructure;
  keep `limit` modest and leave the cache on.
- To adapt this to another public API, keep `server.py` and replace
  `hn_client.py` — the tool layer only depends on `stories()` and `item()`.