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

@besales/mcp

v0.47.1

Published

Model Context Protocol server for Animaly / Besales

Readme

@besales/mcp

Model Context Protocol server for Animaly / Besales.

Status: public npm package candidate.

Install

Requirements:

  • Node.js 20+
  • BeSales account with an active workspace
  • Claude Desktop, Claude Code, Codex CLI, or another MCP-capable host

No global install is required. Production users can run the package with npx:

npx -y @besales/mcp connect
npx -y @besales/mcp status
npx -y @besales/mcp

connect opens the browser consent flow and stores an MCP API key plus the selected workspace in the local keychain. With one connection or an explicit session pin, workspace-level tools resolve it automatically. With several unpinned connections they refuse the call as ambiguous; inspect besales_workspace_current and select one deliberately.

For production, do not set BESALES_API_BASE_URL or BESALES_OAUTH_AUTHORIZE_URL. They default to:

  • BESALES_API_BASE_URL → https://core.besales.ai/api/v2 (backend API host — used for MCP tool calls)
  • BESALES_OAUTH_AUTHORIZE_URL → https://app.besales.ai/settings/mcp/consent (frontend bridge page — opened in the browser during connect; it mints a one-time setup ticket and forwards to the backend consent page. After approval connect receives a one-time code on the loopback callback and exchanges it with the PKCE code_verifier at /api/v2/auth/mcp-token — the API key never travels in a URL)

For staging or local development override them per command:

BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
BESALES_OAUTH_AUTHORIZE_URL=http://localhost:5173/settings/mcp/consent \
  npx -y @besales/mcp connect

Configure Your MCP Host

Step-by-step guide in Russian (Claude Code, Codex, several workspaces, updating): docs/connect.ru.md. Release order for maintainers: RELEASING.md.

After connect, add the server to your MCP host and restart the host.

Claude Desktop config:

{
  "mcpServers": {
    "besales": {
      "command": "npx",
      "args": ["-y", "@besales/mcp"]
    }
  }
}

Claude Code:

claude mcp add --scope user besales -- npx -y @besales/mcp

Codex CLI:

codex mcp add besales -- npx -y @besales/mcp

Equivalent Codex config:

[mcp_servers.besales]
command = "npx"
args = ["-y", "@besales/mcp"]

For staging/local backends, include both backend and frontend bridge URLs in the host env. For example:

[mcp_servers.besales.env]
BESALES_API_BASE_URL = "http://localhost:3000/api/v2"
BESALES_OAUTH_AUTHORIZE_URL = "http://localhost:5173/settings/mcp/consent"

Multiple Workspaces

besales-mcp can hold keys for several workspaces at once — even across different accounts. Connect each one once (switch the account/workspace in the browser before each connect); keys are cached in your OS keychain and switching afterwards needs no browser.

npx -y @besales/mcp connect          # workspace A (browser logged into account A)
npx -y @besales/mcp connect          # workspace B (re-login in the browser first)
npx -y @besales/mcp connections      # list all connections, marks the active one

connect stores the workspace name and a masked account label such as o***@example.com; the raw email is never stored in the MCP index. These labels are refreshed by a lightweight scoped endpoint (at most once per day) and never require a full workspace overview. besales_workspace_current reports current, cached, stale, or unknown plus a safe refresh reason. Reconnect also refreshes a renamed workspace; legacy connections migrate on first read.

Bind a host session to a specific workspace with BESALES_WORKSPACE_ID in that server's env block — the cleanest "one session = one workspace" setup (one project/profile per workspace):

{
  "mcpServers": {
    "besales": {
      "command": "npx",
      "args": ["-y", "@besales/mcp"],
      "env": { "BESALES_WORKSPACE_ID": "<workspace-uuid>" }
    }
  }
}
[mcp_servers.besales.env]
BESALES_WORKSPACE_ID = "<workspace-uuid>"

Workspace selection precedence, per tool call:

  1. BESALES_WORKSPACE_ID (env) — authoritative; an explicit, mismatching workspace_id argument is refused.
  2. an explicit workspace_id argument (only when the env var is not set).
  3. the session binding set by the besales_workspace_use tool (in-memory, this session/process only — see below).
  4. the only connected workspace.
  5. otherwise the call is refused — several workspaces are connected and none was chosen.

