agentic-wallet-mcp 0.7.0 → 0.8.0

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 CHANGED
@@ -8,6 +8,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  > Entries for 0.5.0 and earlier were reconstructed from commit history when this file was
9
9
  > introduced in 0.6.0, so they summarise each release rather than being exhaustive.
10
10
 
11
+ ## [0.8.0] — 17 August 2026
12
+
13
+ ### Added
14
+
15
+ - **`request_ai_birthcert_verification` / `check_ai_birthcert_verification` tools** — a
16
+ **Verified** AI Birthcert flow via myid's SSIVC API, distinct from `subscribe_and_issue`'s
17
+ self-declared **Basic** AI Birthcert. `request_ai_birthcert_verification` starts a session and
18
+ returns a `verificationUrl` for the human owner to complete MyDigital ID verification;
19
+ `check_ai_birthcert_verification` polls that session and, once `status: "issued"`, fetches the
20
+ credential from MBI, verifies its subject against this wallet's `holderDid`, and caches it
21
+ locally (returned as `vc`, and from then on also visible via `wallet_status` and usable by
22
+ `prove_identity`) — a `cacheError` instead of `vc` means the credential was issued but could not
23
+ yet be fetched/verified/cached, which is not the same as issuance failing. Both tools are wired
24
+ whenever `SSIVC_BASE_URL` resolves — always on testnet; on mainnet only once set explicitly,
25
+ since the mainnet host was never actually confirmed reachable. `request_ai_birthcert_verification`'s
26
+ session creation is x402-payment-gated: the tool self-pays myid's 402 challenge, subject to the
27
+ wallet's `MAX_PAYMENT_AMOUNT` cap, the same as `pay_and_fetch`/`subscribe_and_issue`. A session
28
+ that goes terminal without minting a credential (owner never verifies, verification fails, etc.)
29
+ can be retried without paying again, since the payment stays valid until actually consumed —
30
+ concurrent requests are serialized so this can't double-pay, and switching to a different agent
31
+ name while a session is still pending is refused rather than silently losing track of it. New env
32
+ vars: `SSIVC_BASE_URL` (auto-derived on testnet only — `ssivc-api-uat.myegdev.com/api`; unset on
33
+ mainnet unless overridden) and `AI_BIRTHCERT_VERIFIED_TEMPLATE_ID` (same pattern — testnet only,
34
+ since the mainnet template id was never confirmed on-chain).
35
+
36
+ ### Fixed
37
+
38
+ - **A malformed or unexpectedly-shaped response from MBI's credential-download endpoint no longer
39
+ crashes `check_ai_birthcert_verification` with a raw, undiagnosable error.** Found via live
40
+ testing against a real verification flow: MBI's response envelope turned out to be nested one
41
+ level deeper than expected. Both the crash and the actual envelope shape are now handled
42
+ correctly, confirmed end-to-end against a live credential issuance.
43
+ - **A downloaded-but-not-yet-cached credential is no longer lost if it fails validation or the
44
+ process crashes before caching.** MBI's download is one-shot — a second attempt just fails — so
45
+ the raw response is now persisted immediately on arrival and re-validated from that copy on any
46
+ retry, instead of being discarded the moment a check (subject match, expiry) rejects it.
47
+ - Fixed several smaller gaps found in the same review: an already-settled payment blob and an empty
48
+ payment-options response now return a clean error instead of an unhandled exception; a 409 with a
49
+ non-JSON body still reports the right error kind; an unrecognized session status is no longer
50
+ assumed safe to retry against; and switching agents no longer risks silently overwriting another
51
+ agent's still-unresolved payment.
52
+
11
53
  ## [0.7.0] — 4 August 2026
12
54
 
13
55
  **Read the breaking change before upgrading if you make x402 payments.** This release lets the wallet
package/README.md CHANGED
@@ -27,6 +27,8 @@ VC issuance → identity proof → pay-per-use).
27
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
28
  | `get_template_schema` | **Free** read of a VC template's declared attribute schema. Call before `subscribe_and_issue` to learn which attributes it requires | `{ templateId }` | `{ templateId, schema: { required, optional } }` or `{ templateId, error }` |
29
29
  | `query_contract` | Read-only query against any Zetrix contract — call an arbitrary method and return its raw result. No signing, no state change | `{ contractAddress, method, params? }` | `{ ok: true, result }` or `{ ok: false, error }` |
30
+ | `request_ai_birthcert_verification` | Start a **Verified** AI Birthcert issuance session with myid (MyDigital ID owner verification) — distinct from `subscribe_and_issue`'s self-declared Basic AI Birthcert | `{ agentName, agentPurpose?, evidenceAssuranceLevel?, ownerType?, ownerVerified? }` | `{ sessionId, verificationUrl, expiresAt }` |
31
+ | `check_ai_birthcert_verification` | Poll the most recently requested Verified AI Birthcert session; on `status: "issued"`, also fetches, verifies, and caches the credential | (none) | `{ status: "pending" \| "issued" \| "no_session", vcId?, vc?, cacheError? }` |
30
32
 
31
33
  > **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
32
34
  > (scoped per network + holder — different identities or networks never share a cache).
@@ -174,8 +176,10 @@ Node ≥ 18 required (built-in `fetch`).
174
176
  | `OID4VP_BASE_URL` | no | OID4VP verifier base URL override — auto-derived from `ZETRIX_NETWORK` by the x401 SDK when not set |
175
177
  | `ZETRIX_NODE_HOST` / `ZETRIX_NODE_PORT` | no | RPC node override (auto-derived from network) |
176
178
  | `ZID_RESOLVER_BASE_URL` | no | ZID resolver override (auto-derived from network: sandbox for testnet, prod for mainnet) |
177
- | `MAX_PAYMENT_AMOUNT` | no** | Per-asset x402 auto-pay cap — JSON `{ "<asset>": "<maxRawUnits>", "*": "<fallback>" }`. `pay_and_fetch`/`subscribe_and_issue` are asset-agnostic: the resource server's 402 challenge may quote the native ZETRIX token (asset code `ZTX`) **or** a ZTP20 token (e.g. `JMYR`) — cap whichever assets you expect, e.g. `{"ZTX":"1000000000","JMYR":"5000000","*":"0"}`. **Defaults to `{"*":"0"}` — every payment is refused until you set this.** |
179
+ | `MAX_PAYMENT_AMOUNT` | no** | Per-asset x402 auto-pay cap — JSON `{ "<asset>": "<maxRawUnits>", "*": "<fallback>" }`. `pay_and_fetch`/`subscribe_and_issue` are asset-agnostic: the resource server's 402 challenge may quote the native ZETRIX token (asset code `ZTX`) **or** a ZTP20 token (e.g. `JMYR`) — cap whichever assets you expect. **Key by contract address, not symbol** — the challenge identifies a ZTP20 token by its contract address, so `{"JMYR":...}` never matches and falls through to `"*"`. e.g. `{"ZTX":"1000000000","ZTX3WeinXtt28YMyr4vUZ14ddTgEMGeuc1e6b":"5000000","*":"0"}`. **Defaults to `{"*":"0"}` — every payment is refused until you set this.** |
178
180
  | `ZETRIX_WALLET_STATE_DIR` | no | Where the wallet keeps `account.json` and its VC cache. Defaults to `~/.agentic-wallet-mcp` |
