Skip to main content
Glama
Riddhimaan-Senapati

unofficial-HackerNews-MCP-CLI

This project wraps the official HackerNews API behind one shared async client and exposes it two ways:

  • hn, a Typer CLI that renders Rich tables in the terminal.

  • hn-mcp, a FastMCP server that exposes the same operations as MCP tools for Claude and other MCP clients.

The API is read-only, needs no authentication, and has no rate limit.

Let your coding agent install it

You do not have to run the install commands yourself. Give this instruction to the coding agent you already use:

Run curl -fsSL https://raw.githubusercontent.com/Riddhimaan-Senapati/unofficial-HackerNews-MCP-CLI/main/skills/install-hackernews/SKILL.md and follow the instructions in its output to install the HackerNews CLI.

That command fetches the install-hackernews skill, which gives the agent each step.

If your agent supports the skills library, install the same skill with:

npx skills add Riddhimaan-Senapati/unofficial-HackerNews-MCP-CLI

Related MCP server: Hacker News MCP Server

Install

Requires Python 3.10+ and uv.

uv sync                  # create .venv and install the locked dependencies
uv run hn stories top   # run the CLI in .venv
uv run hn-mcp           # run the MCP server in .venv

To install the published commands outside the project, use an isolated uv tool environment:

uv tool install hackernews-mcp-cli

Upgrade an existing tool installation with:

uv tool upgrade hackernews-mcp-cli

The examples below use the installed commands. From a clone, prefix hn and hn-mcp commands with uv run.

CLI

hn stories top             # front-page (top) stories
hn stories top --limit 10  # -n 10
hn stories top --offset 30 --limit 10  # skip the first 30 stories
hn stories new             # newest stories
hn stories best            # best recent stories
hn stories ask             # latest Ask HN
hn stories show            # latest Show HN
hn stories job             # latest job postings

hn item 8863               # a single story, comment, job, or poll
hn comments 8863           # threaded comment tree (--depth, --limit, --offset)
hn user pg                 # a user's profile (case-sensitive name)

hn max-item                # id of the most recently created item
hn updates                 # recently changed items and profiles

hn --version               # print the installed version

Every command takes --format (-f): table (the default), json, markdown, or text. Use json for scripting; markdown and text strip HTML. Use --json as a shortcut for --format json. If you pass both options, --json takes precedence. For the story lists, --offset plus --limit must stay within the category's cap, 500 stories for top, new, and best, or 200 for ask, show, and job.

hn stories top -n 5 --json | jq -r '.[] | "\(.title) (\(.url // .hn_url))"'
hn user pg --json | jq .karma

Run hn --help for the full command list, or hn <command> --help for that command's options and examples.

MCP server

The server uses FastMCP 4 and MCP SDK v2. It supports stdio and streamable HTTP transports.

Run over stdio, the default transport for MCP clients:

hn-mcp

Or over HTTP:

hn-mcp --http --host 127.0.0.1 --port 8000

Register it with an MCP client such as Claude Desktop or Claude Code:

{
  "mcpServers": {
    "hackernews": { "command": "hn-mcp" }
  }
}

Tools

Tool

Description

get_stories(category, limit, offset, strip_html)

A list of stories; category is one of top, new, best, ask, show, or job

get_item(item_id, strip_html)

A single story, comment, job, or poll

get_items(item_ids, strip_html)

A batch of items by id, in order, with missing ids dropped

get_comments(item_id, max_depth, max_per_level, offset, strip_html)

Threaded comment tree

get_user(username, strip_html)

A user's public profile

get_max_item_id()

id of the most recently created item

get_updates()

Items and profiles that changed most recently

Every tool that returns items, comments, or users takes strip_html to return plain text instead of HTML.

Resources

The server also exposes read-only JSON at stable URIs: hn://stories/{category}, hn://item/{item_id}, and hn://user/{username}.

Prompts

  • daily_digest(category, limit) asks the agent to summarize the day's stories.

  • thread_summary(item_id, max_depth) asks the agent to summarize a comment thread.

Skill

The repo ships two Agent Skills, published on skills.sh:

  • skills/hackernews/SKILL.md teaches an agent how and when to use the hn CLI and the hn-mcp MCP server.

  • skills/install-hackernews/SKILL.md installs the package.

Install both with the skills CLI, or add the console scripts and let your agent load them from this directory:

