npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@firstfluke/haejo-mcp

v0.1.0

Published

MCP server for 콘텐츠해줘 (Contents) — Threads management insights, bookmarks, and engagers over the Contents API

Readme

@firstfluke/haejo-mcp

An MCP (Model Context Protocol) stdio server that exposes 콘텐츠해줘 (Contents) Threads analytics to MCP hosts such as Claude Desktop, Cursor, and other LLM clients that speak the Model Context Protocol.

The server proxies six read-only tools over the Contents API: account-level insights, per-post metrics, profile-view timeseries, saved bookmarks, top engagers, and connection health — all scoped to a single Threads-connected channel at a time.

Pro plan required. Every tool is gated at the Pro tier. Calls on a Free or Basic account return tier_required before any API request is made. See Troubleshooting.

Requirements

  • Node.js >= 24
  • A 콘텐츠해줘 Pro account (Pro, Modoo Pro, or Ultra tier)
  • The threads:read scope (granted by haejo-mcp login)

Installation

npx (no install)

npx @firstfluke/haejo-mcp login   # authorize once
// Claude Desktop / MCP client config
{
  "mcpServers": {
    "haejo": {
      "command": "npx",
      "args": ["-y", "@firstfluke/haejo-mcp"]
    }
  }
}

Global install

npm install -g @firstfluke/haejo-mcp
haejo-mcp login   # authorize once
// Claude Desktop / MCP client config
{
  "mcpServers": {
    "haejo": {
      "command": "haejo-mcp",
      "args": []
    }
  }
}

The MCP host runs haejo-mcp (or npx …) with no arguments to start the server. You run haejo-mcp login separately in a terminal before the first tool call.

Authentication

haejo-mcp uses OAuth 2.0 Authorization Code + PKCE (RFC 7636). It is a public client — no client secret is embedded or required.

Why login is a separate step

The server speaks stdio JSON-RPC. If a tool call triggered a browser flow, the host's conversation would stall until you found your browser and completed the redirect — with no way to communicate what was happening. haejo-mcp login is a foreground terminal program that can wait five minutes for you; the MCP server cannot.

Authorize this machine

haejo-mcp login

The command prints an authorization URL, opens it in your browser, waits on a loopback listener for the callback, and stores the resulting tokens.

Tokens persist to $XDG_CONFIG_HOME/haejo-mcp/token.json (falls back to ~/.config/haejo-mcp/token.json). The file is written with 0600 permissions (owner-only). Tokens refresh automatically when they expire; you should not need to run login again unless you explicitly log out or the refresh grant is revoked.

Revoke credentials

haejo-mcp logout

Deletes the stored credential file. The next tool call will return auth_required.

Scope

Only threads:read is requested. Write operations (adding or removing bookmarks, posting) are intentionally absent from this server.

Tools

Every tool accepts a channelId (the Contents channel UUID — not the Threads account id or handle) and an optional format parameter ("markdown" or "json", defaulting to "markdown"). JSON format returns the raw validated payload; markdown format returns a human-readable digest.

threads_insights_summary

Account-level Threads performance for one channel: followers, profile views, lifetime 7-day/30-day metric blocks, a windowed aggregate, and top-performing posts.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | days | integer | 30 | 7–90 | Top-performer window; lifetime cards (7d/30d) ignore it | | format | "markdown" | "json" | "markdown" | — | Output format |

threads_insights_posts

Post-level Threads metrics for one channel — views, likes, replies, reposts, and engagement rate per post, sortable and filterable.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | days | integer | 30 | 1–90 | Lookback window | | limit | integer | 100 | 1–200 | Maximum posts to return | | order | enum | "engagement" | see below | Sort key | | search | string | — | max 200 chars, optional | Substring filter on post text | | format | "markdown" | "json" | "markdown" | — | Output format |

order values: "engagement", "views", "likes", "recent", "replies", "reposts", "engagement_rate", "engagement_amount".

threads_insights_timeseries

Daily profile-view timeseries for one channel, for trend and seasonality analysis.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | days | integer | 30 | 7–90 | Window length | | format | "markdown" | "json" | "markdown" | — | Output format |

threads_bookmarks_list

