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

@finchatbot/opus-mcp

v0.4.0

Published

MCP server exposing tenant-scoped tools over the FCB OPUS API

Readme

@finchatbot/opus-mcp

MCP (Model Context Protocol) server that gives an MCP client — Claude Desktop, Claude Code, Managed Agents — tenant-scoped access to the FCB OPUS API.

With it, Claude can list and configure chatbots, stage and publish widget styling, promote a chatbot's config between environments, trigger n8n project deploys, assign n8n credentials, and provision clients — all pinned to a single tenant it cannot change.

Requires Node 18 or newer. Nothing is installed globally; npx fetches it on demand.

Quick start

Add a server to your MCP client's config, one per OPUS environment:

"opus-dev": {
  "command": "npx",
  "args": ["-y", "@finchatbot/opus-mcp"],
  "env": {
    "OPUS_API_URL": "https://dashboard-api-dev.finchatbot.com",
    "OPUS_API_KEY": "fcb_…",
    "OPUS_TENANT_ID": "…"
  }
}

Restart the client afterwards — the tool list is read once at startup, so a new version's tools stay invisible until you do.

Two things that account for most setup failures:

  • Claude Desktop does not expand ${VARS}. A "${OPUS_TENANT_ID}" reaches the server as that literal string and fails with 400 Tenant not found. Use literal values.
  • Keys are per-tenant and per-environment. A dev key against the QA URL is a 401, not a permissions problem.

Name the server after the environment it points at (opus-dev, opus-qa, opus-uat, opus-prod). With a tenant key, switching tenants means a different server entry with a different key — never a tool argument. A master key is the one exception; see Master keys below.

Environment variables

| Variable | Required | Description | | ---------------- | -------- | ------------------------------------------------------------ | | OPUS_API_URL | yes | Base URL of the OPUS API for one environment | | OPUS_API_KEY | yes | OPUS API key, fcb_ followed by 64 hex characters | | OPUS_TENANT_ID | tenant keys only | Tenant UUID this server instance is scoped to |

OPUS_API_URL and OPUS_API_KEY are always required. OPUS_TENANT_ID is required for a tenant (fcb_) key and must be omitted for a master (fcbm_) key. The server exits at startup if a required one is missing, which the client reports as Server disconnected.

Mint keys at Admin → Claude (MCP) in the OPUS dashboard. Keep real values in your client's own config or your shell — never in a committed file.

Tenant safety model

The tenant is fixed at startup from OPUS_TENANT_ID and sent as x-tenant-id on every upstream request. No tool accepts a tenant argument — inputs are parsed with a strict schema, so a smuggled tenantId is dropped before any call is made.

That is the client-side half. The server side is what actually holds: the OPUS API resolves the key within the tenant named in the header, so a key that does not exist in that tenant fails authentication. Pointing this server at another tenant's UUID does not grant access to it.

Master keys

An FCB master super user can mint a master key (fcbm_…) at Admin → Claude (MCP) with no tenant selected. It reaches every tenant and carries every permission scope, including creating new clients.

Configure it by leaving OPUS_TENANT_ID out entirely. The server then runs in cross-tenant mode, which changes two things:

  • Every tenant-scoped tool takes an extra tenantId argument naming the tenant to act on. Omitting it on a tool that needs one is an error, not a default.
  • A list_tenants tool appears, so Claude can discover the ids to pass.

The tenant-key guarantees above are unchanged: a session started with a fcb_ key stays pinned, has no tenantId argument on any tool, and drops one that is smuggled in. Prefer a tenant key whenever one will do — a master key is a production credential with no blast radius limit.

Because the package is public, treat the tool list and endpoint paths below as visible. Nothing here grants access on its own — every call needs a scoped API key.

Tools

Chatbots — read

| Tool | Does | Scope | | --- | --- | --- | | list_chatbots | Lists chatbots as summaries (id, name, domain, status, webhook, timestamps). includeArchived adds archived ones; verbose returns whole records. | chatbots:read | | get_chatbot | One chatbot in full, including any pending style draft. | chatbots:read | | get_chatbot_stats | Aggregate stats for the tenant. | chatbots:read | | get_chatbot_webhook_recipe | How to wire an n8n workflow to this chatbot: the exact signed-callback URL, the reply payload shape, the signing rules, and an importable five-node workflow. | chatbots:read |