npx skills add Riddhimaan-Senapati/unofficial-HackerNews-MCP-CLI

The root skills.sh.json groups the two skills on the skills.sh repo page.

Project layout

src/hn/
  client.py   # shared async HackerNews API client (httpx)
  models.py   # Pydantic models + StoryCategory (endpoint, title, cap)
  server.py   # FastMCP server (hn-mcp)
  cli.py      # Typer CLI (hn)
skills/hackernews/SKILL.md
skills/install-hackernews/SKILL.md
tests/        # pytest + respx (mocked API)

How it works

client.py owns every network call and bounds concurrency. server.py and cli.py stay thin over it, so the CLI and the MCP tools behave identically. StoryCategory in models.py is the single source of truth for the six story lists: it holds a category's endpoint stem, display title, and cap. Add a category or a model field there, not in each interface.

The client keeps a short in-process cache, about 60 seconds, for item, user, and story responses. It mainly helps the long-lived MCP server; the CLI exits between commands. max_item and updates are never cached.

Changing behavior also means updating the docs in the same change: README, CONTRIBUTING, RELEASING, the source skills, and AGENTS.md. Only the maintainer publishes a release, so follow RELEASING.md and push a tag only when asked.

Read AGENTS.md for the agent-facing rules, and CONTRIBUTING.md for the contribution workflow.

Development

uv run ruff check .          # lint
uv run ruff format --check . # formatting
uv run pytest                # test suite (API calls are mocked)

CI runs lint, formatting, and the test suite on Python 3.10 through 3.13 via GitHub Actions. See CONTRIBUTING.md for the full workflow.

License

MIT. See LICENSE. This is an unofficial project and is not affiliated with Hacker News or Y Combinator.

Available Tools

7 tools
get_commentsGet commentsA
Read-onlyIdempotent

Get the threaded comment tree for an item.

Each comment includes a nested replies list bounded by max_depth and max_per_level. Comment text is HTML unless strip_html is set.

ParametersJSON Schema
NameRequiredDescriptionDefault
offsetNoSkip this many top-level comments.
item_idYesId of the story or comment to expand.
max_depthNoReply nesting depth (0 = top-level comments only).
strip_htmlNoReturn plain text instead of HTML in comment bodies.
max_per_levelNoMax comments to expand per level.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, so the safety profile is covered. The description adds genuinely useful behavioral context beyond those hints: replies are nested and bounded by max_depth and max_per_level, and comment text defaults to HTML unless strip_html is set. This clarifies output-shaping behavior that annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences: the first states the primary purpose, the second delivers the key behavioral details. There is no filler, repetition of schema data, or redundant phrasing; every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists and the input schema covers all parameter details, the description needs only to convey the core purpose and non-obvious behavior. It covers the threaded nature, bounding limits, and HTML default, leaving an agent well-equipped to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, establishing a baseline of 3, and the description adds value by explaining the effect of max_depth, max_per_level, and strip_html on the returned comment tree and text format. Offset and item_id semantics remain in the schema, but the description meaningfully connects parameters to behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Get') and resource ('threaded comment tree for an item'), immediately distinguishing it from sibling tools that retrieve items, stories, users, or updates. The qualifier 'threaded' adds precision beyond the name alone, so an agent can tell this tool apart from get_item for comment-related needs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use case is implied: call this tool when you want comments for an item rather than items or stories. However, the description does not explicitly state when to prefer get_comments over get_item, whether get_item already returns some comment data, or any exclusions. The guidance is adequate but inferred from the name and resource scope.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_itemGet itemA
Read-onlyIdempotent

Get a single HackerNews item (story, comment, job, poll, or poll option).

Returns null if no item exists with that id.

ParametersJSON Schema
NameRequiredDescriptionDefault
item_idYesThe item's numeric id.
strip_htmlNoReturn plain text instead of HTML in text and title.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=true, so the safety profile is covered. The description adds a specific behavioral detail—returns null if no item exists—which is not in the annotations and is valuable for an agent deciding how to handle missing data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences, front-loaded with the core purpose and followed by a key behavioral note. There is no redundancy or irrelevant information, making it efficient for an agent to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with only two well-documented parameters, an output schema exists (so return structure is covered), and the description covers purpose and null behavior. Given the low complexity and rich annotations, nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with both parameters (item_id and strip_html) already described in the input schema. The description does not add any parameter-level detail beyond the schema, so it meets the baseline of 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Get') and the resource ('a single HackerNews item'), and explicitly enumerates the item types (story, comment, job, poll, or poll option), which differentiates it from sibling tools like get_stories or get_comments. It also notes the null return for nonexistent ids, adding precision.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies single-item usage via the word 'single', which distinguishes it from get_items, but it does not explicitly state when to use this tool over alternatives or provide any exclusions. The guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_itemsGet itemsA
Read-onlyIdempotent

