agentic-wallet-mcp 0.3.1 → 0.4.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/README.md CHANGED
@@ -20,15 +20,23 @@ 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 held VCs | `{ 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 }` |
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` | Build the signed payload pay x402 → MBI issues → return the VC | `{ templateId, attributes, expirationDate? }` | `{ issued, vcId, vc, txHash }` |
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
27
  | `create_holder_account` | Onboarding: manually mint an additional/replacement HSM account (the MCP already auto-creates one at startup if `ZETRIX_ADDRESS` is omitted — see Environment below) | `{ password, label?, purpose? }` | `{ zetrixAddress, holderDid, publicKeyHex, message }` |
28
28
 
29
- > The wallet **never persists VCs** the client holds them and passes them in (e.g. `vc` on
30
- > `prove_identity`, `heldCredentials` on `wallet_status`). All Ed25519 signing goes through
31
- > Wallet BE HSM; no plaintext private keys.
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
42
  > way to provision a pre-chosen address. It returns the new `zetrixAddress`/`holderDid` for you
@@ -126,8 +134,9 @@ network:
126
134
 
127
135
  Only set any of the four explicitly if you run your own instance of that service instead of the
128
136
  default one — an explicit value always wins over the network default. The `*.myegdev.com`
129
- (testnet) hosts are internalunreachable without VPN; see "Network reachability &
130
- troubleshooting" below if any of them time out.
137
+ (testnet) hosts are public endpoints no VPN needed; if you're on a corporate VPN and one of
138
+ them times out, disconnecting it is more likely to fix that than connecting it. See "Network
139
+ reachability & troubleshooting" below.
131
140
 
132
141
  That's the complete list — no VC-MCP subprocess, no BaaS gateway key, no manually-configured
133
142
  BBS+ key to set up.
@@ -220,13 +229,13 @@ Independent of Phases 2/3 — no VC or identity proof involved, just a fresh pay
220
229
 
221
230
  ## Network reachability & troubleshooting
222
231
 
223
- Several dependencies sit behind a corporate VPN or a CDN edge. When something that worked before
224
- suddenly times out or 403s, check this table before assuming a code regression in every case
225
- observed so far, the code was correct and the network/edge state had changed.
232
+ The `*.myegdev.com` testnet endpoints are public — no VPN needed to reach them. One dependency
233
+ sits behind a CDN edge (see the ZID resolver row below). When something that worked before
234
+ suddenly times out or 403s, check this table before assuming a code regression.
226
235
 
227
236
  | Symptom | Cause | Fix / status |
228
237
  |---|---|---|
229
- | `Wallet BE /wallet/hsm/sign-blob request failed <- fetch failed <- UND_ERR_CONNECT_TIMEOUT` (or same for `mbi-vc.myegdev.com`) | `*.myegdev.com` hosts are internal unreachable without VPN | Connect/reconnect your VPN, then retry. Verify first: `curl -I https://wallet-api.myegdev.com/server` |
238
+ | `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
239
  | `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
240
  | `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
241
  | `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. |
@@ -2233,8 +2233,8 @@ var require_resolve = __commonJS({
2233
2233
  }
2234
2234
  return count;
2235
2235
  }