Chatbots — create and configure

| Tool | Does | Scope | | --- | --- | --- | | create_chatbot | Creates a chatbot. Only name and domain are required; the domain is private by default. | chatbots:write | | update_chatbot_config | Content and behaviour — welcome message, initial prompt, language, feedback and sound toggles. | chatbots:write | | update_chatbot_identity | Identity and lifecycle — name, webhook URL, active/archived state. Confirmation-gated; refuses non-HTTPS webhooks. | chatbots:write | | import_chatbot_config | Promotes a chatbot's config from the environment upstream of this one. Confirmation-gated. | chatbots:write |

Styling — staged, so nothing reaches the live widget until you publish.

| Tool | Does | Scope | | --- | --- | --- | | update_chatbot_style | Stages theme, colours, fonts, bubbles, buttons, images, position and animation into a preview draft. Repeated calls merge. | chatbots:write | | publish_chatbot_style | Applies the staged draft to the live config and clears it. Fails if no draft is pending. | chatbots:write | | discard_chatbot_style | Drops the draft, leaving the live config untouched. | chatbots:write |

Media

| Tool | Does | Scope | | --- | --- | --- | | upload_media | Uploads a local file (≤10 MB) and returns a URL for avatars, logos and banners. | media:write | | list_media | Lists uploaded media, with category, page and limit. | media:read |

Deployments and n8n

| Tool | Does | Scope | | --- | --- | --- | | list_deployments | Lists deployments, optionally filtered by status. | deployments:read | | deploy_n8n_project | Deploys an n8n project to the next environment. Checks promotability first. Confirmation-gated. | deployments:trigger | | assign_n8n_credential | Assigns an n8n credential to one of your tenant's projects. | integrations:write |

Clients (tenants)

| Tool | Does | Scope | | --- | --- | --- | | create_tenant | Provisions a client — a dedicated database, its credentials, and a migration run. country and serverLocation are required. Confirmation-gated, and there is no undo. | tenants:write | | update_tenant | Corrects a client's details. Cannot activate or deactivate a tenant. Confirmation-gated. | tenants:write |

Confirmation-gated tools

create_tenant, update_tenant, deploy_n8n_project, import_chatbot_config and update_chatbot_identity each require a confirm argument echoing the exact target — a business name, a tenant id, a project id. Claude has to state what it is about to act on before it can act, so an ambiguous instruction stops rather than guesses.

Note what create_tenant really does: it provisions infrastructure, and OPUS has no delete-tenant endpoint. A mistake there is not undoable from Claude.

A typical styling pass

upload_media → take the returned URL → update_chatbot_style (stages a draft) → open the chat app with ?preview=1 to watch it render → then publish_chatbot_style to go live, or discard_chatbot_style to revert.

Linking a chatbot to an n8n workflow

Call get_chatbot_webhook_recipe first. It returns the wiring filled in for that specific chatbot, which is where hand-built workflows go wrong — the callback URL embeds the chatbot's domain and id, and those differ per environment.

The shape it returns, proven end to end:

Chat Webhook ──┬── Ack                      (respond immediately)
               └── Build Payload ── Sign Payload ── Reply to OPUS

Three rules make or break it:

  • OPUS ignores what the webhook returns. The reply is a separate POST to /webhooks/chatbots/{domain}/{chatbotId}/messages.
  • Serialize the reply exactly once, sign that string, and send that same string as a raw body. The signature is checked against the raw bytes received, so re-serializing fails even when the JSON is equivalent.
  • The workflow must be active, or its webhook path 404s and the link looks correct while doing nothing.

The HMAC secret lives in an n8n credential (Crypto Signing Key Opus) and never passes through this server.