Get many items in one call, preserving order and dropping missing ids.

ParametersJSON Schema
NameRequiredDescriptionDefault
item_idsYesThe item ids to fetch, in order.
strip_htmlNoReturn plain text instead of HTML in text and title.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses two important behavioral traits not captured by annotations: input order is preserved in the result, and missing ids are silently dropped. Since readOnlyHint and idempotentHint already cover safety and repeatability, these additional details meaningfully enrich the agent's understanding of call outcomes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence conveys the core action, resource, batch nature, order behavior, and missing-id handling with no filler. The most decision-relevant information is front-loaded and every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple two-parameter schema, complete parameter descriptions, clear annotations (read-only and idempotent), and an output schema, the description covers everything an agent needs to decide when and how to call this tool. No critical behavioral gap remains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides 100% documentation coverage for both parameters, including 'in order' for item_ids and a clear description for strip_html. The description's order/dropping language mostly restates or reframes schema-level information rather than adding new parameter-specific meaning, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Get'), a resource ('many items'), and the batching behavior ('in one call'). It clearly differentiates from the sibling get_item by emphasizing multi-item retrieval, and the order/dropping semantics add precision beyond the tool name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Get many items in one call' provides clear context for when to use this tool: when multiple items are needed and a single call is preferred. It does not explicitly name alternatives or exclusion conditions, but the sibling get_item and the wording make the batch-vs-single distinction obvious.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_max_item_idGet max item idA
Read-onlyIdempotent

Get the id of the most recently created item, useful for walking all items.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, so the agent knows the operation is safe and non-mutating. The description adds the behavioral trait of returning the maximum id, which aligns with the annotations. It does not contradict them. It does not add extra detail like rate limits or aggregate nature, but the annotations cover the key safety profile, so a 3 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence, concise and front-loaded with the primary purpose, followed by a brief use case. Every word serves a function; there is no wasted text. It is appropriately sized for a zero-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with an output schema and annotations covering safety, the description is nearly complete. It explains the purpose and suggests a use case, which is sufficient for an agent to decide when to call it. The only minor gap is not mentioning alternatives, but given the simplicity, it is complete enough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is no parameter semantics to add. The description adds value by clarifying that the result is the latest item id, which is more than the schema provides (schema is empty). With no parameters, a baseline of 4 is justified, as the description effectively communicates the tool's interface.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: getting the id of the most recently created item, which is a specific verb and resource. It distinguishes itself by mentioning its usefulness for walking all items, which hints at its role relative to siblings like get_items, though it does not explicitly compare to them. With no parameters and a clear output, the purpose is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says 'useful for walking all items,' which indicates a clear use case: iterating over items in creation order. It does not explicitly state when not to use it or mention alternatives (e.g., get_updates for incremental updates), but the context implies it is for initial or full traversal. The clear context earns above average, but the lack of explicit exclusions keeps it from a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_storiesGet storiesA
Read-onlyIdempotent

Get a list of HackerNews stories from a category, with full details.

'top', 'new' and 'best' draw from up to 500 stories; 'ask', 'show' and 'job' from up to 200. Each result includes title, url, score, author, comment count and the canonical HN discussion link.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many stories to return (1-500).
offsetNoSkip this many stories from the top of the list.
categoryNoWhich list: top, new, best, ask, show, or job stories.top
strip_htmlNoReturn plain text instead of HTML in text and title.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, so the safety profile is covered. The description adds meaningful behavioral context beyond the annotations, such as per-category upper bounds (500 vs 200 stories) and the specific fields returned in each result.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core action, followed by two sentences of helpful specifics. Every sentence earns its place, and there is no redundant repetition of schema or annotation content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a read-only list tool given the rich annotations and full schema coverage. It explains category-specific limits, what fields appear in results, and the general scope, so an agent has enough context to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema coverage is 100%, with descriptions already provided for limit, offset, category, and strip_html. The description does not add significant parameter-level semantics beyond the schema; it mentions categories and result details but not new guidance on how parameters should be used.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb-resource pair ('Get a list of HackerNews stories') and immediately clarifies it returns full details per story. It distinguishes itself from sibling tools like get_item, get_comments, and get_user by focusing on category-based story lists.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes usage context clear: it is for retrieving stories by category, and it names the supported categories. However, it does not explicitly tell the agent when to choose this over get_items or get_item, so guidance on alternatives is only implied by the sibling tool names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_updatesGet updatesA
Read-onlyIdempotent

