mcpField
B
Bitwarden Public API and Secrets Manager (unofficial)

Bitwarden Public API and Secrets Manager (unofficial)

Local
github.comSecurity0Updated 2d ago

About Bitwarden Public API and Secrets Manager (unofficial)

An unofficial local MCP server for administering Bitwarden organizations and Secrets Manager through typed tools. It supports self-hosted instances and Bitwarden Cloud, with confirmation-gated writes and separate handling for secret metadata and values.

Key features

  1. 01Manage members, groups, collections, and policies
  2. 02Query events and adjust subscription licenses
  3. 03Manage Secrets Manager projects and secrets
  4. 04Connect to self-hosted Bitwarden, cloud, or both
  5. 05Preview writes and require explicit confirmation
  6. 06Return secret metadata separately from values

Use cases

  1. 01Invite members and update group access
  2. 02Review organization events and license seats
  3. 03Manage projects and secrets through the SDK
  4. 04Operate a self-hosted and cloud organization from one host

MCP support

Exposes Bitwarden Public API and Secrets Manager operations as typed read and write tools over stdio. It also provides resource-management actions, with writes returning previews unless explicitly confirmed.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"mguttmann-the-real-bitwarden": {
"command": "npx",
"args": [
"-y",
"the-real-bitwarden-mcp"
],
"env": {
"BITWARDEN_SELFHOST_API_BASE": "<BITWARDEN_SELFHOST_API_BASE>",
"BITWARDEN_SELFHOST_IDENTITY_URL": "<BITWARDEN_SELFHOST_IDENTITY_URL>",
"BITWARDEN_SELFHOST_CLIENT_ID": "<BITWARDEN_SELFHOST_CLIENT_ID>",
"BITWARDEN_SELFHOST_CLIENT_SECRET": "<BITWARDEN_SELFHOST_CLIENT_SECRET>",
"BITWARDEN_CLOUD_API_BASE": "<BITWARDEN_CLOUD_API_BASE>",
"BITWARDEN_CLOUD_IDENTITY_URL": "<BITWARDEN_CLOUD_IDENTITY_URL>",
"BITWARDEN_CLOUD_CLIENT_ID": "<BITWARDEN_CLOUD_CLIENT_ID>",
"BITWARDEN_CLOUD_CLIENT_SECRET": "<BITWARDEN_CLOUD_CLIENT_SECRET>",
"BWS_ACCESS_TOKEN": "<BWS_ACCESS_TOKEN>",
"BWS_ORG_ID": "<BWS_ORG_ID>",
"BWS_API_BASE": "<BWS_API_BASE>",
"BWS_IDENTITY_URL": "<BWS_IDENTITY_URL>",
"BITWARDEN_CONFIRM_WRITES": "<BITWARDEN_CONFIRM_WRITES>",
"BITWARDEN_READ_ONLY": "<BITWARDEN_READ_ONLY>",
"LOG_LEVEL": "<LOG_LEVEL>",
"BW_MCP_MAX_RESPONSE_BYTES": "<BW_MCP_MAX_RESPONSE_BYTES>"
}
}
}
}
Run commandnpx -y the-real-bitwarden-mcp
Environment variables to set
BITWARDEN_SELFHOST_API_BASESelf-hosted instance: API base URL, e.g. https://vault.example.com/api. Configure all four BITWARDEN_SELFHOST_* or all four BITWARDEN_CLOUD_* variables (or both); a partial set is fatal.
BITWARDEN_SELFHOST_IDENTITY_URLSelf-hosted instance: identity URL, e.g. https://vault.example.com/identity.
BITWARDEN_SELFHOST_CLIENT_IDSelf-hosted instance: organization Public API client id (organization.<uuid>).
BITWARDEN_SELFHOST_CLIENT_SECRETSecretSelf-hosted instance: organization Public API client secret. It can manage members, groups, collections and policies, including creating Owners.
BITWARDEN_CLOUD_API_BASEBitwarden Cloud: API base URL, e.g. https://api.bitwarden.com. Used for license and subscription tools.
BITWARDEN_CLOUD_IDENTITY_URLBitwarden Cloud: identity URL, e.g. https://identity.bitwarden.com.
BITWARDEN_CLOUD_CLIENT_IDBitwarden Cloud: organization Public API client id (organization.<uuid>).
BITWARDEN_CLOUD_CLIENT_SECRETSecretBitwarden Cloud: organization Public API client secret.
BWS_ACCESS_TOKENSecretOptional: Secrets Manager machine account access token; enables the bws_* tools. A malformed token is fatal at startup.
BWS_ORG_IDOptional: organization UUID for the Secrets Manager tools, used when a call passes no organizationId.
BWS_API_BASEOptional: Secrets Manager API base URL; defaults to BITWARDEN_SELFHOST_API_BASE.
BWS_IDENTITY_URLOptional: Secrets Manager identity URL; defaults to BITWARDEN_SELFHOST_IDENTITY_URL.
BITWARDEN_CONFIRM_WRITEStrue: write tools return a preview unless the call carries confirm "YES". The model sets confirm itself, so this is friction, not a security boundary.
BITWARDEN_READ_ONLYtrue: write tools stay listed but refuse to run, and bw_raw_request allows GET only.
LOG_LEVELLog level of the JSON log lines on stderr.
BW_MCP_MAX_RESPONSE_BYTESSuccessful tool output above this many bytes is cut to valid JSON with a responseTruncated marker that says how to narrow or page the query.

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
npmthe-real-bitwarden-mcp0.3.1

