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

rocket-chat-mcp

v0.1.0

Published

MCP server for self-hosted Rocket.Chat — six tools (5 read, 1 write) over stdio. Works with Claude Code, Claude Desktop, and MCP Inspector.

Readme

rocket-chat-mcp

npm version license node

MCP server for self-hosted Rocket.Chat. Connects to any instance via env-var configuration; no hostname is hard-coded. Six tools — five read, one write — exposed over stdio for Claude Code, Claude Desktop, MCP Inspector, or any MCP-aware client.

  • Read tools: list_unread, list_my_rooms, get_room_history, search_messages, get_user
  • Write tool: post_message
  • Auth: Personal Access Token (X-User-Id + X-Auth-Token headers)
  • Transport: stdio
  • Runtime: Node.js 20+, zero non-JS dependencies

Requirements

  • Node.js 20.0+ (uses native fetch)
  • A Rocket.Chat user account with a Personal Access Token

Setup

1. Create a Personal Access Token in Rocket.Chat

  1. Open Rocket.Chat in your browser.

  2. Go to My Account → Personal Access Tokens.

  3. Enter a name (e.g. mcp-server).

  4. Check "Ignore Two Factor Authentication" before clicking Add. Without this, every API call fails with a totp-required error.

  5. After clicking Add, Rocket.Chat shows two values:

    • Token — this is your ROCKETCHAT_AUTH_TOKEN
    • User Id — this is your ROCKETCHAT_USER_ID

    Copy both immediately. The token is shown only once.

2. Install

From npm (recommended):

npm install -g rocket-chat-mcp

This puts a rocket-chat-mcp binary on your PATH. You can also run it ad-hoc with npx rocket-chat-mcp (no install).

From source:

git clone https://github.com/k1sina/rocket-chat-mcp.git
cd rocket-chat-mcp
npm install
npm run build

3. Configure environment

Copy .env.example to .env and fill in:

ROCKETCHAT_URL=https://chat.example.com   # base URL, no trailing slash, no /api/v1
ROCKETCHAT_USER_ID=...
ROCKETCHAT_AUTH_TOKEN=...
# DEBUG=1                                  # optional: log API metadata to stderr

The server validates these on launch and refuses to start if anything is missing or malformed (URL with trailing /, URL containing /api/v1, missing token, etc.). On a successful start it calls GET /api/v1/me and logs Connected as @<username> to stderr.

Run

Claude Code (CLI)

Register the server with one command:

claude mcp add rocket-chat \
  -e ROCKETCHAT_URL=https://chat.example.com \
  -e ROCKETCHAT_USER_ID=... \
  -e ROCKETCHAT_AUTH_TOKEN=... \
  -- npx -y rocket-chat-mcp

Add -s user to make the server available across all projects (default is project-local). Verify with claude mcp list, then restart your Claude Code session so the tools load.

If you installed from source instead of npm, replace the -- npx -y rocket-chat-mcp part with an absolute path to your built binary:

  -- /absolute/path/to/node /absolute/path/to/rocket-chat-mcp/dist/index.js

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on your platform:

{
  "mcpServers": {
    "rocket-chat": {
      "command": "npx",
      "args": ["-y", "rocket-chat-mcp"],
      "env": {
        "ROCKETCHAT_URL": "https://chat.example.com",
        "ROCKETCHAT_USER_ID": "...",
        "ROCKETCHAT_AUTH_TOKEN": "..."
      }
    }
  }
}

If you installed from source, point command at an absolute Node 20+ binary and args at dist/index.js. Claude Desktop does not source nvm, so something like /Users/you/.nvm/versions/node/v20.19.5/bin/node is typical. Restart Claude Desktop after editing the config.

MCP Inspector (ad-hoc testing)

ROCKETCHAT_URL=https://chat.example.com \
ROCKETCHAT_USER_ID=... \
ROCKETCHAT_AUTH_TOKEN=... \
DEBUG=1 \
npx @modelcontextprotocol/inspector npx -y rocket-chat-mcp