181
+ | `SSIVC_BASE_URL` | no | myid's SSIVC API base URL, for the Verified AI Birthcert flow (`request_ai_birthcert_verification`/`check_ai_birthcert_verification`). **Auto-derived per network** — testnet `https://ssivc-api-uat.myegdev.com/api`, mainnet `https://verifyid-api.zetrix.com/api`. Override only if either changes |
182
+ | `AI_BIRTHCERT_VERIFIED_TEMPLATE_ID` | no | The Verified AI Birthcert's on-chain `did:zid:...` template id. Auto-derived per network by default — **the mainnet default is unverified**, so override this explicitly once the mainnet template id is confirmed |
179
183
 
180
184
  \* sensitive — never logged, never returned in a tool result, and never a tool parameter.
181
185
 
@@ -12540,15 +12540,15 @@ __export(index_exports, {
12540
12540
  buildToolList: () => buildToolList
12541
12541
  });
12542
12542
  module.exports = __toCommonJS(index_exports);
12543
- var import_node_crypto3 = require("node:crypto");
12543
+ var import_node_crypto5 = require("node:crypto");
12544
12544
  var import_node_fs = require("node:fs");
12545
12545
  var import_node_os2 = require("node:os");
12546
- var import_node_path4 = require("node:path");
12546
+ var import_node_path6 = require("node:path");
12547
12547
 
12548
12548
  // package.json
12549
12549
  var package_default = {
12550
12550
  name: "agentic-wallet-mcp",
12551
- version: "0.7.0",
12551
+ version: "0.8.0",
12552
12552
  description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
12553
12553
  keywords: [
12554
12554
  "mcp",
@@ -21561,6 +21561,12 @@ function deriveMbiBaseUrl(network) {
21561
21561
  function deriveTemplateRegistryAddress(network) {
21562
21562
  return network.includes("testnet") ? "ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz" : "ZTX3GqJM1U6ifMPonwD4fGvrgoTKJua7b2cKX";
21563
21563
  }
21564
+ function deriveSsivcBaseUrl(network) {
21565
+ return network.includes("testnet") ? "https://ssivc-api-uat.myegdev.com/api" : void 0;
21566
+ }
21567
+ function deriveAiBirthcertVerifiedTemplateId(network) {
21568
+ return network.includes("testnet") ? "did:zid:9641ee92552e9bcec672f300b071ff86d340ac78c83c225e95971cab8108fb80" : void 0;
21569
+ }
21564
21570
  var TOKEN_REGISTRY = {
21565
21571
  JMYR: { testnet: "ZTX3WeinXtt28YMyr4vUZ14ddTgEMGeuc1e6b", mainnet: "ZTX3NCkXBqbyJWjZZxciQez945Lu6tGAcjNJr" }
21566
21572
  };
@@ -21597,10 +21603,15 @@ function loadConfig(env) {
21597
21603
  nodePort: opt("ZETRIX_NODE_PORT") ?? "",
21598
21604
  templateRegistryAddress: opt("ZETRIX_TEMPLATE_REGISTRY_ADDRESS") ?? deriveTemplateRegistryAddress(network),
21599
21605
  zidResolverBaseUrl: stripTrailingSlash(opt("ZID_RESOLVER_BASE_URL") ?? deriveZidResolverBaseUrl(network)),
21600
- // Fail closed: an unset cap means "spend nothing", not "spend anything". A wallet that
21601
- // starts with no configuration at all (plugin install, see docs/openclaw) must not be
21602
- // able to auto-pay a hostile x402 challenge. Raising it is a deliberate act.
21603
- maxPaymentAmount: parsePaymentCaps(opt("MAX_PAYMENT_AMOUNT")) ?? { "*": "0" }
21606
+ // Fail closed: an unset cap means "spend nothing", not "spend anything". A wallet that starts
21607
+ // with no configuration at all must not be able to auto-pay a hostile x402 challenge. Raising
21608
+ // it is a deliberate act.
21609
+ maxPaymentAmount: parsePaymentCaps(opt("MAX_PAYMENT_AMOUNT")) ?? { "*": "0" },
21610
+ ssivcBaseUrl: (() => {
21611
+ const v = opt("SSIVC_BASE_URL") ?? deriveSsivcBaseUrl(network);
21612
+ return v ? stripTrailingSlash(v) : void 0;
21613
+ })(),
21614
+ aiBirthcertVerifiedTemplateId: opt("AI_BIRTHCERT_VERIFIED_TEMPLATE_ID") ?? deriveAiBirthcertVerifiedTemplateId(network)
21604
21615
  };
21605
21616
  }
21606
21617
 
@@ -22002,6 +22013,18 @@ var MbiClient = class _MbiClient {
22002
22013
  if (!res.ok) throw await this.error(res, "vp/ext/submit failed");
22003
22014
  return this.unwrap(res);
22004
22015
  }
22016
+ /** POST /v1/vc/ext/download — holder-authenticated; returns EVERY VC for the address, not one. */
22017
+ async downloadVcs(body, auth) {
22018
+ const res = await this.fetch("POST", "/v1/vc/ext/download", body, { signedData: auth.signedData, publicKey: auth.publicKey });
22019
+ if (!res.ok) throw await this.error(res, "vc/ext/download failed");
22020
+ const raw = await this.unwrap(res);
22021
+ const nested = raw !== null && typeof raw === "object" ? raw.data : void 0;
22022
+ const data = Array.isArray(raw) ? raw : Array.isArray(nested) ? nested : void 0;
22023
+ if (!data) {
22024
+ throw new MbiError(`MBI vc/ext/download succeeded (2xx) but returned a non-array data envelope: ${JSON.stringify(raw)}`, res.status);
22025
+ }
22026
+ return data;
22027
+ }
22005
22028
  fetch(method, path, body, extraHeaders) {
22006
22029
  const headers = { Accept: "application/json", ...extraHeaders ?? {} };
22007
22030
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -22820,6 +22843,7 @@ async function loadValidCachedCredentials(cache) {
22820
22843
  const all = await cache.list();
22821
22844
  return all.filter((entry) => isVcValid(entry));
22822
22845
  }
22846
+ var AI_BIRTHCERT_NOT_CONFIGURED_ERROR = "AI Birthcert verification is not configured on this wallet. On mainnet this is expected until SSIVC_BASE_URL is set explicitly (the mainnet host was never confirmed reachable \u2014 APP-M04); on testnet it means verifyAiBirthcert was not wired at all.";
22823
22847
  function createTools(deps) {
22824
22848
  return {
22825
22849
  async wallet_status(input = {}) {
@@ -22922,6 +22946,18 @@ function createTools(deps) {
22922
22946
  },
22923
22947
  input
22924
22948
  );
22949
+ },
22950
+ request_ai_birthcert_verification(input) {
22951
+ if (!deps.verifyAiBirthcert) {
22952
+ return { error: AI_BIRTHCERT_NOT_CONFIGURED_ERROR };
22953
+ }
22954
+ return deps.verifyAiBirthcert.request(input);
22955
+ },
22956
+ check_ai_birthcert_verification() {
22957
+ if (!deps.verifyAiBirthcert) {
22958
+ return { error: AI_BIRTHCERT_NOT_CONFIGURED_ERROR };
22959
+ }
22960
+ return deps.verifyAiBirthcert.check();
22925
22961
  }
22926
22962
  };
22927
22963
  }
@@ -22938,12 +22974,12 @@ async function resolveHolder(deps, input) {
22938
22974
  finalActivated = false;
22939
22975
  }
22940
22976
  }
22941
- return { zetrixAddress, holderDid: deriveHolderDid(publicKeyHex), created: true, didMismatch: false, activated: finalActivated };
22977
+ return { zetrixAddress, holderDid: deriveHolderDid(publicKeyHex), publicKeyHex, created: true, didMismatch: false, activated: finalActivated };
22942
22978
  }
