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

mcp-secrets-runner

v0.3.0

Published

Run MCP servers with secrets injected from Infisical, 1Password or HashiCorp Vault. No plaintext API keys in .mcp.json, no login sessions that expire.

Readme

mcp-secrets-runner

A .mcp.json with a live Stripe key in it, next to the same config running through mcp-secrets-runner with no key in the file

CI

Start MCP servers with their secrets injected from a secret manager, so your .mcp.json holds references instead of API keys, and nothing stops working three days later because a login session expired.

Supports Infisical, 1Password and HashiCorp Vault.

The problem

Nearly every MCP setup guide tells you to do this:

{
  "mcpServers": {
    "stripe": {
      "command": "npx",
      "args": ["-y", "@stripe/mcp@latest"],
      "env": { "STRIPE_SECRET_KEY": "sk_live_51J..." }
    }
  }
}

That is a live production key in a config file that people commit, sync between machines, screenshot in tutorials, and paste into bug reports. It is the single biggest reason MCP stalls at the security review in a company.

The obvious fix, wrapping the server in your secret manager's CLI, has its own failure mode. Those CLIs authenticate with a developer session stored in your keyring, and that session expires. When it does, every MCP server wrapped this way stops starting at the same time, with an error that points at the MCP client rather than at the expired login.

What this does

mcp-secrets-runner sits between the MCP client and the server. On startup it authenticates with credentials built for unattended use, ones that do not expire out from under you, fetches the secrets, and execs the real server.

Infisical, 1Password and HashiCorp Vault all feeding into the runner, which then execs your MCP server

{
  "mcpServers": {
    "stripe": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "mcp-secrets-runner", "--env=prod", "--path=/mcp",
               "--", "npx", "-y", "@stripe/mcp@latest"]
    }
  }
}

No secret values in the file. No expiring sessions. The same config works on every machine that has the machine credentials in its environment.

Install

npm install -g mcp-secrets-runner

Or skip the install and let npx fetch it, as in the config above. Requires Node 18+.

Providers

Pick one with --provider=<name>, or set MCP_SECRETS_PROVIDER. Infisical is the default.

Infisical

Authenticates with a machine identity (Universal Auth) instead of the keyring session that infisical login creates. The client secret has no TTL, so a short-lived token is fetched as needed and nothing requires re-logging in.

"args": ["-y", "mcp-secrets-runner", "--env=prod", "--path=/mcp",
         "--", "npx", "-y", "@stripe/mcp@latest"]

| Variable | Purpose | |---|---| | INFISICAL_UA_CLIENT_ID | machine identity client id | | INFISICAL_UA_CLIENT_SECRET | machine identity client secret | | INFISICAL_DOMAIN | instance URL, overrides .infisical.json | | INFISICAL_PROJECT_ID | overrides workspaceId from .infisical.json | | INFISICAL_BIN | path to the infisical binary if it is not on PATH |

Flags before -- are passed to infisical run. Requires the Infisical CLI.

1Password

Delegates to op run, which resolves op://vault/item/field references. Use a service account token for unattended servers; an interactive op session works but expires, with the same all-servers-at-once failure described above.

"args": ["-y", "mcp-secrets-runner", "--provider=1password", "--env-file=./mcp.env",
         "--", "npx", "-y", "@stripe/mcp@latest"]

| Variable | Purpose | |---|---| | OP_SERVICE_ACCOUNT_TOKEN | service account token | | OP_BIN | path to the op binary if it is not on PATH |

Flags before -- are passed to op run. Requires the 1Password CLI.

Note that this provider passes --no-masking by default. op run otherwise rewrites secret values in the child's output to <concealed by 1Password>. A stdio MCP server's stdout carries framed JSON-RPC, so rewriting bytes in it corrupts the stream. Pass --masking to opt back in if your server does not use stdio.

HashiCorp Vault

Reads the secret over Vault's HTTP API and starts the MCP server directly. No Vault CLI is required. Authenticates with AppRole, or with a token you supply.

"args": ["-y", "mcp-secrets-runner", "--provider=vault", "--path=mcp",
         "--", "npx", "-y", "@stripe/mcp@latest"]