Opens a browser UI where you can invoke each tool with arbitrary arguments.

Direct stdio

ROCKETCHAT_URL=... ROCKETCHAT_USER_ID=... ROCKETCHAT_AUTH_TOKEN=... npx -y rocket-chat-mcp

Tools

All tools take JSON input and return JSON output (wrapped in MCP content[type=text] blocks). Room IDs are opaque strings — never invent them; always look them up via list_my_rooms or list_unread.

list_unread

Rooms with unread messages, mentions, or that you've manually marked unread. Use this for "what needs my attention right now."

| Param | Type | Default | Description | |---|---|---|---| | mentions_only | boolean | false | If true, only return rooms where you have unread @-mentions. |

Output:

{
  rooms: Array<{
    room_id: string;
    room_type: "c" | "p" | "d";   // channel / private group / DM
    name: string;
    unread_count: number;
    mention_count: number;
    manually_marked_unread: boolean; // true when you marked the room unread but no new messages
    last_message_at: string | null;  // ISO 8601
    last_message_preview: string;
  }>;
}

Sorted by last_message_at desc. First call may take a few seconds — see Architecture: caching.

Endpoints called: channels.list.joined, groups.list, im.list, then subscriptions.getOne per room.

list_my_rooms

All rooms you're subscribed to. Use this to look up a room_id by name.

| Param | Type | Default | Description | |---|---|---|---| | type | "channel" \| "private" \| "dm" \| "all" | "all" | Filter by room type. | | limit | integer 1..500 | 100 | Maximum number of rooms to return. |

Output: same shape as list_unread but without unread_count / mention_count (skipping the per-room subscriptions.getOne fan-out makes this cheap). Sorted by last_message_at desc.

Endpoints called: channels.list.joined, groups.list, im.list.

get_room_history

Recent messages in a room. Pass any room_id; type (channel / private / DM) is detected and cached for the session.

| Param | Type | Default | Description | |---|---|---|---| | room_id | string | required | From list_my_rooms / list_unread / get_user. | | limit | integer 1..200 | 50 | Maximum number of messages. | | before | string (ISO 8601) | — | For pagination — pass the ts of the oldest message from the previous page. |

Output:

{
  room_id: string;
  room_type: "c" | "p" | "d";
  messages: Array<{
    id: string;
    ts: string | null;          // ISO 8601
    user: { id: string; username: string; name: string };
    msg: string;
    edited_at: string | null;
    attachments_count: number;
    reactions: Record<string, number>; // emoji -> count
    is_thread: boolean;
    thread_count: number;
  }>;
}

Messages are returned newest-first.

Endpoints called: channels.history / groups.history / im.history (auto-selected by room type), and rooms.info once per session per room to determine the type.

search_messages

Full-text search within a single room. Requires a room_id — there is no global search.

| Param | Type | Default | Description | |---|---|---|---| | room_id | string | required | From list_my_rooms / list_unread. | | query | string | required | Free-text search string. | | limit | integer 1..200 | 30 | Maximum number of results. |

Output: { room_id, query, messages } where messages has the same shape as get_room_history.

Endpoint called: chat.search.

get_user

Look up a Rocket.Chat user by username. Useful for resolving @someone to their full name, role, or status before posting.

| Param | Type | Default | Description | |---|---|---|---| | username | string | required | Username to look up. The leading @ is optional. |

Output:

{
  id: string;
  username: string;
  name: string;
  status: string;             // "online", "offline", "away", "busy", "unknown"
  status_text: string | null;
  email: string | null;       // only when visible to you
  active: boolean;
  roles: string[];
}

Endpoint called: users.info.

post_message

WRITES TO ROCKET.CHAT. Posts a new message. Do not call without explicit user intent.