Tools

No tool list yet

Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.

Details

From the project README

Bitwarden MCP Server

npm version Node.js License: MIT TypeScript CI

Unofficial. This project is not made by, affiliated with or endorsed by Bitwarden Inc. "Bitwarden" is a trademark of Bitwarden Inc. See NOTICE.

A local Model Context Protocol (stdio) server that exposes Bitwarden's Public API (organization management) and the Bitwarden Secrets Manager (projects + secrets via the official SDK) as typed tools, read and write.

Intended use: an admin-facing MCP that lets an AI assistant manage members, groups, collections, policies, license seats, and Secrets-Manager data from a single host. Designed for a self-hosted Bitwarden instance, the official Bitwarden Cloud, or both.

Quick links: Architecture · Contributing · Security policy · Releasing · Changelog · Issues

Maintenance status
  • Maintained on a best-effort basis by one maintainer (@mguttmann) in spare time. The author uses it in production. There is no support contract and no guaranteed response time.
  • Security fixes take priority over features. Report vulnerabilities privately as described in SECURITY.md, never in a public issue.
  • Dependency updates come from Dependabot; CI fails when a runtime dependency has a known advisory.
  • Only the latest minor release gets fixes.
  • Issues and pull requests are welcome; see CONTRIBUTING.md.

Features

  • Public API: members, groups, collections, policies, events, organization import, subscription/licensing.
  • Secrets Manager: projects and secrets via the official @bitwarden/sdk-napi (Zero-Knowledge crypto; cleartext names and values returned directly).
  • Dual-instance aware: runs against a self-hosted Bitwarden instance, the official cloud, or both. Tools are wired to their instance at registration time so routing mistakes are impossible by accident.
  • Hand-written tools for common flows (bw_member_list, bw_license_subscription_get, bws_secret_get_value, ...) plus the bw_raw_request escape hatch. With self-host and cloud configured the server lists 54 tools, with or without Secrets Manager; names and required parameters are pinned by tests/tool-contract.test.ts, and parameters are only ever added (optional), never removed or renamed.
  • No generated tools in the shipped package: the generator for bw_<resource>_<op>_raw tools exists (npm run generate), but the shipped spec (specs/public-api.spec.json) is empty, so 0 generated tools are registered. See Generated raw tools.
  • Confirm-gated writes: every write tool returns a request preview unless invoked with confirm: "YES". Default is on. The model sets confirm itself, so this is friction, not a security boundary (see Safety model).
  • Secret-safe by design: the metadata list never returns values; access tokens are managed in the web vault only.
  • Token-budget-aware list defaults: list tools (bw_member_list, bw_collection_list, bw_group_list, bw_event_query) return slim projections by default to stay within LLM tool-call token budgets. See List-response shape for the opt-in to full nested responses.

What this MCP can and cannot do

Bitwarden's APIs expose different surfaces with different auth and crypto requirements. This MCP wraps everything that's cleanly reachable. Some operations require Bitwarden's encryption layer (so we use the official SDK); others have no public API at all and need the web vault.

