Repository navigation
@muhaven/mcp mcp-v0.2.1
Changelog
All notable changes to @muhaven/mcp will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
Unreleased
[0.2.0] — 2026-05-18
Minor bump signals a breaking change to position.buy's input
shape. This release consolidates the pre-Codex review of the 0.1.7
Path C bundle: 4 parallel agents (Code Reviewer, Frontend Developer,
Security Engineer, Reality Checker) surfaced ≥5 cross-confirmed HIGH
findings and several MEDIUM/LOW items. 0.2.0 lands all of them.
Breaking
-
muhaven.position.buy.amountUsdc6→amountUsdc(human decimal).
Pre-0.2.0, the field was base-6 integer string ("5000000" = $5).
An LLM hearing "buy 5 dollars" would naively emit"5"and silently
produce a URL withamount=0.000005— user buys $5e-6 instead of $5.
0.2.0 unifies the unit convention across the whole Path C surface
(matchescash.wrap.amountUsdcshape):"5"means 5 mhUSDC. Max
6 fractional digits, 48-char length cap. Server-side schema rejects
scientific notation, leading +, thousands separators, leading zeros,
bare leading/trailing dots. -
muhaven.position.sell.amountSharesrejects fractional input.
fhERC-20 shares are integer base units (memory:
project_decimals_lie_wave4_p0). Pre-0.2.0 accepted "2.5" which
silently floored to 2 on the on-chain submit. 0.2.0 schema regex
^[1-9]\d*$rejects any fractional or leading-zero input at the
MCP-server boundary. -
Position tool response shape stays the
{ dashboardUrl, action, instructions, echo }format from 0.1.7 — no change here, but the
schema breaking changes above are what trigger the minor bump per
semver.
Removed (cleanup of 0.1.7 deprecation candidates)
__resetSessionKeyProbeCacheForTests— was retained as a no-op in
0.1.7 for "back-compat with downstream test consumers." Verified
empirically (Security Engineer review) that no consumer outside our
own tests imported it. Deleted entirely.formatUsdc6ToDecimal+computeIntentHash+
PLACEHOLDER_INTENT_DOMAIN— orphaned helpers tied to the
pre-0.1.7 attestation path. No external consumers; deleted.
Fixed
-
MUHAVEN_DASHBOARD_URLenv-poisoning (Security M-2): the URL
is used to build every position deep-link. Pre-0.2.0, a malicious
npm dep or attacker with write access to~/.claude.jsoncould
setMUHAVEN_DASHBOARD_URL=https://muhaven-app.comand have the
MCP server route every user click to a typosquat phishing clone.
Validation now happens at boot inloadMcpConfig+loadBrokerConfig
using the samehttps-or-loopbackrule as the existing
--dashboard-base-urlCLI flag. Hard-fails at server start with a
clear error message if invalid. -
buildRegisterEnvshell-metachar sanitization (Security M-1):
the JSON config blob passed via argv toclaude mcp add-jsoncould
carry a craftedMUHAVEN_KEYRING='file" & calc.exe &"'past
Windows'sshell: trueinvocation, reachingcmd.exe's parser as
a command-injection vector. 0.2.0 rejects (not escapes) any value
containing shell metacharacters ("\newline&|;
`<>()%$) and restrictsMUHAVEN_KEYRINGto
the recognized values (file/os). Rejected values surface as
warnings on stderr; setup continues with the cleaned env. -
claude mcp removeexit-code capture (Code Reviewer H2 +
Security M-3): pre-0.2.0 swallowed every exit code from the
idempotent remove step. A perm-locked scope or stale lockfile that
failed remove + then failed add showed an opaque error attributing
the failure only to add. 0.2.0 captures remove's exit + stderr,
swallows only the expected "no such server" pattern, and surfaces
any anomaly as a warning on the success path or folds it into the
failure reason on the error path. Closes the split-brain
~/.claude.jsonoperator-confusion class. -
decimalUsdcAmountSchema.max(48)length cap (Security L-5):
defense-in-depth against URL bloat (LLM emits a 10MB digit string).
Sibling commits (same hotfix bundle, deployed in lockstep)
- Backend
pg-portfolio.repository.findByUserIdnow orders by
last_synced_at DESC NULLS LASTso the frontend's first-seen Map
dedup picks the freshest row — aligns the tiebreak across backend +
frontend + dedup script (Code Reviewer N2). backend/scripts/dedup-portfolios.tsmoved the discovery SELECT
inside the dedup transaction + addedSELECT FOR UPDATErow lockspg_advisory_xact_lock. Closes the TOCTOU window where a
concurrent backend write could lose data (Security H-1).
- Frontend TradePage / YieldsPage now render an inline AlertTriangle
banner when?token=doesn't resolve instead of silently falling
back tomarketplace.filtered[0]. Closes the LLM-token-swap footgun
(Frontend H-1). - TradePage marketplace.load() failure surfaces a Retry CTA instead
of half-rendering (Frontend H-2). - TradePage / CashPage
?amount=pre-fill uses a shared
sanitizePrefillAmounthelper that bounds precision to 6 dp and
rejects fractional shares (Frontend H-4 / Code Reviewer L1). - YieldsPage scroll-into-view moved into a watch keyed on
epochsStore.items.lengthso deep-link landings on disconnected
wallets still scroll once the items render (Frontend H-3). - YieldsPage
selectedTokendeclaration hoisted above onMounted so
the deep-link path sets the ref synchronously before the
selectableTokenswatcher (immediate: true) snaps it tolist[0]
(Frontend H-5).
Internal
__tests__/position-deeplink.test.ts: rewrote amount tests for the
decimal-string schema; added schema-level corpus tests pinning the
reject list (negative, scientific notation, leading +, thousands
separator, leading zeros, etc.); added a regression test for the
base-6 footgun (position.buy({amountUsdc: '5'})must produce
amount=5, NOT0.000005).__tests__/mcp-redteam.test.ts: updated field name fromamountUsdc6
toamountUsdcin 4 call sites.- Total tool count unchanged at 23.
[0.1.7] — 2026-05-18
position.* tools can now drive real on-chain action via @muhaven/mcp
— Path C of MCP Option A (dashboard URL elicitation → existing passkey
ceremony). Pre-0.1.7, position tools returned a placeholder UserOp
envelope + broker signature that no host could submit; the path was
attestation-only despite implying buy/sell/claim. 0.1.7 swaps the
envelope for a pre-filled dashboard deep-link URL the user opens to
review + tap their passkey through the existing dashboard flow.
Added
-
muhaven.cash.wrap— new tool. Returns a/cash?amount=deep-
link for USDC → mhUSDC conversion. Common LLM chain:read.portfolio
→ notice 0 mhUSDC →cash.wrap→ thenposition.buy(each is its
own user-confirmed deep-link). Input is human-readable USDC ("100" =
$100). 23 tools total now (was 22). -
Token identifier accepts symbols OR addresses. Every
position.*
tool'stokenfield used to require a 0x-address. Now accepts either
a symbol ("TBILL1") or a 0x-address. The dashboard pages resolve the
symbol via the marketplace store; unknown identifiers leave the form
blank for the user to fill in. Saves the LLM a round-trip through
read.tokensfor the common "buy 5 of TBILL1" flow. -
Exported pure helpers (
buildPositionDeeplink,
formatUsdc6ToDecimal) so third-party MCP servers + tests can reason
about the URL shape without spawning anything.
Changed
-
position.buy/sell/claimreturn shape is now{ dashboardUrl, action, instructions, echo }instead of{ intentHash, unsignedUserOp, brokerSignature, signerAddress }. Theinstructions
field is a pre-formatted two-line string the LLM can show the user
verbatim ("Open this link to review and authorize..."). Theecho
field mirrors input for LLM self-verification. Breaking for any
consumer that pinned the 0.1.6 response shape — the prior shape was
itself never end-to-end usable (placeholder envelope), so the
practical impact is "MCP buy now actually works" rather than
regression. -
position.rebalancereturnsnot_implementedwith a clear
next-step hint pointing at single-legposition.buy/position.sell
or the dashboard. Multi-legexecute_plan(one URL, one passkey,
one batched UserOp) lands in Wave 5 with composite preview UI. -
Broker dep no longer required for position tools. Previously, a
position.buycall without a runningmuhaven-brokerdaemon
returnedbroker.unavailable. Now position tools talk only to the
dashboard URL — the broker is still needed forread.*/ governance
/ issuer / policy tools (those use the JWT-authed path).
Removed
signEnvelope+PositionEnvelopeData+ the per-process
hasSessionKeyprobe cache +__resetSessionKeyProbeCacheForTests's
cache (the function is retained as a no-op for back-compat with any
test harness importing it). The whole broker-attestation path for
position tools is gone — they don't need a signing key at all.
Internal
- 21 new vitest cases in
__tests__/position-deeplink.test.ts,
replacingsession-key-required.test.ts(deleted — covered a
removed code path). Total@muhaven/mcpsuite: 262/262 passing
(was 241). Tool-hash count: 23 (was 22).
Operator notes
- Once installed, the fresh-install ritual is unchanged:
muhaven-broker setup --register claude-codestill wires the MCP server into Claude
Code viaclaude mcp add-json. - Existing 0.1.6 installs:
npm install -g @muhaven/mcp@latestpicks up
the new bin; no setup re-run needed. MUHAVEN_DASHBOARD_URLenv var (defaults tohttps://muhaven.app)
now drives the deep-link URL prefix; staging operators set it to
https://muhaven-staging.exampleand the URLs flow through.
[0.1.6] — 2026-05-17
Adds muhaven-broker setup --register HOST so a fresh install no longer
requires hand-writing a .mcp.json (or equivalent host-config file).
This closes the last manual step of the install ritual — operators run
one command end-to-end from npm install -g @muhaven/mcp to a working
MCP server registered with their host.
Initial host coverage: Claude Code (via claude mcp add-json).
claude-desktop and cursor are reserved as known host names — they
parse cleanly today but the registrar declines to act and points the
operator at the per-host JSON snippet in docs.muhaven.app/mcp/install.
Both ship in a Wave 5 follow-up (file-edit registrars need merge-then-
write semantics + dedicated tests).
Added
-
muhaven-broker setup --register HOST[,HOST...]flag — auto-wire
the MCP server into one or more host configs after the login step:- claude-code (live): probes
claude --version, removes any
existingmuhavenentry (idempotent), then runs
claude mcp add-json muhaven '{"type":"stdio","command":"muhaven-mcp","env":{...}}' --scope <scope>.
envcarriesMUHAVEN_BACKEND_URL,MUHAVEN_DASHBOARD_URL, and
MUHAVEN_KEYRINGwhen set (the broker session key + endpoint stay
daemon-only — never baked into the host config). - claude-desktop / cursor: reserved names. Parse cleanly; registrar
short-circuits with a "not implemented yet" hint pointing at the
docs snippet. Adding a host is a focused diff: implement the
registrar + extendKNOWN_REGISTER_HOSTS. - Accepts comma-separated values (
--register claude-code,cursor) and
repeated flags (--register claude-code --register cursor). Dedupes
across both forms. - Unknown host names fail fast with exit code 2 + the allowlist in the
error message.
- claude-code (live): probes
-
--register-scope user|project|localflag — scope for the
claude mcp add-jsoncall. Defaultuser(every project on this
machine sees the server — matches the per-user broker model);
projectwrites.mcp.jsonat CWD (git-shared if you commit it);
localwrites~/.claude.jsonas a per-project user-only entry
(Claude Code'sclaude mcp adddefault). -
Pure helpers exported from
src/broker/setup.tsfor testing +
third-party reuse:buildRegisterEnv,buildClaudeMcpRegisterJson,
buildClaudeMcpAddJsonArgv,buildClaudeMcpRemoveArgv,
registerWithHost. Plus type exports forRegisterHost,
RegisterScope,RegisterHostOutcome,ShellResult, and the
KNOWN_REGISTER_HOSTS+KNOWN_REGISTER_SCOPESconstants. -
SetupDeps.shellOutseam — abstracts child-process execution so
tests can script the host-CLI responses without spawning real
binaries. Default implementation incli.tsusesnode:child_process
spawn(argv-safe — no shell interpolation of the JSON payload).
Operator UX
-
Setup's exit code is 0 on register failure. The broker daemon and
JWT (the load-bearing artifacts) are already in place; an opt-in
registration failure surfaces as a warning on stderr with the exact
re-run hint and a fallback link to the per-host JSON snippet. This
matches the existing pattern for--skip-login(operator can complete
the missing step in isolation later). -
A
cli_missingoutcome (claude binary not on PATH) is distinct from
afailedoutcome (claude ran but errored). The error copy reflects
which: operators on a machine without Claude Code get an "install
Claude Code" prompt; operators with Claude Code installed get the
CLI's actual error message.
Tests
- 35 new vitest cases covering
parseSetupFlags --register / --register-scope(11), pure helpers (12), and theregisterWithHostrunSetupintegration (12). Total__tests__/setup.test.tsnow
93 cases. Full@muhaven/mcpsuite: 241 cases passing.
[0.1.5] — 2026-05-17
Adds the muhaven-broker stop subcommand so operators can cleanly tear
down a detached daemon spawned by muhaven-broker setup without
hunting for PIDs in ps output or hand-rolling taskkill recipes.
Surfaced after muhaven-broker setup lands in 0.1.4 — operators
naturally asked "how do I stop this?" and the answer (muhaven-broker logout + manual kill) was non-obvious.
Added
muhaven-broker stopsubcommand — clean shutdown:- Probes the broker via
hello(). Unreachable → "not running,
nothing to stop." exit 0. - Best-effort
clearJwt()so the OS keychain doesn't keep a stale
JWT after shutdown. Warning + continue on failure (don't abort
the kill). - Reads
hello.pid(new optional field — see below). On pre-0.1.5
daemons that omit the field, prints a manual-kill hint with
cross-platform commands and exits 1. process.kill(pid, 'SIGTERM')→ pollshello()until it fails
(clean exit) or 5s elapses, thenprocess.kill(pid, 'SIGKILL').- Pure orchestrator (
runStopinsrc/broker/stop.ts) with
injectable IO so every branch is unit-testable without spawning
real processes.
- Probes the broker via
Changed
helloresponse gains optionalpid?: numberfield (broker
protocol stays at 0.3.0 — additive optional field, back-compat with
pre-0.1.5 daemons). Populated fromprocess.pidat request-handle
time; consumers MUST handleundefinedfor older daemons.
Tests
- 206 vitest pass (up from 197 in 0.1.4). Net +9 cases in new
__tests__/stop.test.ts:runStopnot-running short-circuit- happy path (hello → clearJwt → SIGTERM → exit-detected → 0)
- pre-0.1.5 daemon (no
pidin hello) returns 1 with manual hint - SIGKILL fallback after gracefulShutdownMs timeout
- SIGTERM permission error returns 1
- SIGKILL permission error returns 1 with "may be orphaned" hint
clearJwtfailure does NOT abort the kill (warning + continue)defaultKillProcessreturns false on ESRCH (process gone)defaultKillProcessrethrows non-ESRCH errors (POSIX-only test)
[0.1.4] — 2026-05-17
Adds the one-shot muhaven-broker setup subcommand so a fresh install
goes from npm install -g @muhaven/mcp straight to a working MCP host
in two commands. Surfaced during the Wave 4 demo-recording prep — the
prior five-line manual ritual (env exports + session-key mint +
background daemon + login) was the longest opaque block in the demo
script. Also adds --version / --help to both muhaven-broker and
muhaven-mcp bins.
Added
-
muhaven-broker setupsubcommand — orchestrates env defaulting +
session-key minting + detached daemon spawn + login in a single
invocation. Flags:--foreground/-f: keep the daemon attached to the current
shell (useful when systemd/launchd will supervise instead of the
backgrounded child).--skip-login: spawn the daemon but defer the device-code flow.--no-launch-browser: pass-through to the embeddedloginstep.--broker-endpoint,--backend-base-url,--dashboard-base-url:
same overrides aslogin.
Env defaults applied (only when the var is unset):
MUHAVEN_BACKEND_URL=https://api.muhaven.appMUHAVEN_DASHBOARD_URL=https://muhaven.appMUHAVEN_KEYRING=file(auto-applied on Windows / WSL2 /
devcontainer / GitHub Codespace / SSH — same heuristic as
muhaven-broker doctor's environment detector). Native macOS +
Linux desktop leave the value unset so the OS keychain remains
the default.
Idempotent: re-running
setupagainst an already-up daemon detects
the existing JWT and short-circuits toLogin: skipped — JWT already in keystore.. Against a daemon that's up but unauthenticated, it
skips the spawn and only runs the login step.Closing summary always surfaces the broker endpoint and a
platform-specific "Stop daemon" command (kill <pid>on POSIX,
Stop-Process -Id <pid>on Windows). Sign-out is explicitly
documented as separate from daemon shutdown —muhaven-broker logout
clears the JWT but leaves the daemon running. -
muhaven-broker --version/-v— printsmuhaven-broker @muhaven/mcp@<version>and exits 0. Wired into the dispatcher
alongside the existing--help/-h. Reads the package version
from the tsup-injected__SERVER_VERSION__constant. -
muhaven-mcp --version/-vand--help/-h— bin shim
short-circuits before requiringdist/index.cjs, so the flags exit
cleanly without spinning up the broker IPC + tool registry. Reads
the version from the siblingpackage.jsondirectly.
Security
-
Session key never lands in
process.env— the orchestrator
builds a localeffectiveEnvsnapshot and passes the minted
session key only to the spawned daemon's env. Prior version
mutatedprocess.env.MUHAVEN_BROKER_SESSION_KEYso any subsequent
child of the operator's shell would inherit the key. Foreground
mode brackets its requiredprocess.envmutation in a try/finally
that restores the original values on exit. -
Spawned daemon strips
NODE_OPTIONS/NODE_TLS_REJECT_UNAUTHORIZED
/NODE_EXTRA_CA_CERTS/NODE_PATHfrom inherited env so a
same-user attacker who set those in the operator's shell can't
hijack the daemon's execution to exfiltrate the session key. -
URL flag validation —
--backend-base-url/--dashboard-base-url
must behttps://(withhttp://localhost/127.0.0.1/
[::1]dev carve-out). Rejectsjavascript:,file:,data:,
and plainhttp:to non-loopback BEFORE the spawn — defense
against the OAuth-device-flow phishing vector where a malicious
--backend-base-urlwould ship the JWT to an attacker host. -
--broker-endpointpath validation — must be a\\.\pipe\…
path on Windows or an absolute path on POSIX. Rejects relative
paths + flag-injection (e.g.--broker-endpoint --from-daemonis
parsed but rejected at validation, preventing the spawned daemon
from being bound to an attacker-controlled location). -
Preserved env values not echoed —
Env preserved: NAME (set in your shell)only — values stay opaque. Prior version printed
Env preserved: NAME=valuewhich would leak operator-supplied
values to shell history / CI logs. -
Session key minted via viem's
generatePrivateKey— guarantees
the result is in the valid secp256k1 scalar range. Prior version
used rawcrypto.randomBytes(32), which had a (negligible but
nonzero) probability of returning an out-of-range value that the
signer would reject as invalid much later in the flow. -
Bin path resolved via
__dirname—resolveBrokerBinPathwalks
from the bundleddist/broker.cjsto the sibling
bin/muhaven-broker.cjsdeterministically, so Windows global-npm
shim wrappers (.cmd/.ps1inprocess.argv[1]) don't end up
as the spawn target. -
detectMcpHostno longer falls through tonpm_lifecycle_event
— that var is the npm script name, not an MCP-host identity. The
device-flow/linkpage's "requesting client" panel would have
displayed "setup" for operators running vianpm run setup,
misleading the passkey ceremony.
Tests
- 197 vitest pass (up from 134 in 0.1.3). Net +58 cases in
__tests__/setup.test.ts(+22 over the initial +36 after the
parallel agent security review) + 5 in__tests__/cli-version-flag.test.ts:- +10
applyEnvDefaults— defaults applied on empty env;
backend/dashboard preserved when set; KEYRING auto-applied on
win32/WSL2/SSH/devcontainer/Codespaces; left unset on native
macOS/Linux desktop; explicitMUHAVEN_KEYRING=ospreserved on
Windows; empty-string vars treated as unset. - +2
mintSessionKey— 0x-prefixed 32-byte hex shape;
non-deterministic across calls. - +3
decideSetupAction— spawn-and-login / login-only /
already-ready decision tree. - +6
parseSetupFlags— defaults;--foregroundand-f
aliases;--skip-login;--no-launch-browserpass-through; value
flag parsing; unknown-flag rejection. - +3
waitForBroker— first-call success; retry-until-success
with virtual clock; timeout throws with last error in message. - +12
runSetuporchestrator — flag-error path returns 2;
foreground mode short-circuits; spawn_and_login happy path;
login_only path; already_ready path;--skip-login; login-failure
bubbles exit code + leaves daemon running; wait timeout returns 1;
--no-launch-browserpass-through; value-flag pass-through;
session key minted vs preserved.
- +10
0.1.3 — 2026-05-16
Q2 fix bundle from the post-§4 queue closing four findings from §3e⁶
(broker-session-key-required-for-reads, broker-env-divergence,
mcp-serverinfo-version-stale) and unblocking the openclaw-skill ClawScan
fix (the noExternal: ['@muhaven/mcp'] inline bundle requires this
version on npm before the skill can be republished).
Added
- Read-only daemon posture: the broker daemon now boots WITHOUT
MUHAVEN_BROKER_SESSION_KEY. In that mode the daemon still serves
hello+ the JWT verbs (somuhaven.read.*tools work end-to-end via
the standalone@muhaven/mcpinstall), but anysign_hashrequest
returns the newsession_key_unavailablebroker error so write paths
fail with a clear remediation message instead of the daemon dying at
startup. Closes §3e⁶ F-broker-session-key-required-for-reads. muhaven-broker login --from-daemonflag: resolves backend +
dashboard URLs from the running daemon'shello.effectiveConfig
rather than the login CLI's env. Solves the daemon-vs-CLI env-divergence
problem when the two processes inherit different shell environments
(e.g. the daemon was launched by systemd/launchd, the CLI by ssh).
Mutually exclusive with explicit--backend-base-url/
--dashboard-base-url. Closes §3e⁶ F-broker-env-divergence.muhaven-broker doctorsurfaces the daemon's effective config
and read-only-posture status — the operator can verify which backend
URL is actually in play before driving a login.
Changed
- Broker protocol bumped 0.2.0 → 0.3.0 (additive — pre-0.3.0 clients
remain compatible):hello.hasSessionKey(optionalboolean) — absence impliestrue
for back-compat.hello.effectiveConfig(optional{ backendBaseUrl, dashboardBaseUrl }).- New
session_key_unavailablebroker error code.
serverInfo.versionin the MCP server'sinitializeresponse is
now build-time injected frompackage.json#version(tsupdefineon
__SERVER_VERSION__) rather than the previously hardcoded'0.1.0'
string insrc/server.ts. Closes §3e⁶ F-mcp-serverinfo-version-stale.
Tests
- 134 vitest pass (up from 101 in 0.1.2). Net +33 cases:
- +6
config.test.ts—loadBrokerConfiglazy-validation
(no key, empty string, valid key, malformed key) + env-driven backend- dashboard URL surface.
- +5
daemon-handler.test.ts— 0.3.0 protocol:hellosurfaces
hasSessionKey+effectiveConfigfrom options, defaultstrue
when omitted, reflectsfalsewhen set;sign_hashwith
NullSignerreturnssession_key_unavailable; re-throws non-Missing
errors verbatim. - +9
cli-parse-login-flags.test.ts— flag parser unit cases
incl.--from-daemonmutual-exclusion guard. - +8
session-key-required.test.ts—signEnvelopeprobe of
hello.hasSessionKey; short-circuit returnsSESSION_KEY_REQUIRED
for buy + claim with mint-URL pointing atdashboardBaseUrl;
safety-net mapping of innersession_key_unavailable;
probe-cache reuse; concurrent-call coalescing (one hello round-trip
for N callers); retry-after-rejection (eager cache clear); trailing-slash
mintUrl strip. - +2
server-version.test.ts— runtime fallback returns
package.json#version; matchesmanifest.json#version. - +2
build-artifacts.test.ts— hostname-migration guard + new
__SERVER_VERSION__literal grep in bundled dist. - +1
daemon-lifecycle.test.ts— read-only-posture boot test
replaces the prior "exits on missing key" assertion; added
"exits on a malformed session key" as the second negative-path test.
- +6
0.1.2 — 2026-05-11
Re-roll of the 0.1.1 workflow-validation cut. 0.1.1 never reached npm:
the tag pointed at the version-bump commit but the workflow at that SHA
lacked two fixes that landed on agenticwave after the tag was first cut.
Bumping to 0.1.2 lets the tag reference the latest agenticwave HEAD
which contains both fixes; subsequent releases follow the normal flow.
Fixed
-
NODE_AUTH_TOKEN was overriding the OIDC trusted-publisher exchange in
.github/workflows/mcp-publish.yml(commite373e36). The
actions/setup-node@v4registry-urlparameter writes an.npmrc
with_authToken=${NODE_AUTH_TOKEN}placeholder; the GitHub Actions
runner's inherited env hadNODE_AUTH_TOKENpopulated (visible in the
failing workflow logs asXXXXX-XXXXX-XXXXX-XXXXX), so npm tried
token-based publish first and 404'd because that token has no
permission on@muhaven/mcp. Fix: explicitenv: NODE_AUTH_TOKEN: ''
on the publish step forces the--provenance-driven OIDC exchange as
the sole auth method. -
OIDC claims diagnostic step added pre-publish (commit
e373e36).
Printsgithub.repository_owner/github.repository/
github.workflow_ref/github.event_name/github.refso that any
future Trusted Publisher binding mismatch can be diff'd
character-by-character against the npm-side configuration. Surfaced
the case-sensitivity gotcha aroundrepository_ownerthat0.1.1's
three failed attempts triggered.
Distribution
- Identical bundle bytes to the
0.1.1artifact except for the embedded
0.1.2version strings inpackage.json+manifest.json. No code
changes to the MCP server or broker daemon. Samedist/shape, same
16 files in the tarball, samebin/entry-points.
0.1.1 — 2026-05-11
Workflow-validation cut. 0.1.0 shipped via a one-time manual npm publish --no-provenance because npm Trusted Publisher could not be configured against
a non-existent package; this release exercises the mcp-publish.yml workflow
end-to-end on the muhaven.app hosts so subsequent releases carry full Sigstore
provenance attestations and npm view dist.signatures populates.
Fixed
- Re-runs the publish path through
.github/workflows/mcp-publish.yml
(Workstream D) on the now-configured Trusted Publisher binding for
@muhaven/mcp. Validates the full OIDC → cosign sign →npm publish --provenance→ post-publish shasum verify chain that0.1.0skipped. - No code change relative to
0.1.0. Bundle bytes identical except for the
embedded0.1.1version string inpackage.json+manifest.json. The
0.1.0"Provenance" badge gap (visible on the npmjs.com sidebar) closes
with this release.
Distribution
- First release where
npm view @muhaven/mcp@0.1.1 dist.signaturesreturns a
populated array,dist.attestations.urlresolves to a GitHub-hosted
attestation, and the npmjs.com sidebar shows the "Provenance" badge linked
to the workflow run.
First publishable cut. All publish-readiness security must-fixes (H-1 / H-2 /
H-3 from MCP_PUBLISH_READINESS.md §2) and package-hygiene work landed on
agenticwave ahead of the npm publish ceremony. The actual npm publish is
gated on the operator-side AGENTIC_TEST_PLAN.md walkthroughs completing.
Added — Tools (22)
muhaven.read.*(7):portfolio·yields·distribution·tokens·
audit·protection_coverage·kyc_attestationmuhaven.position.*(4):buy·sell·claim·rebalancemuhaven.policy.*(4):set_tier·pause·audit_export·
session_key_statusmuhaven.issuer.*(5):distribute_yield·kyc_add·kyc_remove·
unpause_token·audit_querymuhaven.governance.*(2):propose·cast_vote(frontend runner
deferred to Wave 5)
Added — Infrastructure
- MCPB-format
manifest.json(manifest_version 0.2) declaring four
user_configentries (backend_url,dashboard_url,broker_endpoint,
read_only) so MCPB hosts (Claude Desktop, Cursor, future MCPB store) can
render install dialogs without the operator hand-editing config files. - Companion
muhaven-brokerdaemon over Unix socket (POSIX) / named pipe
(Windows). Holds the session-key private half; the MCP server never sees
the key, only signed UserOps it relays back to the LLM host. - OAuth 2.0 Device Authorization Grant flow with scoped JWTs
(mcp.read.*+mcp.propose.*). Replaces paste-JWT UX; mitigates the
R-7 lethal-trifecta concern (env-block credential storage). - Tool-description SHA-256 hash pinning (
tool-hashes.json+ the
verify-tool-hashesscript). Server startup re-verifies and exits with
code70(EX_CONFIG) on drift; CI gate via the same script with
--check. @napi-rs/keyringintegration for the broker's JWT keystore (Windows
DPAPI / macOS Security framework / Linux Secret Service via D-Bus) with
file-backed fallback (MUHAVEN_KEYRING=file) for WSL2 / devcontainer /
SSH-remote where Secret Service is absent.
Security
- Broker isolation: no TCP transport; POSIX socket created at mode
0700
with file mode0600; Windows named-pipe ACL inherits user. - STDIO-only MCP transport;
mcp-remote(CVE-2025-6514) banned in README. - Position / policy / issuer / governance tools return unsigned UserOps +
broker signatures; never auto-submit to a bundler. - Tool-description hash pinning makes MCPoison-style descriptor-tampering
attacks fail-closed (server exits before the LLM sees the drifted text). - Workstream A security must-fixes (commit
44bd8b2):- H-1: sourcemaps stripped from publish bundle (
tsup.config.ts
gates onMUHAVEN_DEV_BUILD=1). Removes.js.map/.cjs.map/
.d.ts.mapcontaining absolute developer paths from npm tarballs. - H-2:
package.jsondeclarespublishConfig.{access:public, registry:https://registry.npmjs.org/, provenance:true}so a manual
npm publishfrom a recovery laptop can't silently publish privately
or without provenance. - H-3: keystore probe round-trip-parses the OS-keychain value via
parseRecord. Malformed JSON / wrong-shape JSON / Secret-Service-down
each fall back toFileKeystorewith a discriminatedfallbackReason.
muhaven-broker doctorperforms a non-destructive sentinel round-trip.
- H-1: sourcemaps stripped from publish bundle (
- Workstream B publint hardening (commit
a01116e):exportsmap fixed
to per-conditionimport.types+require.typesso TypeScript
resolution is honest on both ESM and CJS consumers.
Tests
- 100 vitest cases (
__tests__/):protocol(13) — IPC frame codec / JSON-RPC envelope shapebackend-client(5) — REST-call shape + error mappingdescriptions(7) — descriptor surface drift detectionjwt-source(5) — broker-cached JWT lifecycledaemon-handler(7) — IPC method dispatchregistry(8) — tool registration invariantsmcp-redteam(46) — adversarial inputs againstbuildMcpServer+
InMemoryTransportdaemon-lifecycle(3) — bin shim survives pastrunMcpStdioCli
resolution (regression guard against the 2026-05-10 ship-blocker
bugs inbin/*.cjs)keystore(5) — H-3 OS-keychain probe regression coveragebuild-artifacts(1) — H-1 publish-bundle map-free assertion
- Three-way subset gate (
scripts/verify-tool-hashes.ts) enforces
consistency betweensrc/index.tsregistry,manifest.json#tools,
andtool-hashes.json.
Known limitations (documented residuals — see SECURITY.md to land in
Workstream H)
- The MCP package is published WITHOUT a domain-bound icon — manifest's
iconfield was deliberately removed in Workstream B because no asset
ships yet. MCPB host install dialogs render the default placeholder.
Real amber-gradient icon ships in a Wave 5 follow-up. - Listed JSON schemas don't reflect per-tool field hints (L-1 backlog).
BackendClienterrors echo URL pathnames (L-2; recommend host-side
scrubbing).
Distribution
- Published via npm OIDC + Sigstore provenance attestations from the
.github/workflows/mcp-publish.ymlworkflow (tag-driven on
mcp-v*; manualworkflow_dispatchavailable for recovery). - Tarball +
.sigstorebundle uploaded as workflow artifacts and as
files on themcp-v0.1.0GitHub Release. - Verify locally with:
cosign verify-blob \ --bundle muhaven-mcp-0.1.0.tgz.sigstore \ --certificate-identity-regexp "^https://github\.com/hasToDev/muhaven/" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ muhaven-mcp-0.1.0.tgz