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

@qatobit/cms-mcp

v0.1.1

Published

MCP server over the Qatobit CMS API. Lets Claude read, write, publish and schedule content without touching the repo.

Readme

Qatobit CMS — MCP server

An MCP server that lets Claude read, write, publish and schedule Qatobit content.

It is a client of the CMS HTTP API, not of the repository. It holds a scoped API key and goes through /api/cms/v1/* exactly as the admin UI does, so every write it makes passes the same key scope check, Zod validation, brand-voice check and optimistic lock. Importing lib/cms directly would have been shorter and would have created a second write path that skips all of them.


What it exposes

Resource

| URI | What | |---|---| | qatobit://brand/design-tokens | The palette, type scale, radii, shadows, layout rhythm and copy rules. Attached to the conversation so a colour is looked up rather than invented. |

Tools

| Tool | Scope needed | Notes | |---|---|---| | list_content | content:read | Page and filter one type. Two cursor kinds, and they do not mix. | | get_content | content:read | One item in full, including the version an update needs. | | search_content | content:read | All three types at once. Metadata only, never body text. | | list_types | content:read | Probes the live API; a type that is not wired up says so. | | get_schema | content:read | The JSON Schema the API actually validates against. | | create_content | content:write | Creates a draft. Never creates something live. | | update_content | content:write | Needs the version. A 409 comes back with the current one. | | bulk_import | content:write | Dry run first, always. The confirm token pins the reviewed payload. | | upload_image | media:write | Returns permanent URLs immediately; derivatives follow. | | delete_image | media:write | Retracts an upload. Idempotent. Says whether the bytes actually went. | | publish | publish | Live within about a minute. Returns the public URL. | | schedule | publish | Book a publish, or cancel one. UTC. | | unpublish | publish | Take down now, or book an expiry. |

publish is a separate scope from content:write on purpose. A key can be issued that stages content it cannot put in front of readers, and that gap is where review happens. A 403 from publish while create_content works is the design, not a misconfiguration.


Install

Nothing to install for normal use: the server is published as @qatobit/cms-mcp and npx fetches it on first run. Node 20 or newer is the only requirement, and a machine that has never cloned this repository can run it.

To work on the server itself, build from source:

cd mcp
npm install
npm run build

Configure

| Variable | Required | Default | Notes | |---|---|---|---| | QATOBIT_CMS_KEY | yes | — | qb_live_…. The server refuses to start without it. | | QATOBIT_CMS_URL | no | https://qatobit.com | An origin or a full API base; both work. | | QATOBIT_CMS_TIMEOUT_MS | no | 30000 | Clamped to 1s–180s. |

Create a local key with the scopes you want it to have:

npm run cms:key -- --label claude-mcp --scopes content:read,content:write,media:write,publish

The plaintext is printed once and is not recoverable. Give the key the narrowest set of scopes the work needs. A key without publish is the useful default while you are still reviewing what Claude writes.

Register it

Claude Code — .mcp.json at a project root

{
  "mcpServers": {
    "qatobit-cms": {
      "command": "npx",
      "args": ["-y", "@qatobit/[email protected]"],
      "env": {
        "QATOBIT_CMS_URL": "https://qatobit.com",
        "QATOBIT_CMS_KEY": "${QATOBIT_CMS_KEY}"
      }
    }
  }
}

.mcp.json is checked in and shared, so keep the key out of it: Claude Code expands ${VAR} from your shell environment, which leaves the file safe to commit. Put the literal key in only if the file is ignored.

Working against the local dev stack, set QATOBIT_CMS_URL to http://localhost:8946 instead.

Claude Desktop — claude_desktop_config.json

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json

Same block. Claude Desktop has no project environment to expand ${VAR} from, so the literal key goes in the file; treat that file as a secret.

{
  "mcpServers": {
    "qatobit-cms": {
      "command": "npx",
      "args": ["-y", "@qatobit/[email protected]"],
      "env": {
        "QATOBIT_CMS_URL": "https://qatobit.com",
        "QATOBIT_CMS_KEY": "qb_live_replace_me"
      }
    }
  }
}

From source, for working on the server itself

Build first (npm --prefix mcp install && npm --prefix mcp run build), then point at the build output instead of the package:

{
  "command": "node",
  "args": ["/absolute/path/to/mcp/dist/index.js"]
}

Or skip the build with npx tsx /absolute/path/to/mcp/src/index.ts.

Publishing a new version

Bump version in mcp/package.json, npm run build, then npm publish from mcp/ (needs npm 2FA and membership of the qatobit org).

Then bump the pin, and only then. The documented configs name an exact version, which now lives in one place: MCP_PACKAGE in app/admin/_lib/mcp-package.ts, which both the Keys page and the connect guide read. Move it after the publish succeeds, never before, or the admin page hands out an install that resolves to nothing. The two JSON blocks above are prose and have to be edited by hand.

Operators do not pick up a new version until that pin moves, which means a fix to mcp/src is not live for anybody until it is published. delete_image was written, merged and documented while every installed copy carried on without it, because 0.1.0 was published the day before and nothing republished it.

That is deliberate, and it is the point. This used to say npx -y "picks up the new version on its next cold run", which describes the problem rather than a feature: every machine running this server hands it a qb_live_ key with write and publish scopes, so an unpinned install means whatever the registry serves next executes with a credential that can change the live site. One compromised npm account, or one mistaken publish, and it runs everywhere on the next cold start with nobody having agreed to it (2026-07-30 audit, M-9 / H-1).

An extra step at release time is the right price for an upgrade being a decision.


Verify

npm run smoke

Spawns the server as a client would and speaks raw JSON-RPC to it: initialize, tools/list, resources/list, and a read of the design tokens. It checks all twelve tools are present and that the tokens carry the brand orange. Nothing here needs the CMS to be running.

To exercise a tool against a live CMS:

QATOBIT_CMS_URL=http://localhost:8946 \
QATOBIT_CMS_KEY=qb_live_… \
node scripts/smoke.mjs --call list_types

node scripts/smoke.mjs --call get_content '{"type":"glossary","slug":"apr-vs-apy-staking"}'

Against the local stack, start it first: npm run cms:up && npm run cms:setup in the repository root, then npm run cms:dev for the API on port 8946.


How to drive it

  1. search_content — is this already covered?
  2. Read qatobit://brand/design-tokens — the voice rules are enforced server-side and a write that breaks a lexical one comes back 422 with the offending span and its character offsets.
  3. get_schema — the field list for the type.
  4. create_content, then publish or schedule.

Updating an existing item needs its version, from get_content. Two writers is the normal case here, so a write without one would be last-write-wins wearing the clothes of a safe update. If the item moved underneath you the API answers 409 with the current version; re-read, merge, retry.

A genuine brand-voice false positive, such as an em-dash inside a quoted source, can be waived with the voice_override argument. The reason is recorded on the item and shown in the review queue, so it is a documented exception rather than a silent bypass.

Notes

  • All times are UTC. Marketing thinks in IST (+05:30, no daylight saving); the conversion belongs wherever a human types a date.
  • A published page appears within about a minute, and a scheduled one within about two. Time-based revalidation is the only refresh mechanism Amplify supports, so this is a property of the hosting, not of the CMS.
  • Everything the server prints goes to stderr. stdout carries the protocol.