Can: Public API (organization and members)

Uses the Organization Public-API key (organization.<uuid> / client_credentials / scope api.organization).

  • List, get, invite, update, remove organization members
  • Create, update, delete groups; manage group membership
  • Update collection access per group (read / hide-passwords / manage); omitted fields of update tools keep their current value
  • Read, set organization policies
  • Query the event log
  • Bulk-import groups + members
  • Read full subscription details (PM seats, SM seats, storage, service-account slots)
  • Adjust PM seats, PM storage, SM seats, SM service-account slots via the unified PUT /public/organization/subscription endpoint: the tools take a delta, send current + delta as the new total and read it back
  • Cloud-only: license inspection on the cloud organization
Can: Secrets Manager (via the official Bitwarden SDK)

Uses the Machine-Account access token (BWS_ACCESS_TOKEN), exchanged through @bitwarden/sdk-napi which handles Bitwarden's Zero-Knowledge encryption.

  • List, get, create, update, delete projects (visible to the configured MA)
  • List secret metadata, fetch full secret incl. cleartext value, create, update, delete secrets
Cannot: these need a human in the web vault

These are not available to the credentials this server uses (organization API key, machine account access token). The SDK and the bws CLI don't offer them either.

  • Create, update, delete Machine Accounts
  • Assign projects to a Machine Account
  • Create or rotate Machine Account Access Tokens
  • List all Machine Accounts in the organization (machine accounts and access tokens can only be managed by a logged-in user in the web vault)
  • Trigger a cloud to self-host license re-sync
  • Read, grant or revoke a member's Secrets Manager access. Bitwarden's Public API neither returns nor applies accessSecretsManager; bw_member_grant_sm / bw_member_revoke_sm stay registered but always return an error.
  • Per-user collection access (bw_collection_update rejects a non-empty users list; give members direct access via bw_member_update collections), group accessAll (removed upstream), and filtering events by IP address.

For all of these: use the web vault at https://<your-host>/#/sm/{orgId}/.

Requirements

  • Node.js 22.12 or newer (engines.node >=22.12; CI tests 22, 24 and 26).
  • A Bitwarden Public-API key (client_id / client_secret) for at least one instance (self-host or cloud).
  • For Secrets Manager: a Machine-Account access token (from the web vault).

Platform support

This MCP works on any platform with Node.js 22.12 or newer for all Public API features (members, groups, collections, policies, events, license adjustments).

The Secrets Manager features (bws_project_*, bws_secret_*) depend on @bitwarden/sdk-napi (1.0.0, an optional dependency, not MIT licensed), which ships native Rust binaries as optional per-platform packages. The lockfile contains @bitwarden/sdk-napi-darwin-arm64, -darwin-x64, -linux-x64-gnu and -win32-x64-msvc:

OS / ArchSDK binarySM tools
macOS Apple Siliconavailableavailable
macOS Intelavailableavailable
Linux x86_64 (glibc)availableavailable
Windows x86_64availableavailable
Linux ARM64 (Raspberry Pi, AWS Graviton)not availablestubbed
Linux Musl / Alpinenot availablestubbed
Othernot availablestubbed

On unsupported platforms the MCP still starts and all Public-API tools work normally. The bws_project_* and bws_secret_* tools stay registered and return "Secrets Manager SDK not available on this platform" at call time; the load error is logged once at startup as a warn line.

If Bitwarden releases an SDK version with binaries for a new platform, updating the dependency and the lockfile picks it up; no code change is needed.

Installation

The package is the-real-bitwarden-mcp on npm, published from GitHub Actions with provenance. Its bin is bitwarden-mcp. Configuration is the same for every path (see Configuration).

Local MCP clients (npx or npm install)

npx fetches and starts the package; pin a version so an update is a decision, not a side effect of a restart:

npx -y the-real-bitwarden-mcp@<version>

Or install it once and start the bin:

npm install -g the-real-bitwarden-mcp@<version>
bitwarden-mcp

See Connector wiring for client configs.

Gateways and servers (pinned, from npm)

Install a pinned version with a lockfile and start it with node, without npx and without install scripts. Once, in an empty directory:

npm init -y
npm install --save-exact the-real-bitwarden-mcp@<version>   # writes package-lock.json