Get the items and user profiles that changed most recently.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
itemsNoChanged item ids.
profilesNoChanged usernames.

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, covering safety and idempotency. The description adds the temporal scope ('most recently changed') but provides no additional behavior such as ordering, limits, or whether the result is a combined list. There is no contradiction with annotations, so a mid score is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single front-loaded sentence with no filler. It efficiently conveys the tool's purpose without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple, has no parameters, annotations cover safety, and an output schema exists. The description is mostly sufficient, but it does not clarify the recency window or any caveats about what 'most recently' means, leaving a small but real gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4; there is nothing for the description to clarify beyond noting this is a parameterless request.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource: it retrieves items and user profiles that changed most recently. This distinguishes it partially from sibling get_* tools, but it does not explicitly disambiguate from get_items or get_user, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use guidance, no mention of when not to use it, and no comparison to alternatives like get_items or get_item. The phrase 'changed most recently' implies a polling/recent-changes use case, but the description leaves the selection entirely to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_userGet userA
Read-onlyIdempotent

Get a HackerNews user's public profile (karma, about, created, submissions).

Returns null if the user has no public activity or does not exist.

ParametersJSON Schema
NameRequiredDescriptionDefault
usernameYesThe exact, case-sensitive HackerNews username.
strip_htmlNoReturn plain text instead of HTML in `about`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, and idempotentHint. The description adds value beyond these by disclosing the null-return behavior for users with no public activity or nonexistent users, and by clarifying that only the public profile is returned. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with no redundant or filler content. The core purpose and returned fields are front-loaded, and the important null behavior is stated immediately after. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only tool with a rich output schema and full annotation coverage, the description is nearly complete. It communicates purpose, core fields, and null behavior. The only minor gap is explicit sibling differentiation, which is partially mitigated by the distinct resource focus.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents both parameters. The description adds context about what fields the tool returns, but does not add meaning to the parameters themselves. Baseline 3 is appropriate given the complete schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Get a HackerNews user's public profile,' and lists the included fields (karma, about, created, submissions). This clearly distinguishes it from the sibling item/story/comment tools, which focus on different HackerNews resources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by naming the target resource, but it does not explicitly state when to prefer this tool over siblings or when not to use it. The null-return behavior provides some guidance for handling nonexistent users, but alternative-selection guidance is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.3.0
    • Changedget_comments2 fields changed
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "description": "Skip this many top-level comments.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / strip_html
        Added value: +{
        +  "default": false,
        +  "description": "Return plain text instead of HTML in comment bodies.",
        +  "type": "boolean"
        +}
    • Changedget_item1 field changed
      • addedInput schema / properties / strip_html
        Added value: +{
        +  "default": false,
        +  "description": "Return plain text instead of HTML in text and title.",
        +  "type": "boolean"
        +}
    • Addedget_items
    • Changedget_stories2 fields changed
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "description": "Skip this many stories from the top of the list.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / strip_html
        Added value: +{
        +  "default": false,
        +  "description": "Return plain text instead of HTML in text and title.",
        +  "type": "boolean"
        +}
    • Changedget_user1 field changed
      • addedInput schema / properties / strip_html
        Added value: +{
        +  "default": false,
        +  "description": "Return plain text instead of HTML in `about`.",
        +  "type": "boolean"
        +}
  2. 5 tool updatesv0.2.0
    • Changedget_comments4 fields changed
      • addedOutput schema / $defs
        Added value: +{
        +  "Comment": {
        +    "additionalProperties": true,
        +    "description": "A comment in a threaded discussion, with its nested replies.\n\nHackerNews comments may be deleted (``deleted``/``dead``) yet still carry\nan id. Extra API fields are always allowed so parsing never breaks.",
        +    "properties": {
        +      "by": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The comment's author."
        +      },
        +      "dead": {
        +        "anyOf": [
        +          {
        +            "type": "boolean"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "True if the comment is dead."
        +      },
        +      "deleted": {
        +        "anyOf": [
        +          {
        +            "type": "boolean"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "True if the comment is deleted."
        +      },
        +      "hn_url": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "Canonical HackerNews discussion link.",
        +        "readOnly": true
        +      },
        +      "id": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The comment's id, if present."
        +      },
        +      "kids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The ids of the comment's replies, in display order."
        +      },
        +      "replies": {
        +        "description": "The comment's expanded child replies.",
        +        "items": {
        +          "$ref": "#/$defs/Comment"
        +        },
        +        "type": "array"
        +      },
        +      "text": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The comment body (HTML)."
        +      },
        +      "time": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "Creation date, in Unix time."
        +      }
        +    },
        +    "required": [
        +      "hn_url"
        +    ],
        +    "type": "object"
        +  }
        +}
      • addedOutput schema / properties / result / items / $ref
        Added value: +"#/$defs/Comment"
      • removedOutput schema / properties / result / items / additionalProperties
        Removed value: -true
      • removedOutput schema / properties / result / items / type
        Removed value: -"object"
    • Changedget_item1 field changed
      • changedOutput schema / properties / result / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": true,
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": true,
        +    "description": "A HackerNews item: story, comment, job, poll, or poll option.",
        +    "properties": {
        +      "by": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The username of the item's author."
        +      },
        +      "dead": {
        +        "anyOf": [
        +          {
        +            "type": "boolean"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "True if the item is dead."
        +      },
        +      "deleted": {
        +        "anyOf": [
        +          {
        +            "type": "boolean"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "True if the item is deleted."
        +      },
        +      "descendants": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "In the case of stories or polls, the total comment count."
        +      },
        +      "hn_url": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "Canonical HackerNews discussion link.",
        +        "readOnly": true
        +      },
        +      "id": {
        +        "description": "The item's unique id.",
        +        "type": "integer"
        +      },
        +      "kids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The ids of the item's comments, in ranked display order."
        +      },
        +      "parent": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The comment's parent: either another comment or the story."
        +      },
        +      "parts": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "A list of related pollopts, in display order."
        +      },
        +      "poll": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The pollopt's associated poll."
        +      },
        +      "score": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The story's score, or poll option votes."
        +      },
        +      "text": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The comment, story or poll text (HTML)."
        +      },
        +      "time": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "Creation date, in Unix time."
        +      },
        +      "title": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The title of the story, poll or job (HTML)."
        +      },
        +      "type": {
        +        "anyOf": [
        +          {
        +            "enum": [
        +              "job",
        +              "story",
        +              "comment",
        +              "poll",
        +              "pollopt"
        +            ],
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "One of 'job', 'story', 'comment', 'poll', 'pollopt'."
        +      },
        +      "url": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The URL of the story."
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "hn_url"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedget_stories3 fields changed
      • addedOutput schema / properties / result / items / description
        Added value: +"A HackerNews item: story, comment, job, poll, or poll option."
      • addedOutput schema / properties / result / items / properties
        Added value: +{
        +  "by": {
        +    "anyOf": [
        +      {
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The username of the item's author."
        +  },
        +  "dead": {
        +    "anyOf": [
        +      {
        +        "type": "boolean"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "True if the item is dead."
        +  },
        +  "deleted": {
        +    "anyOf": [
        +      {
        +        "type": "boolean"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "True if the item is deleted."
        +  },
        +  "descendants": {
        +    "anyOf": [
        +      {
        +        "type": "integer"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "In the case of stories or polls, the total comment count."
        +  },
        +  "hn_url": {
        +    "anyOf": [
        +      {
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "description": "Canonical HackerNews discussion link.",
        +    "readOnly": true
        +  },
        +  "id": {
        +    "description": "The item's unique id.",
        +    "type": "integer"
        +  },
        +  "kids": {
        +    "anyOf": [
        +      {
        +        "items": {
        +          "type": "integer"
        +        },
        +        "type": "array"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The ids of the item's comments, in ranked display order."
        +  },
        +  "parent": {
        +    "anyOf": [
        +      {
        +        "type": "integer"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The comment's parent: either another comment or the story."
        +  },
        +  "parts": {
        +    "anyOf": [
        +      {
        +        "items": {
        +          "type": "integer"
        +        },
        +        "type": "array"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "A list of related pollopts, in display order."
        +  },
        +  "poll": {
        +    "anyOf": [
        +      {
        +        "type": "integer"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The pollopt's associated poll."
        +  },
        +  "score": {
        +    "anyOf": [
        +      {
        +        "type": "integer"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The story's score, or poll option votes."
        +  },
        +  "text": {
        +    "anyOf": [
        +      {
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The comment, story or poll text (HTML)."
        +  },
        +  "time": {
        +    "anyOf": [
        +      {
        +        "type": "integer"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "Creation date, in Unix time."
        +  },
        +  "title": {
        +    "anyOf": [
        +      {
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The title of the story, poll or job (HTML)."
        +  },
        +  "type": {
        +    "anyOf": [
        +      {
        +        "enum": [
        +          "job",
        +          "story",
        +          "comment",
        +          "poll",
        +          "pollopt"
        +        ],
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "One of 'job', 'story', 'comment', 'poll', 'pollopt'."
        +  },
        +  "url": {
        +    "anyOf": [
        +      {
        +        "type": "string"
        +      },
        +      {
        +        "type": "null"
        +      }
        +    ],
        +    "default": null,
        +    "description": "The URL of the story."
        +  }
        +}
      • addedOutput schema / properties / result / items / required
        Added value: +[
        +  "id",
        +  "hn_url"
        +]
    • Changedget_updates3 fields changed
      • removedOutput schema / additionalProperties
        Removed value: -true
      • addedOutput schema / description
        Added value: +"The set of items and profiles that changed most recently."
      • addedOutput schema / properties
        Added value: +{
        +  "items": {
        +    "description": "Changed item ids.",
        +    "items": {
        +      "type": "integer"
        +    },
        +    "type": "array"
        +  },
        +  "profiles": {
        +    "description": "Changed usernames.",
        +    "items": {
        +      "type": "string"
        +    },
        +    "type": "array"
        +  }
        +}
    • Changedget_user1 field changed
      • changedOutput schema / properties / result / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": true,
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": true,
        +    "description": "A HackerNews user profile. Only users with public activity are available.",
        +    "properties": {
        +      "about": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The user's optional self-description (HTML)."
        +      },
        +      "created": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "Creation date of the user, in Unix time."
        +      },
        +      "hn_url": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "Canonical HackerNews profile link.",
        +        "readOnly": true
        +      },
        +      "id": {
        +        "description": "The user's unique username. Case-sensitive.",
        +        "type": "string"
        +      },
        +      "karma": {
        +        "anyOf": [
        +          {
        +            "type": "integer"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "The user's karma."
        +      },
        +      "submitted": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "description": "List of the user's stories, polls and comments."
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "hn_url"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
  3. 6 tool updatesv0.1.0
    • First observedget_comments
    • First observedget_item
    • First observedget_max_item_id
    • First observedget_stories
    • First observedget_updates
    • First observedget_user

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct resource or access mode: single item, batch items, category stories, threaded comments, user profile, latest ID, and recent updates. There is no meaningful overlap that would cause an agent to select the wrong tool.

Naming Consistency5/5

All tool names follow the same get_* snake_case convention, with clear noun targets. Pluralization is used sensibly to distinguish batch endpoints from singular endpoints.

Tool Count5/5

Seven tools is well-scoped for a read-only Hacker News API client covering items, stories, comments, users, and metadata. Each tool adds distinct value without bloat.

Completeness5/5

The set covers the core Hacker News API surface: fetching items individually or in bulk, story categories, comment threads, user profiles, latest item ID, and recent updates. No significant dead ends or missing read operations are apparent.

Maintenance

ActivityNo data
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    F
    maintenance
    A Model Context Protocol (MCP) server that provides tools for searching and fetching information from Hacker News.
    4
    76
    MIT
  • A
    license
    D
    quality
    D
    maintenance
    An MCP server that enables AI assistants to access real-time Hacker News data including top stories, story details, comments, and search functionality.
    1
    23 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for Hacker News providing tools to fetch stories, threads, users, and search content via Firebase and Algolia APIs.
    315 npm
    4
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    MCP server enabling AI agents to interact with Hacker News, including fetching full comment trees with depth control, searching stories and comments, and retrieving user profiles.
    6
    5
    MIT