22943
22979
  const { publicKey } = await deps.signMessage(input.zetrixAddress, input.zetrixAddress, input.hsmPassword);
22944
22980
  const derivedDid = deriveHolderDid(publicKey);
22945
22981
  const didMismatch = input.holderDid !== void 0 && input.holderDid !== derivedDid;
22946
- return { zetrixAddress: input.zetrixAddress, holderDid: derivedDid, created: false, didMismatch };
22982
+ return { zetrixAddress: input.zetrixAddress, holderDid: derivedDid, publicKeyHex: publicKey, created: false, didMismatch };
22947
22983
  }
22948
22984
 
22949
22985
  // src/startup-env.ts
@@ -22985,12 +23021,12 @@ function configPathFrom(argv) {
22985
23021
  if (!path || path.startsWith("--")) throw new Error("agentic-wallet-mcp: --config requires a file path");
22986
23022
  return path;
22987
23023
  }
22988
- function loadConfigFileEnv(argv, readFile3) {
23024
+ function loadConfigFileEnv(argv, readFile5) {
22989
23025
  const path = configPathFrom(argv);
22990
23026
  if (path === null) return {};
22991
23027
  let raw;
22992
23028
  try {
22993
- raw = readFile3(path);
23029
+ raw = readFile5(path);
22994
23030
  } catch (e) {
22995
23031
  throw new Error(`agentic-wallet-mcp: cannot read config file ${path}: ${e.message}`);
22996
23032
  }
@@ -23007,7 +23043,7 @@ function loadConfigFileEnv(argv, readFile3) {
23007
23043
  for (const [key, value] of Object.entries(parsed)) {
23008
23044
  if (FORBIDDEN_KEYS.includes(key)) {
23009
23045
  throw new Error(
23010
- `agentic-wallet-mcp: config file ${path} must not contain a secret ("${key}") \u2014 the wallet generates and stores its own HSM password; see docs/openclaw/DEPLOYMENT_DESIGN.md`
23046
+ `agentic-wallet-mcp: config file ${path} must not contain a secret ("${key}") \u2014 the wallet generates and stores its own HSM password, so no credential belongs in this file.`
23011
23047
  );
23012
23048
  }
23013
23049
  const envKey = KEY_TO_ENV[key];
@@ -23084,6 +23120,363 @@ function createFsAccountStore(filePath) {
23084
23120
  };
23085
23121
  }
23086
23122
 
23123
+ // src/clients/ssivc-client.ts
23124
+ var SsivcError = class extends Error {
23125
+ httpStatus;
23126
+ /** SSIVC's own `status_code` string (distinct from the HTTP status) — e.g. "50" signature invalid, "55" expired timestamp, "23" not found. */
23127
+ statusCode;
23128
+ /** Classifies the x402-era error responses — see ADDENDUM_X402_SESSION_GATING.md §6. Undefined for the older 400/404/malformed-envelope cases. */
23129
+ kind;
23130
+ constructor(message, httpStatus, statusCode, kind) {
23131
+ super(message);
23132
+ this.name = "SsivcError";
23133
+ this.httpStatus = httpStatus;
23134
+ this.statusCode = statusCode;
23135
+ this.kind = kind;
23136
+ }
23137
+ };
23138
+ var SESSIONS_PATH = "/v2/verify/ai-birthcert/sessions";
23139
+ var SsivcClient = class {
23140
+ baseUrl;
23141
+ constructor(baseUrl) {
23142
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
23143
+ }
23144
+ /** Phase 1 — POST with no payment headers. SSIVC always responds 402 with the raw x402 envelope. */
23145
+ async createSessionChallenge(body) {
23146
+ const res = await this.fetch("POST", SESSIONS_PATH, body);
23147
+ if (res.status !== 402) throw await this.error(res, "createSessionChallenge: expected 402 payment_required");
23148
+ const raw = await res.json();
23149
+ return { x402Version: raw.x402Version, accepts: raw.accepts ?? [] };
23150
+ }
23151
+ /** Fresh pay — retry with `X-Payment`. Returns the session plus the settlement receipt to persist. */
23152
+ createSessionSettle(body, xPayment) {
23153
+ return this.createSessionPaid(body, { "X-Payment": xPayment });
23154
+ }
23155
+ /** Retry after a failed-issuance terminal session — replays a still-unconsumed settlement receipt. Never sent alongside `X-Payment`. */
23156
+ createSessionWithReceipt(body, paymentReceipt) {
23157
+ return this.createSessionPaid(body, { "X-Payment-Response": paymentReceipt });
23158
+ }
23159
+ async createSessionPaid(body, paymentHeader) {
23160
+ const res = await this.fetch("POST", SESSIONS_PATH, body, paymentHeader);
23161
+ if (!res.ok) throw await this.error(res, "createSession (paid) failed");
23162
+ const env = await res.json();
23163
+ const data = env.data;
23164
+ if (!data || typeof data !== "object" || !data.sessionId || !data.verification_url || !data.expiresAt) {
23165
+ throw new SsivcError("SSIVC createSession succeeded (2xx) but returned a malformed data envelope", res.status);
23166
+ }
23167
+ const paymentReceipt = res.headers.get("x-payment-response");
23168
+ if (!paymentReceipt) {
23169
+ throw new SsivcError("SSIVC createSession succeeded (2xx) but returned no X-Payment-Response settlement receipt header", res.status);
23170
+ }
23171
+ return {
23172
+ session: { sessionId: data.sessionId, verificationUrl: data.verification_url, expiresAt: data.expiresAt },
23173
+ paymentReceipt
23174
+ };
23175
+ }
23176
+ async getSession(sessionId) {
23177
+ const res = await this.fetch("GET", `${SESSIONS_PATH}/${encodeURIComponent(sessionId)}`);
23178
+ if (!res.ok) throw await this.error(res);
23179
+ const env = await res.json();
23180
+ const data = env.data;
23181
+ if (!data || typeof data !== "object" || typeof data.sessionId !== "string" || typeof data.status !== "string") {
23182
+ throw new SsivcError("SSIVC getSession succeeded (2xx) but returned a malformed data envelope", res.status);
23183
+ }
23184
+ return data;
23185
+ }
23186
+ fetch(method, path, body, extraHeaders) {
23187
+ const headers = { Accept: "application/json", ...extraHeaders ?? {} };
23188
+ if (body !== void 0) headers["Content-Type"] = "application/json";
23189
+ return fetch(`${this.baseUrl}${path}`, {
23190
+ method,
23191
+ headers,
23192
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
23193
+ }).catch((e) => {
23194
+ throw new SsivcError(`SSIVC ${path} request failed: ${e.message}`);
23195
+ });
23196
+ }
23197
+ async error(res, context) {
23198
+ const text = await res.text().catch(() => "");
23199
+ let msg = text;
23200
+ let statusCode;
23201
+ let kind;
23202
+ if (res.status === 409) kind = "blob_already_settled";
23203
+ else if (res.status === 400 || res.status === 422) kind = "validation";
23204
+ try {
23205
+ const j = JSON.parse(text);
23206
+ statusCode = j.status_code;
23207
+ msg = j.errors?.length ? j.errors.join("; ") : j.message ?? j.error ?? text;
23208
+ if (res.status === 402 && j.error === "payment_invalid") kind = "payment_invalid";
23209
+ else if (res.status === 503 && j.error === "facilitator_unavailable") kind = "facilitator_unavailable";
23210
+ } catch {
23211
+ }
23212
+ const prefix = context ? `${context} \u2014 ` : "";
23213
+ return new SsivcError(`${prefix}SSIVC request failed \u2014 HTTP ${res.status}: ${msg}`, res.status, statusCode, kind);
23214
+ }
23215
+ };
23216
+
23217
+ // src/clients/ssivc-session-store.ts
23218
+ var import_promises3 = require("node:fs/promises");
23219
+ var import_node_path4 = require("node:path");
23220
+ function isStoredSessionShape(value) {
23221
+ if (typeof value !== "object" || value === null) return false;
23222
+ const v = value;
23223
+ return typeof v.sessionId === "string" && typeof v.agentName === "string" && typeof v.createdAt === "string" && typeof v.verificationUrl === "string" && typeof v.paymentReceipt === "string";
23224
+ }
23225
+ function createFsSsivcSessionStore(filePath) {
23226
+ return {
23227
+ async get() {
23228
+ try {
23229
+ const raw = await (0, import_promises3.readFile)(filePath, "utf8");
23230
+ const parsed = JSON.parse(raw);
23231
+ return isStoredSessionShape(parsed) ? parsed : null;
23232
+ } catch {
23233
+ return null;
23234
+ }
23235
+ },
23236
+ async set(session) {
23237
+ await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(filePath), { recursive: true, mode: 448 });
23238
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
23239
+ await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(session, null, 2), { encoding: "utf8", mode: 384 });
23240
+ await (0, import_promises3.rename)(tmpPath, filePath);
23241
+ }
23242
+ };
23243
+ }
23244
+
23245
+ // src/clients/ssivc-download-quarantine-store.ts
23246
+ var import_node_crypto3 = require("node:crypto");
23247
+ var import_promises4 = require("node:fs/promises");
23248
+ var import_node_path5 = require("node:path");
23249
+ function quarantineFileName(vcId) {
23250
+ return `${(0, import_node_crypto3.createHash)("sha256").update(vcId).digest("hex")}.json`;
23251
+ }
23252
+ function isQuarantinedDownloadShape(value) {
23253
+ if (typeof value !== "object" || value === null) return false;
23254
+ const v = value;
23255
+ return typeof v.vcId === "string" && "entries" in v && typeof v.downloadedAt === "string";
23256
+ }
23257
+ function createFsDownloadQuarantineStore(baseDir) {
23258
+ const pathFor = (vcId) => (0, import_node_path5.join)(baseDir, quarantineFileName(vcId));
23259
+ return {
23260
+ filePathFor: pathFor,
23261
+ async get(vcId) {
23262
+ try {
23263
+ const raw = await (0, import_promises4.readFile)(pathFor(vcId), "utf8");
23264
+ const parsed = JSON.parse(raw);
23265
+ return isQuarantinedDownloadShape(parsed) ? parsed : null;
23266
+ } catch {
23267
+ return null;
23268
+ }
23269
+ },
23270
+ async set(entry) {
23271
+ await (0, import_promises4.mkdir)(baseDir, { recursive: true, mode: 448 });
23272
+ const filePath = pathFor(entry.vcId);
23273
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
23274
+ await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(entry, null, 2), { encoding: "utf8", mode: 384 });
23275
+ await (0, import_promises4.rename)(tmpPath, filePath);
23276
+ }
23277
+ };
23278
+ }
23279
+
23280
+ // src/orchestrator/verify-ai-birthcert.ts
23281
+ var import_node_crypto4 = require("node:crypto");
23282
+
23283
+ // src/canonical-json.ts
23284
+ function canonicalizeJson(value) {
23285
+ if (Array.isArray(value)) {
23286
+ return `[${value.map((v) => canonicalizeJson(v)).join(",")}]`;
23287
+ }
23288
+ if (value !== null && typeof value === "object") {
23289
+ const record2 = value;
23290
+ const keys = Object.keys(record2).filter((k) => record2[k] !== void 0).sort();
23291
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeJson(record2[k])}`);
23292
+ return `{${parts.join(",")}}`;
23293
+ }
23294
+ return JSON.stringify(value);
23295
+ }
23296
+
23297
+ // src/orchestrator/verify-ai-birthcert.ts
23298
+ function subjectMatches(vc, holderDid) {
23299
+ if (typeof vc !== "object" || vc === null) return false;
23300
+ const subject = vc.credentialSubject;
23301
+ const subjectId = typeof subject === "object" && subject !== null ? subject.id : void 0;
23302
+ return subjectId === holderDid;
23303
+ }
23304
+ function observedSubjectId(vc) {
23305
+ if (typeof vc !== "object" || vc === null) return "missing";
23306
+ const subject = vc.credentialSubject;
23307
+ const subjectId = typeof subject === "object" && subject !== null ? subject.id : void 0;
23308
+ return typeof subjectId === "string" && subjectId ? subjectId : "missing";
23309
+ }
23310
+ function isoSeconds(date3) {
23311
+ return date3.toISOString().replace(/\.\d{3}Z$/, "Z");
23312
+ }
23313
+ async function getConfirmedStatus(deps, sessionId) {
23314
+ try {
23315
+ return await deps.ssivc.getSession(sessionId);
23316
+ } catch (err) {
23317
+ if (!(err instanceof SsivcError) || err.httpStatus !== 404) throw err;
23318
+ return "gone";
23319
+ }
23320
+ }
23321
+ async function decidePriorSession(deps, agentName) {
23322
+ const stored = await deps.sessionStore.get();
23323
+ if (!stored) return { kind: "pay_fresh" };
23324
+ if (stored.agentName !== agentName) {
23325
+ const otherStatus = await getConfirmedStatus(deps, stored.sessionId);
23326
+ if (otherStatus === "gone") return { kind: "replay_receipt", receipt: stored.paymentReceipt };
23327
+ if (otherStatus.status !== "issued") {
23328
+ return {
23329
+ kind: "blocked",
23330
+ message: `a verification session for a different agent ("${stored.agentName}") is still in progress (status: "${otherStatus.status}") and its payment has not been consumed yet \u2014 starting a new session for "${agentName}" would lose track of it. Call check_ai_birthcert_verification to resolve or confirm it first.`
23331
+ };
23332
+ }
23333
+ return { kind: "pay_fresh" };
23334
+ }
23335
+ const status = await getConfirmedStatus(deps, stored.sessionId);
23336
+ if (status === "gone") return { kind: "replay_receipt", receipt: stored.paymentReceipt };
23337
+ if (status.status === "pending") {
23338
+ return {
23339
+ kind: "still_pending",
23340
+ result: { sessionId: stored.sessionId, verificationUrl: stored.verificationUrl, expiresAt: status.expiresAt }
23341
+ };
23342
+ }
23343
+ if (status.status === "issued") return { kind: "pay_fresh" };
23344
+ throw new Error(
23345
+ `requestAiBirthcertVerification: unrecognized prior session status "${status.status}" for session ${stored.sessionId} \u2014 refusing to guess whether its settlement receipt is safe to replay (see SPEC.md D8)`
23346
+ );
23347
+ }
23348
+ var requestQueue = Promise.resolve();
23349
+ function withRequestLock(fn) {
23350
+ const run = requestQueue.then(fn, fn);
23351
+ requestQueue = run.then(
23352
+ () => void 0,
23353
+ () => void 0
23354
+ );
23355
+ return run;
23356
+ }
23357
+ var NoPaymentOptionsError = class extends Error {
23358
+ };
23359
+ async function payAndCreateSession(deps, body) {
23360
+ const challenge = await deps.ssivc.createSessionChallenge(body);
23361
+ const accept = challenge.accepts[0];
23362
+ if (!accept) throw new NoPaymentOptionsError("SSIVC 402 returned no payment options");
23363
+ const xPayment = await deps.pay(accept);
23364
+ return deps.ssivc.createSessionSettle(body, xPayment);
23365
+ }
23366
+ async function requestAiBirthcertVerification(deps, input) {
23367
+ if (!input.agentName || !input.agentName.trim()) {
23368
+ throw new Error("requestAiBirthcertVerification: agentName is required");
23369
+ }
23370
+ const agentName = input.agentName.trim();
23371
+ return withRequestLock(() => requestAiBirthcertVerificationLocked(deps, agentName, input));
23372
+ }
23373
+ async function requestAiBirthcertVerificationLocked(deps, agentName, input) {
23374
+ const decision = await decidePriorSession(deps, agentName);
23375
+ if (decision.kind === "still_pending") return decision.result;
23376
+ if (decision.kind === "blocked") return { error: decision.message };
23377
+ const fields = {
23378
+ publicKey: deps.publicKeyHex,
23379
+ address: deps.address,
23380
+ timestamp: isoSeconds(deps.now()),
23381
+ agentName,
23382
+ id: agentName,
23383
+ ownerReference: deps.holderDid
23384
+ };
23385
+ if (input.agentPurpose) fields.agentPurpose = input.agentPurpose;
23386
+ if (input.evidenceAssuranceLevel) fields.evidenceAssuranceLevel = input.evidenceAssuranceLevel;
23387
+ if (input.ownerType) fields.ownerType = input.ownerType;
23388
+ if (input.ownerVerified) fields.ownerVerified = input.ownerVerified;
23389
+ const digestHex = (0, import_node_crypto4.createHash)("sha256").update(canonicalizeJson(fields), "utf8").digest("hex");
23390
+ const { signBlob: signedData } = await deps.signHexBlob(digestHex);
23391
+ const body = { ...fields, signedData };
23392
+ let paid;
23393
+ try {
23394
+ paid = decision.kind === "replay_receipt" ? await deps.ssivc.createSessionWithReceipt(body, decision.receipt) : await payAndCreateSession(deps, body);
23395
+ } catch (err) {
23396
+ if (err instanceof PaymentReadinessError) return { error: `insufficient funds: ${err.message}` };
23397
+ if (err instanceof PaymentCapError) return { error: err.message };
23398
+ if (err instanceof NoPaymentOptionsError) return { error: err.message };
23399
+ if (err instanceof SsivcError && err.kind === "blob_already_settled") {
23400
+ return { error: `payment already settled for this attempt: ${err.message}` };
23401
+ }
23402
+ throw err;
23403
+ }
23404
+ await deps.sessionStore.set({
23405
+ sessionId: paid.session.sessionId,
23406
+ agentName,
23407
+ createdAt: deps.now().toISOString(),
23408
+ verificationUrl: paid.session.verificationUrl,
23409
+ paymentReceipt: paid.paymentReceipt
23410
+ });
23411
+ return paid.session;
23412
+ }
23413
+ async function checkAiBirthcertVerification(deps) {
23414
+ const stored = await deps.sessionStore.get();
23415
+ if (!stored) {
23416
+ return {
23417
+ status: "no_session",
23418
+ message: "No verification session found for this wallet \u2014 call request_ai_birthcert_verification first."
23419
+ };
23420
+ }
23421
+ const status = await deps.ssivc.getSession(stored.sessionId);
23422
+ if (status.status !== "issued" || !status.vcId) return status;
23423
+ if (deps.verifiedTemplateId && deps.cache) {
23424
+ const cached2 = await deps.cache.get(deps.verifiedTemplateId);
23425
+ if (cached2 && cached2.vcId === status.vcId && isVcValid(cached2)) return { ...status, vc: cached2.vc };
23426
+ }
23427
+ if (!deps.verifiedTemplateId || !deps.cache) {
23428
+ return { ...status, cacheError: "AI Birthcert verified-template id is not configured \u2014 cannot fetch or cache the credential yet." };
23429
+ }
23430
+ const quarantined = await deps.quarantine.get(status.vcId);
23431
+ const quarantineFilePath = deps.quarantine.filePathFor(status.vcId);
23432
+ let entries;
23433
+ if (quarantined) {
23434
+ entries = quarantined.entries;
23435
+ } else {
23436
+ const auth = await deps.messageSigner(deps.address).then((r) => ({ signedData: r.signBlob, publicKey: r.publicKey }));
23437
+ try {
23438
+ entries = await deps.mbi.downloadVcs({ address: deps.address }, auth);
23439
+ } catch (err) {
23440
+ return { ...status, cacheError: `failed to fetch credential from MBI: ${err instanceof Error ? err.message : String(err)}` };
23441
+ }
23442
+ await deps.quarantine.set({ vcId: status.vcId, entries, downloadedAt: (/* @__PURE__ */ new Date()).toISOString() });
23443
+ }
23444
+ const match = entries.find((e) => typeof e.vc === "object" && e.vc !== null && e.vc.id === status.vcId);
23445
+ if (!match) {
23446
+ return {
23447
+ ...status,
23448
+ cacheError: `no matching credential found in MBI's download for vcId ${status.vcId} \u2014 raw response preserved for recovery at ${quarantineFilePath}`
23449
+ };
23450
+ }
23451
+ if (!subjectMatches(match.vc, deps.holderDid)) {
23452
+ return {
23453
+ ...status,
23454
+ cacheError: `downloaded credential subject (${observedSubjectId(match.vc)}) does not match this wallet's holderDid (${deps.holderDid}) \u2014 refusing to cache; raw response preserved for recovery at ${quarantineFilePath}`
23455
+ };
23456
+ }
23457
+ const validUntil = extractValidUntil(match.vc);
23458
+ if (!validUntil) {
23459
+ return {
23460
+ ...status,
23461
+ cacheError: `downloaded credential has no validUntil \u2014 refusing to cache indefinitely; raw response preserved for recovery at ${quarantineFilePath}`
23462
+ };
23463
+ }
23464
+ if (!isVcValid({ validUntil })) {
23465
+ return {
23466
+ ...status,
23467
+ cacheError: `downloaded credential expired at ${validUntil} \u2014 refusing to cache or return; raw response preserved for recovery at ${quarantineFilePath}`
23468
+ };
23469
+ }
23470
+ await deps.cache.set(deps.verifiedTemplateId, {
23471
+ templateId: deps.verifiedTemplateId,
23472
+ vc: match.vc,
23473
+ vcId: status.vcId,
23474
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
23475
+ validUntil
23476
+ });
23477
+ return { ...status, vc: match.vc };
23478
+ }
23479
+
23087
23480
  // src/index.ts