Commit or keep package.json and package-lock.json. On every deploy:

npm ci --omit=dev --ignore-scripts
node --env-file=/path/to/.env node_modules/the-real-bitwarden-mcp/dist/index.js

The package has no install script and no runtime dependency in devDependencies, so --omit=dev --ignore-scripts is a complete install. The Secrets Manager SDK binary is an optional per-platform package that npm selects by OS and CPU; it needs no script either.

From source
git clone https://github.com/mguttmann/the-real-bitwarden-mcp.git
cd the-real-bitwarden-mcp
npm ci               # also pulls the platform-specific SDK binary
cp .env.example .env
# Fill in .env, see "Configuration".
npm run build
node --env-file=.env dist/index.js

The SDK binary is an optional dependency: npm ci succeeds even on unsupported platforms (the binary is simply not present, and SM tools return a clear "not available" message).

Generated raw tools

npm run refresh-spec pulls the Swagger document of your self-host instance into specs/public-api.spec.json, and npm run generate builds one bw_<resource>_<op>_raw tool per operation from it. The shipped spec is empty ({"paths": {}}), so a normal build registers 0 generated tools. Generating tools changes the tool list; if the server runs behind a gateway that pins the tool surface, treat that as a deploy change.

Configuration

All values are environment variables. They come from the process environment (for example node --env-file=.env, or the env block of an MCP client config). When the server is not started with --env-file (or --env-file-if-exists), a .env file in the process working directory is read as well and only fills variables that are not already set; with --env-file it is ignored. At least one instance must be fully configured, otherwise the server refuses to start. A partially configured instance (some but not all four variables) is also fatal, and so is an API base or identity URL that is not an http:// or https:// URL or that has a query or fragment (the error names the variable). Invalid values of the optional numeric and boolean variables fall back to the default.

VariableRequired whenPurpose
BITWARDEN_SELFHOST_API_BASEself-host modehttps://vault.example.com/api
BITWARDEN_SELFHOST_IDENTITY_URLself-host modehttps://vault.example.com/identity
BITWARDEN_SELFHOST_CLIENT_IDself-host modePublic-API client id (organization.{orgId})
BITWARDEN_SELFHOST_CLIENT_SECRETself-host modePublic-API client secret
BITWARDEN_CLOUD_API_BASEcloud modehttps://api.bitwarden.com
BITWARDEN_CLOUD_IDENTITY_URLcloud modehttps://identity.bitwarden.com
BITWARDEN_CLOUD_CLIENT_IDcloud modePublic-API client id (cloud)
BITWARDEN_CLOUD_CLIENT_SECRETcloud modePublic-API client secret (cloud)
BWS_ACCESS_TOKENSM toolsMachine-account access token (3-part format, see below); malformed is fatal
BWS_API_BASESM tools (optional)Defaults to selfhost API base
BWS_IDENTITY_URLSM tools (optional)Defaults to selfhost identity URL
BWS_ORG_IDSM project/secret toolsOrganization UUID, used when organizationId is not passed
BITWARDEN_CONFIRM_WRITESdefault true: write tools return a preview unless confirm: "YES"
BITWARDEN_READ_ONLYdefault false; true: write tools stay listed but refuse to run
LOG_LEVELdebug | info | warn (default) | error
HTTP_TIMEOUT_MSdefault 30000; API and OAuth token requests; one deadline per bws_* call
PAGINATION_MAX_ITEMSdefault 1000
PAGINATION_MAX_PAGESdefault 20
BW_MCP_MAX_LIST_RESPONSE_BYTESdefault 60000; adds a note warning to list responses that exceed this size
BW_MCP_MAX_RESPONSE_BYTESdefault 1048576; successful tool output above this size is cut (see Limits)
Mode matrix
BITWARDEN_SELFHOST_*BITWARDEN_CLOUD_*Result
presentabsentPublic-API operational tools + SM tools registered. License tools not registered. bw_raw_request(instance:"cloud") rejects.
absentpresentOnly license tools + raw cloud requests usable.
presentpresentAll tool groups registered.
absentabsentServer fails to start.
Getting a Secrets Manager access token
  1. In the self-host vault, open Secrets Manager > Machine accounts and create one (a neutral name like mcp-bitwarden works).
  2. Grant the machine account access to the projects you want the MCP to manage.
  3. Create an access token for the machine account and copy the cleartext value into .env as BWS_ACCESS_TOKEN.
  4. Also set BWS_ORG_ID to the organization UUID (from the URL of the organization in the web vault).
  5. Restart the MCP; the bws_* tools become operational.

