feat: wire mcpm secrets command + keychain env-placeholder resolver#8
Merged
Conversation
Register the documented-but-unregistered `mcpm secrets` command and connect the existing (built, tested, orphaned) AES-GCM keychain to the guard launch path, so credentials can live encrypted instead of as plaintext in client config files. - store/keychain.ts: add resolveEnvPlaceholders() — decrypts `mcpm:keychain:server/KEY` env values, passes everything else through, throws an actionable error on a missing secret; add listAll() — key names grouped by server, never values. - guard/run-inner.ts: resolve placeholders into the wrapped server's child env before startRelay(). Secrets stay encrypted on disk and exist only in-memory at launch; a missing secret writes a [mcpm-guard] SECRET-MISSING line to stderr and exits non-zero. - commands/secrets.ts: new command (set/list/get/rm) with injected deps for testability. `get` requires --reveal; list/set never print values. - commands/index.ts: register + barrel-export. Read-side (resolver) ships before any write-side change, so no config is ever written with a placeholder nothing can resolve. Tests: 20 new (resolver, listAll, handlers, registration). 1073 pass; coverage 80.85% lines / 87.33% branches. typecheck + build clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the documented-but-unregistered
mcpm secretscommand real, and connects the existing AES-GCM keychain (store/keychain.ts— fully built and tested, but until now with zero non-test importers) to the guard launch path.Today
mcpm installwrites API keys as plaintext into client config files and prints achmod 600warning (install.ts:492-498). This PR lays the foundation to fix that: secrets can be stored encrypted and referenced from a server'senvas amcpm:keychain:server/KEYplaceholder, whichmcpm guardresolves to the real value at launch — so the plaintext never touches disk.This is Phase 1: the read side + the CLI. It deliberately ships the resolver before any change to how install/up write config, so we never write a placeholder that nothing can resolve.
Key design point
mcpm only sits in a server's launch path when guard wraps it. The IDE launches
mcpm guard run --inner …with the config'senvas real environment variables, andrun-inner.tsforwards them to the real child. That forward point is the one clean place to swap placeholder → decrypted secret. So encrypted-secret resolution is a property of guarded servers (coherent with guard being the flagship). A guard-independent launch shim is noted as a possible V2.Changes
store/keychain.ts— addresolveEnvPlaceholders(env)(decryptsmcpm:keychain:…values, passes everything else through, throws an actionable error on a missing secret) andlistAll()(key names grouped by server — never values). Additive; existing exports untouched.guard/run-inner.ts— resolve placeholders into the wrapped server's child env beforestartRelay(). Missing secret →[mcpm-guard] SECRET-MISSINGon stderr + non-zero exit. (One-lineenv:swap + guard clause.)commands/secrets.ts(new) —set/list/get/rm, built on injectedSecretsDepsfor testability.getrequires--reveal;list/setnever print secret values.commands/index.ts— register + barrel-export.Security properties
secrets list/secrets setoutput is asserted (in tests) to never contain secret values.secrets getrefuses to print without an explicit--reveal.secrets.enc.json, mode0600).Test plan
pnpm test— 1073 pass (20 new: resolver,listAll, all four handlers, command registration)pnpm lint(tsc) — cleanpnpm build(tsup) — cleanpnpm test:coverage— 80.85% lines / 87.33% branches (above repo thresholds)HOME):secretsappears in help;listempty-state;getrefuses without--reveal;rm --yesruns end-to-end through bundleddistsetmasked prompt — same@inquirerpassword()already used bymcpm install; not driveable in CI without a TTY (handler logic covered by unit tests with injected prompt)Follow-ups (not in this PR)
install/upto write placeholders forisSecretenv vars, gated on guard being enabled (else keep plaintext + a tip) — no regression.guard disablemust re-materialize or warn on placeholder env values, so disabling guard doesn't brick a server that relies on resolution.