An MCP server that lets Claude (or any MCP client) search and read Hacker News, backed by HN's free Algolia search API. Ask in plain language; Claude calls the tools.
You: What's the discussion on Rust async runtimes been like this past month?
Claude: → search_hackernews(query="rust async runtime", time_range="past_month")
Here are the threads HN has been talking about… [summary of real stories]
You: Dive into the comments on the top one.
Claude: → get_hackernews_thread(item_id="…", max_comments=30)
The top commenters are split on… [summary of the thread]
The two tools compose — a follow-up like “pull comments on the first item”
feeds the story id straight from the search into get_hackernews_thread:
See
examples/for full transcripts, anddocs/claude-desktop.mdto wire it into Claude Desktop in about five minutes.
Two MCP tools:
search_hackernews— search stories and comments by query, with filters for tag (story/comment/ask_hn/show_hn/all), time range, sort (relevance or date), and result limit.get_hackernews_thread— fetch a story's comment tree by id, flattened depth-first and bounded bymax_comments/max_depthto keep the response within an honest token budget (with atruncatedflag when it was trimmed).
Tech stack: Python 3.11+, the official MCP Python SDK,
httpx, and — for development —
pytest, ruff,
and pyright.
Uses uv:
git clone https://github.com/ccozad/hackernews-mcp.git
cd hackernews-mcp
uv syncRun the stdio server directly with uv run hackernews-mcp (it speaks the MCP
protocol on stdout, so you normally let a client launch it rather than running it
by hand).
Add this to your claude_desktop_config.json (full guide, config-file locations,
and troubleshooting in docs/claude-desktop.md):
{
"mcpServers": {
"hackernews": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/hackernews-mcp", "run", "hackernews-mcp"]
}
}
}Restart Claude Desktop, then ask it to "search HN for Rust async" and confirm a tool call happens. The first time Claude uses a tool, Claude Desktop asks you to approve it:
As shown in the diagram at the top, Claude Desktop is the MCP client: on startup it spawns this server as a subprocess and talks to it over stdio. The server exposes two tools and forwards their work to HN's Algolia API over HTTPS.
A typical two-tool session — search surfaces a story, then a follow-up dives into its comments:
sequenceDiagram
actor User
participant Desktop as Claude Desktop
participant Server as hackernews-mcp
participant Algolia as HN Algolia API
Note over Desktop,Server: On launch, Desktop spawns the server<br/>and negotiates initialize + tools/list over stdio
User->>Desktop: "search HN for Rust async"
Desktop->>Server: tools/call search_hackernews(query="rust async")
Server->>Algolia: GET /search?query=rust+async&tags=story
Algolia-->>Server: matching hits (JSON)
Server-->>Desktop: hits array
Desktop-->>User: ranked list of stories
User->>Desktop: "pull comments on the first item"
Desktop->>Server: tools/call get_hackernews_thread(item_id="…")
Server->>Algolia: GET /items/{item_id}
Algolia-->>Server: full nested thread (JSON)
Note over Server: flatten depth-first, then bound<br/>by max_comments / max_depth
Server-->>Desktop: root, comments, truncated
Desktop-->>User: thread summary
Both tools are thin wrappers over HN's Algolia API. search_hackernews maps its
arguments to Algolia's /search (or /search_by_date) endpoint — tag filters,
a numericFilters time window, and hitsPerPage. get_hackernews_thread pulls
the full nested thread from /items/{id} and trims it client-side. Input is
validated before any network call; upstream errors, timeouts, and empty results
all have defined behavior. See the tool docstrings in
src/hackernews_mcp/ for the full contract.
uv sync --extra dev # install dev tools
uv run pytest # run the test suite (network-mocked)
uv run ruff check . # lint
uv run ruff format --check .
uv run pyright # type-checkAll four checks run in CI on every pull request across Python 3.11 and 3.12. The
suite mocks Algolia and never hits the network; a gated live smoke test runs only
when HACKERNEWS_MCP_LIVE_TEST=1 is set.