Until then, the SM tools stay registered but return "Secrets Manager not configured" so the surface remains discoverable.

BWS_ACCESS_TOKEN format

The access token has a three-part format, separated by . and ::

0.{ACCESS_TOKEN_UUID}.{CLIENT_SECRET}:{ENCRYPTION_KEY}

Example (all values fictional):

0.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCCCC==

The MCP passes this token directly to the Bitwarden SDK, which handles all credential parsing and crypto internally.

PartValueUsed by
Segment 10Version prefix
Segment 2{ACCESS_TOKEN_UUID}SDK, as OAuth client_id
Segment 3 (before :){CLIENT_SECRET}SDK, as OAuth client_secret
After :{ENCRYPTION_KEY}SDK, decrypts the payload. Never sent to the server.

The OAuth request to the identity server uses scope=api.secrets (not api.organization). The Org Public-API key (organization.* client_id with api.organization scope) cannot access Secrets Manager endpoints; this is by design, not a bug.

Self-hosted Bitwarden: Secrets Manager availability

Current self-hosted Bitwarden (server 2026.x) bundles the Secrets Manager API inside the standard bitwarden-api container; there is no separate sm-api container and no enable_secrets_manager toggle in bwdata/config.yml. If your docker-compose.yml does not show an sm-api service, that is correct and expected.

The SM endpoints are accessible as soon as you create a machine account with a valid access token in the web vault.

List-response shape

The four list tools (bw_member_list, bw_collection_list, bw_group_list, bw_event_query) return slim projections by default because Bitwarden's Public API embeds the full permission graph in every list item, which inflates the response several times over and overruns LLM tool-call token budgets at modest organization sizes.

Default fields:

ToolDefault fields
bw_member_listid, email, name, type, status, twoFactorEnabled
bw_collection_listid, externalId
bw_group_listid, name, externalId
bw_event_queryDefaults to last 24 h when start/end are omitted

For the full response, pass the include parameter (whitelist of nested fields):

{ "include": ["collections", "groups"] }   // member list with nested data
{ "include": ["all"] }                     // full Bitwarden response

For per-item details, use the dedicated detail endpoints (bw_member_get, bw_collection_get, bw_group_get, bw_member_get_groups, bw_group_get_members).

A response-size guard adds a note field to any list response that exceeds BW_MCP_MAX_LIST_RESPONSE_BYTES characters (default in the table above). The note is a hint only; a response that also exceeds BW_MCP_MAX_RESPONSE_BYTES is cut as described under Limits, and the note stays in the cut response.

Limits

The values live in src/limits.ts; tests/docs-numbers.test.ts holds this section to them.

  • Response size. A successful tool result larger than BW_MCP_MAX_RESPONSE_BYTES (bytes, default 1048576) is cut, never failed. The result stays valid JSON: the leading items of its largest list are kept, together with a responseTruncated object (originalBytes, limitBytes, cutArray, keptItems, totalItems and a hint on how to narrow or page the query). A result whose root is a list becomes an object: the kept items move to items next to responseTruncated. A continuationToken next to a cut list is removed (continuationTokenRemoved: true), because it would resume after the items that were cut. Output without a list (for example a preview) is replaced by the responseTruncated object alone. Error results are not cut here; upstream error texts are shortened separately.
  • Input size. A tool call is rejected before anything is sent when any string in its arguments is longer than 102400 bytes (100 KiB) or any array has more than 10000 items (for example bw_org_import with more than 10000 members or groups). The error names the parameter path and the size, never the value.
  • Token backoff. After 3 consecutive failed token requests of one client (self-host, cloud, Secrets Manager REST, and the Secrets Manager SDK login) new token requests pause for 30 s. Calls during the pause fail at once with identity backoff: ... retry after N s; a success resets the count, and a failure right after a pause starts the next one.

Running