2236
- function getFullPath(resolver, id = "", normalize) {
2237
- if (normalize !== false)
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 normalize(uri, options) {
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 normalize = (
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 normalize2(path2) {
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 = normalize(includePath);
8573
+ includePath = normalize2(includePath);
8574
8574
  if (isAbsolute(includePath))
8575
8575
  return includePath;
8576
8576
  if (!alreadyNormalized)
8577
- originPath = normalize(originPath);
8578
- return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize(originPath + "/" + includePath) : includePath;
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_path2 = require("node:path");
12543
12546
 
12544
12547
  // package.json
12545
12548
  var package_default = {
12546
12549
  name: "agentic-wallet-mcp",
12547
- version: "0.3.1",
12550
+ version: "0.4.0",
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 fetchTemplateRequiredFields(templateId, registryAddress, nodeBaseUrl, query) {
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
- return format.filter((e) => e && e.mandatory === 1 && typeof e.key === "string" && e.key !== "").map((e) => e.key);
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) {
@@ -21950,17 +21965,92 @@ function zetrixHexStringToBytes(s) {
21950
21965
  return out;
21951
21966
  }
21952
21967
 
21968
+ // src/clients/vc-cache.ts
21969
+ var import_node_crypto = require("node:crypto");
21970
+ var import_promises = require("node:fs/promises");
21971
+ var import_node_path = require("node:path");
21972
+ function cacheFileName(templateId) {
21973
+ return `${(0, import_node_crypto.createHash)("sha256").update(templateId).digest("hex")}.json`;
21974
+ }
21975
+ function isCachedVcShape(value) {
21976
+ return typeof value === "object" && value !== null && typeof value.templateId === "string" && "vc" in value && typeof value.issuedAt === "string";
21977
+ }
21978
+ function createFsVcCache(baseDir) {
21979
+ return {
21980
+ async get(templateId) {
21981
+ try {
21982
+ const raw = await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), "utf8");
21983
+ const parsed = JSON.parse(raw);
21984
+ return isCachedVcShape(parsed) ? parsed : null;
21985
+ } catch {
21986
+ return null;
21987
+ }
21988
+ },
21989
+ async set(templateId, entry) {
21990
+ await (0, import_promises.mkdir)(baseDir, { recursive: true, mode: 448 });
21991
+ await (0, import_promises.writeFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), JSON.stringify(entry), { encoding: "utf8", mode: 384 });
21992
+ },
21993
+ async list() {
21994
+ let files;
21995
+ try {
21996
+ files = await (0, import_promises.readdir)(baseDir);
21997
+ } catch {
21998
+ return [];
21999
+ }
22000
+ const entries = await Promise.all(
22001
+ files.filter((f) => f.endsWith(".json")).map(async (f) => {
22002
+ try {
22003
+ const parsed = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, f), "utf8"));
22004
+ return isCachedVcShape(parsed) ? parsed : null;
22005
+ } catch {
22006
+ return null;
22007
+ }
22008
+ })
22009
+ );
22010
+ return entries.filter((e) => e !== null);
22011
+ }
22012
+ };
22013
+ }
22014
+ function isVcValid(entry, now = /* @__PURE__ */ new Date()) {
22015
+ if (!entry.validUntil) return true;
22016
+ const expiry = new Date(entry.validUntil);
22017
+ if (Number.isNaN(expiry.getTime())) return false;
22018
+ return expiry.getTime() > now.getTime();
22019
+ }
22020
+ function extractValidUntil(vc, fallback) {
22021
+ if (typeof vc === "object" && vc !== null && "validUntil" in vc) {
22022
+ const v = vc.validUntil;
22023
+ if (typeof v === "string") return v;
22024
+ }
22025
+ return fallback;
22026
+ }
22027
+
21953
22028
  // src/orchestrator/subscribe.ts
