@muhaven/mcp 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -2
- package/README.md +125 -125
- package/bin/muhaven-broker.cjs +11 -11
- package/bin/muhaven-mcp.cjs +11 -11
- package/dist/broker.cjs +138 -22
- package/dist/broker.d.cts +21 -1
- package/dist/broker.d.ts +21 -1
- package/dist/broker.js +138 -23
- package/dist/index.cjs +124 -20
- package/dist/index.d.cts +143 -13
- package/dist/index.d.ts +143 -13
- package/dist/index.js +120 -21
- package/manifest.json +98 -98
- package/package.json +104 -104
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,138 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.1.
|
|
10
|
+
## [0.1.3] — 2026-05-16
|
|
11
|
+
|
|
12
|
+
Q2 fix bundle from the post-§4 queue closing four findings from §3e⁶
|
|
13
|
+
(broker-session-key-required-for-reads, broker-env-divergence,
|
|
14
|
+
mcp-serverinfo-version-stale) and unblocking the openclaw-skill ClawScan
|
|
15
|
+
fix (the `noExternal: ['@muhaven/mcp']` inline bundle requires this
|
|
16
|
+
version on npm before the skill can be republished).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Read-only daemon posture**: the broker daemon now boots WITHOUT
|
|
21
|
+
`MUHAVEN_BROKER_SESSION_KEY`. In that mode the daemon still serves
|
|
22
|
+
`hello` + the JWT verbs (so `muhaven.read.*` tools work end-to-end via
|
|
23
|
+
the standalone `@muhaven/mcp` install), but any `sign_hash` request
|
|
24
|
+
returns the new `session_key_unavailable` broker error so write paths
|
|
25
|
+
fail with a clear remediation message instead of the daemon dying at
|
|
26
|
+
startup. Closes §3e⁶ F-broker-session-key-required-for-reads.
|
|
27
|
+
- **`muhaven-broker login --from-daemon` flag**: resolves backend +
|
|
28
|
+
dashboard URLs from the running daemon's `hello.effectiveConfig`
|
|
29
|
+
rather than the login CLI's env. Solves the daemon-vs-CLI env-divergence
|
|
30
|
+
problem when the two processes inherit different shell environments
|
|
31
|
+
(e.g. the daemon was launched by systemd/launchd, the CLI by ssh).
|
|
32
|
+
Mutually exclusive with explicit `--backend-base-url` /
|
|
33
|
+
`--dashboard-base-url`. Closes §3e⁶ F-broker-env-divergence.
|
|
34
|
+
- **`muhaven-broker doctor` surfaces the daemon's effective config**
|
|
35
|
+
and read-only-posture status — the operator can verify which backend
|
|
36
|
+
URL is actually in play before driving a login.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Broker protocol bumped 0.2.0 → 0.3.0** (additive — pre-0.3.0 clients
|
|
41
|
+
remain compatible):
|
|
42
|
+
- `hello.hasSessionKey` (optional `boolean`) — absence implies `true`
|
|
43
|
+
for back-compat.
|
|
44
|
+
- `hello.effectiveConfig` (optional `{ backendBaseUrl, dashboardBaseUrl }`).
|
|
45
|
+
- New `session_key_unavailable` broker error code.
|
|
46
|
+
- **`serverInfo.version`** in the MCP server's `initialize` response is
|
|
47
|
+
now build-time injected from `package.json#version` (tsup `define` on
|
|
48
|
+
`__SERVER_VERSION__`) rather than the previously hardcoded `'0.1.0'`
|
|
49
|
+
string in `src/server.ts`. Closes §3e⁶ F-mcp-serverinfo-version-stale.
|
|
50
|
+
|
|
51
|
+
### Tests
|
|
52
|
+
|
|
53
|
+
- 134 vitest pass (up from 101 in 0.1.2). Net +33 cases:
|
|
54
|
+
- **+6** `config.test.ts` — `loadBrokerConfig` lazy-validation
|
|
55
|
+
(no key, empty string, valid key, malformed key) + env-driven backend
|
|
56
|
+
+ dashboard URL surface.
|
|
57
|
+
- **+5** `daemon-handler.test.ts` — 0.3.0 protocol: `hello` surfaces
|
|
58
|
+
`hasSessionKey` + `effectiveConfig` from options, defaults `true`
|
|
59
|
+
when omitted, reflects `false` when set; `sign_hash` with
|
|
60
|
+
`NullSigner` returns `session_key_unavailable`; re-throws non-Missing
|
|
61
|
+
errors verbatim.
|
|
62
|
+
- **+9** `cli-parse-login-flags.test.ts` — flag parser unit cases
|
|
63
|
+
incl. `--from-daemon` mutual-exclusion guard.
|
|
64
|
+
- **+8** `session-key-required.test.ts` — `signEnvelope` probe of
|
|
65
|
+
`hello.hasSessionKey`; short-circuit returns `SESSION_KEY_REQUIRED`
|
|
66
|
+
for buy + claim with mint-URL pointing at `dashboardBaseUrl`;
|
|
67
|
+
safety-net mapping of inner `session_key_unavailable`;
|
|
68
|
+
probe-cache reuse; concurrent-call coalescing (one hello round-trip
|
|
69
|
+
for N callers); retry-after-rejection (eager cache clear); trailing-slash
|
|
70
|
+
mintUrl strip.
|
|
71
|
+
- **+2** `server-version.test.ts` — runtime fallback returns
|
|
72
|
+
`package.json#version`; matches `manifest.json#version`.
|
|
73
|
+
- **+2** `build-artifacts.test.ts` — hostname-migration guard + new
|
|
74
|
+
`__SERVER_VERSION__` literal grep in bundled dist.
|
|
75
|
+
- **+1** `daemon-lifecycle.test.ts` — read-only-posture boot test
|
|
76
|
+
replaces the prior "exits on missing key" assertion; added
|
|
77
|
+
"exits on a malformed session key" as the second negative-path test.
|
|
78
|
+
|
|
79
|
+
## [0.1.2] — 2026-05-11
|
|
80
|
+
|
|
81
|
+
Re-roll of the `0.1.1` workflow-validation cut. `0.1.1` never reached npm:
|
|
82
|
+
the tag pointed at the version-bump commit but the workflow at that SHA
|
|
83
|
+
lacked two fixes that landed on `agenticwave` after the tag was first cut.
|
|
84
|
+
Bumping to `0.1.2` lets the tag reference the latest `agenticwave` HEAD
|
|
85
|
+
which contains both fixes; subsequent releases follow the normal flow.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **NODE_AUTH_TOKEN was overriding the OIDC trusted-publisher exchange in
|
|
90
|
+
`.github/workflows/mcp-publish.yml`** (commit `e373e36`). The
|
|
91
|
+
`actions/setup-node@v4` `registry-url` parameter writes an `.npmrc`
|
|
92
|
+
with `_authToken=${NODE_AUTH_TOKEN}` placeholder; the GitHub Actions
|
|
93
|
+
runner's inherited env had `NODE_AUTH_TOKEN` populated (visible in the
|
|
94
|
+
failing workflow logs as `XXXXX-XXXXX-XXXXX-XXXXX`), so npm tried
|
|
95
|
+
token-based publish first and 404'd because that token has no
|
|
96
|
+
permission on `@muhaven/mcp`. Fix: explicit `env: NODE_AUTH_TOKEN: ''`
|
|
97
|
+
on the publish step forces the `--provenance`-driven OIDC exchange as
|
|
98
|
+
the sole auth method.
|
|
99
|
+
|
|
100
|
+
- **OIDC claims diagnostic step added pre-publish** (commit `e373e36`).
|
|
101
|
+
Prints `github.repository_owner` / `github.repository` /
|
|
102
|
+
`github.workflow_ref` / `github.event_name` / `github.ref` so that any
|
|
103
|
+
future Trusted Publisher binding mismatch can be diff'd
|
|
104
|
+
character-by-character against the npm-side configuration. Surfaced
|
|
105
|
+
the case-sensitivity gotcha around `repository_owner` that `0.1.1`'s
|
|
106
|
+
three failed attempts triggered.
|
|
107
|
+
|
|
108
|
+
### Distribution
|
|
109
|
+
|
|
110
|
+
- Identical bundle bytes to the `0.1.1` artifact except for the embedded
|
|
111
|
+
`0.1.2` version strings in `package.json` + `manifest.json`. No code
|
|
112
|
+
changes to the MCP server or broker daemon. Same `dist/` shape, same
|
|
113
|
+
16 files in the tarball, same `bin/` entry-points.
|
|
114
|
+
|
|
115
|
+
## [0.1.1] — 2026-05-11
|
|
116
|
+
|
|
117
|
+
Workflow-validation cut. `0.1.0` shipped via a one-time manual `npm publish
|
|
118
|
+
--no-provenance` because npm Trusted Publisher could not be configured against
|
|
119
|
+
a non-existent package; this release exercises the `mcp-publish.yml` workflow
|
|
120
|
+
end-to-end on the muhaven.app hosts so subsequent releases carry full Sigstore
|
|
121
|
+
provenance attestations and `npm view dist.signatures` populates.
|
|
122
|
+
|
|
123
|
+
### Fixed
|
|
124
|
+
|
|
125
|
+
- Re-runs the publish path through `.github/workflows/mcp-publish.yml`
|
|
126
|
+
(Workstream D) on the now-configured Trusted Publisher binding for
|
|
127
|
+
`@muhaven/mcp`. Validates the full OIDC → cosign sign → `npm publish
|
|
128
|
+
--provenance` → post-publish shasum verify chain that `0.1.0` skipped.
|
|
129
|
+
- No code change relative to `0.1.0`. Bundle bytes identical except for the
|
|
130
|
+
embedded `0.1.1` version string in `package.json` + `manifest.json`. The
|
|
131
|
+
`0.1.0` "Provenance" badge gap (visible on the npmjs.com sidebar) closes
|
|
132
|
+
with this release.
|
|
133
|
+
|
|
134
|
+
### Distribution
|
|
135
|
+
|
|
136
|
+
- First release where `npm view @muhaven/mcp@0.1.1 dist.signatures` returns a
|
|
137
|
+
populated array, `dist.attestations.url` resolves to a GitHub-hosted
|
|
138
|
+
attestation, and the npmjs.com sidebar shows the "Provenance" badge linked
|
|
139
|
+
to the workflow run.
|
|
140
|
+
|
|
141
|
+
|
|
11
142
|
|
|
12
143
|
First publishable cut. All publish-readiness security must-fixes (H-1 / H-2 /
|
|
13
144
|
H-3 from `MCP_PUBLISH_READINESS.md` §2) and package-hygiene work landed on
|
|
@@ -119,5 +250,8 @@ Workstream H)
|
|
|
119
250
|
muhaven-mcp-0.1.0.tgz
|
|
120
251
|
```
|
|
121
252
|
|
|
122
|
-
[Unreleased]: https://github.com/hasToDev/muhaven/compare/mcp-v0.1.
|
|
253
|
+
[Unreleased]: https://github.com/hasToDev/muhaven/compare/mcp-v0.1.3...HEAD
|
|
254
|
+
[0.1.3]: https://github.com/hasToDev/muhaven/releases/tag/mcp-v0.1.3
|
|
255
|
+
[0.1.2]: https://github.com/hasToDev/muhaven/releases/tag/mcp-v0.1.2
|
|
256
|
+
[0.1.1]: https://github.com/hasToDev/muhaven/releases/tag/mcp-v0.1.1
|
|
123
257
|
[0.1.0]: https://github.com/hasToDev/muhaven/releases/tag/mcp-v0.1.0
|
package/README.md
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
# `@muhaven/mcp` — MCP server for MuHaven RWA portfolios
|
|
2
|
-
|
|
3
|
-
Confidential RWA portfolio management on Fhenix CoFHE, exposed as a Model
|
|
4
|
-
Context Protocol server installable in Claude Desktop / Cursor / Claude Code.
|
|
5
|
-
|
|
6
|
-
## What it does
|
|
7
|
-
|
|
8
|
-
22 tools across five groups (P3 + P7 + P11):
|
|
9
|
-
|
|
10
|
-
| Group | Tools | Description |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| `muhaven.read.*` | `portfolio` · `yields` · `distribution` · `tokens` · `audit` · `protection_coverage` · `kyc_attestation` | Read your encrypted-balance portfolio, yield history, audit log, and P11 governance/KYC state |
|
|
13
|
-
| `muhaven.position.*` | `buy` · `sell` · `claim` · `rebalance` | **Propose** trades — returns unsigned UserOps + broker signature; never auto-submits |
|
|
14
|
-
| `muhaven.policy.*` | `set_tier` · `pause` · `audit_export` · `session_key_status` | Manage the tiered-autonomy state machine |
|
|
15
|
-
| `muhaven.issuer.*` | `distribute_yield` · `kyc_add` · `kyc_remove` · `unpause_token` · `audit_query` | Issuer-side: distribute yield, manage KYC whitelist, NAV-set+unpause, query own audit trail |
|
|
16
|
-
| `muhaven.governance.*` | `propose` · `cast_vote` | P11 encrypted-governance ceremony (cast-vote frontend runner deferred to Wave 5) |
|
|
17
|
-
|
|
18
|
-
`MUHAVEN_READ_ONLY=true` exposes only the 7 `muhaven.read.*` tools.
|
|
19
|
-
|
|
20
|
-
## Architecture (one paragraph)
|
|
21
|
-
|
|
22
|
-
The MCP server runs as an MCPB STDIO subprocess of the host LLM (Claude
|
|
23
|
-
Desktop, Cursor, Claude Code). It speaks HTTPS to the MuHaven backend at
|
|
24
|
-
`https://api.muhaven.app` and IPC to a long-running sibling daemon
|
|
25
|
-
called `muhaven-broker`. The broker holds two secrets — your ZeroDev
|
|
26
|
-
session-key private half (for signing UserOps) and your scoped JWT (for
|
|
27
|
-
authenticating to the backend) — both in your OS keychain. The broker
|
|
28
|
-
NEVER speaks TCP and NEVER reaches out to the network. It exposes one
|
|
29
|
-
signing primitive over a Unix socket (POSIX) or named pipe (Windows).
|
|
30
|
-
This split — network-facing MCP server / signing-only broker — is the
|
|
31
|
-
**lethal-trifecta** mitigation: an attacker who compromises the LLM
|
|
32
|
-
process cannot exfiltrate your key without also compromising a separate
|
|
33
|
-
process running under your user.
|
|
34
|
-
|
|
35
|
-
The `muhaven-broker login` ceremony uses the OAuth 2.0 Device
|
|
36
|
-
Authorization Grant (RFC 8628) — same shape as `gh auth login --web`,
|
|
37
|
-
`wrangler login`, `gcloud auth login`. You never paste a JWT.
|
|
38
|
-
|
|
39
|
-
## Install (development)
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# In the muhaven monorepo, from repo root
|
|
43
|
-
pnpm install
|
|
44
|
-
pnpm --filter @muhaven/mcp build
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The `bin/` shims will be invokable as `muhaven-mcp` and `muhaven-broker`
|
|
48
|
-
once the package is linked.
|
|
49
|
-
|
|
50
|
-
## Setup (end-user, post-MCPB-publish)
|
|
51
|
-
|
|
52
|
-
1. **Install the MCPB package** in your host (Claude Desktop / Cursor / Claude Code).
|
|
53
|
-
2. **Provision a session key.** This is the private half the broker holds for signing UserOps. The dashboard-side mint UI is a Wave 5 deliverable — until then, generate one yourself:
|
|
54
|
-
```bash
|
|
55
|
-
node -e "console.log('0x' + require('crypto').randomBytes(32).toString('hex'))"
|
|
56
|
-
```
|
|
57
|
-
The corresponding kernel session key install on-chain runs through the dashboard `/agent/policy/transition` flow (one-time per tier). For read-only smokes you can skip the install — the broker only needs the private half to start.
|
|
58
|
-
3. **Start the broker daemon.** Set `MUHAVEN_BROKER_SESSION_KEY=0x…` and run:
|
|
59
|
-
```bash
|
|
60
|
-
muhaven-broker
|
|
61
|
-
```
|
|
62
|
-
Recipes for systemd / launchd / Windows Service in `docs/runbook.md` (TODO).
|
|
63
|
-
4. **Authenticate via device-code flow:**
|
|
64
|
-
```bash
|
|
65
|
-
muhaven-broker login
|
|
66
|
-
```
|
|
67
|
-
The broker prints a URL like `https://muhaven.app/link?code=ABCD-1234` and (when not run with `--no-launch-browser`) opens it. Sign in with your passkey on the dashboard, verify the device fingerprint shown on the `/link` page, click **Authorize**. The CLI exits with success when the JWT lands in your keystore.
|
|
68
|
-
5. **Use any MCP tool** from your host LLM. First call may take a moment as the broker fetches the JWT from the keystore.
|
|
69
|
-
|
|
70
|
-
> **Windows / WSL2 / devcontainer / SSH-remote operators:** export `MUHAVEN_KEYRING=file` to skip the OS-keychain probe and use the file-backed keystore at `~/.muhaven/jwt` (mode 0600, parent dir mode 0700). The keychain backend depends on `@napi-rs/keyring` which needs platform-specific build prerequisites; the file fallback works everywhere.
|
|
71
|
-
|
|
72
|
-
## Hardening invariants (`THREAT_MODEL_P0.md` aligned)
|
|
73
|
-
|
|
74
|
-
- **Transport is STDIO + Unix-socket only.** The MCP server's `StdioServerTransport` is the only transport mounted; the broker's IPC is a Unix socket on POSIX (parent dir mode `0700`, socket file mode `0600`) or a per-user named pipe on Windows. **Never bind TCP.**
|
|
75
|
-
- **`mcp-remote` is banned.** CVE-2025-6514 disclosed an arbitrary-command-execution path through that proxy. Do not use it; do not set `MUHAVEN_BACKEND_URL` to anything that wraps it.
|
|
76
|
-
- **`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1`** is recommended in your shell rc when running Claude Code locally — it prevents inherited env vars from leaking into the MCP subprocess. The MCP package's `MUHAVEN_*` env vars are read at boot, but adopting the scrub habit limits collateral exposure.
|
|
77
|
-
- **Tool descriptions are pinned** at build time in `tool-hashes.json`. The server exits with code 70 (`EX_CONFIG`) on startup if the live descriptors don't match the pinned hashes — defends against tool-description-poisoning patches per the mcp-context-protector pattern (post-MCPoison, March 2026).
|
|
78
|
-
- **Position tools never auto-submit.** They return an unsigned UserOp envelope plus a broker signature. The host LLM is expected to present this to the user for explicit confirmation before bundler submission. The MCP server does not speak to any bundler.
|
|
79
|
-
|
|
80
|
-
## Environment variables
|
|
81
|
-
|
|
82
|
-
| Var | Required | Default | Purpose |
|
|
83
|
-
|---|---|---|---|
|
|
84
|
-
| `MUHAVEN_BACKEND_URL` | no | `https://api.muhaven.app` | Backend host. Use staging URL for development. |
|
|
85
|
-
| `MUHAVEN_DASHBOARD_URL` | no | `https://muhaven.app` | Dashboard origin used for the `/link` URL. |
|
|
86
|
-
| `MUHAVEN_BROKER_ENDPOINT` | no | `~/.muhaven/broker.sock` (POSIX) / `\\.\pipe\muhaven-broker-<user>` (Windows) | IPC path. Set if running multiple isolated brokers. |
|
|
87
|
-
| `MUHAVEN_BROKER_SESSION_KEY` | **yes** (broker) | — | 0x-prefixed 32-byte hex; the session-key private half. |
|
|
88
|
-
| `MUHAVEN_READ_ONLY` | no | `false` | When `true`, only `muhaven.read.*` tools are registered. |
|
|
89
|
-
| `MUHAVEN_KEYRING` | no | auto | Set to `file` to force the file-backed keystore (required on WSL2 / devcontainer / SSH-remote). |
|
|
90
|
-
| `MUHAVEN_REQUEST_TIMEOUT_MS` | no | `15000` | Backend HTTP timeout. |
|
|
91
|
-
| `MUHAVEN_BROKER_TIMEOUT_MS` | no | `5000` | Broker IPC timeout. |
|
|
92
|
-
| `MUHAVEN_JWT_CACHE_TTL_SEC` | no | `30` | In-process JWT cache TTL. |
|
|
93
|
-
| `MUHAVEN_BROKER_MAX_BYTES` | no | `65536` | Per-request payload cap on the broker IPC. |
|
|
94
|
-
|
|
95
|
-
The MCPB `manifest.json` declares the user-facing subset (`backend_url`, `dashboard_url`, `broker_endpoint`, `read_only`); the host's secret manager handles the values.
|
|
96
|
-
|
|
97
|
-
## CLI subcommands (`muhaven-broker`)
|
|
98
|
-
|
|
99
|
-
| Command | Effect |
|
|
100
|
-
|---|---|
|
|
101
|
-
| (none) | Run the daemon (production mode). |
|
|
102
|
-
| `muhaven-broker login [--no-launch-browser]` | Run the device-code ceremony; on success store the JWT in the keystore. |
|
|
103
|
-
| `muhaven-broker logout` | Clear the JWT from the keystore. |
|
|
104
|
-
| `muhaven-broker doctor` | Print environment + keystore + reachability report. |
|
|
105
|
-
|
|
106
|
-
## Threat model in 30 seconds
|
|
107
|
-
|
|
108
|
-
Per `development/DEV_WAVE_4/THREAT_MODEL_P0.md`:
|
|
109
|
-
|
|
110
|
-
| Risk | Control |
|
|
111
|
-
|---|---|
|
|
112
|
-
| **R-1** Prompt injection escalating into a tx | Position tools return *unsigned* UserOps; host MUST present to user for explicit passkey confirmation. |
|
|
113
|
-
| **R-2** Hallucinated tool call | Strict-enum tool registry + `additionalProperties: false` Zod schemas. |
|
|
114
|
-
| **R-3** Replay of confirmation tokens | Single-use server-side nonced tokens via existing P1 confirm-token-service. |
|
|
115
|
-
| **R-6** ZeroDev session-key escape | Session key lives in broker keystore (OS keychain) — never in the LLM-process env. |
|
|
116
|
-
| **R-7** MCP env-block exfiltration | MCPB `sensitive: true` for secrets → OS keychain; broker isolation; no plaintext disk. |
|
|
117
|
-
| **R-8** FHE ACL bypass | Backend enforces every read; MCP server never decrypts FHE handles. |
|
|
118
|
-
|
|
119
|
-
## License
|
|
120
|
-
|
|
121
|
-
MIT
|
|
122
|
-
|
|
123
|
-
## Status: `0.1.0` — first publish-ready cut
|
|
124
|
-
|
|
125
|
-
Wave 4 Phase P3 deliverable per `development/DEV_WAVE_4/PROGRESS.md`. Workstreams A–D of the npm-publish ceremony (security must-fixes, `package.json` hygiene, `LICENSE` + `CHANGELOG`, GitHub Actions workflows) are landed on `agenticwave`; the actual `npm publish` is operator-driven via the tag-push of `mcp-v0.1.0` against the `npm-publish` GitHub Environment (2-reviewer gate · OIDC trusted-publishing · Sigstore provenance). See `development/DEV_WAVE_4/MCP_PUBLISH_READINESS.md` §6 for the operator runbook.
|
|
1
|
+
# `@muhaven/mcp` — MCP server for MuHaven RWA portfolios
|
|
2
|
+
|
|
3
|
+
Confidential RWA portfolio management on Fhenix CoFHE, exposed as a Model
|
|
4
|
+
Context Protocol server installable in Claude Desktop / Cursor / Claude Code.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
22 tools across five groups (P3 + P7 + P11):
|
|
9
|
+
|
|
10
|
+
| Group | Tools | Description |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `muhaven.read.*` | `portfolio` · `yields` · `distribution` · `tokens` · `audit` · `protection_coverage` · `kyc_attestation` | Read your encrypted-balance portfolio, yield history, audit log, and P11 governance/KYC state |
|
|
13
|
+
| `muhaven.position.*` | `buy` · `sell` · `claim` · `rebalance` | **Propose** trades — returns unsigned UserOps + broker signature; never auto-submits |
|
|
14
|
+
| `muhaven.policy.*` | `set_tier` · `pause` · `audit_export` · `session_key_status` | Manage the tiered-autonomy state machine |
|
|
15
|
+
| `muhaven.issuer.*` | `distribute_yield` · `kyc_add` · `kyc_remove` · `unpause_token` · `audit_query` | Issuer-side: distribute yield, manage KYC whitelist, NAV-set+unpause, query own audit trail |
|
|
16
|
+
| `muhaven.governance.*` | `propose` · `cast_vote` | P11 encrypted-governance ceremony (cast-vote frontend runner deferred to Wave 5) |
|
|
17
|
+
|
|
18
|
+
`MUHAVEN_READ_ONLY=true` exposes only the 7 `muhaven.read.*` tools.
|
|
19
|
+
|
|
20
|
+
## Architecture (one paragraph)
|
|
21
|
+
|
|
22
|
+
The MCP server runs as an MCPB STDIO subprocess of the host LLM (Claude
|
|
23
|
+
Desktop, Cursor, Claude Code). It speaks HTTPS to the MuHaven backend at
|
|
24
|
+
`https://api.muhaven.app` and IPC to a long-running sibling daemon
|
|
25
|
+
called `muhaven-broker`. The broker holds two secrets — your ZeroDev
|
|
26
|
+
session-key private half (for signing UserOps) and your scoped JWT (for
|
|
27
|
+
authenticating to the backend) — both in your OS keychain. The broker
|
|
28
|
+
NEVER speaks TCP and NEVER reaches out to the network. It exposes one
|
|
29
|
+
signing primitive over a Unix socket (POSIX) or named pipe (Windows).
|
|
30
|
+
This split — network-facing MCP server / signing-only broker — is the
|
|
31
|
+
**lethal-trifecta** mitigation: an attacker who compromises the LLM
|
|
32
|
+
process cannot exfiltrate your key without also compromising a separate
|
|
33
|
+
process running under your user.
|
|
34
|
+
|
|
35
|
+
The `muhaven-broker login` ceremony uses the OAuth 2.0 Device
|
|
36
|
+
Authorization Grant (RFC 8628) — same shape as `gh auth login --web`,
|
|
37
|
+
`wrangler login`, `gcloud auth login`. You never paste a JWT.
|
|
38
|
+
|
|
39
|
+
## Install (development)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# In the muhaven monorepo, from repo root
|
|
43
|
+
pnpm install
|
|
44
|
+
pnpm --filter @muhaven/mcp build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `bin/` shims will be invokable as `muhaven-mcp` and `muhaven-broker`
|
|
48
|
+
once the package is linked.
|
|
49
|
+
|
|
50
|
+
## Setup (end-user, post-MCPB-publish)
|
|
51
|
+
|
|
52
|
+
1. **Install the MCPB package** in your host (Claude Desktop / Cursor / Claude Code).
|
|
53
|
+
2. **Provision a session key.** This is the private half the broker holds for signing UserOps. The dashboard-side mint UI is a Wave 5 deliverable — until then, generate one yourself:
|
|
54
|
+
```bash
|
|
55
|
+
node -e "console.log('0x' + require('crypto').randomBytes(32).toString('hex'))"
|
|
56
|
+
```
|
|
57
|
+
The corresponding kernel session key install on-chain runs through the dashboard `/agent/policy/transition` flow (one-time per tier). For read-only smokes you can skip the install — the broker only needs the private half to start.
|
|
58
|
+
3. **Start the broker daemon.** Set `MUHAVEN_BROKER_SESSION_KEY=0x…` and run:
|
|
59
|
+
```bash
|
|
60
|
+
muhaven-broker
|
|
61
|
+
```
|
|
62
|
+
Recipes for systemd / launchd / Windows Service in `docs/runbook.md` (TODO).
|
|
63
|
+
4. **Authenticate via device-code flow:**
|
|
64
|
+
```bash
|
|
65
|
+
muhaven-broker login
|
|
66
|
+
```
|
|
67
|
+
The broker prints a URL like `https://muhaven.app/link?code=ABCD-1234` and (when not run with `--no-launch-browser`) opens it. Sign in with your passkey on the dashboard, verify the device fingerprint shown on the `/link` page, click **Authorize**. The CLI exits with success when the JWT lands in your keystore.
|
|
68
|
+
5. **Use any MCP tool** from your host LLM. First call may take a moment as the broker fetches the JWT from the keystore.
|
|
69
|
+
|
|
70
|
+
> **Windows / WSL2 / devcontainer / SSH-remote operators:** export `MUHAVEN_KEYRING=file` to skip the OS-keychain probe and use the file-backed keystore at `~/.muhaven/jwt` (mode 0600, parent dir mode 0700). The keychain backend depends on `@napi-rs/keyring` which needs platform-specific build prerequisites; the file fallback works everywhere.
|
|
71
|
+
|
|
72
|
+
## Hardening invariants (`THREAT_MODEL_P0.md` aligned)
|
|
73
|
+
|
|
74
|
+
- **Transport is STDIO + Unix-socket only.** The MCP server's `StdioServerTransport` is the only transport mounted; the broker's IPC is a Unix socket on POSIX (parent dir mode `0700`, socket file mode `0600`) or a per-user named pipe on Windows. **Never bind TCP.**
|
|
75
|
+
- **`mcp-remote` is banned.** CVE-2025-6514 disclosed an arbitrary-command-execution path through that proxy. Do not use it; do not set `MUHAVEN_BACKEND_URL` to anything that wraps it.
|
|
76
|
+
- **`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1`** is recommended in your shell rc when running Claude Code locally — it prevents inherited env vars from leaking into the MCP subprocess. The MCP package's `MUHAVEN_*` env vars are read at boot, but adopting the scrub habit limits collateral exposure.
|
|
77
|
+
- **Tool descriptions are pinned** at build time in `tool-hashes.json`. The server exits with code 70 (`EX_CONFIG`) on startup if the live descriptors don't match the pinned hashes — defends against tool-description-poisoning patches per the mcp-context-protector pattern (post-MCPoison, March 2026).
|
|
78
|
+
- **Position tools never auto-submit.** They return an unsigned UserOp envelope plus a broker signature. The host LLM is expected to present this to the user for explicit confirmation before bundler submission. The MCP server does not speak to any bundler.
|
|
79
|
+
|
|
80
|
+
## Environment variables
|
|
81
|
+
|
|
82
|
+
| Var | Required | Default | Purpose |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `MUHAVEN_BACKEND_URL` | no | `https://api.muhaven.app` | Backend host. Use staging URL for development. |
|
|
85
|
+
| `MUHAVEN_DASHBOARD_URL` | no | `https://muhaven.app` | Dashboard origin used for the `/link` URL. |
|
|
86
|
+
| `MUHAVEN_BROKER_ENDPOINT` | no | `~/.muhaven/broker.sock` (POSIX) / `\\.\pipe\muhaven-broker-<user>` (Windows) | IPC path. Set if running multiple isolated brokers. |
|
|
87
|
+
| `MUHAVEN_BROKER_SESSION_KEY` | **yes** (broker) | — | 0x-prefixed 32-byte hex; the session-key private half. |
|
|
88
|
+
| `MUHAVEN_READ_ONLY` | no | `false` | When `true`, only `muhaven.read.*` tools are registered. |
|
|
89
|
+
| `MUHAVEN_KEYRING` | no | auto | Set to `file` to force the file-backed keystore (required on WSL2 / devcontainer / SSH-remote). |
|
|
90
|
+
| `MUHAVEN_REQUEST_TIMEOUT_MS` | no | `15000` | Backend HTTP timeout. |
|
|
91
|
+
| `MUHAVEN_BROKER_TIMEOUT_MS` | no | `5000` | Broker IPC timeout. |
|
|
92
|
+
| `MUHAVEN_JWT_CACHE_TTL_SEC` | no | `30` | In-process JWT cache TTL. |
|
|
93
|
+
| `MUHAVEN_BROKER_MAX_BYTES` | no | `65536` | Per-request payload cap on the broker IPC. |
|
|
94
|
+
|
|
95
|
+
The MCPB `manifest.json` declares the user-facing subset (`backend_url`, `dashboard_url`, `broker_endpoint`, `read_only`); the host's secret manager handles the values.
|
|
96
|
+
|
|
97
|
+
## CLI subcommands (`muhaven-broker`)
|
|
98
|
+
|
|
99
|
+
| Command | Effect |
|
|
100
|
+
|---|---|
|
|
101
|
+
| (none) | Run the daemon (production mode). |
|
|
102
|
+
| `muhaven-broker login [--no-launch-browser]` | Run the device-code ceremony; on success store the JWT in the keystore. |
|
|
103
|
+
| `muhaven-broker logout` | Clear the JWT from the keystore. |
|
|
104
|
+
| `muhaven-broker doctor` | Print environment + keystore + reachability report. |
|
|
105
|
+
|
|
106
|
+
## Threat model in 30 seconds
|
|
107
|
+
|
|
108
|
+
Per `development/DEV_WAVE_4/THREAT_MODEL_P0.md`:
|
|
109
|
+
|
|
110
|
+
| Risk | Control |
|
|
111
|
+
|---|---|
|
|
112
|
+
| **R-1** Prompt injection escalating into a tx | Position tools return *unsigned* UserOps; host MUST present to user for explicit passkey confirmation. |
|
|
113
|
+
| **R-2** Hallucinated tool call | Strict-enum tool registry + `additionalProperties: false` Zod schemas. |
|
|
114
|
+
| **R-3** Replay of confirmation tokens | Single-use server-side nonced tokens via existing P1 confirm-token-service. |
|
|
115
|
+
| **R-6** ZeroDev session-key escape | Session key lives in broker keystore (OS keychain) — never in the LLM-process env. |
|
|
116
|
+
| **R-7** MCP env-block exfiltration | MCPB `sensitive: true` for secrets → OS keychain; broker isolation; no plaintext disk. |
|
|
117
|
+
| **R-8** FHE ACL bypass | Backend enforces every read; MCP server never decrypts FHE handles. |
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
|
122
|
+
|
|
123
|
+
## Status: `0.1.0` — first publish-ready cut
|
|
124
|
+
|
|
125
|
+
Wave 4 Phase P3 deliverable per `development/DEV_WAVE_4/PROGRESS.md`. Workstreams A–D of the npm-publish ceremony (security must-fixes, `package.json` hygiene, `LICENSE` + `CHANGELOG`, GitHub Actions workflows) are landed on `agenticwave`; the actual `npm publish` is operator-driven via the tag-push of `mcp-v0.1.0` against the `npm-publish` GitHub Environment (2-reviewer gate · OIDC trusted-publishing · Sigstore provenance). See `development/DEV_WAVE_4/MCP_PUBLISH_READINESS.md` §6 for the operator runbook.
|
package/bin/muhaven-broker.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable */
|
|
3
|
-
const { runCli } = require('../dist/broker.cjs');
|
|
4
|
-
|
|
5
|
-
runCli(process.argv.slice(2)).then(
|
|
6
|
-
(code) => process.exit(code ?? 0),
|
|
7
|
-
(err) => {
|
|
8
|
-
process.stderr.write(`fatal: ${err && err.stack ? err.stack : String(err)}\n`);
|
|
9
|
-
process.exit(1);
|
|
10
|
-
},
|
|
11
|
-
);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
const { runCli } = require('../dist/broker.cjs');
|
|
4
|
+
|
|
5
|
+
runCli(process.argv.slice(2)).then(
|
|
6
|
+
(code) => process.exit(code ?? 0),
|
|
7
|
+
(err) => {
|
|
8
|
+
process.stderr.write(`fatal: ${err && err.stack ? err.stack : String(err)}\n`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
},
|
|
11
|
+
);
|
package/bin/muhaven-mcp.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable */
|
|
3
|
-
const { runMcpStdioCli } = require('../dist/index.cjs');
|
|
4
|
-
|
|
5
|
-
runMcpStdioCli().then(
|
|
6
|
-
() => process.exit(0),
|
|
7
|
-
(err) => {
|
|
8
|
-
process.stderr.write(`fatal: ${err && err.stack ? err.stack : String(err)}\n`);
|
|
9
|
-
process.exit(1);
|
|
10
|
-
},
|
|
11
|
-
);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
const { runMcpStdioCli } = require('../dist/index.cjs');
|
|
4
|
+
|
|
5
|
+
runMcpStdioCli().then(
|
|
6
|
+
() => process.exit(0),
|
|
7
|
+
(err) => {
|
|
8
|
+
process.stderr.write(`fatal: ${err && err.stack ? err.stack : String(err)}\n`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
},
|
|
11
|
+
);
|