npm start              # production: dist/index.js (stdio)
npm run dev            # development: tsx src/index.ts (stdio)
npm test               # unit tests with mocked HTTP
npm run test:contract  # tool contract and protocol versions only
npm run test:live      # opt-in live smoke tests (read-only, RUN_LIVE_TESTS=1)
npm run inspector      # open the MCP Inspector against the built bundle

Connector wiring

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the platform equivalent. From npm:

{
  "mcpServers": {
    "bitwarden": {
      "command": "npx",
      "args": ["-y", "the-real-bitwarden-mcp@<version>"],
      "env": {
        "BITWARDEN_SELFHOST_API_BASE": "https://vault.example.com/api",
        "BITWARDEN_SELFHOST_IDENTITY_URL": "https://vault.example.com/identity",
        "BITWARDEN_SELFHOST_CLIENT_ID": "organization.<uuid>",
        "BITWARDEN_SELFHOST_CLIENT_SECRET": "<client secret>"
      }
    }
  }
}

From a source checkout:

{
  "mcpServers": {
    "bitwarden": {
      "command": "node",
      "args": [
        "--env-file=/absolute/path/to/the-real-bitwarden-mcp/.env",
        "/absolute/path/to/the-real-bitwarden-mcp/dist/index.js"
      ]
    }
  }
}

Instead of --env-file, the variables can also go into an env object next to args. Without either, the server only finds a .env in the working directory the client starts it in, which usually is not the repository, and exits with "No Bitwarden instance configured".

Claude Code

Register the server with claude mcp add (user or project scope), or add the same mcpServers block to a .mcp.json file at the project root:

claude mcp add bitwarden -- node --env-file=/absolute/path/to/.env /absolute/path/to/dist/index.js
Gateway and headless use

The server speaks MCP over stdio only and needs no terminal. A gateway or supervisor installs the pinned npm package (see Gateways and servers) and starts it with node --env-file=/path/to/.env node_modules/the-real-bitwarden-mcp/dist/index.js, or builds a source checkout:

npm ci --no-audit --no-fund && npm run build
node --env-file=/path/to/.env /path/to/dist/index.js
  • Logs are JSON lines on stderr (pino, level LOG_LEVEL); stdout carries only the MCP stream.
  • Protocol revisions: clients that open with initialize for 2025-06-18 or 2025-11-25 get that revision; clients that open with the 2026-07-28 revision (server/discover) are served at 2026-07-28. Pinned by tests/protocol-versions.test.ts.
  • Message size limit: the MCP SDK's stdio transport accepts incoming lines (one JSON-RPC message each) of at most 10 MiB. A larger line closes the transport; the server logs an error line and exits with code 1, and the client has to start it again.
  • Tool names are stable. Tool names, parameter names and required parameters do not change and no tool or parameter is removed; a tool may get a new optional parameter. Changes that a caller can notice are listed under "Behaviour changes" in the changelog. scripts/container-smoke.sh builds and probes the server the way a gateway does (see CONTRIBUTING).
  • No user separation: every caller acts with the full rights of the configured keys. Restrict which tools each user may call in the gateway (see SECURITY.md).

Tool reference

Run bw_help from inside the MCP to get a grouped, always-current catalogue. A short list:

Public API: operational (self-host)
  • bw_member_list, bw_member_get, bw_member_invite, bw_member_update, bw_member_remove, bw_member_reinvite, bw_member_get_groups, bw_member_set_groups, bw_member_grant_sm, bw_member_revoke_sm (the last two always return an error, see "Cannot")
  • bw_group_list, bw_group_get, bw_group_create, bw_group_update, bw_group_delete, bw_group_get_members, bw_group_set_members
  • bw_collection_list, bw_collection_get, bw_collection_update, bw_collection_delete
  • bw_policy_list, bw_policy_get, bw_policy_set
  • bw_event_query
  • bw_org_import