The global active workspace is not an implicit fallback. It is set on the very first connect and never revisited, so using it silently would send calls to whichever workspace you happened to connect first — returning another workspace's data with no error at all. When several workspaces are connected and nothing above selects one, the call fails closed; the error names the current active so you can pass it explicitly if that is what you meant. With exactly one connection nothing changes. besales_workspace_current reports source: "ambiguous" and workspace_id: null in the same situation, so the diagnostic never claims a binding the real call would not use.

BESALES_WORKSPACE_ID always overrides everything. besales_workspace_current lists names, safe account labels, UUIDs, the selection source, and label freshness. The assistant shows those names to the user and passes the chosen UUID itself.

GUI hosts without per-session env (e.g. Claude Desktop)

When the host has no place to set BESALES_WORKSPACE_ID per session (you just click "new session"), there are two ways to keep several workspaces apart.

Recommended — one pinned named server per workspace (survives reconnects). Run once per workspace:

yarn install:claude-desktop --name besales-acme   --workspace <uuid-A>
yarn install:claude-desktop --name besales-globex --workspace <uuid-B>

This adds env-pinned mcpServers entries (with a timestamped config backup); restart Claude Desktop. Their tools appear namespaced — mcp__besales-acme__besales_* (always workspace A) and mcp__besales-globex__besales_* (always B). Because the pin lives in the host config, it survives MCP reconnects and never silently drifts to the wrong workspace. Both toolsets are visible in every session; you just address the one you mean.

Quick / in-session — besales_workspace_use (does NOT survive a reconnect). Ask the assistant to call besales_workspace_use <workspaceId> to bind the current session in-memory. Each host session runs its own besales-mcp process, so it is per-session and touches neither the global active nor other sessions. Caveat: the binding is in-memory only — when the host respawns the server (reconnect, which GUI hosts do often), it is lost and falls back to the global active. Use it for a stable stretch, not as a durable lock; for that use the pinned named servers above. besales_workspace_current shows the current binding

  • source and lists connected workspaces; it is refused under a BESALES_WORKSPACE_ID pin (env wins).
  • BESALES_WORKSPACE_ID is read once at process start — exporting it after a session is already open has no effect. Restart the session to re-pin via env.

First Smoke

Start a fresh host session after changing MCP config, then ask a natural business request:

Use besales MCP to create an ICP for SaaS B2B sales automation.

The assistant should discover and call the relevant besales_* tools itself. The user can ask in ordinary business language. If several workspaces are connected without a pin, the assistant must show the choices from besales_workspace_current; the user does not need to invent an ID.

Expected quick checks:

  1. npx -y @besales/mcp status shows connected credentials.
  2. The host shows the besales MCP server as connected.
  3. Direct tools such as besales_icp_create work.
  4. External flows use *_get_instructions, execute stages locally in the host, then call the matching *_submit tool.

If the host does not expose MCP resources to the model, ask the same request in ordinary words. The model can call besales_help_search and then besales_help_get; both tools read the same packaged documents as besales://... resources. besales_help_get returns a compact heading index by default, one section with section, or the complete document with full=true. See docs/help-navigation.md.

Hosts with MCP prompts may also show five optional task entries: configure an agent, improve an agent, audit dialogues, build a campaign report, and diagnose a failure. They expand to the same ordinary instructions and tool navigation; a host without prompts loses no operation.

To switch workspace, connect each one once (see Multiple Workspaces) and either set BESALES_WORKSPACE_ID per host session or run npx -y @besales/mcp use <workspaceId>. No browser re-auth is needed once a workspace is connected.

If a call asks for workspace_id, first run besales_workspace_current: the cause may be a real multi-workspace ambiguity, an env pin conflict, a missing key, or an old schema. Restart the host with @besales/mcp@latest only when the schema/version is actually stale; reconnect only when the credential or granted scopes are the problem.

besales_workspace_current reports effective_scopes from the exact selected credential and marks their source. It never unions permissions from other MCP keys in the same workspace.

Development

yarn install
yarn types:gen:api
yarn types:gen:mcp
yarn build

The generated files in src/types/ and src/schemas/ are committed because the source contracts live outside this independent repository.

Run a lightweight local mock API for MCP Inspector tool-call smoke tests:

Terminal 1:

yarn dev:mock

Terminal 2:

yarn dev:mock:seed
BESALES_API_BASE_URL=http://127.0.0.1:3100/api/v2 node bin/besales-mcp.js

yarn dev:mock:seed and yarn dev:mock:clear use the same keytar entries as real besales-mcp connect credentials. They can overwrite or clear a local real connection for the current macOS user.

The mock server only verifies request wiring. Live tool E2E depends on the matching ai-aniomaly /api/v2/* endpoints. Remove seeded mock credentials after smoke testing:

yarn dev:mock:clear

CLI

node bin/besales-mcp.js connect              # connect (or refresh) a workspace
node bin/besales-mcp.js connections          # list connected workspaces (* = active)
node bin/besales-mcp.js use <workspaceId>    # set the active (default) workspace
node bin/besales-mcp.js status               # show the active workspace
node bin/besales-mcp.js disconnect           # disconnect the active workspace only
node bin/besales-mcp.js disconnect <id>      # disconnect one workspace
node bin/besales-mcp.js disconnect --all     # disconnect every workspace
node bin/besales-mcp.js                       # start the MCP server

BESALES_API_BASE_URL defaults to https://core.besales.ai/api/v2 (backend). BESALES_OAUTH_AUTHORIZE_URL defaults to https://app.besales.ai/settings/mcp/consent (frontend bridge). For local development point them at your local backend and frontend respectively.

BESALES_MCP_RESULT_ENVELOPE=on turns on the response size ceiling: oversized answers are shaped or refused instead of flooding the caller's context. It is off by default for one release because the package is normally run unpinned, so a change in response semantics would land mid-session. See docs/response-size-budget.md for the measured numbers and the per-tool classes.

BESALES_MCP_TOOLSETS limits the session to named toolsets, comma separated — readonly (every tool that only reads), or a domain group derived from the tool name (knowledge, crm, agent, prompt, …). Unset or empty means the full surface: a setting nobody filled in must never narrow access silently. The filter applies to both tools/list and tools/call, and an unknown name fails loudly with the list of valid ones. See docs/tool-surface.md for why the surface gets profiles rather than fewer, larger tools.

Tool calls require stored credentials. Run node bin/besales-mcp.js connect against a backend that implements the MCP OAuth endpoints (/api/v2/auth/mcp-connect* + /api/v2/auth/mcp-token), or run yarn dev:mock:seed for mock-only smoke tests.

For production users, do not set BESALES_API_BASE_URL or BESALES_OAUTH_AUTHORIZE_URL. Run:

npx -y @besales/mcp connect

The browser consent flow stores the connected workspace key in the local keychain. Workspace-level tools default to the session's workspace, so users should not provide workspace_id manually. To work with several workspaces, connect each once and pin sessions via BESALES_WORKSPACE_ID (or switch the global default with besales-mcp use <id>) — see Multiple Workspaces.

Available tools (253 tools by category)

Итог и категории строятся из активных имён src/schemas/mcp-tools.json. Тест сверяет этот блок, summary.tools_by_category и сумму категорий с tools.length.

| Category | Tools | Name prefixes | |---|---:|---| | Workspace, help, tasks and audit | 18 | workspace_*, help_*, task_*, audit_* | | Agents, behavior and routing | 14 | agent_*, behavior_*, facts_*, pipeline_*, routing_*, router_* | | Prompts, sandbox and evaluation | 33 | prompt_*, qa_*, sandbox_*, simulation_*, test_*, model_* | | ICP | 9 | icp_* | | Knowledge, workbooks, files and variables | 50 | knowledge_*, workbook_*, feedback_*, variable_*, file_* | | CRM and data sources | 27 | crm_*, datasource_* | | Channels and external API credentials | 14 | platform_*, external_* | | Dialogues and contacts | 16 | dialogue_*, dialogues_*, dialog_*, contact_* | | Triggers | 5 | trigger_* | | Outbound, follow-up and analytics | 67 | broadcast_*, getcourse_*, wazzup_*, followup_*, scheduled_*, tracked_*, link_*, inbound_*, funnel_*, ai_*, custom_* |

Recent capabilities:

  • Intent navigation — besales_workspace_navigate combines the requested task, bot type, selected objects and live workspace overview into one next tool call, required user input and a result check. It does not perform the recommended mutation.
  • Complete async lifecycle — every background start has a read-only status tool; see docs/async-operation-lifecycle.md. Contact imports and a single cold-welcome launch use besales_getcourse_contact_import_status; anchor refresh uses besales_getcourse_anchor_refresh_status and reports a lost worker as STALLED.
  • Funnel test outside the web UI — besales_followup_funnel_test_plan freezes expanded texts and dynamic generations under one runId; start sends exactly that snapshot to the live contact and requires both explicit_confirmation=true and confirmation_reason. Status/cancel wrap the durable 15-second-gap run. All four tools require the dedicated mcp:funnel-test scope, which is not covered by mcp:*.
  • Prompt lint diagnostics — a rejected besales_prompt_patch_commit now carries the backend's lintIssues (up to 3, e.g. the exact character count over the 80000 hard limit) instead of a bare 400. besales_prompt_headroom answers the same question before a draft is built; besales_prompt_diff compares two agents' prompts without pulling both full texts into context.
  • Fleet prompt rollout — besales_prompt_batch_start applies one set of patch operations across many agents as a background run (33 agents × 3 calls do not fit the 60s client timeout). Every agent is prepared and fully linted first; only those that pass are committed. Poll besales_prompt_batch_status; roll forward the failures with besales_prompt_batch_resume — never by starting a second run.
  • OAuth PKCE provisioning — besales_platform_create_oauth_init opens the authorization flow in the browser for Instagram/Avito so the OAuth code never passes through the LLM.
  • CRM connection via browser-secrets — besales_crm_create_init provisions AmoCRM/Bitrix24 (and GetCourse/Telegram) using the Setup URL pattern, where the user pastes credentials in the browser instead of through the chat.
  • External API credential handoff — channel create/key rotation/secret rotation return revealUrl and its expiry. The signed-in user opens that page to receive credentials once; keys and webhook secrets never enter MCP content or host logs.
  • Setup URL pattern — *_init returns a one-time URL; the user completes sensitive input in the browser and the host polls *_status until ready.
  • External execution pairs — schemas returned by *_get_instructions use canonical camelCase fields. Matching *_submit tools accept those fields directly and retain legacy snake_case aliases, then send one canonical camelCase body to the API.
  • File upload — two-step flow (besales_file_upload_request → besales_file_upload_status) so binaries are uploaded directly, not via MCP.
  • Platform provisioning & clone — create channels and clone an existing platform configuration (besales_platform_clone_preview / _execute).
  • Knowledge ingestion — full namespace/document/Q&A/website/table CRUD plus workbook ingestion from xlsx and Google Sheets. besales_workbook_inspect inventories sheets and returns a bounded sample; besales_workbook_sheet_read pages through late rows and columns with source identity and a page fingerprint. Metadata/classification and archival are available through dedicated workbook tools. Namespace/table reindex returns a job ID; poll the matching _reindex_status tool to a terminal state instead of reading worker logs.
  • GetCourse wave-run batches — queue 1–10 status-mode segments sequentially through besales_getcourse_wave_run_batch_start; use one _batch_status poll, reuse a generated batch_key on retry, and resume only after resolving two consecutive rejected segments. UNKNOWN и небезопасный FAILED жёстко блокируются между волнами; repeat mode не является обходом неопределённого исхода. Контакты import/run/batch принимают email, phone, first_name, last_name и передают их backend-у без потери snake_case→camelCase.
  • Broadcast mailing variants — besales_broadcast_mailing_variant_list/create/update/delete; архивирование через delete или update(archived=true) требует explicit_confirmation=true управляют workspace-каталогом «Вариантов рассылки». Передавай выбранный UUID как mailing_variant_id в direct, durable или batch GetCourse wave; analytics принимает тот же фильтр. Удаление использованного варианта архивирует его и сохраняет историю когорт.
  • GetCourse school bindings — besales_platform_settings_update принимает getcourse_settings.wave_field_bindings: роли launch_type/product/segment/funnel/channels связываются с numeric custom-field ID сделки; product_value_source выбирает код или название продукта, а null очищает настройку школы.

Resources

The package exposes 38 MCP resources. The dialogue-audit workflow is:

  • besales://workflows/audit-dialogues

The seven base concept resources are:

  • besales://concepts/icp
  • besales://concepts/triggers
  • besales://concepts/sandbox
  • besales://concepts/handoff
  • besales://concepts/external-execution
  • besales://concepts/feedback-sheets
  • besales://concepts/workbook-classification

Inspect them locally:

yarn dlx @modelcontextprotocol/inspector node bin/besales-mcp.js

The Inspector should show 253 tools, 38 resources, and 5 optional prompts.

Codex Dialogue Audit Skill

Install the packaged read-only audit skill on Anton's machine:

npx -y @besales/mcp install-skill besales-dialogue-audit

Restart Codex after installation. To update an existing copy:

npx -y @besales/mcp install-skill besales-dialogue-audit --force

The skill uses only besales_dialogues_query and besales_dialogue_transcript, follows besales://workflows/audit-dialogues, and never mutates agents or CRM data.

Claude Desktop

For local development before the package is published, install a Claude Desktop config entry with:

yarn install:claude-desktop

The installer creates a timestamped backup of the existing config and writes only the mcpServers.besales entry. It uses the absolute Node executable path because GUI apps may not inherit the shell PATH.

Host Setup

Host-specific local setup and smoke steps for MCP Inspector, Claude Desktop, Claude Code, and Codex CLI are documented in docs/host-setup.md.

Scope

  • Claude Desktop / Claude Code / Codex call this package through MCP.
  • This package calls Animaly only through ai-aniomaly /api/v2/*.
  • Direct calls to prompt-services are intentionally out of scope.

The package exposes 253 active MCP tools from src/schemas/mcp-tools.json.

Token scopes

besales-mcp connect requests one canonical set, declared as MCP_OAUTH_SCOPES in src/auth/oauth-client.ts:

| Scope | Covers | |---|---| | mcp:* | composite base — reads, PromptHub, ICP, sandbox, knowledge, dialogue export, analytics aggregates | | mcp:platform-setup | setup mutations: knowledge ingestion, variables, CRM pipelines, platform settings/provisioning, clone, the Platform Setup Bridge, waves | | mcp:funnel-test | frozen follow-up funnel test against a live person | | read:campaigns | cold-welcome preview, broadcast reads and their message texts | | launch:campaigns | cold-welcome and broadcast launches | | read:contacts | contact search, cards, and safe diagnostics | | write:operators | CAS-protected workspace-operator assignment; no message send or CRM mutation |

None of these is implied by another: a plain mcp:* key authorizes neither setup mutations nor campaigns, and mcp:platform-setup does not imply the right to send to real people. Existing connections are never widened silently — after upgrading, reconnect once to be granted a newly added scope.

⚠️ Invariant — the scope set only ever GROWS. A new scope is appended to MCP_OAUTH_SCOPES; existing entries are never removed, renamed or reordered. Replacing an entry silently narrows every key issued from then on, and connect gives no sign of it: consent opens, the key is issued, tools load, and the loss only surfaces much later as a 403 from one endpoint. That is exactly how read:campaigns + launch:campaigns were lost when mcp:funnel-test was added (2026-08-19) — the set used to be one flat string, and the string was rewritten. The same rule applies to the backend's MCP_OPTIONAL_CONNECT_SCOPES, the consent-page allowlist OPTIONAL_OAUTH_SCOPES, and the scope enum in the OpenAPI contract: add a line, never swap one.

connect prints the granted set, and warns on stderr when the new key carries fewer permissions than the one it replaces:

Connected to workspace Animaly [de86534d-…] (key: mcp_a1b2…, account: o***@besales.ai).
Scopes: mcp:* mcp:platform-setup mcp:funnel-test read:campaigns launch:campaigns read:contacts write:operators
Active workspace: de86534d-….

WARNING: this key has FEWER permissions than the one it replaced.
  Lost: read:campaigns, launch:campaigns
  Tools needing them will fail with 403 "API key does not have required scopes".
  Update the client (npx -y @besales/mcp@latest) and run `besales-mcp connect` again.

The comparison is made by the backend (dropped_scopes in the /auth/mcp-token response) against the newest live MCP key of that workspace — the client cannot read an old key's scopes on its own.