Lists the Threads posts bookmarked in a channel, newest first, as Threads media_id values. Pair with threads_insights_posts to turn ids into text and metrics. Adding or removing bookmarks is not available in this version.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | format | "markdown" | "json" | "markdown" | — | Output format |

threads_engagers

Ranks the accounts that comment most on a channel's Threads posts over a recent window (Top 20, comments only). The Threads API does not expose per-user like data, so this is a comment ranking. Accounts linked to the channel itself are excluded; one-off commenters (fewer than 2 interactions) are omitted.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | months | integer | 3 | 1–12 | Look-back window in months | | format | "markdown" | "json" | "markdown" | — | Output format |

threads_connection_status

Checks whether a channel's Threads account is still connected and whether the stored token needs reconnecting. Returns one of three statuses: connected, reconnect_required, or not_connected. No token material is exposed.

| Input | Type | Default | Constraints | Description | |---|---|---|---|---| | channelId | UUID string | — | UUID | Channel UUID | | format | "markdown" | "json" | "markdown" | — | Output format |

Configuration

| Variable | Default | Description | |---|---|---| | CONTENTS_API_BASE_URL | https://haejo-api.firstfluke.com | Contents API origin. Set to http://localhost:8020 to target a local dev server. | | XDG_CONFIG_HOME | ~/.config | Base directory for the credential file (haejo-mcp/token.json inside). |

CONTENTS_API_BASE_URL is the only setting relevant to normal use. Trailing slashes are stripped automatically; an empty or whitespace-only value falls back to the production URL.

To pass it to the MCP host:

{
  "mcpServers": {
    "haejo": {
      "command": "haejo-mcp",
      "args": [],
      "env": {
        "CONTENTS_API_BASE_URL": "http://localhost:8020"
      }
    }
  }
}

Troubleshooting

Every tool returns errors as an MCP error result (visible to the model and relayed to you) carrying a stable code, a message, and — where there is a concrete next step — a hint. All six tools use the same vocabulary, so a client can branch on code without knowing which tool produced it.

Codes are split by where the failure happened. Anything prefixed upstream_ came back from the Contents API; everything else was decided locally, before or after the call.

Local (this server, before a request is sent)

| Code | Meaning | Remedy | |---|---|---| | auth_required | No credential is stored, or the stored token could not be refreshed. | Run haejo-mcp login. | | insufficient_scope | The token does not carry threads:read. | Run haejo-mcp login again to re-consent. | | invalid_token | The stored token failed signature or claim validation (clock skew, key rotation, wrong issuer). | haejo-mcp logout, then haejo-mcp login. | | tier_required | The account is below Pro. Also emitted when the API answers 402, i.e. the server disagrees with the local gate. | Upgrade to Pro. |

Upstream (returned by the Contents API)

| Code | HTTP | Meaning | Remedy | |---|---|---|---| | upstream_unauthorized | 401 | The token verified locally but the server refused it — revoked grant, rotated key, clock skew. | Run haejo-mcp login. | | upstream_forbidden | 403 | The request was denied. | Confirm the channel belongs to the authenticated workspace. | | channel_not_found | 404 | No such channel, or the workspace on the token does not own it. | Check the channel UUID; threads_connection_status confirms it exists and is linked. | | invalid_request | 422 | The API rejected the request parameters. | Check the tool arguments against the docs above. | | rate_limited | 429 | Too many requests. | Retry after a short pause. | | upstream_error | 5xx | Server-side failure. | Usually transient; retry shortly. | | upstream_request_failed | other | A non-2xx status with no more specific mapping. | Read the message; it carries the status and the API's detail. | | upstream_unreachable | — | No response at all: DNS, refused connection, TLS, or timeout. | Check connectivity and that CONTENTS_API_BASE_URL points at the right origin. |

Response problems

| Code | Meaning | Remedy | |---|---|---| | invalid_upstream_response | A 2xx whose body did not match the expected schema — the API contract moved. | Upgrade @firstfluke/haejo-mcp; report it if the newest version still fails. | | unexpected_error | Anything not covered above. | Read the message; report it if it recurs. |

Input validation failures (a channelId that is not a UUID, an out-of-range months) are rejected by the MCP layer before the handler runs, and surface as an Invalid arguments error naming the offending field.

License

MIT