21954
22029
  async function subscribeAndIssue(deps, opts) {
21955
22030
  if (!/^did:zid:/.test(opts.templateId)) {
21956
22031
  return { issued: false, reason: `templateId must be a did:zid:... credential-definition id, got "${opts.templateId}"` };
21957
22032
  }
22033
+ if (deps.cache && !opts.forceReissue && !opts.dryRun) {
22034
+ const cached2 = await deps.cache.get(opts.templateId);
22035
+ if (cached2 && isVcValid(cached2)) {
22036
+ return {
22037
+ issued: true,
22038
+ vcId: cached2.vcId,
22039
+ vc: cached2.vc,
22040
+ txHash: cached2.txHash,
22041
+ paidAsset: cached2.paidAsset,
22042
+ amountPaid: cached2.amountPaid,
22043
+ fromCache: true
22044
+ };
22045
+ }
22046
+ }
22047
+ const fields = deps.resolveTemplateFields ? await deps.resolveTemplateFields(opts.templateId) : null;
21958
22048
  const { agentDid, ...rest } = opts.attributes ?? {};
21959
- const attributes = agentDid ? opts.attributes : { agentDid: deps.holderDid, ...rest };
21960
- const required2 = deps.resolveRequiredFields ? await deps.resolveRequiredFields(opts.templateId) : null;
21961
- if (!opts.dryRun && required2) {
22049
+ const shouldAutoFillAgentDid = !agentDid && fields !== null && fields.allKeys.includes("agentDid");
22050
+ const attributes = shouldAutoFillAgentDid ? { agentDid: deps.holderDid, ...rest } : opts.attributes ?? {};
22051
+ if (!opts.dryRun && fields) {
21962
22052
  const attrs = attributes;
21963
- const missing = required2.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
22053
+ const missing = fields.required.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
21964
22054
  if (missing.length > 0) {
21965
22055
  return {
21966
22056
  issued: false,
@@ -21986,7 +22076,7 @@ async function subscribeAndIssue(deps, opts) {
21986
22076
  asset: quotedAsset,
21987
22077
  maxAmountRequired: accept.maxAmountRequired,
21988
22078
  payTo: accept.payTo,
21989
- ...required2 ? { requiredAttributes: required2 } : {}
22079
+ ...fields ? { requiredAttributes: fields.required } : {}
21990
22080
  }
21991
22081
  };
21992
22082
  }
@@ -21994,13 +22084,26 @@ async function subscribeAndIssue(deps, opts) {
21994
22084
  const issued = await deps.mbi.applySettle({ ...body, paymentId: challenge.paymentId }, xPayment);
21995
22085
  const rawAsset = String(accept.asset ?? "");
21996
22086
  const paidAsset = deps.resolveSymbol ? await deps.resolveSymbol(rawAsset) : rawAsset;
22087
+ const amountPaid = String(accept.maxAmountRequired ?? "");
22088
+ if (deps.cache) {
22089
+ await deps.cache.set(opts.templateId, {
22090
+ templateId: opts.templateId,
22091
+ vc: issued.verifiableCredential,
22092
+ vcId: issued.vcId,
22093
+ txHash: issued.txHash,
22094
+ paidAsset,
22095
+ amountPaid,
22096
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22097
+ validUntil: extractValidUntil(issued.verifiableCredential, opts.expirationDate)
22098
+ });
22099
+ }
21997
22100
  return {
21998
22101
  issued: true,
21999
22102
  vcId: issued.vcId,
22000
22103
  vc: issued.verifiableCredential,
22001
22104
  txHash: issued.txHash,
22002
22105
  paidAsset,
22003
- amountPaid: String(accept.maxAmountRequired ?? "")
22106
+ amountPaid
22004
22107
  };
22005
22108
  }
22006
22109
 
@@ -22018,22 +22121,59 @@ async function createHolderAccount(create, input) {
22018
22121
  return { zetrixAddress, holderDid, publicKeyHex, message };
22019
22122
  }
22020
22123
 
22124
+ // src/template-aliases.ts
22125
+ var TEMPLATE_ALIASES = [
22126
+ {
22127
+ match: "birthcert",
22128
+ testnet: "did:zid:d6b783559acf6ba0f7ef6e1365bdaf0774d622d8d22728ca6323677f49ee94f8",
22129
+ mainnet: "did:zid:032cb99be3577beccfc6252783c49c83673af38f8456d73462043654d7764e83"
22130
+ }
22131
+ ];
22132
+ function normalize(s) {
22133
+ return s.toLowerCase().replace(/[^a-z0-9]/g, "");
22134
+ }
22135
+ function resolveTemplateAlias(input, network) {
22136
+ if (/^did:zid:/.test(input)) return void 0;
22137
+ const normalized = normalize(input);
22138
+ const entry = TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
22139
+ if (!entry) return void 0;
22140
+ return network.includes("testnet") ? entry.testnet : entry.mainnet;
22141
+ }
22142
+
22021
22143
  // src/mcp-tools.ts
22144
+ async function loadValidCachedCredentials(cache) {
22145
+ if (!cache) return [];
22146
+ const all = await cache.list();
22147
+ return all.filter((entry) => isVcValid(entry));
22148
+ }
22022
22149
  function createTools(deps) {
22023
22150
  return {
22024
22151
  async wallet_status(input = {}) {
22025
22152
  const balances = deps.getBalances ? await deps.getBalances() : void 0;
22153
+ const credentials = input.heldCredentials ?? (await loadValidCachedCredentials(deps.cache)).map((entry) => entry.vc);
22026
22154
  return {
22027
22155
  holderDid: deps.config.holderDid,
22028
22156
  zetrixAddress: deps.config.zetrixAddress,
22029
22157
  network: deps.config.network,
22030
- credentials: input.heldCredentials ?? [],
22158
+ credentials,
22031
22159
  ...balances !== void 0 ? { balances } : {}
22032
22160
  };
22033
22161
  },
22034
- prove_identity(input) {
22162
+ async prove_identity(input) {
22163
+ let vc = input.vc;
22164
+ if (vc === void 0) {
22165
+ const cached2 = await loadValidCachedCredentials(deps.cache);
22166
+ if (cached2.length === 0) {
22167
+ throw new Error("prove_identity: no vc supplied and no valid credential is cached \u2014 call subscribe_and_issue first, or pass vc explicitly.");
22168
+ }
22169
+ if (cached2.length > 1) {
22170
+ const ids = cached2.map((entry) => entry.templateId).join(", ");
22171
+ throw new Error(`prove_identity: no vc supplied and multiple credentials are cached (templateIds: ${ids}) \u2014 pass vc explicitly to select one.`);
22172
+ }
22173
+ vc = cached2[0].vc;
22174
+ }
22035
22175
  const wallet = deps.makeWallet({
22036
- vc: input.vc,
22176
+ vc,
22037
22177
  revealAttribute: input.revealAttribute,
22038
22178
  issuerKeys: input.issuerKeys
22039
22179
  });
@@ -22043,7 +22183,8 @@ function createTools(deps) {
22043
22183
  return payAndFetch(deps.payer, input);
22044
22184
  },
22045
22185
  subscribe_and_issue(input) {
22046
- return subscribeAndIssue(deps.subscribeDeps, input);
22186
+ const resolved = resolveTemplateAlias(input.templateId, deps.config.network);
22187
+ return subscribeAndIssue(deps.subscribeDeps, resolved ? { ...input, templateId: resolved } : input);
22047
22188
  },
22048
22189
  create_holder_account(input) {
22049
22190
  return createHolderAccount(deps.createAccount, input);
@@ -22073,7 +22214,7 @@ function buildToolList() {
22073
22214
  inputSchema: {
22074
22215
  type: "object",
22075
22216
  properties: {
22076
- heldCredentials: { type: "array", items: { type: "object" }, description: "VCs the client holds (the wallet does not persist them)." }
22217
+ 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
22218
  }
22078
22219
  }
22079
22220
  },
@@ -22084,7 +22225,7 @@ function buildToolList() {
22084
22225
  type: "object",
22085
22226
  properties: {
22086
22227
  proofRequest: { type: "string", description: "The PROOF-REQUEST header value from the 401 challenge." },
22087
- vc: { type: "object", description: "The client-held VerifiableCredential to present." },
22228
+ 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
22229
  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
22230
  issuerKeys: {
22090
22231
  type: "object",
@@ -22095,7 +22236,7 @@ function buildToolList() {
22095
22236
  description: "Optional issuer verification keys to bypass the ZID resolver when it is unreachable (e.g. Cloudflare-gated). When set, resolution is skipped."
22096
22237
  }
22097
22238
  },
22098
- required: ["proofRequest", "vc"]
22239
+ required: ["proofRequest"]
22099
22240
  }
22100
22241
  },
22101
22242
  {
@@ -22114,13 +22255,13 @@ function buildToolList() {
22114
22255
  },
22115
22256
  {
22116
22257
  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.",
22258
+ 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
22259
  inputSchema: {
22119
22260
  type: "object",
22120
22261
  properties: {
22121
22262
  templateId: {
22122
22263
  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").`
22264
+ 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
22265
  },
22125
22266
  attributes: {
22126
22267
  type: "object",
@@ -22130,6 +22271,10 @@ function buildToolList() {
22130
22271
  dryRun: {
22131
22272
  type: "boolean",
22132
22273
  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."
22274
+ },
22275
+ forceReissue: {
22276
+ type: "boolean",
22277
+ description: "Skip the local cache and pay + issue a fresh credential regardless of what is already cached."
22133
22278
  }
22134
22279
  },
22135
22280
  required: ["templateId", "attributes"]
@@ -22186,7 +22331,9 @@ async function main() {
22186
22331
  const resolveSymbol = (asset) => resolveAssetSymbol(asset, contractQuery);
22187
22332
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
22188
22333
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
22189
- const resolveRequiredFields = (templateId) => fetchTemplateRequiredFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
22334
+ const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
22335
+ const cacheScope = (0, import_node_crypto2.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
22336
+ const vcCache = createFsVcCache((0, import_node_path2.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
22190
22337
  const pay = (accept) => {
22191
22338
  assertWithinPaymentCap(accept, config2.maxPaymentAmount);
22192
22339
  return import_x402_zetrix_client.PaymentEngine.pay(asPayRequest(accept), walletCfg, node, {}, walletBeSignerFn);
@@ -22229,8 +22376,9 @@ async function main() {
22229
22376
  config: { holderDid, zetrixAddress, network: config2.network },
22230
22377
  makeWallet,
22231
22378
  payer,
22232
- subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveRequiredFields },
22233
- createAccount: (password, label, purpose) => be.createAccount(password, label, purpose)
22379
+ subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
22380
+ createAccount: (password, label, purpose) => be.createAccount(password, label, purpose),
22381
+ cache: vcCache
22234
22382
  };
22235
22383
  const tools = createTools(deps);
22236
22384
  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.1",
3
+ "version": "0.4.0",
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",