Request behaviour

  • Errors carry the upstream body. A failure reads as OPUS API 403 on POST /tenants — {"message":"missing required scope: tenants:write"}, so the actionable part is visible. The body is capped at 500 characters and the API key is redacted from it.
  • Every request has a deadline — 30s, or 120s for upload_media. A hung API surfaces as OPUS API timed out after 30000ms on GET /chatbots instead of an open-ended wait.
  • Results are capped at 25,000 characters and say so when truncated, rather than cutting off silently.

Audit trail

Every request this server makes carries two headers so the API can attribute it:

  • x-mcp-version — the client and its version, e.g. @finchatbot/[email protected]
  • x-mcp-tool — the tool that caused the request, e.g. update_chatbot_identity

The tool header matters because several tools share a route: update_chatbot_identity and update_chatbot_config are both PATCH /chatbots/:id, and without attribution the trail cannot tell a reworded greeting from a retargeted webhook.

Both are hints for tracing only. They are client-supplied, so the API records them and never uses them for access decisions.

The API stores one metadata row per API-key request — tool, route, status, duration and the acting key. Request and response bodies are never recorded, so no audit row can carry customer content.

Scopes

Beyond the chatbot and media scopes above, three unlock specific tools: tenants:write for client provisioning, deployments:trigger for n8n deploys, and integrations:write for credential assignment. A missing one surfaces as 403 … missing required scope: <name>, which is the quickest way to see what a key lacks.

Grant only what the work needs. A key used for day-to-day chatbot configuration has no reason to carry tenants:write.

Changelog

0.2.0 — Errors now include the upstream response body, redacted and capped, so a missing scope names itself instead of surfacing as a bare 403. Every request carries a timeout (30s, 120s for uploads), so a hung API reports as one rather than waiting forever. list_chatbots returns summaries by default (verbose: true for full records) and every result is capped at 25,000 characters with an explicit truncation notice. Requests now carry x-mcp-tool and x-mcp-version so the API can attribute each one in its audit trail. The reported server version tracks the package version.

0.1.0 — Initial release.


For the FinChatBot team

Everything below needs a checkout of the monorepo and is not relevant to using the published package.

Generating a client config. Prefer this to hand-writing JSON — it never prints a real credential, so its output is safe to paste into a ticket:

npm run mcp:config                                              # opus-dev
npm run mcp:config -- opus-qa https://dashboard-api-qa.finchatbot.com
npm run mcp:config -- --local                                   # this checkout

Use --local only when developing this server itself — it emits an absolute path to your working tree, which will not exist on anyone else's machine.

Standing up a new client end to end — tenant → chatbot → n8n project → signed workflow → test — is covered by the Playbook in this package's directory in the monorepo, and by the Claude (MCP) Playbook page in the OPUS dashboard under Experimental → Docs. Both are internal.

Develop and test:

npm install
npm test          # jest unit suite
npm start         # run over stdio (requires env vars)

Protocol-level debugging without a Claude client:

npx @modelcontextprotocol/inspector npx tsx src/index.ts

The repo-root .mcp.json registers this server as opus-local for Claude Code; restart the session after code changes to pick them up. stdout carries the MCP protocol, so any diagnostics must go to stderr.

Publishing. prepublishOnly builds and runs the tests, so a broken build cannot ship.

npm version patch                 # or minor / major — versions are immutable
npm publish --otp=123456          # code from your authenticator
  • Bump first. A published version can never be reused, even after npm unpublish. Publishing without a bump fails with 403 cannot publish over previously published version.
  • --otp is not optional. The account runs 2FA at auth-and-writes, and a web-login session does not prompt — it fails with a bare 403 … Two-factor authentication … is required. Codes expire in ~30s, so a stale one looks identical to no code at all.
  • Ship from a clean tree. prepublishOnly builds your working files, so uncommitted changes go out in the tarball.
  • The README on npm only updates when you publish. Editing it in the repo changes nothing on the package page until the next release.

For CI, use a granular access token scoped to @finchatbot/* with Bypass 2FA, stored as a CI secret. Don't weaken the account's 2FA setting to avoid the prompt.

Keep the @finchatbot/ scope. An unscoped opus-mcp is unclaimed on the public registry — anyone could publish to that name, and it would run with OPUS_API_KEY in its environment.