⚠️ Rocket.Chat does not deduplicate messages. If a call appears to fail, the message may still have been delivered, and retrying could post it twice. If unsure, use get_room_history immediately afterwards to verify before retrying.

| Param | Type | Default | Description | |---|---|---|---| | room_id | string | required | From list_my_rooms / list_unread / get_user. | | text | string (non-whitespace) | required | Message body. | | thread_id | string | — | If set, post as a reply in this thread. Pass the parent message's id. |

Output:

{
  posted: true;
  message_id: string;
  room_id: string;
  thread_id: string | null;
  ts: string | null;
}

Every call also writes a [WRITE] chat.postMessage {...} line to stderr regardless of DEBUG, as a cheap audit trail.

Endpoint called: chat.postMessage.

Architecture

  • Auth. Every request carries X-User-Id and X-Auth-Token headers, read from env vars at request time. No token is ever logged or echoed in error messages.
  • Transport. stdio (@modelcontextprotocol/sdk's StdioServerTransport). The server name is rocket-chat-mcp, version mirrors package.json.
  • Self-test on startup. On launch the server calls GET /api/v1/me and prints Connected as @<username> to stderr, or exits non-zero with a diagnostic if it can't.
  • Room-type cache. Per-session, in-memory map of room_id → "c"|"p"|"d". Primed by list_my_rooms / list_unread, so subsequent get_room_history / post_message calls don't re-query rooms.info.
  • Subscription-map cache. list_unread fans out to subscriptions.getOne per room. The result is cached for 60 seconds so repeat calls in the same session are free.
  • Concurrency. The subscriptions.getOne fan-out is concurrency-limited to 4 in-flight requests to stay within typical Rocket.Chat per-endpoint rate limits.
  • 429 handling. On 429 Too Many Requests, the client parses the "wait X seconds" hint from the response body, sleeps for min(X, 5s), and retries once. After that it gives up gracefully (the affected room is reported with whatever metadata is available; the tool does not error out).
  • Manual "Mark as Unread". Rocket.Chat sets alert: true while leaving unread: 0 for rooms you marked unread without new messages. list_unread includes both signals; rooms in this state are flagged with manually_marked_unread: true.

Errors

All Rocket.Chat-side errors are surfaced as RocketChatApiError with:

{
  status: number;           // HTTP status
  errorType?: string;       // e.g. "totp-required", "error-not-allowed"
  path?: string;            // e.g. "/api/v1/chat.postMessage"
  message: string;          // human-readable, includes URL and Rocket.Chat error text
}

Network-level failures (DNS, TLS, connection reset) come through as plain Error with the original cause chained.

| Symptom | Likely cause | |---|---| | Network error … fetch failed with ENOTFOUND | Wrong hostname or VPN required | | Network error … UNABLE_TO_VERIFY_LEAF_SIGNATURE | Internal CA — set NODE_EXTRA_CA_CERTS=/path/to/ca.pem | | Rocket.Chat 401 … mentioning ROCKETCHAT_USER_ID / ROCKETCHAT_AUTH_TOKEN | Token revoked, expired, or paired with the wrong user id | | totp-required errors | Personal Access Token was created without "Ignore Two Factor Authentication" | | list_unread sometimes empty after recent calls | Rate limit hit and cache expired — wait ~60s | | ROCKETCHAT_URL must not include /api/v1 on startup | Set ROCKETCHAT_URL to the base URL only — the client appends /api/v1 itself |

Contributing

git clone https://github.com/k1sina/rocket-chat-mcp.git
cd rocket-chat-mcp
npm install
npm run dev          # tsx watch
npm test             # mocked-fetch unit tests, no network access required
npm run typecheck    # tsc --noEmit
npm run build        # tsc -> dist/

All write-tool behavior (post_message) is covered exclusively by mocked-fetch tests; no live chat.postMessage is ever invoked from the test suite. PRs and issues welcome at https://github.com/k1sina/rocket-chat-mcp.

License

MIT — see LICENSE.