23088
23481
  var packageVersion = package_default.version;
23089
23482
  function buildToolList() {
@@ -23163,13 +23556,13 @@ function buildToolList() {
23163
23556
  },
23164
23557
  {
23165
23558
  name: "subscribe_and_issue",
23166
- 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. What a call actually cost is reported precisely: paidAsset/amountPaid are set ONLY when this call paid, a cache hit reports the earlier charge under originalPayment instead (never as amountPaid, so summing spend cannot double-count), and any failure after the payment has settled on chain reports paymentAttempted: { asset, amount, paymentId }. Two such failures exist and mean different things: MBI 4006 is a definitive facilitator rejection, while 4012 (HTTP 502) means the outcome is INDETERMINATE \u2014 the payment may well have landed. On 4012 the wallet automatically polls MBI's recovery endpoint and reports recovery: { status, txHash?, vcId?, polls }, where status is ISSUED (the credential exists after all \u2014 fetch it by vcId, since recovery returns no VC body), FAILED, or REQUIRED/SETTLED (still unresolved) / UNKNOWN (recovery itself unreachable). NEVER retry a payment after either failure: the funds may already be gone, and a retry charges the full amount again \u2014 look the paymentId up instead. Every response except a cache hit also includes { schema: { required, optional } } \u2014 the template's full declared attribute schema read from chain \u2014 so you see the complete field list, not just what went wrong; a cache hit skips the chain lookup and omits it.",
23559
+ 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. What a call actually cost is reported precisely: paidAsset/amountPaid are set ONLY when this call paid, a cache hit reports the earlier charge under originalPayment instead (never as amountPaid, so summing spend cannot double-count), and any failure after the payment has settled on chain reports paymentAttempted: { asset, amount, paymentId }. Two such failures exist and mean different things: MBI 4006 is a definitive facilitator rejection, while 4012 (HTTP 502) means the outcome is INDETERMINATE \u2014 the payment may well have landed. On 4012 the wallet automatically polls MBI's recovery endpoint and reports recovery: { status, txHash?, vcId?, polls }, where status is ISSUED (the credential exists after all \u2014 fetch it by vcId, since recovery returns no VC body), FAILED, or REQUIRED/SETTLED (still unresolved) / UNKNOWN (recovery itself unreachable). NEVER retry a payment after either failure: the funds may already be gone, and a retry charges the full amount again \u2014 look the paymentId up instead. Every response except a cache hit also includes { schema: { required, optional } } \u2014 the template's full declared attribute schema read from chain \u2014 so you see the complete field list, not just what went wrong; a cache hit skips the chain lookup and omits it. For the AI Birthcert specifically: this issues the BASIC one \u2014 self-declared by the agent, agent-paid via x402, owner identity NOT identity-verified. If the user asked for a "verified" AI birthcert (owner identity confirmed via MyDigital ID), use request_ai_birthcert_verification instead \u2014 this tool cannot produce that credential.`,
23167
23560
  inputSchema: {
23168
23561
  type: "object",
23169
23562
  properties: {
23170
23563
  templateId: {
23171
23564
  type: "string",
23172
- 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.`
23565
+ 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. This resolves to the BASIC (self-declared, non-verified) template \u2014 for the Verified AI Birthcert, use request_ai_birthcert_verification, not this tool.`
23173
23566
  },
23174
23567
  attributes: {
23175
23568
  type: "object",
@@ -23188,6 +23581,29 @@ function buildToolList() {
23188
23581
  required: ["templateId", "attributes"]
23189
23582
  }
23190
23583
  },
23584
+ {
23585
+ name: "request_ai_birthcert_verification",
23586
+ description: `Start a Verified AI Birthcert issuance session with myid (MyDigital ID owner verification). Returns { sessionId, verificationUrl, expiresAt } \u2014 show verificationUrl to the human owner and ask them to open it and complete MyDigital ID verification (typically finishes in seconds). Once they confirm they are done, call check_ai_birthcert_verification to see whether the credential was issued. IMPORTANT: agentName must be unique \u2014 if this exact name has already been used to request a Verified AI Birthcert, issuance will fail. Before calling, ask the human owner whether they want to supply any of the optional fields \u2014 agentPurpose, evidenceAssuranceLevel, ownerType, ownerVerified \u2014 do not silently omit them; they only need to say no. Calling this again with the SAME agentName while a prior session is still pending returns that same session unchanged \u2014 no new session is started and nothing is paid again. This tool spends real funds: it self-pays an x402 challenge, subject to the wallet's configured MAX_PAYMENT_AMOUNT cap, the same as pay_and_fetch/subscribe_and_issue. It can return { error: "..." } instead of a session if that payment fails (insufficient funds, or the payment cap blocked it) \u2014 nothing is created in that case. If the user did NOT ask for a "verified" credential specifically, they most likely want the self-declared, non-verified Basic AI Birthcert instead \u2014 use subscribe_and_issue for that.`,
23587
+ inputSchema: {
23588
+ type: "object",
23589
+ properties: {
23590
+ agentName: {
23591
+ type: "string",
23592
+ description: "A unique, human-readable name for this agent. Must not already be in use for a Verified AI Birthcert, or issuance will fail."
23593
+ },
23594
+ agentPurpose: { type: "string", description: 'Optional \u2014 what this agent does, e.g. "Negotiate and settle supplier invoices".' },
23595
+ evidenceAssuranceLevel: { type: "string", description: 'Optional \u2014 assurance level of the identity evidence, e.g. "high".' },
23596
+ ownerType: { type: "string", description: `Optional \u2014 the owner's type, e.g. "Individual".` },
23597
+ ownerVerified: { type: "string", description: 'Optional \u2014 whether the owner is already verified, as the string "true" or "false".' }
23598
+ },
23599
+ required: ["agentName"]
23600
+ }
23601
+ },
23602
+ {
23603
+ name: "check_ai_birthcert_verification",
23604
+ description: 'Check the status of the most recently requested Verified AI Birthcert session (see request_ai_birthcert_verification). Returns { status: "pending" } while the owner has not yet completed MyDigital ID verification, or { status: "issued", vcId } once myid has minted the credential \u2014 myid returns vcId ONLY when status is "issued", never otherwise. On { status: "issued" }, the wallet also fetches the credential from MBI, verifies it, and caches it locally, returning it as `vc` \u2014 it is then also visible via wallet_status and usable by prove_identity without any further call. If `cacheError` is present instead of `vc`, the credential WAS issued successfully but could not be fetched/verified/cached yet (e.g. a transient MBI error) \u2014 this is NOT the same as issuance failing, so do not retry request_ai_birthcert_verification; call check_ai_birthcert_verification again instead. Returns { status: "no_session" } if request_ai_birthcert_verification has never been called.',
23605
+ inputSchema: { type: "object", properties: {} }
23606
+ },
23191
23607
  {
23192
23608
  name: "create_holder_account",
23193
23609
  description: "Create a new holder HSM account on Wallet BE (onboarding). 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. The wallet manages its own credentials; you neither need nor can supply any. 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.",
@@ -23211,8 +23627,8 @@ function asPayRequest(accept) {
23211
23627
  }
23212
23628
  async function main() {
23213
23629
  const fileEnv = loadConfigFileEnv(process.argv, (p) => (0, import_node_fs.readFileSync)(p, "utf8"));
23214
- const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".agentic-wallet-mcp")).replace(/\/+$/, "");
23215
- const accountStore = createFsAccountStore((0, import_node_path4.join)(stateDir, "account.json"));
23630
+ const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".agentic-wallet-mcp")).replace(/\/+$/, "");
23631
+ const accountStore = createFsAccountStore((0, import_node_path6.join)(stateDir, "account.json"));
23216
23632
  if (process.argv[2] === "export-credentials") {
23217
23633
  const { exitCode } = await exportCredentials({
23218
23634
  getAccount: () => accountStore.get(),
@@ -23233,7 +23649,7 @@ async function main() {
23233
23649
  const hsmPassword = config2.hsmPassword;
23234
23650
  const be = new WalletBeClient(config2.walletBeUrl);
23235
23651
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23236
- const { zetrixAddress, holderDid, created, didMismatch, activated } = await resolveHolder(
23652
+ const { zetrixAddress, holderDid, publicKeyHex, created, didMismatch, activated } = await resolveHolder(
23237
23653
  {
23238
23654
  createAccount: (password) => be.createAccount(password),
23239
23655
  signMessage: (message, address, password) => be.signMessage(message, address, password),
@@ -23285,8 +23701,10 @@ async function main() {
23285
23701
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
23286
23702
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
23287
23703
  const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
23288
- const cacheScope = (0, import_node_crypto3.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
23289
- const vcCache = createFsVcCache((0, import_node_path4.join)(config2.stateDir, "vc-cache", cacheScope));
23704
+ const cacheScope = (0, import_node_crypto5.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
23705
+ const vcCache = createFsVcCache((0, import_node_path6.join)(config2.stateDir, "vc-cache", cacheScope));
23706
+ const mbi = new MbiClient(config2.mbiBaseUrl);
23707
+ const messageSigner = (message) => be.signMessage(message, zetrixAddress, hsmPassword);
23290
23708
  const pay = (accept) => {
23291
23709
  assertWithinPaymentCap(accept, config2.maxPaymentAmount);
23292
23710
  return payWithReadinessCheck(
@@ -23295,6 +23713,30 @@ async function main() {
23295
23713
  activated
23296
23714
  );
23297
23715
  };
23716
+ const verifyAiBirthcert = config2.ssivcBaseUrl ? (() => {
23717
+ const ssivcSessionStore = createFsSsivcSessionStore((0, import_node_path6.join)(config2.stateDir, "ssivc-session.json"));
23718
+ const downloadQuarantine = createFsDownloadQuarantineStore((0, import_node_path6.join)(config2.stateDir, "ssivc-download-quarantine"));
23719
+ const ssivc = new SsivcClient(config2.ssivcBaseUrl);
23720
+ const verifyAiBirthcertDeps = {
23721
+ ssivc,
23722
+ signHexBlob: walletBeSignerFn,
23723
+ messageSigner,
23724
+ mbi,
23725
+ pay,
23726
+ publicKeyHex,
23727
+ address: zetrixAddress,
23728
+ holderDid,
23729
+ now: () => /* @__PURE__ */ new Date(),
23730
+ sessionStore: ssivcSessionStore,
23731
+ verifiedTemplateId: config2.aiBirthcertVerifiedTemplateId,
23732
+ cache: vcCache,
23733
+ quarantine: downloadQuarantine
23734
+ };
23735
+ return {
23736
+ request: (input) => requestAiBirthcertVerification(verifyAiBirthcertDeps, input),
23737
+ check: () => checkAiBirthcertVerification(verifyAiBirthcertDeps)
23738
+ };
23739
+ })() : void 0;
23298
23740
  const payer = async (req) => {
23299
23741
  const init = { method: req.method ?? "GET", headers: req.headers, body: req.body };
23300
23742
  const res = await fetch(req.url, init);
@@ -23325,8 +23767,6 @@ async function main() {
23325
23767
  };
23326
23768
  };
23327
23769
  const subscribeSign = (blob) => be.signBlob(blob, zetrixAddress, hsmPassword);
23328
- const mbi = new MbiClient(config2.mbiBaseUrl);
23329
- const messageSigner = (message) => be.signMessage(message, zetrixAddress, hsmPassword);
23330
23770
  const zidResolver = new ZidResolverClient(config2.zidResolverBaseUrl);
23331
23771
  const resolveIssuerKeys = (vc) => resolveIssuerProofKeys(vc, zidResolver);
23332
23772
  const submitAuth = async () => {
@@ -23350,7 +23790,8 @@ async function main() {
23350
23790
  saveAccount: (account) => accountStore.set({ ...account, hsmPassword, createdAt: (/* @__PURE__ */ new Date()).toISOString() }),
23351
23791
  checkActivationStatus: (address) => be.checkActivationStatus(address),
23352
23792
  sleep,
23353
- cache: vcCache
23793
+ cache: vcCache,
23794
+ verifyAiBirthcert
23354
23795
  };
23355
23796
  const tools = createTools(deps);
23356
23797
  const server = new Server({ name: "agentic-wallet-mcp", version: packageVersion }, { capabilities: { tools: {} } });
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "agentic-wallet-mcp",
3
- "version": "0.7.0",
4
- "description": "Agent-facing MCP wallet for Zetrix — orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
5
- "keywords": [
6
- "mcp",
7
- "model-context-protocol",
8
- "zetrix",
9
- "wallet",
10
- "x401",
11
- "x402",
12
- "blockchain"
13
- ],
14
- "license": "MIT",
15
- "author": "MyEG Services Berhad",
16
- "homepage": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet",
17
- "repository": {
18
- "type": "git",
19
- "url": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet.git"
20
- },
21
- "bugs": {
22
- "url": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet/issues"
23
- },
24
- "type": "module",
25
- "engines": {
26
- "node": ">=18"
27
- },
28
- "bin": {
29
- "agentic-wallet-mcp": "dist/server-bundle.cjs"
30
- },
31
- "files": [
32
- "dist/server-bundle.cjs",
33
- "README.md",
34
- "CHANGELOG.md",
35
- "LICENSE"
36
- ],
37
- "scripts": {
38
- "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node18 --format=cjs --external:zetrix-sdk-nodejs --outfile=dist/server-bundle.cjs",
39
- "test": "vitest run",
40
- "test:watch": "vitest",
41
- "typecheck": "tsc --noEmit"
42
- },
43
- "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.0.0",
45
- "x401-zetrix-client": "^0.2.1",
46
- "x402-zetrix-client": "^0.2.2",
47
- "zetrix-sdk-nodejs": "^1.0.3"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^20.0.0",
51
- "esbuild": "^0.24.0",
52
- "typescript": "^5.9.0",
53
- "vitest": "^1.6.0"
54
- }
55
- }
1
+ {
2
+ "name": "agentic-wallet-mcp",
3
+ "version": "0.8.0",
4
+ "description": "Agent-facing MCP wallet for Zetrix — orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "zetrix",
9
+ "wallet",
10
+ "x401",
11
+ "x402",
12
+ "blockchain"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "MyEG Services Berhad",
16
+ "homepage": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/Zetrix-Chain/zetrix-agentic-wallet/issues"
23
+ },
24
+ "type": "module",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "bin": {
29
+ "agentic-wallet-mcp": "dist/server-bundle.cjs"
30
+ },
31
+ "files": [
32
+ "dist/server-bundle.cjs",
33
+ "README.md",
34
+ "CHANGELOG.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node18 --format=cjs --external:zetrix-sdk-nodejs --outfile=dist/server-bundle.cjs",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.0.0",
45
+ "x401-zetrix-client": "^0.2.1",
46
+ "x402-zetrix-client": "^0.2.2",
47
+ "zetrix-sdk-nodejs": "^1.0.3"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "esbuild": "^0.24.0",
52
+ "typescript": "^5.9.0",
53
+ "vitest": "^1.6.0"
54
+ }
55
+ }