| Variable | Purpose | |---|---| | VAULT_ROLE_ID + VAULT_SECRET_ID | AppRole credentials | | VAULT_TOKEN | a token to use directly; skips AppRole | | VAULT_ADDR | instance URL (default http://127.0.0.1:8200) | | VAULT_NAMESPACE | namespace header, for Vault Enterprise / HCP |

| Flag | Purpose | |---|---| | --path=<path> | secret to read (required) | | --mount=<mount> | KV mount (default secret) | | --kv-version=1\|2 | KV engine version (default 2) |

Every key in the secret becomes an environment variable for the MCP server. Structured values are passed through as JSON.

doctor

"My MCP server won't start" is almost always one of four things, and an MCP client reports all four identically. doctor checks them one at a time:

mcp-secrets-runner doctor --provider=vault

doctor output listing the instance, AppRole credentials, cached token and successful authentication, each marked ok

Add --offline to skip the authentication test when you do not want to touch the network.

Usage

mcp-secrets-runner [--provider=<name>] [provider flags...] -- <mcp server command>
mcp-secrets-runner doctor [--provider=<name>] [--offline]

Everything before -- configures the runner; everything after it is the server command, passed through untouched.

Token caching

An MCP client starts every configured server at once. With ten servers, an uncached setup performs ten identical logins in the same second, which is slow for you and noisy for the auth backend. The runner caches the token in the per-user temp directory, keyed by identity and instance, and reuses it until shortly before it expires. In practice this roughly halves startup time.

What is cached is a short-lived token, never the long-lived credential that obtained it, and that credential already sits in the process environment, so the cache is strictly less sensitive than what is available to the same user anyway. The file is written with 0600 permissions, and a cache that cannot be read or written is treated as a miss rather than an error. Pass --no-token-cache to authenticate every time.

Keep the token TTL on your machine identity or AppRole short. The cache honours whatever lifetime the backend returns, so a 30-day token means a 30-day file on disk.

Behaviour worth knowing about

These are deliberate, and each exists because the alternative broke something:

  • Credentials never reach a command line. Infisical tokens are fetched over HTTPS rather than by shelling out to infisical login, which accepts the client secret only as a flag and so publishes it to the process list for the duration of the call.
  • All diagnostics go to stderr. stdout carries the MCP protocol; one stray byte there corrupts the JSON-RPC stream, and the client reports it as an unrelated parse error.
  • It fails closed. If credentials are set but authentication fails, the runner exits instead of falling back. A credential-less infisical run opens an interactive browser login, which hangs the MCP server on stdin forever, and each failed attempt extends the server-side auth lockout.
  • It surfaces auth lockouts explicitly. Infisical locks an auth method after repeated failures (by default 3 attempts, 5 minutes), and every further attempt restarts that window. Retrying in a loop is the worst possible response, so the error says so.
  • It injects --projectId when using Infisical token auth. With a token, the CLI stops reading workspaceId from .infisical.json, and secret fetching dies on Project ID is required. Without credentials it injects nothing, so your config keeps deciding.
  • Vault errors name the likely cause. A 403 is reported as a policy problem, a 404 points at the --mount / --path / --kv-version triple, since a KV v2 read uses a different URL than v1.
  • It resolves executables through PATHEXT on Windows. Node refuses to spawn .cmd shims without shell: true, and shell: true would push your server command through cmd.exe quoting. Resolving the path directly avoids both.
  • Exit codes pass through, and a killed child reports failure rather than a misleading 0.

Adding a provider

A provider exports prepare(flags, command, options) returning { command, args, env }, the child process that will hold the secrets, plus checks() for doctor, and gets listed in src/providers/index.mjs.

The three shipped ones work quite differently: Infisical authenticates over HTTP then hands off to a CLI, 1Password hands off to a CLI that authenticates itself, and Vault does the whole thing over HTTP and starts the server directly. AWS Secrets Manager and Doppler would both fit. PRs welcome.

Development

npm test

The Vault provider has integration tests that run against a stub of Vault's HTTP API in the same process, so the suite needs no external services.

The images above are generated from HTML in assets/. Edit the source and re-run assets/render.sh (needs Chrome) rather than editing the PNGs.

License

MIT