Optional parameters worth knowing:

  • bw_member_invite: permissions sets the Custom role flags of a type 4 member (accessEventLogs, manageUsers, ...; flags not given are false).
  • bw_member_update: email and name change a claimed member's address and name; the API rejects them for other members. Omitted: unchanged.
  • bw_org_import: inviteUsersAfterProvisioning: false creates new members as Staged without an invitation email; a member with deleted: true needs only externalId.
  • bw_policy_set: metadata is passed with the save (for example defaultUserCollectionName for policy type 5); it is not stored with the policy.
  • bw_event_query: continuationToken resumes a truncated result; pass the token it returned with the same start, end, filters and limit. limit returns at most that many events (an integer of at least 1, capped at PAGINATION_MAX_ITEMS); when more exist the result is truncated: true with a continuationToken, also when the stop falls inside an API page. bw_member_list, bw_group_list and bw_collection_list take no token: the Public API returns those lists in one response.
  • truncated: true means the server stopped at PAGINATION_MAX_ITEMS or PAGINATION_MAX_PAGES while the API still offered a next page. If the last page ended exactly at the limit, that next page can turn out empty: the API sends a token without saying whether more items follow, and the server does not spend an extra request to find out. Resuming with the returned token then gives an empty list.
Public API: generated raw tools (self-host)

None in the shipped package (the spec is empty, see Generated raw tools). src/tools/generated/snapshot.json lists the set produced from the current spec.

License / subscription (cloud)
  • bw_license_subscription_get
  • bw_license_seats_update: adjusts Password Manager seats (passwordManager.seats)
  • bw_license_storage_update: adjusts Password Manager storage (passwordManager.storage)
  • bw_license_sm_seats_update: adjusts Secrets Manager seats (secretsManager.seats)
  • bw_license_sm_service_accounts_update: adjusts Secrets Manager service-account slots (secretsManager.serviceAccounts)

All four take a delta, send current + delta as the new absolute total through PUT /public/organization/subscription, read the subscription back and return an error if the total did not change.

Secrets Manager: SDK-backed (require BWS_ACCESS_TOKEN and a supported platform)
  • Projects: bws_project_list, bws_project_get, bws_project_create, bws_project_update, bws_project_delete
  • Secrets: bws_secret_list (identifiers only), bws_secret_get_value, bws_secret_create, bws_secret_update, bws_secret_delete

Without organizationId in the call, every bws_project_* / bws_secret_* tool that needs one uses BWS_ORG_ID. bws_secret_update keeps the current note and project when note or projectIds is omitted (it reads the secret first); pass them to change them. A machine account cannot remove all projects from a secret: the server rejects an update with projectIds: [] for a secret that is in a project.

The SDK logs in with BWS_ACCESS_TOKEN on the first bws_* call, not at startup. One HTTP_TIMEOUT_MS deadline covers a whole bws_* call, including that login and, for bws_secret_update without note or projectIds, the read of the current secret. A login that runs into the deadline keeps running and the next call waits for it instead of starting a second one. A timed-out write may or may not have been applied.

Secrets Manager: web vault only (always stubbed)
  • Machine accounts: bws_machine_account_list, ..._get, ..._create, ..._update, ..._delete, ..._projects, ..._grant_project, ..._revoke_project
  • Access tokens: bws_access_token_list, ..._create, ..._revoke

They are not available to the credentials this server uses; only a logged-in user in the web vault can manage machine accounts and access tokens. The tools are discoverable via tools/list and bw_help, but calling them always returns isError: true with a link to the web vault. Use https://<your-host>/#/sm/{orgId}/machine-accounts to manage them.

Universal
  • bw_raw_request: escape hatch with instance parameter (selfhost, cloud, or sm). Use this for any Public API or SM REST call not covered by the hand-written tools. Note: SM bws_* CRUD goes through the SDK (not raw REST); raw_request with instance: "sm" still works for read operations but cannot create/update projects or secrets because the API requires encrypted names/values.
  • bw_help: grouped catalogue of registered tools.

