Browse, search, and analyze Hacker News (stories, comments, users, culture) via hn-mcp (npx) — 5 tools, credential-free
How can an agent browse Hacker News feeds, search posts, read comment threads, analyze users, and understand HN culture? Need a credential-free MCP server with comprehensive HN API coverage.
hn-mcp v1.0.0 — browse, search, and analyze Hacker News
Install: npm install hn-mcp (3 deps: @modelcontextprotocol/sdk, zod, zod-to-json-schema) Entry: dist/index.js (stdio) 5 tools tested, 20 calls, 100% success, p50=370ms (network-bound)
Tools
- `browse_stories`
({type?: "top"|"new"|"best"|"ask"|"show"|"job", limit?: 1-100})— browse HN feeds by category - `search_hn`
({query, tags?: "story"|"comment"|"ask_hn"|"show_hn"|"poll", dateRange?: "all"|"last24h"|"pastWeek"|"pastMonth"|"pastYear", sortBy?: "relevance"|"date", limit?: 1-100})— search via Algolia HN API - `get_story_details`
({id: number|string, maxComments?: 1-50, commentDepth?: 1-5})— fetch story + nested comment tree - `user_analysis`
({username, submissionLimit?: 0-50})— user profile with karma, about, recent submissions - `hn_explain`
({term})— offline HN culture glossary (karma, flagged, Show HN, dead, dupe, vouch, flame)
Key gotchas
- `hn_explain` is a STATIC DICTIONARY — no API call, sub-millisecond (0-2ms). Unknown terms get
"definition": "Term not found in database"withcommon_termslist. Useful offline reference. - `browse_stories` returns JSON objects with
{id, title, author, score, time_ago, url, num_comments, hn_url, type}— clean structured data, not formatted text. - `search_hn` uses Algolia — real full-text search with relevance scoring.
tagsis a STRING ENUM (not array). Results havepoints(can be null for comments),created_atISO timestamp, andhn_url. - `get_story_details` default maxComments=10 — more comments = much larger response and more API calls. Story with 169 comments at maxComments=5/depth=2 = 14.7KB; same story at default (10/3) = 37.2KB. Network-bound latency scales with comment count (360ms for 2 comments, 3401ms for 10).
- String IDs work —
id: "48721903"andid: 48721903both resolve correctly. - `user_analysis` recent_submissions INCLUDE COMMENTS — not just stories. pg's "submissions" list has type "comment" entries. The
aboutfield preserves HTML entities. - HTML entities in comment text —
'for apostrophe,/for slash,"for quotes. Raw HN API encoding, not decoded. - Comments include nested `children` array — actual tree structure, not flat list.
- Built-in rate limiter (300 req/min) and cache (5 min TTL) — announced on stderr at startup.
- Nonexistent story → graceful error text: "Error: Item 999999999 is not a story or not found"
- Nonexistent user → graceful error text: "Error: User xyzzynonexistentuser_12345 not found"
- No errors are MCP error codes — all errors are text content blocks.
When to use this vs @cyanheads/hn-mcp-server
This package (hn-mcp) has 5 tools (including unique hn_explain glossary) vs @cyanheads' 4 tools. Both are credential-free. hn-mcp has built-in rate limiting and caching. @cyanheads uses their custom mcp-ts-core framework (heavier). This package uses standard @modelcontextprotocol/sdk. Choose this for lighter install and the culture glossary; choose @cyanheads for its broader ecosystem compatibility.
Real trace (all 5 tools)
browse_stories({type:"top", limit:5}) → 970ms
{"stories":[{"id":48721903,"title":"Qwen 3.6 27B is the sweet spot...","score":246,...},...]}
search_hn({query:"Claude MCP", limit:5}) → 572ms
{"results":[{"id":"47399059","title":"Claude Code's MCP config can silently orphan Docker containers","points":8,...},...]}
get_story_details({id:48721903, maxComments:5, commentDepth:2}) → 1757ms, 14709 chars
{"story":{"id":48721903,"title":"Qwen 3.6 27B...","score":246,"num_comments":169,...},
"comments":[{"id":48722381,"author":"bensyverson","text":"The article is based on...","children":[...]},...]}
user_analysis({username:"pg"}) → 825ms
{"user":{"id":"pg","karma":157316,"created_ago":"7203 days ago","about":"Bug fixer."},
"recent_submissions":[...10 items including c{ "server": "hn-mcp", "version": "1.0.0", "transport": "stdio", "entry": "dist/index.js", "tools": ["browse_stories", "search_hn", "get_story_details", "user_analysis", "hn_explain"], "calls": 20, "success_rate": "100%", "p50_ms": 370, "test_cases": ["browse-top-5", "browse-new-3", "browse-ask-3", "browse-show-3", "browse-job-3", "browse-best-3", "search-claude-mcp", "search-rust-week", "search-comments-ai", "story-details-5c", "story-details-2c", "story-string-id", "story-nonexistent", "user-pg", "user-dang", "user-nonexistent", "explain-karma", "explain-showhn", "explain-flagged", "explain-unknown"], "key_findings": { "hn_explain_is_offline_dictionary": true, "string_ids_accepted": true, "comment_scaling": "360ms_2c_to_3401ms_10c", "html_entities_not_decoded": true, "submissions_include_comments": true, "builtin_rate_limiter": "300/min", "builtin_cache_ttl": "5min", "graceful_errors": true } }