Repository navigation
Major release: the resource URIs changed (see Breaking below), and it includes security hardening from an internal review. Read Changed before upgrading: the vault allow-list now applies server-wide, and item_get hides more field types by default. Tool names and input parameters, prompts, and the Node.js requirement are unchanged.
Security
item_getno longer returns secrets held in non-Concealedfields — OnlyConcealedfields were masked, so SSH private keys, one-time-password (TOTP) seeds, and card numbers came back in plaintext withoutreveal: true. Masking is now deny-by-default: only known non-secret field types (text, URL, email, phone, date, month/year, menu, card type, address, reference) are shown, and every other type, including any added by future SDK versions, needsreveal: true. Notes are still returned as-is, matchingop item get; don't keep secrets in the notes of vaults an agent can read.- Vault allow-list is now enforced server-wide —
OP_MCP_ALLOWED_VAULTS/--allowed-vaultsonly coveredop_runandop_check_ref, and only compared the vault segment as written in anop://reference. It now applies to every tool and resource that touches a vault:vault_listandonepassword://vaultsare filtered,onepassword://vaults/{vaultId}/itemsrefuses vaults outside the list, and vault IDs are checked before anything is returned or modified.op://references are checked both as written and by the vault they actually resolve to. It fails closed if vaults can't be listed. op_runredaction no longer leaks the remainder of overlapping secrets — Redaction now masks every occurrence of every secret in a single pass over the original output, so overlapping or nested secrets can't leak: a username that is a prefix of a credential string is no longer replaced first, leaving the password visible. It also masks common encodings of each secret: base64 (including inside a larger base64 payload such as an HTTP Basic auth header), JSON-escaped and URL-encoded forms, and multi-line secrets line by line and with CRLF line endings.op_runoutput cap no longer exhausts memory — The 5 MiB per-stream cap is now applied while the command runs. Excess output is discarded instead of buffered, which fixes a memory-exhaustion crash, and a secret cut off at the cap never survives as a partial prefix.op_runtimeouts kill the whole process tree — On timeout the process group (POSIX) or process tree viataskkill /T(Windows) is killed, andop_runalways returns shortly afterwards, even if a background process holds the output pipes. Previously it could hang forever and leave processes running with injected secrets.- Case-insensitive credential scrub in
op_run— The server's own credential variables (OP_SERVICE_ACCOUNT_TOKEN,OP_KEYCHAIN_SERVICE,OP_KEYCHAIN_ACCOUNT) are now stripped from the child environment regardless of letter case. - Removed a leftover CI workflow and hardened publishing — The one-time
mcp-v2-migration.ymlis deleted: any GitHub user could trigger it with a PR comment, and it rannpm installwith a write-scoped token.publish.ymlno longer interpolates the release tag into shell, checkouts no longer persist credentials, and publishing is split into two jobs: build, test, and pack run without OIDC access (and without dependency install scripts), and a separate job that alone hasid-token: writepublishes the prebuilt tarball with--ignore-scripts. - macOS Keychain lookup uses an absolute path — The token lookup runs
/usr/bin/securityinstead of resolvingsecuritythroughPATH, so a binary planted earlier onPATHcan't hand the server an attacker-chosen service account token. - Warning when the token is passed on the command line —
--service-account-token/--tokenput the token in the process arguments, which other local processes (including commands run throughop_run) can read. The server now logs a startup warning; preferOP_SERVICE_ACCOUNT_TOKENor, on macOS, the Keychain. - All of the above came out of an internal security review.
Changed
- Breaking: resource URIs use the
onepassword://scheme —1password://config→onepassword://config,1password://vaults→onepassword://vaults, and1password://vaults/{vaultId}/items→onepassword://vaults/{vaultId}/items. Replace any hard-coded1password://URIs. The old URIs could never be read (see Fixed), so this only affects code or configuration that hard-codes them. They were still published identifiers with no possible alias, because the SDK rejects them before the server sees the request; hence the major version. - Behavior change: the vault allow-list is server-wide — If you set
OP_MCP_ALLOWED_VAULTS/--allowed-vaultsexpecting it to affect onlyop_runandop_check_ref, it now restricts every tool and resource. Tools that take avaultIdneed the vault's ID, not its name. With an allow-list set, each guarded call makes one extravaults.listread (mind service account rate limits). It is defense in depth; scope the service account's own vault access in 1Password first. item_gethides more by default — SSH private keys, OTP seeds, and card numbers now show[concealed]unless you passreveal: true.- Stricter
op://validation — Malformed references passed toitem_getandpassword_readare now rejected locally with a clear error. - More candid
op_rundescription — The tool description no longer claims plaintext is "NEVER" returned. Redaction is best effort and protects against accidental disclosure; it is not a sandbox, because a command can deliberately transform or transmit a secret it was given. - Publishing —
prepublishOnlynow only matters for manualnpm publish; the automated workflow publishes a prebuilt tarball. - Documentation — README, CONTRIBUTING, and agents.md cover the new resource URIs, the server-wide allow-list, best-effort redaction, unconcealed notes, and the two-job publish workflow.
buildServer()moved tosrc/server.ts(still re-exported from the entrypoint), so tests can build the real server without starting stdio.
Fixed
- Resources are now readable by MCP clients — Every
resources/readfailed with-32602 Resource URI … is invalid. The SDK parses the URI with WHATWGnew URL()before dispatching, and a URI scheme must start with a letter (RFC 3986 §3.1), so no1password://URI ever reached the server's handlers. - Per-vault items is a real resource template —
onepassword://vaults/{vaultId}/itemsis registered as aResourceTemplate(listed byresources/templates/list) and readsvaultIdfrom the template variables, percent-decoded. It used to be a static resource whose URI was the literal template string, so no concrete vault URI could ever match it.
Added
- End-to-end resource tests — A real MCP client reads the resources from
serveStdio(() => buildServer())over an in-memory transport, in both the 2025 (initialize) and 2026-07-28 protocol eras, so an advertised URI that the SDK cannot parse or route fails CI. They also cover the vault allow-list throughresources/read, including that a percent-encoded vault ID can't bypass it. Adds the@modelcontextprotocol/clientdev dependency (tests only).
Combined in #33, which supersedes #32.
Full Changelog: v4.0.3...v5.0.0