agentic-wallet-mcp 0.3.1 → 0.4.2
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/README.md +47 -26
- package/dist/server-bundle.cjs +284 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,20 +20,34 @@ VC issuance → identity proof → pay-per-use).
|
|
|
20
20
|
|
|
21
21
|
| Tool | Does | Input | Output (shape) |
|
|
22
22
|
|---|---|---|---|
|
|
23
|
-
| `wallet_status` | Report holder DID/address/network + client-supplied
|
|
24
|
-
| `prove_identity` | Answer an x401 `PROOF-REQUEST` → return the `PROOF-RESPONSE` header to replay | `{ proofRequest, vc
|
|
23
|
+
| `wallet_status` | Report holder DID/address/network + held VCs (client-supplied, or the local cache) | `{ heldCredentials? }` | `{ holderDid, zetrixAddress, network, credentials }` |
|
|
24
|
+
| `prove_identity` | Answer an x401 `PROOF-REQUEST` → return the `PROOF-RESPONSE` header to replay | `{ proofRequest, vc?, revealAttribute?, issuerKeys? }` | `{ proofResponseHeader, verified, presentationId }` |
|
|
25
25
|
| `pay_and_fetch` | Fetch a URL, auto-pay with x402 (self-pay via Wallet BE) on `402` | `{ url, method?, headers?, body? }` | `{ status, body, paymentMade, amountPaid, amountPaidHuman, asset }` |
|
|
26
|
-
| `subscribe_and_issue` |
|
|
27
|
-
| `create_holder_account` | Onboarding:
|
|
28
|
-
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
>
|
|
26
|
+
| `subscribe_and_issue` | Reuse a cached VC if still valid, else pay x402 → MBI issues → return the VC | `{ templateId, attributes, expirationDate?, dryRun?, forceReissue? }` | `{ issued, vcId, vc, txHash, fromCache? }` |
|
|
27
|
+
| `create_holder_account` | Onboarding: mint an HSM account (the MCP already auto-creates one at startup if `ZETRIX_ADDRESS` is omitted — see Environment below). Always checks for an existing account first — if one is active for this session, returns `{ alreadyExists: true, existing }` without creating anything; pass `confirmNew: true` (after asking the user) to mint a new one anyway | `{ password, label?, purpose?, confirmNew? }` | `{ created, alreadyExists, existing?, zetrixAddress?, holderDid?, publicKeyHex?, message }` |
|
|
28
|
+
|
|
29
|
+
> **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
|
|
30
|
+
> (scoped per network + holder — different identities or networks never share a cache).
|
|
31
|
+
> `subscribe_and_issue` checks the cache before paying: a still-valid cached VC is returned
|
|
32
|
+
> immediately with `fromCache: true` and **no payment made**; pass `forceReissue: true` to pay
|
|
33
|
+
> and issue fresh regardless. `wallet_status`/`prove_identity` fall back to the cache
|
|
34
|
+
> automatically when you don't pass `heldCredentials`/`vc` explicitly — `prove_identity` only
|
|
35
|
+
> auto-selects when there's exactly one valid cached VC; with zero or several, it errors and
|
|
36
|
+
> asks you to pass `vc` explicitly. Explicitly passing `vc`/`heldCredentials` (including `[]`)
|
|
37
|
+
> always overrides the cache. Validity is read from the VC's own `validUntil` field, falling
|
|
38
|
+
> back to the `expirationDate` requested at issuance; a VC with neither is cached indefinitely.
|
|
39
|
+
> All Ed25519 signing still goes through Wallet BE HSM; no plaintext private keys.
|
|
32
40
|
|
|
33
41
|
> `create_holder_account` mints a **brand-new** keypair — Wallet BE's `/account/create` has no
|
|
34
|
-
> way to provision a pre-chosen address. It
|
|
35
|
-
>
|
|
36
|
-
>
|
|
42
|
+
> way to provision a pre-chosen address. It always checks first whether an account is already
|
|
43
|
+
> active for this session; if so, it returns `{ alreadyExists: true, existing }` and creates
|
|
44
|
+
> nothing — ask the user whether to keep the existing account or replace it, then call again
|
|
45
|
+
> with `confirmNew: true` only if they want a new one. A freshly minted account (address, DID,
|
|
46
|
+
> **and** password) is saved to this MCP's own local account store
|
|
47
|
+
> (`~/.agentic-wallet-mcp/account.json`, owner-only) and reused automatically on the next
|
|
48
|
+
> restart — no manual config edit needed. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` still set
|
|
49
|
+
> in your MCP config always overrides the saved account (see Environment below); the tool never
|
|
50
|
+
> writes the MCP host's own config file or restarts the server for you.
|
|
37
51
|
|
|
38
52
|
> `revealAttribute` on `prove_identity` is optional and usually should stay that way. Omitted,
|
|
39
53
|
> it's derived automatically from the challenge's DCQL `credential_requirements` — each claim path
|
|
@@ -92,9 +106,11 @@ startup, in one of two ways:
|
|
|
92
106
|
|
|
93
107
|
1. **First-time user — only `HSM_PASSWORD` set.** The MCP creates a brand-new HSM account on
|
|
94
108
|
Wallet BE (`POST /wallet/hsm/account/create`) and derives the DID from the returned public
|
|
95
|
-
key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup, and saves the
|
|
110
|
+
address, DID, and password to a local account store
|
|
111
|
+
(`~/.agentic-wallet-mcp/account.json`, owner-only) — it's reused automatically next run, no
|
|
112
|
+
config edit required. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` set later in your MCP
|
|
113
|
+
config still overrides the saved account.
|
|
98
114
|
2. **Existing user — `ZETRIX_ADDRESS` + `HSM_PASSWORD` set, `HOLDER_DID` optional.** The MCP
|
|
99
115
|
always self-signs the address via the existing `POST /wallet/hsm/sign-message` call and
|
|
100
116
|
derives the DID from the `publicKey` the response carries — no separate lookup endpoint
|
|
@@ -126,8 +142,9 @@ network:
|
|
|
126
142
|
|
|
127
143
|
Only set any of the four explicitly if you run your own instance of that service instead of the
|
|
128
144
|
default one — an explicit value always wins over the network default. The `*.myegdev.com`
|
|
129
|
-
(testnet) hosts are
|
|
130
|
-
|
|
145
|
+
(testnet) hosts are public endpoints — no VPN needed; if you're on a corporate VPN and one of
|
|
146
|
+
them times out, disconnecting it is more likely to fix that than connecting it. See "Network
|
|
147
|
+
reachability & troubleshooting" below.
|
|
131
148
|
|
|
132
149
|
That's the complete list — no VC-MCP subprocess, no BaaS gateway key, no manually-configured
|
|
133
150
|
BBS+ key to set up.
|
|
@@ -162,8 +179,9 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
|
|
|
162
179
|
```
|
|
163
180
|
|
|
164
181
|
> First run, no account yet? Omit `ZETRIX_ADDRESS` (and `HOLDER_DID`) entirely — the MCP creates
|
|
165
|
-
> one for you at startup
|
|
166
|
-
> "Onboarding" under
|
|
182
|
+
> one for you at startup, logs it to stderr, and saves it (address, DID, password) to
|
|
183
|
+
> `~/.agentic-wallet-mcp/account.json` for automatic reuse next run. See "Onboarding" under
|
|
184
|
+
> Environment above.
|
|
167
185
|
|
|
168
186
|
> Working on this repo locally instead of the published package? Point `command`/`args` at the
|
|
169
187
|
> local build directly: `"command": "node"`, `"args": ["/absolute/path/to/zetrix-agentic-wallet/dist/server-bundle.cjs"]`.
|
|
@@ -181,7 +199,7 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
|
|
|
181
199
|
- *"I got a 401 with this PROOF-REQUEST header — prove my identity and give me the PROOF-RESPONSE to replay."* → `prove_identity`
|
|
182
200
|
- *"Fetch `https://api.example/data` and pay automatically if it asks."* → `pay_and_fetch`
|
|
183
201
|
- *"Apply for the agent-identity credential with these attributes and pay for it."* → `subscribe_and_issue`
|
|
184
|
-
- *"My wallet_status call is failing — I don't have a holder account yet. Set one up."* → `create_holder_account` (asks you for a password, then
|
|
202
|
+
- *"My wallet_status call is failing — I don't have a holder account yet. Set one up."* → `create_holder_account` (asks you for a password; if an account already exists it reports that instead of creating — confirm with the user, then re-call with `confirmNew: true` to replace it)
|
|
185
203
|
|
|
186
204
|
For the full ordered script (onboarding → check → issue → prove → pay), see [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md).
|
|
187
205
|
|
|
@@ -191,9 +209,12 @@ Full narrative version with example prompts: [`docs/USAGE_FLOW.md`](docs/USAGE_F
|
|
|
191
209
|
|
|
192
210
|
**Step 0 — onboarding (once).** Only if `ZETRIX_ADDRESS` isn't set yet: the MCP creates an HSM
|
|
193
211
|
account automatically at startup from `HSM_PASSWORD` alone (see "Onboarding" under Environment
|
|
194
|
-
above)
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
above) and saves it locally for automatic reuse. Alternatively, call `create_holder_account
|
|
213
|
+
{ password }` manually — it always checks for an existing account first and reports it instead
|
|
214
|
+
of creating (pass `confirmNew: true`, after asking the user, to replace it anyway). Either way,
|
|
215
|
+
the account is saved to `~/.agentic-wallet-mcp/account.json` and picked up automatically on the
|
|
216
|
+
next restart; no manual config edit needed unless your MCP config also sets `ZETRIX_ADDRESS`/
|
|
217
|
+
`HSM_PASSWORD` via env, in which case those still take precedence and should be updated too.
|
|
197
218
|
|
|
198
219
|
**Phase 1 — `wallet_status` — pre-check.** Pass any VCs the caller already holds via
|
|
199
220
|
`heldCredentials`; the response tells you whether the agent-identity credential you need is
|
|
@@ -220,13 +241,13 @@ Independent of Phases 2/3 — no VC or identity proof involved, just a fresh pay
|
|
|
220
241
|
|
|
221
242
|
## Network reachability & troubleshooting
|
|
222
243
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
244
|
+
The `*.myegdev.com` testnet endpoints are public — no VPN needed to reach them. One dependency
|
|
245
|
+
sits behind a CDN edge (see the ZID resolver row below). When something that worked before
|
|
246
|
+
suddenly times out or 403s, check this table before assuming a code regression.
|
|
226
247
|
|
|
227
248
|
| Symptom | Cause | Fix / status |
|
|
228
249
|
|---|---|---|
|
|
229
|
-
| `Wallet BE /wallet/hsm/sign-blob request failed <- fetch failed <- UND_ERR_CONNECT_TIMEOUT` (or same for `mbi-vc.myegdev.com`) |
|
|
250
|
+
| `Wallet BE /wallet/hsm/sign-blob request failed <- fetch failed <- UND_ERR_CONNECT_TIMEOUT` (or same for `mbi-vc.myegdev.com`) | Transient network issue, or (if you're on a corporate VPN) the VPN routing away from the public internet | If you're on a VPN, try disconnecting it and retrying. Verify reachability directly: `curl -I https://wallet-api.myegdev.com/server` |
|
|
230
251
|
| `VP derivation failed <- ZID resolver HTTP 403 ... cf-mitigated: challenge` | ZID resolver (`zid-resolver-sandbox.zetrix.com`) sitting behind a Cloudflare **managed challenge** that blocks plain server-to-server `fetch` | Server-to-server access to the resolver must be allowlisted so it returns `200` directly. If the challenge is active, `prove_identity`'s `issuerKeys` input is the fallback — fetch the DID document via a real browser (it clears the JS challenge) and pass its `verificationMethod` entries' `publicKeyMultibase` (BBS+) / `publicKeyHex` (Ed25519) directly. |
|
|
231
252
|
| `OID4VP backend returned a malformed presentation definition` | Historical SDK bug: the live sandbox's `GET /v1/presentation/{id}` response has no `expires_at` field, but the SDK guard required one | **Fixed** in `x401-zetrix-client` — `expiresAt` is now optional on `PresentationDefinition`. If you see this, you're on a stale cached `npx` install — clear it (`npx clear-npx-cache` or bump the version) to pick up the current published `agentic-wallet-mcp`. |
|
|
232
253
|
| `SUBMIT_FAILED: OID4VP backend returned 401 ... Missing X-Wallet-Public-Key header` | Historical SDK gap: `POST /v1/presentation/submit` requires wallet-auth headers (`X-Wallet-Public-Key` / `X-Wallet-Signed-Data`, holder signs their own address) that the SDK didn't send | **Fixed** — `X401Wallet` now accepts an injected `submitAuth` provider and the wallet wires it automatically; nothing to configure. |
|
package/dist/server-bundle.cjs
CHANGED
|
@@ -2233,8 +2233,8 @@ var require_resolve = __commonJS({
|
|
|
2233
2233
|
}
|
|
2234
2234
|
return count;
|
|
2235
2235
|
}
|
|
2236
|
-
function getFullPath(resolver, id = "",
|
|
2237
|
-
if (
|
|
2236
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
2237
|
+
if (normalize2 !== false)
|
|
2238
2238
|
id = normalizeId(id);
|
|
2239
2239
|
const p = resolver.parse(id);
|
|
2240
2240
|
return _getFullPath(resolver, p);
|
|
@@ -3630,7 +3630,7 @@ var require_fast_uri = __commonJS({
|
|
|
3630
3630
|
"use strict";
|
|
3631
3631
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3632
3632
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3633
|
-
function
|
|
3633
|
+
function normalize2(uri, options) {
|
|
3634
3634
|
if (typeof uri === "string") {
|
|
3635
3635
|
uri = /** @type {T} */
|
|
3636
3636
|
normalizeString(uri, options);
|
|
@@ -3897,7 +3897,7 @@ var require_fast_uri = __commonJS({
|
|
|
3897
3897
|
}
|
|
3898
3898
|
var fastUri = {
|
|
3899
3899
|
SCHEMES,
|
|
3900
|
-
normalize,
|
|
3900
|
+
normalize: normalize2,
|
|
3901
3901
|
resolve,
|
|
3902
3902
|
resolveComponent,
|
|
3903
3903
|
equal,
|
|
@@ -8541,13 +8541,13 @@ var require_path = __commonJS({
|
|
|
8541
8541
|
return /^(?:\/|\w+:)/.test(path2);
|
|
8542
8542
|
}
|
|
8543
8543
|
);
|
|
8544
|
-
var
|
|
8544
|
+
var normalize2 = (
|
|
8545
8545
|
/**
|
|
8546
8546
|
* Normalizes the specified path.
|
|
8547
8547
|
* @param {string} path Path to normalize
|
|
8548
8548
|
* @returns {string} Normalized path
|
|
8549
8549
|
*/
|
|
8550
|
-
path.normalize = function
|
|
8550
|
+
path.normalize = function normalize3(path2) {
|
|
8551
8551
|
path2 = path2.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
8552
8552
|
var parts = path2.split("/"), absolute = isAbsolute(path2), prefix = "";
|
|
8553
8553
|
if (absolute)
|
|
@@ -8570,12 +8570,12 @@ var require_path = __commonJS({
|
|
|
8570
8570
|
);
|
|
8571
8571
|
path.resolve = function resolve(originPath, includePath, alreadyNormalized) {
|
|
8572
8572
|
if (!alreadyNormalized)
|
|
8573
|
-
includePath =
|
|
8573
|
+
includePath = normalize2(includePath);
|
|
8574
8574
|
if (isAbsolute(includePath))
|
|
8575
8575
|
return includePath;
|
|
8576
8576
|
if (!alreadyNormalized)
|
|
8577
|
-
originPath =
|
|
8578
|
-
return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ?
|
|
8577
|
+
originPath = normalize2(originPath);
|
|
8578
|
+
return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize2(originPath + "/" + includePath) : includePath;
|
|
8579
8579
|
};
|
|
8580
8580
|
}
|
|
8581
8581
|
});
|
|
@@ -12540,11 +12540,14 @@ __export(index_exports, {
|
|
|
12540
12540
|
buildToolList: () => buildToolList
|
|
12541
12541
|
});
|
|
12542
12542
|
module.exports = __toCommonJS(index_exports);
|
|
12543
|
+
var import_node_crypto2 = require("node:crypto");
|
|
12544
|
+
var import_node_os = require("node:os");
|
|
12545
|
+
var import_node_path3 = require("node:path");
|
|
12543
12546
|
|
|
12544
12547
|
// package.json
|
|
12545
12548
|
var package_default = {
|
|
12546
12549
|
name: "agentic-wallet-mcp",
|
|
12547
|
-
version: "0.
|
|
12550
|
+
version: "0.4.2",
|
|
12548
12551
|
description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
|
|
12549
12552
|
keywords: ["mcp", "model-context-protocol", "zetrix", "wallet", "x401", "x402", "blockchain"],
|
|
12550
12553
|
license: "MIT",
|
|
@@ -21607,7 +21610,7 @@ async function resolveAssetSymbol(asset, query) {
|
|
|
21607
21610
|
}
|
|
21608
21611
|
|
|
21609
21612
|
// src/clients/template-info-client.ts
|
|
21610
|
-
async function
|
|
21613
|
+
async function fetchTemplateFields(templateId, registryAddress, nodeBaseUrl, query) {
|
|
21611
21614
|
const key = `template__${templateId}`;
|
|
21612
21615
|
const url = `${nodeBaseUrl.replace(/\/+$/, "")}/getAccountMetaData?address=${encodeURIComponent(registryAddress)}&key=${encodeURIComponent(key)}`;
|
|
21613
21616
|
let res;
|
|
@@ -21621,10 +21624,16 @@ async function fetchTemplateRequiredFields(templateId, registryAddress, nodeBase
|
|
|
21621
21624
|
if (!value) return null;
|
|
21622
21625
|
try {
|
|
21623
21626
|
const parsed = JSON.parse(value);
|
|
21624
|
-
if (parsed.applyFormat === void 0 || parsed.applyFormat === null) return [];
|
|
21627
|
+
if (parsed.applyFormat === void 0 || parsed.applyFormat === null) return { required: [], allKeys: [] };
|
|
21625
21628
|
const format = typeof parsed.applyFormat === "string" ? JSON.parse(parsed.applyFormat) : parsed.applyFormat;
|
|
21626
21629
|
if (!Array.isArray(format)) return null;
|
|
21627
|
-
|
|
21630
|
+
const entries = format.filter(
|
|
21631
|
+
(e) => !!e && typeof e.key === "string" && e.key !== ""
|
|
21632
|
+
);
|
|
21633
|
+
return {
|
|
21634
|
+
required: entries.filter((e) => e.mandatory === 1).map((e) => e.key),
|
|
21635
|
+
allKeys: entries.map((e) => e.key)
|
|
21636
|
+
};
|
|
21628
21637
|
} catch {
|
|
21629
21638
|
return null;
|
|
21630
21639
|
}
|
|
@@ -21721,6 +21730,10 @@ function findLeafPath(obj, key) {
|
|
|
21721
21730
|
}
|
|
21722
21731
|
return null;
|
|
21723
21732
|
}
|
|
21733
|
+
function flattenLeafPaths(obj, prefix = []) {
|
|
21734
|
+
if (!isRecord2(obj)) return prefix.length ? [prefix.join(".")] : [];
|
|
21735
|
+
return Object.entries(obj).flatMap(([k, v]) => flattenLeafPaths(v, [...prefix, k]));
|
|
21736
|
+
}
|
|
21724
21737
|
function dcqlToRevealAttributes(credentialQuery, vc) {
|
|
21725
21738
|
if (!isRecord2(credentialQuery) || !Array.isArray(credentialQuery.credentials)) return [];
|
|
21726
21739
|
const subject = isRecord2(vc) && isRecord2(vc.credentialSubject) ? vc.credentialSubject : void 0;
|
|
@@ -21743,7 +21756,9 @@ function dcqlToRevealAttributes(credentialQuery, vc) {
|
|
|
21743
21756
|
}
|
|
21744
21757
|
}
|
|
21745
21758
|
}
|
|
21746
|
-
return out;
|
|
21759
|
+
if (!subject) return out;
|
|
21760
|
+
const canonicalIndex = new Map(flattenLeafPaths(subject).map((path, i) => [path, i]));
|
|
21761
|
+
return out.map((path, i) => ({ path, key: canonicalIndex.get(path) ?? Number.MAX_SAFE_INTEGER, i })).sort((a, b) => a.key - b.key || a.i - b.i).map((entry) => entry.path);
|
|
21747
21762
|
}
|
|
21748
21763
|
var MbiVpAdapter = class {
|
|
21749
21764
|
constructor(mbi, signHexBlob, signMessage, holderAddress, resolveIssuerKeys, present) {
|
|
@@ -21794,14 +21809,22 @@ var MbiError = class extends Error {
|
|
|
21794
21809
|
this.httpStatus = httpStatus;
|
|
21795
21810
|
}
|
|
21796
21811
|
};
|
|
21797
|
-
var MbiClient = class {
|
|
21812
|
+
var MbiClient = class _MbiClient {
|
|
21798
21813
|
baseUrl;
|
|
21799
21814
|
constructor(baseUrl) {
|
|
21800
21815
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
21801
21816
|
}
|
|
21802
|
-
/**
|
|
21817
|
+
/**
|
|
21818
|
+
* Phase 1 — POST /v1/vc/pay/apply without X-PAYMENT; expects the 402 challenge.
|
|
21819
|
+
* A free template short-circuits this: MBI issues the VC synchronously and returns
|
|
21820
|
+
* 200 instead, with no phase-2 settle to follow — surfaced via the `issued` field.
|
|
21821
|
+
*/
|
|
21803
21822
|
async applyChallenge(body) {
|
|
21804
21823
|
const res = await this.fetch("POST", "/v1/vc/pay/apply", body);
|
|
21824
|
+
if (res.status === 200) {
|
|
21825
|
+
const issued = await this.unwrap(res);
|
|
21826
|
+
return { x402Version: 1, accepts: [], issued };
|
|
21827
|
+
}
|
|
21805
21828
|
if (res.status !== 402) {
|
|
21806
21829
|
throw await this.error(res, "apply (phase 1) expected 402");
|
|
21807
21830
|
}
|
|
@@ -21849,12 +21872,14 @@ var MbiClient = class {
|
|
|
21849
21872
|
const body = await res.json();
|
|
21850
21873
|
return body.data;
|
|
21851
21874
|
}
|
|
21875
|
+
static ERROR_BODY_MAX_LEN = 500;
|
|
21852
21876
|
async error(res, context) {
|
|
21853
21877
|
const text = await res.text().catch(() => "");
|
|
21854
21878
|
let msg = text;
|
|
21855
21879
|
try {
|
|
21856
21880
|
const j = JSON.parse(text);
|
|
21857
|
-
|
|
21881
|
+
const truncated = text.length > _MbiClient.ERROR_BODY_MAX_LEN ? `${text.slice(0, _MbiClient.ERROR_BODY_MAX_LEN)}\u2026 (truncated, ${text.length} bytes total)` : text;
|
|
21882
|
+
msg = `${j.message ?? j.error ?? text} | full body: ${truncated}`;
|
|
21858
21883
|
} catch {
|
|
21859
21884
|
}
|
|
21860
21885
|
return new MbiError(`MBI ${context} \u2014 HTTP ${res.status}: ${msg}`, res.status);
|
|
@@ -21950,17 +21975,92 @@ function zetrixHexStringToBytes(s) {
|
|
|
21950
21975
|
return out;
|
|
21951
21976
|
}
|
|
21952
21977
|
|
|
21978
|
+
// src/clients/vc-cache.ts
|
|
21979
|
+
var import_node_crypto = require("node:crypto");
|
|
21980
|
+
var import_promises = require("node:fs/promises");
|
|
21981
|
+
var import_node_path = require("node:path");
|
|
21982
|
+
function cacheFileName(templateId) {
|
|
21983
|
+
return `${(0, import_node_crypto.createHash)("sha256").update(templateId).digest("hex")}.json`;
|
|
21984
|
+
}
|
|
21985
|
+
function isCachedVcShape(value) {
|
|
21986
|
+
return typeof value === "object" && value !== null && typeof value.templateId === "string" && "vc" in value && typeof value.issuedAt === "string";
|
|
21987
|
+
}
|
|
21988
|
+
function createFsVcCache(baseDir) {
|
|
21989
|
+
return {
|
|
21990
|
+
async get(templateId) {
|
|
21991
|
+
try {
|
|
21992
|
+
const raw = await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), "utf8");
|
|
21993
|
+
const parsed = JSON.parse(raw);
|
|
21994
|
+
return isCachedVcShape(parsed) ? parsed : null;
|
|
21995
|
+
} catch {
|
|
21996
|
+
return null;
|
|
21997
|
+
}
|
|
21998
|
+
},
|
|
21999
|
+
async set(templateId, entry) {
|
|
22000
|
+
await (0, import_promises.mkdir)(baseDir, { recursive: true, mode: 448 });
|
|
22001
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
22002
|
+
},
|
|
22003
|
+
async list() {
|
|
22004
|
+
let files;
|
|
22005
|
+
try {
|
|
22006
|
+
files = await (0, import_promises.readdir)(baseDir);
|
|
22007
|
+
} catch {
|
|
22008
|
+
return [];
|
|
22009
|
+
}
|
|
22010
|
+
const entries = await Promise.all(
|
|
22011
|
+
files.filter((f) => f.endsWith(".json")).map(async (f) => {
|
|
22012
|
+
try {
|
|
22013
|
+
const parsed = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, f), "utf8"));
|
|
22014
|
+
return isCachedVcShape(parsed) ? parsed : null;
|
|
22015
|
+
} catch {
|
|
22016
|
+
return null;
|
|
22017
|
+
}
|
|
22018
|
+
})
|
|
22019
|
+
);
|
|
22020
|
+
return entries.filter((e) => e !== null);
|
|
22021
|
+
}
|
|
22022
|
+
};
|
|
22023
|
+
}
|
|
22024
|
+
function isVcValid(entry, now = /* @__PURE__ */ new Date()) {
|
|
22025
|
+
if (!entry.validUntil) return true;
|
|
22026
|
+
const expiry = new Date(entry.validUntil);
|
|
22027
|
+
if (Number.isNaN(expiry.getTime())) return false;
|
|
22028
|
+
return expiry.getTime() > now.getTime();
|
|
22029
|
+
}
|
|
22030
|
+
function extractValidUntil(vc, fallback) {
|
|
22031
|
+
if (typeof vc === "object" && vc !== null && "validUntil" in vc) {
|
|
22032
|
+
const v = vc.validUntil;
|
|
22033
|
+
if (typeof v === "string") return v;
|
|
22034
|
+
}
|
|
22035
|
+
return fallback;
|
|
22036
|
+
}
|
|
22037
|
+
|
|
21953
22038
|
// src/orchestrator/subscribe.ts
|
|
21954
22039
|
async function subscribeAndIssue(deps, opts) {
|
|
21955
22040
|
if (!/^did:zid:/.test(opts.templateId)) {
|
|
21956
22041
|
return { issued: false, reason: `templateId must be a did:zid:... credential-definition id, got "${opts.templateId}"` };
|
|
21957
22042
|
}
|
|
22043
|
+
if (deps.cache && !opts.forceReissue && !opts.dryRun) {
|
|
22044
|
+
const cached2 = await deps.cache.get(opts.templateId);
|
|
22045
|
+
if (cached2 && isVcValid(cached2)) {
|
|
22046
|
+
return {
|
|
22047
|
+
issued: true,
|
|
22048
|
+
vcId: cached2.vcId,
|
|
22049
|
+
vc: cached2.vc,
|
|
22050
|
+
txHash: cached2.txHash,
|
|
22051
|
+
paidAsset: cached2.paidAsset,
|
|
22052
|
+
amountPaid: cached2.amountPaid,
|
|
22053
|
+
fromCache: true
|
|
22054
|
+
};
|
|
22055
|
+
}
|
|
22056
|
+
}
|
|
22057
|
+
const fields = deps.resolveTemplateFields ? await deps.resolveTemplateFields(opts.templateId) : null;
|
|
21958
22058
|
const { agentDid, ...rest } = opts.attributes ?? {};
|
|
21959
|
-
const
|
|
21960
|
-
const
|
|
21961
|
-
if (!opts.dryRun &&
|
|
22059
|
+
const shouldAutoFillAgentDid = !agentDid && fields !== null && fields.allKeys.includes("agentDid");
|
|
22060
|
+
const attributes = shouldAutoFillAgentDid ? { agentDid: deps.holderDid, ...rest } : opts.attributes ?? {};
|
|
22061
|
+
if (!opts.dryRun && fields) {
|
|
21962
22062
|
const attrs = attributes;
|
|
21963
|
-
const missing =
|
|
22063
|
+
const missing = fields.required.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
|
|
21964
22064
|
if (missing.length > 0) {
|
|
21965
22065
|
return {
|
|
21966
22066
|
issued: false,
|
|
@@ -21974,6 +22074,30 @@ async function subscribeAndIssue(deps, opts) {
|
|
|
21974
22074
|
const body = { data, signData, publicKey };
|
|
21975
22075
|
if (opts.expirationDate) body.expirationDate = opts.expirationDate;
|
|
21976
22076
|
const challenge = await deps.mbi.applyChallenge(body);
|
|
22077
|
+
if (challenge.issued) {
|
|
22078
|
+
const issued2 = challenge.issued;
|
|
22079
|
+
if (deps.cache) {
|
|
22080
|
+
await deps.cache.set(opts.templateId, {
|
|
22081
|
+
templateId: opts.templateId,
|
|
22082
|
+
vc: issued2.verifiableCredential,
|
|
22083
|
+
vcId: issued2.vcId,
|
|
22084
|
+
txHash: issued2.txHash,
|
|
22085
|
+
paidAsset: "none",
|
|
22086
|
+
amountPaid: "0",
|
|
22087
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22088
|
+
validUntil: extractValidUntil(issued2.verifiableCredential, opts.expirationDate)
|
|
22089
|
+
});
|
|
22090
|
+
}
|
|
22091
|
+
return {
|
|
22092
|
+
issued: true,
|
|
22093
|
+
vcId: issued2.vcId,
|
|
22094
|
+
vc: issued2.verifiableCredential,
|
|
22095
|
+
txHash: issued2.txHash,
|
|
22096
|
+
paidAsset: "none",
|
|
22097
|
+
amountPaid: "0",
|
|
22098
|
+
...opts.dryRun ? { reason: "this template requires no payment \u2014 MBI issues synchronously at phase 1, so dryRun could not prevent this issuance" } : {}
|
|
22099
|
+
};
|
|
22100
|
+
}
|
|
21977
22101
|
const accept = challenge.accepts[0];
|
|
21978
22102
|
if (!accept) return { issued: false, reason: "MBI 402 returned no payment options" };
|
|
21979
22103
|
if (opts.dryRun) {
|
|
@@ -21986,7 +22110,7 @@ async function subscribeAndIssue(deps, opts) {
|
|
|
21986
22110
|
asset: quotedAsset,
|
|
21987
22111
|
maxAmountRequired: accept.maxAmountRequired,
|
|
21988
22112
|
payTo: accept.payTo,
|
|
21989
|
-
...
|
|
22113
|
+
...fields ? { requiredAttributes: fields.required } : {}
|
|
21990
22114
|
}
|
|
21991
22115
|
};
|
|
21992
22116
|
}
|
|
@@ -21994,13 +22118,26 @@ async function subscribeAndIssue(deps, opts) {
|
|
|
21994
22118
|
const issued = await deps.mbi.applySettle({ ...body, paymentId: challenge.paymentId }, xPayment);
|
|
21995
22119
|
const rawAsset = String(accept.asset ?? "");
|
|
21996
22120
|
const paidAsset = deps.resolveSymbol ? await deps.resolveSymbol(rawAsset) : rawAsset;
|
|
22121
|
+
const amountPaid = String(accept.maxAmountRequired ?? "");
|
|
22122
|
+
if (deps.cache) {
|
|
22123
|
+
await deps.cache.set(opts.templateId, {
|
|
22124
|
+
templateId: opts.templateId,
|
|
22125
|
+
vc: issued.verifiableCredential,
|
|
22126
|
+
vcId: issued.vcId,
|
|
22127
|
+
txHash: issued.txHash,
|
|
22128
|
+
paidAsset,
|
|
22129
|
+
amountPaid,
|
|
22130
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22131
|
+
validUntil: extractValidUntil(issued.verifiableCredential, opts.expirationDate)
|
|
22132
|
+
});
|
|
22133
|
+
}
|
|
21997
22134
|
return {
|
|
21998
22135
|
issued: true,
|
|
21999
22136
|
vcId: issued.vcId,
|
|
22000
22137
|
vc: issued.verifiableCredential,
|
|
22001
22138
|
txHash: issued.txHash,
|
|
22002
22139
|
paidAsset,
|
|
22003
|
-
amountPaid
|
|
22140
|
+
amountPaid
|
|
22004
22141
|
};
|
|
22005
22142
|
}
|
|
22006
22143
|
|
|
@@ -22011,29 +22148,76 @@ function deriveHolderDid(publicKeyHex) {
|
|
|
22011
22148
|
if (hex.length === 76 && hex.slice(0, 4).toLowerCase() === "b001") return `did:zid:${hex.slice(4, 68)}`;
|
|
22012
22149
|
throw new Error(`onboard: unrecognized public key hex format (length ${hex.length})`);
|
|
22013
22150
|
}
|
|
22014
|
-
async function createHolderAccount(
|
|
22015
|
-
const
|
|
22151
|
+
async function createHolderAccount(deps, input) {
|
|
22152
|
+
const existing = await deps.getExistingAccount();
|
|
22153
|
+
if (existing && !input.confirmNew) {
|
|
22154
|
+
return {
|
|
22155
|
+
created: false,
|
|
22156
|
+
alreadyExists: true,
|
|
22157
|
+
existing,
|
|
22158
|
+
message: `An account already exists for this wallet (zetrixAddress=${existing.zetrixAddress}, holderDid=${existing.holderDid}). Ask the user whether to keep using it or create a brand-new one \u2014 call create_holder_account again with confirmNew:true to mint a new account.`
|
|
22159
|
+
};
|
|
22160
|
+
}
|
|
22161
|
+
const { zetrixAddress, publicKeyHex } = await deps.create(input.password, input.label, input.purpose);
|
|
22016
22162
|
const holderDid = deriveHolderDid(publicKeyHex);
|
|
22017
|
-
|
|
22018
|
-
|
|
22163
|
+
await deps.saveAccount({ zetrixAddress, holderDid, hsmPassword: input.password, label: input.label, purpose: input.purpose });
|
|
22164
|
+
const message = `New holder HSM account created \u2014 address, DID, and password saved to the wallet's local account store. Both will be used automatically on the next server restart; no manual config edit needed. If your MCP config also sets ZETRIX_ADDRESS/HSM_PASSWORD via environment variables, update or remove those too: an explicit env ZETRIX_ADDRESS/HSM_PASSWORD always takes precedence over the saved account. ZETRIX_ADDRESS=${zetrixAddress} (HOLDER_DID=${holderDid} is optional \u2014 it re-derives automatically).`;
|
|
22165
|
+
return { created: true, alreadyExists: Boolean(existing), zetrixAddress, holderDid, publicKeyHex, message };
|
|
22166
|
+
}
|
|
22167
|
+
|
|
22168
|
+
// src/template-aliases.ts
|
|
22169
|
+
var TEMPLATE_ALIASES = [
|
|
22170
|
+
{
|
|
22171
|
+
match: "birthcert",
|
|
22172
|
+
testnet: "did:zid:d6b783559acf6ba0f7ef6e1365bdaf0774d622d8d22728ca6323677f49ee94f8",
|
|
22173
|
+
mainnet: "did:zid:032cb99be3577beccfc6252783c49c83673af38f8456d73462043654d7764e83"
|
|
22174
|
+
}
|
|
22175
|
+
];
|
|
22176
|
+
function normalize(s) {
|
|
22177
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
22178
|
+
}
|
|
22179
|
+
function resolveTemplateAlias(input, network) {
|
|
22180
|
+
if (/^did:zid:/.test(input)) return void 0;
|
|
22181
|
+
const normalized = normalize(input);
|
|
22182
|
+
const entry = TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
|
|
22183
|
+
if (!entry) return void 0;
|
|
22184
|
+
return network.includes("testnet") ? entry.testnet : entry.mainnet;
|
|
22019
22185
|
}
|
|
22020
22186
|
|
|
22021
22187
|
// src/mcp-tools.ts
|
|
22188
|
+
async function loadValidCachedCredentials(cache) {
|
|
22189
|
+
if (!cache) return [];
|
|
22190
|
+
const all = await cache.list();
|
|
22191
|
+
return all.filter((entry) => isVcValid(entry));
|
|
22192
|
+
}
|
|
22022
22193
|
function createTools(deps) {
|
|
22023
22194
|
return {
|
|
22024
22195
|
async wallet_status(input = {}) {
|
|
22025
22196
|
const balances = deps.getBalances ? await deps.getBalances() : void 0;
|
|
22197
|
+
const credentials = input.heldCredentials ?? (await loadValidCachedCredentials(deps.cache)).map((entry) => entry.vc);
|
|
22026
22198
|
return {
|
|
22027
22199
|
holderDid: deps.config.holderDid,
|
|
22028
22200
|
zetrixAddress: deps.config.zetrixAddress,
|
|
22029
22201
|
network: deps.config.network,
|
|
22030
|
-
credentials
|
|
22202
|
+
credentials,
|
|
22031
22203
|
...balances !== void 0 ? { balances } : {}
|
|
22032
22204
|
};
|
|
22033
22205
|
},
|
|
22034
|
-
prove_identity(input) {
|
|
22206
|
+
async prove_identity(input) {
|
|
22207
|
+
let vc = input.vc;
|
|
22208
|
+
if (vc === void 0) {
|
|
22209
|
+
const cached2 = await loadValidCachedCredentials(deps.cache);
|
|
22210
|
+
if (cached2.length === 0) {
|
|
22211
|
+
throw new Error("prove_identity: no vc supplied and no valid credential is cached \u2014 call subscribe_and_issue first, or pass vc explicitly.");
|
|
22212
|
+
}
|
|
22213
|
+
if (cached2.length > 1) {
|
|
22214
|
+
const ids = cached2.map((entry) => entry.templateId).join(", ");
|
|
22215
|
+
throw new Error(`prove_identity: no vc supplied and multiple credentials are cached (templateIds: ${ids}) \u2014 pass vc explicitly to select one.`);
|
|
22216
|
+
}
|
|
22217
|
+
vc = cached2[0].vc;
|
|
22218
|
+
}
|
|
22035
22219
|
const wallet = deps.makeWallet({
|
|
22036
|
-
vc
|
|
22220
|
+
vc,
|
|
22037
22221
|
revealAttribute: input.revealAttribute,
|
|
22038
22222
|
issuerKeys: input.issuerKeys
|
|
22039
22223
|
});
|
|
@@ -22043,10 +22227,18 @@ function createTools(deps) {
|
|
|
22043
22227
|
return payAndFetch(deps.payer, input);
|
|
22044
22228
|
},
|
|
22045
22229
|
subscribe_and_issue(input) {
|
|
22046
|
-
|
|
22230
|
+
const resolved = resolveTemplateAlias(input.templateId, deps.config.network);
|
|
22231
|
+
return subscribeAndIssue(deps.subscribeDeps, resolved ? { ...input, templateId: resolved } : input);
|
|
22047
22232
|
},
|
|
22048
22233
|
create_holder_account(input) {
|
|
22049
|
-
return createHolderAccount(
|
|
22234
|
+
return createHolderAccount(
|
|
22235
|
+
{
|
|
22236
|
+
create: deps.createAccount,
|
|
22237
|
+
getExistingAccount: () => Promise.resolve(deps.config.zetrixAddress ? { zetrixAddress: deps.config.zetrixAddress, holderDid: deps.config.holderDid } : null),
|
|
22238
|
+
saveAccount: deps.saveAccount
|
|
22239
|
+
},
|
|
22240
|
+
input
|
|
22241
|
+
);
|
|
22050
22242
|
}
|
|
22051
22243
|
};
|
|
22052
22244
|
}
|
|
@@ -22063,6 +22255,30 @@ async function resolveHolder(deps, input) {
|
|
|
22063
22255
|
return { zetrixAddress: input.zetrixAddress, holderDid: derivedDid, created: false, didMismatch };
|
|
22064
22256
|
}
|
|
22065
22257
|
|
|
22258
|
+
// src/clients/account-store.ts
|
|
22259
|
+
var import_promises2 = require("node:fs/promises");
|
|
22260
|
+
var import_node_path2 = require("node:path");
|
|
22261
|
+
function isStoredAccountShape(value) {
|
|
22262
|
+
return typeof value === "object" && value !== null && typeof value.zetrixAddress === "string" && typeof value.holderDid === "string" && typeof value.hsmPassword === "string";
|
|
22263
|
+
}
|
|
22264
|
+
function createFsAccountStore(filePath) {
|
|
22265
|
+
return {
|
|
22266
|
+
async get() {
|
|
22267
|
+
try {
|
|
22268
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf8");
|
|
22269
|
+
const parsed = JSON.parse(raw);
|
|
22270
|
+
return isStoredAccountShape(parsed) ? parsed : null;
|
|
22271
|
+
} catch {
|
|
22272
|
+
return null;
|
|
22273
|
+
}
|
|
22274
|
+
},
|
|
22275
|
+
async set(account) {
|
|
22276
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true, mode: 448 });
|
|
22277
|
+
await (0, import_promises2.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
|
|
22278
|
+
}
|
|
22279
|
+
};
|
|
22280
|
+
}
|
|
22281
|
+
|
|
22066
22282
|
// src/index.ts
|
|
22067
22283
|
var packageVersion = package_default.version;
|
|
22068
22284
|
function buildToolList() {
|
|
@@ -22073,7 +22289,7 @@ function buildToolList() {
|
|
|
22073
22289
|
inputSchema: {
|
|
22074
22290
|
type: "object",
|
|
22075
22291
|
properties: {
|
|
22076
|
-
heldCredentials: { type: "array", items: { type: "object" }, description: "VCs the client holds
|
|
22292
|
+
heldCredentials: { type: "array", items: { type: "object" }, description: "VCs the client holds. Omit to report whatever the wallet has cached locally from prior subscribe_and_issue calls instead." }
|
|
22077
22293
|
}
|
|
22078
22294
|
}
|
|
22079
22295
|
},
|
|
@@ -22084,7 +22300,7 @@ function buildToolList() {
|
|
|
22084
22300
|
type: "object",
|
|
22085
22301
|
properties: {
|
|
22086
22302
|
proofRequest: { type: "string", description: "The PROOF-REQUEST header value from the 401 challenge." },
|
|
22087
|
-
vc: { type: "object", description: "The
|
|
22303
|
+
vc: { type: "object", description: "The VerifiableCredential to present. Omit to use the wallet's single locally-cached credential, if there is exactly one \u2014 the call fails with a clear error if none or several are cached." },
|
|
22088
22304
|
revealAttribute: { type: "array", items: { type: "string" }, description: "Dotted disclosure paths to reveal. Omit to reveal exactly the claims the challenge (DCQL) requests; a challenge naming no claims reveals all." },
|
|
22089
22305
|
issuerKeys: {
|
|
22090
22306
|
type: "object",
|
|
@@ -22095,7 +22311,7 @@ function buildToolList() {
|
|
|
22095
22311
|
description: "Optional issuer verification keys to bypass the ZID resolver when it is unreachable (e.g. Cloudflare-gated). When set, resolution is skipped."
|
|
22096
22312
|
}
|
|
22097
22313
|
},
|
|
22098
|
-
required: ["proofRequest"
|
|
22314
|
+
required: ["proofRequest"]
|
|
22099
22315
|
}
|
|
22100
22316
|
},
|
|
22101
22317
|
{
|
|
@@ -22114,13 +22330,13 @@ function buildToolList() {
|
|
|
22114
22330
|
},
|
|
22115
22331
|
{
|
|
22116
22332
|
name: "subscribe_and_issue",
|
|
22117
|
-
description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay.",
|
|
22333
|
+
description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. If a still-valid credential for this templateId is already cached locally, it is returned directly with no payment (fromCache: true) \u2014 pass forceReissue:true to pay and issue fresh regardless. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay.",
|
|
22118
22334
|
inputSchema: {
|
|
22119
22335
|
type: "object",
|
|
22120
22336
|
properties: {
|
|
22121
22337
|
templateId: {
|
|
22122
22338
|
type: "string",
|
|
22123
|
-
description: `The MBI credential-definition id to issue, e.g. "did:zid:...". Take this from the x401 challenge's credential_requirements.query.credentials[].id \u2014 NOT from requirementsId (that's just a label for the requirement set, e.g. "agent-identity").`
|
|
22339
|
+
description: `The MBI credential-definition id to issue, e.g. "did:zid:...". Take this from the x401 challenge's credential_requirements.query.credentials[].id \u2014 NOT from requirementsId (that's just a label for the requirement set, e.g. "agent-identity"). A known template's natural-language name (e.g. "AI Birthcert") is also accepted and resolved to the right did:zid:... for the configured network.`
|
|
22124
22340
|
},
|
|
22125
22341
|
attributes: {
|
|
22126
22342
|
type: "object",
|
|
@@ -22130,6 +22346,10 @@ function buildToolList() {
|
|
|
22130
22346
|
dryRun: {
|
|
22131
22347
|
type: "boolean",
|
|
22132
22348
|
description: "Stop after MBI's free phase-1 quote and return { quote: { asset, maxAmountRequired, payTo, requiredAttributes? } } without paying or issuing \u2014 use this to check the payment requirement AND the template's required attributes (read from chain) before spending funds."
|
|
22349
|
+
},
|
|
22350
|
+
forceReissue: {
|
|
22351
|
+
type: "boolean",
|
|
22352
|
+
description: "Skip the local cache and pay + issue a fresh credential regardless of what is already cached."
|
|
22133
22353
|
}
|
|
22134
22354
|
},
|
|
22135
22355
|
required: ["templateId", "attributes"]
|
|
@@ -22137,13 +22357,17 @@ function buildToolList() {
|
|
|
22137
22357
|
},
|
|
22138
22358
|
{
|
|
22139
22359
|
name: "create_holder_account",
|
|
22140
|
-
description: "Create a new holder HSM account on Wallet BE (onboarding
|
|
22360
|
+
description: "Create a new holder HSM account on Wallet BE (onboarding). Ask the user for a password first \u2014 never invent one. ALWAYS check first: if an account already exists for this session, this returns { alreadyExists: true, existing: {...} } WITHOUT creating anything \u2014 ask the user whether to keep using the existing account or create a new one, then call again with confirmNew:true only if they choose new. A freshly created account is saved to this MCP's local account store and reused automatically on the next restart; an explicit ZETRIX_ADDRESS in the MCP config still overrides it.",
|
|
22141
22361
|
inputSchema: {
|
|
22142
22362
|
type: "object",
|
|
22143
22363
|
properties: {
|
|
22144
22364
|
password: { type: "string", description: "HSM password to protect the new account. Must come from the user." },
|
|
22145
22365
|
label: { type: "string" },
|
|
22146
|
-
purpose: { type: "string" }
|
|
22366
|
+
purpose: { type: "string" },
|
|
22367
|
+
confirmNew: {
|
|
22368
|
+
type: "boolean",
|
|
22369
|
+
description: "Set true to mint a new account even though one already exists for this session \u2014 only after the user has confirmed they want a new one."
|
|
22370
|
+
}
|
|
22147
22371
|
},
|
|
22148
22372
|
required: ["password"]
|
|
22149
22373
|
}
|
|
@@ -22155,7 +22379,13 @@ function asPayRequest(accept) {
|
|
|
22155
22379
|
return { ...accept, extra: { gasModel: "client", ...extra } };
|
|
22156
22380
|
}
|
|
22157
22381
|
async function main() {
|
|
22158
|
-
const
|
|
22382
|
+
const accountStore = createFsAccountStore((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "account.json"));
|
|
22383
|
+
const storedAccount = process.env.ZETRIX_ADDRESS ? null : await accountStore.get();
|
|
22384
|
+
const env = { ...process.env };
|
|
22385
|
+
if (!env.ZETRIX_ADDRESS && storedAccount) env.ZETRIX_ADDRESS = storedAccount.zetrixAddress;
|
|
22386
|
+
if (!env.HOLDER_DID && storedAccount) env.HOLDER_DID = storedAccount.holderDid;
|
|
22387
|
+
if (!env.HSM_PASSWORD && storedAccount) env.HSM_PASSWORD = storedAccount.hsmPassword;
|
|
22388
|
+
const config2 = loadConfig(env);
|
|
22159
22389
|
const hsmPassword = config2.hsmPassword;
|
|
22160
22390
|
const be = new WalletBeClient(config2.walletBeUrl);
|
|
22161
22391
|
const { zetrixAddress, holderDid, created, didMismatch } = await resolveHolder(
|
|
@@ -22166,8 +22396,14 @@ async function main() {
|
|
|
22166
22396
|
{ zetrixAddress: config2.zetrixAddress, holderDid: config2.holderDid, hsmPassword }
|
|
22167
22397
|
);
|
|
22168
22398
|
if (created) {
|
|
22399
|
+
await accountStore.set({ zetrixAddress, holderDid, hsmPassword, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
22400
|
+
process.stderr.write(
|
|
22401
|
+
`agentic-wallet-mcp: no ZETRIX_ADDRESS was set \u2014 created a new HSM account and saved it (address, DID, and password) to ~/.agentic-wallet-mcp/account.json; it will be reused automatically next run. ZETRIX_ADDRESS=${zetrixAddress} (HOLDER_DID=${holderDid} is optional; it re-derives automatically).
|
|
22402
|
+
`
|
|
22403
|
+
);
|
|
22404
|
+
} else if (storedAccount && config2.zetrixAddress === storedAccount.zetrixAddress) {
|
|
22169
22405
|
process.stderr.write(
|
|
22170
|
-
`agentic-wallet-mcp:
|
|
22406
|
+
`agentic-wallet-mcp: using the holder account saved in ~/.agentic-wallet-mcp/account.json (ZETRIX_ADDRESS=${zetrixAddress}) \u2014 no ZETRIX_ADDRESS/HSM_PASSWORD was set in the MCP config.
|
|
22171
22407
|
`
|
|
22172
22408
|
);
|
|
22173
22409
|
}
|
|
@@ -22186,7 +22422,9 @@ async function main() {
|
|
|
22186
22422
|
const resolveSymbol = (asset) => resolveAssetSymbol(asset, contractQuery);
|
|
22187
22423
|
const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
|
|
22188
22424
|
const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
|
|
22189
|
-
const
|
|
22425
|
+
const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
|
|
22426
|
+
const cacheScope = (0, import_node_crypto2.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
|
|
22427
|
+
const vcCache = createFsVcCache((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
|
|
22190
22428
|
const pay = (accept) => {
|
|
22191
22429
|
assertWithinPaymentCap(accept, config2.maxPaymentAmount);
|
|
22192
22430
|
return import_x402_zetrix_client.PaymentEngine.pay(asPayRequest(accept), walletCfg, node, {}, walletBeSignerFn);
|
|
@@ -22229,8 +22467,10 @@ async function main() {
|
|
|
22229
22467
|
config: { holderDid, zetrixAddress, network: config2.network },
|
|
22230
22468
|
makeWallet,
|
|
22231
22469
|
payer,
|
|
22232
|
-
subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid,
|
|
22233
|
-
createAccount: (password, label, purpose) => be.createAccount(password, label, purpose)
|
|
22470
|
+
subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
|
|
22471
|
+
createAccount: (password, label, purpose) => be.createAccount(password, label, purpose),
|
|
22472
|
+
saveAccount: (account) => accountStore.set({ ...account, createdAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
22473
|
+
cache: vcCache
|
|
22234
22474
|
};
|
|
22235
22475
|
const tools = createTools(deps);
|
|
22236
22476
|
const server = new Server({ name: "agentic-wallet-mcp", version: packageVersion }, { capabilities: { tools: {} } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-wallet-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Agent-facing MCP wallet for Zetrix — orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
|
|
5
5
|
"keywords": ["mcp", "model-context-protocol", "zetrix", "wallet", "x401", "x402", "blockchain"],
|
|
6
6
|
"license": "MIT",
|