Safety model

  • BITWARDEN_CONFIRM_WRITES=true (default): write tools return a preview unless invoked with confirm: "YES". This is a self-confirmation by the model, not a security boundary: the model decides to send confirm: "YES", also on the first call, and text in Bitwarden data can steer it. Read-only tools, including bws_secret_get_value, are never gated. Limit what the configured keys and the machine account may do, and which tools each user may call.
  • BITWARDEN_READ_ONLY=true (default false): write tools keep their names and schemas but refuse to run, also with confirm: "YES", and bw_raw_request allows GET only. Unlike the confirm gate, the model cannot switch this off.
  • Both switches accept true/false, 1/0, yes/no and on/off in any case. An empty or whitespace-only BITWARDEN_READ_ONLY counts as unset (off); any other unrecognised value turns read-only mode on, and an unrecognised BITWARDEN_CONFIRM_WRITES keeps the confirm gate on.
  • The organization API key acts as a system user, not as a member, so Bitwarden applies no role hierarchy to it: bw_member_invite and bw_member_update can create or promote Owners. Use read-only mode or gateway rules where callers must not do that.
  • The server sends MCP instructions telling the model to treat Bitwarden data as data, never as instructions. This is a hint to the model, not a control.
  • The bws_secret_list tool returns secret identifiers only (no value field). Cleartext values are only returned through bws_secret_get_value(id), one secret at a time: bws_secret_create / bws_secret_update return the metadata without the value, and confirm-gate previews show secret values as <redacted, N chars>.
  • Logs and error texts returned to the client go through one scrubber (src/redact.ts): the keys listed there (bearer tokens, client secrets, access tokens, value, ...) are removed at any depth, and secret-shaped substrings in messages are replaced with [REDACTED].
  • Redirects are never followed; bw_raw_request paths and ids with dot segments or encoded separators are rejected.
  • The bw_raw_request escape hatch defaults to instance: "selfhost" and rejects calls against instances that are not configured.
  • The {ENCRYPTION_KEY} portion of BWS_ACCESS_TOKEN is passed to the SDK and never sent to any server as a credential.

Troubleshooting

OAuth client_credentials returns invalid_client

The client_id must be segment 2 of the BWS_ACCESS_TOKEN (the UUID), and client_secret must be segment 3 (between the second . and the :). Do not use the full raw token or the encryption key as either field.

Fastest diagnosis: run docker logs bitwarden-identity on the server host immediately after a failed auth attempt. Duende IdentityServer logs exactly which client_id was attempted and whether the client was found.

Quick smoke test (replace placeholders with real values):

# Step 1: obtain a Secrets Manager JWT
curl -sS -X POST "https://vault.example.com/identity/connect/token" \
    -d grant_type=client_credentials \
    -d "client_id={ACCESS_TOKEN_UUID}" \
    -d "client_secret={CLIENT_SECRET}" \
    -d scope=api.secrets
# Expected: HTTP 200, JSON with access_token, token_type, expires_in, scope="api.secrets"

# Step 2: list projects (replace JWT and ORG_ID)
curl -sS "https://vault.example.com/api/organizations/{ORG_ID}/projects" \
    -H "Authorization: Bearer {JWT}"
# Expected: HTTP 200, JSON with a "data" array (empty if no projects assigned)
bws_* tools return "SDK not available on this platform"

The @bitwarden/sdk-napi native binary is not available for the current platform (see the Platform support table above). The startup warn log line on stderr has the reason in smSdk.kind (module_not_found or binary_not_available).

A failed login (wrong token, identity server unreachable) gives a different tool error, "Secrets Manager SDK login failed: ..." or "... login timed out ...". Fix the token or URLs; the next bws_* call logs in again (or waits for a login that is still running), no restart needed.

On unsupported platforms, all Public-API tools (bw_*) continue to work normally. Only the bws_project_* and bws_secret_* tools are affected.

POST to /api/organizations/.../projects returns 400 "Name is not a valid encrypted string"

Do not use bw_raw_request to create or update projects/secrets. Bitwarden's SM API requires encrypted names and values at the REST layer. The bws_* wrapper tools go through the official SDK which handles encryption transparently. Use bws_project_create, bws_secret_create, etc. instead.

Empty project / secret lists

If bws_project_list or bws_secret_list return empty arrays, the machine account authenticated successfully but has no project assignments. This is a permission issue, not an auth bug.

To fix: go to the web vault → /#/sm/{ORG_ID}/machine-accounts/{SERVICE_ACCOUNT_ID}/projects and assign the desired projects to the machine account.

The bws_project_list tool surfaces this hint inline when it detects an empty response.

Self-host auth debugging

Run docker logs bitwarden-identity on the server host after a failed auth. Duende IdentityServer logs the client_id attempted and the exact failure reason (client not found vs. secret mismatch).

License

MIT, see LICENSE. NOTICE covers what the MIT License does not: the trademark, the optional @bitwarden/sdk-napi dependency (Bitwarden SDK License Agreement, installed from npm, not bundled) and the API specification.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.