hn-mcp-server

v0.5.20 pre-1.0

Browse Hacker News feeds, threads, and user profiles with full-text search via MCP. STDIO or Streamable HTTP.

hn.caseyjhand.com/mcp
claude mcp add --transport http hn-mcp-server https://hn.caseyjhand.com/mcp
codex mcp add hn-mcp-server --url https://hn.caseyjhand.com/mcp
{
  "mcpServers": {
    "hn-mcp-server": {
      "url": "https://hn.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http hn-mcp-server https://hn.caseyjhand.com/mcp
{
  "mcpServers": {
    "hn-mcp-server": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://hn.caseyjhand.com/mcp"
      ]
    }
  }
}
{
  "mcpServers": {
    "hn-mcp-server": {
      "type": "http",
      "url": "https://hn.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://hn.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

4

hn_get_stories

Fetch stories from an HN feed (top, new, best, ask, show, jobs), with title, URL, score, author, and comment count for each story.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "hn_get_stories",
    "arguments": {
      "feed": "<feed>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "feed": {
      "type": "string",
      "enum": [
        "top",
        "new",
        "best",
        "ask",
        "show",
        "jobs"
      ],
      "description": "Which HN feed to fetch. \"top\" includes jobs. \"ask\" and \"show\" are Ask HN / Show HN posts."
    },
    "count": {
      "default": 30,
      "description": "Number of stories to return. Larger counts take longer.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "offset": {
      "default": 0,
      "description": "Number of stories to skip from the start of the feed. Use with count for pagination.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "feed",
    "count",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

hn_get_thread

Get an item and its comment tree as a threaded discussion, with child comments resolved recursively. Use depth 0 for an item-only lookup. A long thread comes back in pages: pass the returned nextCursor as cursor to continue.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "hn_get_thread",
    "arguments": {
      "itemId": "<itemId>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "itemId": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991,
      "description": "ID of the story, comment, job, poll, or poll option to fetch the thread for."
    },
    "depth": {
      "default": 3,
      "description": "How many levels of replies to resolve. 0 = just the item, no comments. 1 = direct replies only. Replies below this depth are never fetched — to read them, raise depth or call again with a specific comment's itemId to drill into its subtree. Ignored when cursor is set: the depth the cursor was issued with applies.",
      "type": "integer",
      "minimum": 0,
      "maximum": 10
    },
    "maxComments": {
      "default": 50,
      "description": "Maximum comments in one response, across all depth levels. Highest-ranked top-level comments resolve first; replies fill in only after the level above is exhausted. A response also stops at a fixed 64,000-byte text budget. When either limit stops the traversal, nextCursor continues it.",
      "type": "integer",
      "minimum": 1,
      "maximum": 200
    },
    "cursor": {
      "description": "nextCursor from a previous call, passed with the same itemId, to continue the traversal at the next unseen comment without repeating or skipping any. Omit to start from the top.",
      "type": "string"
    }
  },
  "required": [
    "itemId",
    "depth",
    "maxComments"
  ],
  "additionalProperties": false
}
view source ↗

hn_get_user

Get an HN user profile with karma, about, and optionally their most recent submissions resolved into full items.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "hn_get_user",
    "arguments": {
      "username": "<username>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "minLength": 1,
      "description": "HN username. Case-sensitive. Trimmed; blank or whitespace-only input is rejected."
    },
    "includeSubmissions": {
      "default": false,
      "description": "Resolve the user's most recent submissions into full items. Without this, only the submission count is available.",
      "type": "boolean"
    },
    "submissionCount": {
      "default": 10,
      "description": "Page size — how many submissions to resolve per call. Only used when includeSubmissions is true.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    },
    "submissionOffset": {
      "default": 0,
      "description": "How many submissions to skip before resolving, counting back from the most recent. Use with submissionCount to page through a long history: request offset 0, then offset submissionCount, and so on. The enrichment block echoes submissionOffset and, when more remain, the offset to send next. Only used when includeSubmissions is true.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "username",
    "includeSubmissions",
    "submissionCount",
    "submissionOffset"
  ],
  "additionalProperties": false
}
view source ↗

hn_search_content

Search Hacker News stories, comments, polls, and jobs via Algolia — by keyword, by filters alone, or both. Filterable by content type, author, parent story, date range, and minimum points.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "hn_search_content",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "query": {
      "description": "Search terms. Supports simple keywords — Algolia handles stemming and relevance. Trimmed before searching; blank or whitespace-only input is rejected. Omit for a filter-only search, which needs at least one of tags, author, storyId, minPoints, or a dateRange bound.",
      "type": "string",
      "minLength": 1
    },
    "tags": {
      "description": "Filter results by content type: \"story\", \"comment\", \"poll\", or \"job\", or the story subsets \"ask_hn\", \"show_hn\", and \"front_page\". Omit to search all types.",
      "type": "string",
      "enum": [
        "story",
        "comment",
        "poll",
        "job",
        "ask_hn",
        "show_hn",
        "front_page"
      ]
    },
    "author": {
      "description": "Filter results to a specific author. Useful for finding a user's posts on a topic (hn_get_user only shows recent submissions). Trimmed before filtering; omit the field to search all authors rather than passing a blank string.",
      "type": "string",
      "minLength": 1
    },
    "storyId": {
      "description": "Restrict results to one discussion: the id of a story or poll root, combined with the other filters. Pair with tags \"comment\" to search within a thread. Take it from hits[].storyId or the root item of hn_get_thread — a comment id matches nothing.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "sort": {
      "default": "relevance",
      "description": "Sort order. \"relevance\" for best match, \"date\" for most recent first.",
      "type": "string",
      "enum": [
        "relevance",
        "date"
      ]
    },
    "dateRange": {
      "description": "Filter to a creation-time window with a start, an end, or both. An empty object is rejected — omit dateRange instead.",
      "type": "object",
      "properties": {
        "start": {
          "description": "Exclusive lower bound — only items created strictly after this instant match. ISO 8601: YYYY, YYYY-MM, YYYY-MM-DD, or YYYY-MM-DDThh:mm[:ss[.sss]] with an optional Z or ±hh:mm offset. Reduced and date-only forms mean UTC midnight at the start of that period; a date-time without an offset is read as UTC.",
          "type": "string",
          "pattern": "^(\\d{4})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,3})?)?(?:Z|[+-](\\d{2}):(\\d{2}))?)?)?)?$"
        },
        "end": {
          "description": "Exclusive upper bound — only items created strictly before this instant match. Same formats and UTC reading as start, and must be later than start. A date-only end excludes that whole UTC day: to include it, pass the next day or a full timestamp.",
          "type": "string",
          "pattern": "^(\\d{4})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,3})?)?(?:Z|[+-](\\d{2}):(\\d{2}))?)?)?)?$"
        }
      },
      "additionalProperties": false
    },
    "minPoints": {
      "description": "Minimum score. Applies to stories and polls, including the ask_hn, show_hn, and front_page subsets. Comments and jobs carry no points in the search index, so any minPoints excludes them — combining it with tags \"comment\" or \"job\" is rejected.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "count": {
      "default": 30,
      "description": "Number of results to return.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    },
    "page": {
      "default": 0,
      "description": "Page number for pagination (0-indexed).",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "view": {
      "default": "full",
      "description": "How much of each hit to return. \"full\" includes every field. \"compact\" omits the two body-text fields — `text` and `highlights.text` — which together can repeat a long comment twice per hit; everything else (id, title, url, domain, author, points, comment count, timestamp, parent story, title highlight, matchedWords) is unchanged. Use \"compact\" to scan many results, then pass a hit id to hn_get_thread to read the body you skipped.",
      "type": "string",
      "enum": [
        "full",
        "compact"
      ]
    }
  },
  "required": [
    "sort",
    "count",
    "page",
    "view"
  ],
  "additionalProperties": false
}
view source ↗