bsv-mcp 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # BSV MCP Server Changelog
2
2
 
3
+ ## [0.3.3] - 2026-09-06
4
+
5
+ ### Fixed
6
+ - Honor `DROPLIT_SITE_URL` for sponsor approval links so staging requests reach the correct owner UI.
7
+ - Build approval links from the configured sponsor and verified wallet identity; reject unsafe site origins.
8
+
9
+ ## [0.3.2] - 2026-09-06
10
+
11
+ ### Added
12
+ - Reveal a human-issued Sigma delegation with the connected agent wallet, using standard certificate acquisition and proof.
13
+ - Reject redirects, automatic payments, and ambiguous mutation retries; submit only verifier-specific keys.
14
+
3
15
  ## [0.3.1] - 2026-09-05
4
16
 
5
17
  ### Added
package/README.md CHANGED
@@ -884,6 +884,8 @@ BRC100_WALLET_URL=http://127.0.0.1:3321
884
884
  BRC100_WALLET_ORIGINATOR=bsv-mcp.local
885
885
  DROPLIT_API_URL=https://api.droplit.dev/droplit
886
886
  DROPLIT_FAUCET_NAME=your-sponsor-slug
887
+ # Optional: set explicitly when the owner approval UI is hosted elsewhere.
888
+ DROPLIT_SITE_URL=https://droplit.dev
887
889
  ```
888
890
 
889
891
  Set both sponsor values explicitly. The URL includes the server's configured
@@ -893,8 +895,15 @@ BRC-100 wallet contexts also support this sponsor pair. `1sat serve` exposes a
893
895
  wallet stack service; it is not itself a BRC-100 signer RPC endpoint.
894
896
 
895
897
  Call `droplit_getAccess` to inspect your own authorization and quotas. An
896
- `approval_required` response includes a `https://droplit.dev` link for manual
898
+ `approval_required` response includes a link to `DROPLIT_SITE_URL` (default
899
+ `https://droplit.dev`) for manual
897
900
  owner review. Do not automatically open, approve, or register to obtain access.
901
+ The site URL must be an HTTPS origin (HTTP is allowed only for loopback),
902
+ without credentials, a path, query, or fragment. For staging, set it to the
903
+ trusted staging frontend origin independently of `DROPLIT_API_URL`. Approval
904
+ links use the configured sponsor and authenticated wallet identity; a server
905
+ response cannot redirect them to another site.
906
+
898
907
  Public status and public-key registration do not prove sponsor approval, and
899
908
  creating your own faucet requires funding rather than providing free credit.
900
909
  Missing quota kinds are unrestricted for authorized users; a configured zero
@@ -912,3 +921,44 @@ BRC-105 payment handling cannot spend funds; a 402 requires human review. Struct
912
921
  available quota reset information is retained. `unknown_outcome` means reconcile
913
922
  transaction/history before retrying; no idempotency-key support is claimed.
914
923
  The legacy `USE_DROPLIT_API=true` wallet mode remains available separately.
924
+
925
+
926
+ ### Reveal a Sigma agent delegation with the connected wallet
927
+
928
+ After the human owner binds this agent wallet and issues its BRC-169 delegation,
929
+ copy the owner's complete handoff JSON into `wallet_revealDelegation`:
930
+
931
+ - `sigmaOrigin`: the intended Sigma HTTPS origin (HTTP is allowed only for loopback testing).
932
+ - `handoffJSON`: the owner's JSON containing `certificate`, `subjectKeyring`, `revealTo`, and `revelationPath`.
933
+
934
+ The explicit tool invocation acquires the certificate into the connected wallet,
935
+ proves all restrictions to the named Sigma verifier, and POSTs only that verifier's
936
+ keyring. Use the same wallet that supplied the bound identity; its Ed25519 agent
937
+ token cannot perform this step. The encrypted subject keyring stays with the agent
938
+ and is never included in the HTTP request or tool result. This does not grant
939
+ Droplit sponsor permission. No automatic payments, redirects, or POST retries are
940
+ allowed. On `outcome_unknown`, ask the owner to refresh delegation status before
941
+ trying again; certificate acquisition may already have succeeded.
942
+
943
+ For a **local 1Sat CLI wallet** used throughout binding, the existing commands
944
+ provide the same operation. Save the owner package as `handoff.json`, then:
945
+
946
+ ```sh
947
+ bun -e 'const h=await Bun.file("handoff.json").json(),c=h.certificate; await Bun.write("acquire.json",JSON.stringify({acquisitionProtocol:"direct",type:c.type,certifier:c.certifier,fields:c.fields,serialNumber:c.serialNumber,revocationOutpoint:c.revocationOutpoint,signature:c.signature,keyringRevealer:"certifier",keyringForSubject:h.subjectKeyring})); await Bun.write("prove.json",JSON.stringify({certificate:c,fieldsToReveal:Object.keys(c.fields),verifier:h.revealTo}));'
948
+ 1sat wallet acquire-certificate "$(cat acquire.json)" --json
949
+ 1sat wallet prove-certificate "$(cat prove.json)" --json > proof.json
950
+ bun -e 'const p=await Bun.file("proof.json").json(); if(!p.keyringForVerifier) throw new Error("Missing verifier keyring"); await Bun.write("revelation.json",JSON.stringify({keyring:p.keyringForVerifier}));'
951
+ ```
952
+
953
+ Set `SIGMA_ORIGIN` to the intended Sigma origin and `REVELATION_PATH` to the
954
+ owner's `/api/agents/{agentId}/delegations/{encodedSerialNumber}/revelation` path;
955
+ percent-encode the base64 serial number as one path segment. Then:
956
+
957
+ ```sh
958
+ 1sat authfetch POST "${SIGMA_ORIGIN}${REVELATION_PATH}" --body @revelation.json --json
959
+ ```
960
+
961
+ Use `@1sat/cli` 0.0.110 or later for stale-session mutation replay protection.
962
+ Keep the same configured wallet and chain for every command. This CLI alternative
963
+ does not use the MCP external wallet connection. The endpoint does not require a
964
+ payment; do not add `--yes` to work around an unexpected 402.
package/dist/index.js CHANGED
@@ -207971,9 +207971,9 @@ var require_vary = __commonJS((exports, module) => {
207971
207971
  if (!field) {
207972
207972
  throw new TypeError("field argument is required");
207973
207973
  }
207974
- var fields = !Array.isArray(field) ? parse8(String(field)) : field;
207975
- for (var j5 = 0;j5 < fields.length; j5++) {
207976
- if (!FIELD_NAME_REGEXP.test(fields[j5])) {
207974
+ var fields2 = !Array.isArray(field) ? parse8(String(field)) : field;
207975
+ for (var j5 = 0;j5 < fields2.length; j5++) {
207976
+ if (!FIELD_NAME_REGEXP.test(fields2[j5])) {
207977
207977
  throw new TypeError("field argument contains an invalid header name");
207978
207978
  }
207979
207979
  }
@@ -207982,14 +207982,14 @@ var require_vary = __commonJS((exports, module) => {
207982
207982
  }
207983
207983
  var val = header;
207984
207984
  var vals = parse8(header.toLowerCase());
207985
- if (fields.indexOf("*") !== -1 || vals.indexOf("*") !== -1) {
207985
+ if (fields2.indexOf("*") !== -1 || vals.indexOf("*") !== -1) {
207986
207986
  return "*";
207987
207987
  }
207988
- for (var i = 0;i < fields.length; i++) {
207989
- var fld = fields[i].toLowerCase();
207988
+ for (var i = 0;i < fields2.length; i++) {
207989
+ var fld = fields2[i].toLowerCase();
207990
207990
  if (vals.indexOf(fld) === -1) {
207991
207991
  vals.push(fld);
207992
- val = val ? val + ", " + fields[i] : fields[i];
207992
+ val = val ? val + ", " + fields2[i] : fields2[i];
207993
207993
  }
207994
207994
  }
207995
207995
  return val;
@@ -213168,13 +213168,13 @@ var require_StorageKnex = __commonJS((exports) => {
213168
213168
  delete e2.certificateId;
213169
213169
  if (e2.logger != null)
213170
213170
  delete e2.logger;
213171
- const fields = e2.fields;
213171
+ const fields2 = e2.fields;
213172
213172
  if (e2.fields != null)
213173
213173
  delete e2.fields;
213174
213174
  const [id] = await this.toDb(trx)("certificates").insert(e2);
213175
213175
  certificate.certificateId = id;
213176
- if (fields != null) {
213177
- for (const field of fields) {
213176
+ if (fields2 != null) {
213177
+ for (const field of fields2) {
213178
213178
  field.certificateId = id;
213179
213179
  field.userId = certificate.userId;
213180
213180
  await this.insertCertificateField(field, trx);
@@ -227862,11 +227862,11 @@ var require_querycompiler = __commonJS((exports, module) => {
227862
227862
  _preValidate() {
227863
227863
  const method = this.method;
227864
227864
  const verb = hasOwn(methodAliases, method) ? methodAliases[method] : method;
227865
- const invalid = this.invalidClauses[verb];
227866
- if (!invalid)
227865
+ const invalid2 = this.invalidClauses[verb];
227866
+ if (!invalid2)
227867
227867
  return;
227868
- for (let i = 0;i < invalid.length; i++) {
227869
- const clause = invalid[i];
227868
+ for (let i = 0;i < invalid2.length; i++) {
227869
+ const clause = invalid2[i];
227870
227870
  const hasNonEmptyGrouped = hasOwn(this.grouped, clause) && this.grouped[clause].length > 0;
227871
227871
  const hasNonEmptySingle = hasOwn(this.single, clause) && this.single[clause] != null;
227872
227872
  if (hasNonEmptyGrouped || hasNonEmptySingle) {
@@ -236146,10 +236146,10 @@ var require_mysql = __commonJS((exports, module) => {
236146
236146
  return;
236147
236147
  }
236148
236148
  const queryOptions = Object.assign({ sql: obj.sql }, obj.options);
236149
- connection.query(queryOptions, obj.bindings, function(err, rows, fields) {
236149
+ connection.query(queryOptions, obj.bindings, function(err, rows, fields2) {
236150
236150
  if (err)
236151
236151
  return rejecter(err);
236152
- obj.response = [rows, fields];
236152
+ obj.response = [rows, fields2];
236153
236153
  resolver(obj);
236154
236154
  });
236155
236155
  });
@@ -236160,9 +236160,9 @@ var require_mysql = __commonJS((exports, module) => {
236160
236160
  const { response } = obj;
236161
236161
  const { method } = obj;
236162
236162
  const rows = response[0];
236163
- const fields = response[1];
236163
+ const fields2 = response[1];
236164
236164
  if (obj.output)
236165
- return obj.output.call(runner, rows, fields);
236165
+ return obj.output.call(runner, rows, fields2);
236166
236166
  switch (method) {
236167
236167
  case "select":
236168
236168
  return rows;
@@ -236439,9 +236439,9 @@ var require_mariadb = __commonJS((exports, module) => {
236439
236439
  return;
236440
236440
  if (obj.returning) {
236441
236441
  const rows = obj.response[0];
236442
- const fields = obj.response[1];
236442
+ const fields2 = obj.response[1];
236443
236443
  if (obj.output) {
236444
- return obj.output.call(runner, rows, fields);
236444
+ return obj.output.call(runner, rows, fields2);
236445
236445
  }
236446
236446
  return rows;
236447
236447
  }
@@ -242385,8 +242385,8 @@ var require_ShamirWalletManager = __commonJS((exports) => {
242385
242385
  await this.entropyCollector.collectFromBrowser(element, onProgress);
242386
242386
  }
242387
242387
  generateUserIdHash(privateKey) {
242388
- const publicKey = privateKey.toPublicKey().toString();
242389
- const hash2 = sdk_1.Hash.sha256(sdk_1.Utils.toArray(publicKey, "utf8"));
242388
+ const publicKey2 = privateKey.toPublicKey().toString();
242389
+ const hash2 = sdk_1.Hash.sha256(sdk_1.Utils.toArray(publicKey2, "utf8"));
242390
242390
  return sdk_1.Utils.toHex(hash2);
242391
242391
  }
242392
242392
  async createNewWallet(authPayload, onUserSharesReady) {
@@ -244852,13 +244852,13 @@ var init_storage_pg = __esm(() => {
244852
244852
  e2.certificateId = undefined;
244853
244853
  if (e2.logger)
244854
244854
  e2.logger = undefined;
244855
- const fields = e2.fields;
244855
+ const fields2 = e2.fields;
244856
244856
  if (e2.fields)
244857
244857
  e2.fields = undefined;
244858
244858
  const id = await this.insertRow("certificates", e2, trx);
244859
244859
  certificate.certificateId = id;
244860
- if (fields) {
244861
- for (const field of fields) {
244860
+ if (fields2) {
244861
+ for (const field of fields2) {
244862
244862
  field.certificateId = id;
244863
244863
  field.userId = certificate.userId;
244864
244864
  await this.insertCertificateField(field, trx);
@@ -256042,7 +256042,7 @@ var package_default = {
256042
256042
  name: "bsv-mcp",
256043
256043
  module: "dist/index.js",
256044
256044
  type: "module",
256045
- version: "0.3.1",
256045
+ version: "0.3.3",
256046
256046
  license: "MIT",
256047
256047
  author: "satchmo",
256048
256048
  description: "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
@@ -284877,6 +284877,15 @@ function registerUtilsTools(server) {
284877
284877
 
284878
284878
  // utils/droplit.ts
284879
284879
  init_mod2();
284880
+ function approvalSiteOrigin(value2 = "https://droplit.dev") {
284881
+ const url3 = new URL(value2);
284882
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
284883
+ if (url3.username || url3.password || url3.pathname !== "/" || url3.href.includes("?") || url3.href.includes("#") || value2.includes("\\") || !(url3.protocol === "https:" || url3.protocol === "http:" && loopback)) {
284884
+ throw new Error("DROPLIT_SITE_URL requires an HTTPS or HTTP loopback origin without credentials, path, query or fragment");
284885
+ }
284886
+ return url3.origin;
284887
+ }
284888
+
284880
284889
  class DroplitError extends Error {
284881
284890
  code;
284882
284891
  status;
@@ -284926,9 +284935,11 @@ function httpFailure(status, details = {}) {
284926
284935
  class DroplitClient {
284927
284936
  config;
284928
284937
  authFetch;
284938
+ siteOrigin;
284929
284939
  wallet;
284930
284940
  constructor(config2) {
284931
284941
  this.config = config2;
284942
+ this.siteOrigin = approvalSiteOrigin(config2.siteUrl);
284932
284943
  if (config2.wallet && config2.authKey)
284933
284944
  throw new Error("Configure Droplit wallet or authKey, not both");
284934
284945
  const url3 = new URL(config2.apiUrl);
@@ -285023,6 +285034,7 @@ class DroplitClient {
285023
285034
  throw new DroplitError("invalid_response", "Sponsor access response did not match the authenticated wallet.");
285024
285035
  }
285025
285036
  access.approval_path = `/droplit/${encodeURIComponent(this.config.faucetName)}?tab=api&request_key=${encodeURIComponent(identity6)}`;
285037
+ access.approval_url = new URL(access.approval_path, this.siteOrigin).href;
285026
285038
  return access;
285027
285039
  }
285028
285040
  async fund(rawtx) {
@@ -285093,7 +285105,7 @@ function registerDroplitTools(server, client, disableBroadcasting = false) {
285093
285105
  ...!access.authorized ? {
285094
285106
  error: "approval_required",
285095
285107
  message: "Ask the sponsor owner to approve this wallet. Copy the approval URL for manual review; do not auto-open or submit approval.",
285096
- approval_url: `https://droplit.dev${access.approval_path}`
285108
+ approval_url: access.approval_url
285097
285109
  } : {}
285098
285110
  };
285099
285111
  return {
@@ -295163,7 +295175,7 @@ var package_default2 = {
295163
295175
  name: "bsv-mcp",
295164
295176
  module: "dist/index.js",
295165
295177
  type: "module",
295166
- version: "0.3.1",
295178
+ version: "0.3.3",
295167
295179
  license: "MIT",
295168
295180
  author: "satchmo",
295169
295181
  description: "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
@@ -297158,6 +297170,195 @@ function registerRefreshUtxosTool(server, ctx) {
297158
297170
  });
297159
297171
  }
297160
297172
 
297173
+ // utils/revealDelegation.ts
297174
+ init_mod2();
297175
+ var publicKey = exports_external.string().regex(/^(02|03)[a-f0-9]{64}$/);
297176
+ var base643 = exports_external.string().min(4).max(16384).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/);
297177
+ var fields = exports_external.record(exports_external.string().min(1).max(49), base643).refine((v7) => Object.keys(v7).length > 0 && Object.keys(v7).length <= 32);
297178
+ var handoffSchema = exports_external.object({
297179
+ certificate: exports_external.object({
297180
+ type: exports_external.literal("30kchAJIGfLxzCNloCJNLI3AtkgA8UbkxlXU2Cj4PpA="),
297181
+ serialNumber: base643.refine((v7) => Buffer.from(v7, "base64").length === 32),
297182
+ subject: publicKey,
297183
+ certifier: publicKey,
297184
+ revocationOutpoint: exports_external.string().regex(/^[a-f0-9]{64}\.\d{1,10}$/),
297185
+ signature: exports_external.string().regex(/^(?:[a-f0-9]{2}){8,72}$/),
297186
+ fields
297187
+ }).strict(),
297188
+ subjectKeyring: fields,
297189
+ revealTo: publicKey,
297190
+ revelationPath: exports_external.string().max(512)
297191
+ }).strict();
297192
+
297193
+ class RevelationError extends Error {
297194
+ code;
297195
+ status;
297196
+ constructor(code, message, status) {
297197
+ super(message);
297198
+ this.code = code;
297199
+ this.status = status;
297200
+ }
297201
+ }
297202
+ function invalid() {
297203
+ throw new RevelationError("invalid_handoff", "Provide a valid Sigma origin and the owner's BRC-169 handoff.");
297204
+ }
297205
+ function parseHandoff(origin, handoffJSON) {
297206
+ if (handoffJSON.length > 131072 || !/^https?:\/\/[^/?#\\]+\/?$/.test(origin))
297207
+ invalid();
297208
+ const url3 = new URL(origin);
297209
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
297210
+ if (url3.username || url3.password || url3.protocol !== "https:" && !(url3.protocol === "http:" && loopback))
297211
+ invalid();
297212
+ const h5 = handoffSchema.parse(JSON.parse(handoffJSON));
297213
+ for (const key of [
297214
+ h5.certificate.subject,
297215
+ h5.certificate.certifier,
297216
+ h5.revealTo
297217
+ ]) {
297218
+ if (!PublicKey.fromString(key).validate())
297219
+ invalid();
297220
+ }
297221
+ const match = /^\/api\/agents\/([A-Za-z0-9_-]{1,128})\/delegations\/(.+)\/revelation$/.exec(h5.revelationPath);
297222
+ if (!match || decodeURIComponent(match[2]) !== h5.certificate.serialNumber)
297223
+ invalid();
297224
+ if (Object.keys(h5.subjectKeyring).sort().join(`
297225
+ `) !== Object.keys(h5.certificate.fields).sort().join(`
297226
+ `))
297227
+ invalid();
297228
+ return {
297229
+ h: h5,
297230
+ endpoint: `${url3.origin}/api/agents/${encodeURIComponent(match[1])}/delegations/${encodeURIComponent(h5.certificate.serialNumber)}/revelation`,
297231
+ origin: url3.origin
297232
+ };
297233
+ }
297234
+ async function revealDelegation(wallet5, sigmaOrigin, handoffJSON) {
297235
+ let input;
297236
+ try {
297237
+ input = parseHandoff(sigmaOrigin, handoffJSON);
297238
+ } catch {
297239
+ invalid();
297240
+ }
297241
+ const { h: h5, endpoint, origin } = input;
297242
+ const c6 = h5.certificate;
297243
+ try {
297244
+ const { publicKey: identity6 } = await wallet5.getPublicKey({
297245
+ identityKey: true
297246
+ });
297247
+ if (identity6 !== c6.subject)
297248
+ throw new RevelationError("subject_mismatch", "The connected wallet is not this delegation's subject.");
297249
+ const certificate = new Certificate(c6.type, c6.serialNumber, c6.subject, c6.certifier, c6.revocationOutpoint, c6.fields, c6.signature);
297250
+ if (!await certificate.verify())
297251
+ invalid();
297252
+ } catch (error53) {
297253
+ if (error53 instanceof RevelationError)
297254
+ throw error53;
297255
+ throw new RevelationError("invalid_certificate", "Could not verify the certificate and connected wallet identity.");
297256
+ }
297257
+ let keyring;
297258
+ try {
297259
+ await wallet5.acquireCertificate({
297260
+ ...c6,
297261
+ acquisitionProtocol: "direct",
297262
+ keyringRevealer: "certifier",
297263
+ keyringForSubject: h5.subjectKeyring
297264
+ });
297265
+ const proof = await wallet5.proveCertificate({
297266
+ certificate: c6,
297267
+ fieldsToReveal: Object.keys(c6.fields),
297268
+ verifier: h5.revealTo
297269
+ });
297270
+ keyring = fields.parse(proof.keyringForVerifier);
297271
+ } catch {
297272
+ throw new RevelationError("wallet_proof_failed", "The wallet could not acquire or prove this delegation. Check its certificate store before retrying.");
297273
+ }
297274
+ return postRevelation(wallet5, origin, endpoint, keyring);
297275
+ }
297276
+ async function postRevelation(wallet5, origin, endpoint, keyring) {
297277
+ const guarded = new Proxy(wallet5, {
297278
+ get(target, property) {
297279
+ if (property === "createAction" || property === "signAction")
297280
+ return async () => {
297281
+ throw new RevelationError("payment_required", "Sigma revelation must not require a payment.", 402);
297282
+ };
297283
+ const value2 = Reflect.get(target, property, target);
297284
+ return typeof value2 === "function" ? value2.bind(target) : value2;
297285
+ }
297286
+ });
297287
+ let refusal;
297288
+ const guardedFetch = async (url3, init) => {
297289
+ if (String(url3) !== `${origin}/.well-known/auth` && String(url3) !== endpoint) {
297290
+ refusal = new RevelationError("unexpected_endpoint", "Sigma authentication requested an unexpected endpoint.");
297291
+ throw refusal;
297292
+ }
297293
+ const response = await fetch(url3, { ...init, redirect: "error" });
297294
+ if ([401, 402, 403, 409, 422, 429].includes(response.status)) {
297295
+ refusal = new RevelationError(response.status === 402 ? "payment_required" : "request_rejected", "Sigma rejected revelation. Check owner delegation status before retrying.", response.status);
297296
+ throw refusal;
297297
+ }
297298
+ return response;
297299
+ };
297300
+ const transport = new SimplifiedFetchTransport(origin, guardedFetch);
297301
+ const sessions = new SessionManager;
297302
+ const auth5 = new AuthFetch(guarded, undefined, sessions);
297303
+ const peer = new Peer(guarded, transport, undefined, sessions);
297304
+ try {
297305
+ await peer.ready;
297306
+ auth5.peers[origin] = { peer, pendingCertificateRequests: [] };
297307
+ const response = await auth5.fetch(endpoint, {
297308
+ method: "POST",
297309
+ headers: { "Content-Type": "application/json" },
297310
+ body: JSON.stringify({ keyring }),
297311
+ retryCounter: 1,
297312
+ paymentRetryAttempts: 1
297313
+ });
297314
+ if (!response.ok)
297315
+ throw new RevelationError("request_rejected", "Sigma rejected revelation. Check owner delegation status before retrying.", response.status);
297316
+ return { status: "revealed", httpStatus: response.status };
297317
+ } catch (error53) {
297318
+ if (refusal)
297319
+ throw refusal;
297320
+ if (error53 instanceof RevelationError)
297321
+ throw error53;
297322
+ throw new RevelationError("outcome_unknown", "Revelation was not confirmed. Ask the owner to refresh delegation status before retrying; no automatic replay was attempted.");
297323
+ }
297324
+ }
297325
+
297326
+ // tools/wallet/revealDelegation.ts
297327
+ function registerRevealDelegationTool(server, ctx) {
297328
+ server.registerTool("wallet_revealDelegation", {
297329
+ description: "Receive the human owner's BRC-169 certificate handoff, acquire and prove it with this connected agent wallet, and reveal its restrictions to the specified Sigma verifier. Imports a certificate and activates its existing delegation. Never pays, broadcasts, follows redirects, or retries an ambiguous POST. The subject keyring stays with the wallet.",
297330
+ inputSchema: {
297331
+ sigmaOrigin: exports_external.string().max(2048).describe("Explicit Sigma HTTPS origin, or HTTP loopback for local testing"),
297332
+ handoffJSON: exports_external.string().max(131072).describe("JSON package copied by the owner: certificate, subjectKeyring, revealTo, revelationPath")
297333
+ },
297334
+ annotations: {
297335
+ readOnlyHint: false,
297336
+ destructiveHint: false,
297337
+ idempotentHint: false,
297338
+ openWorldHint: true
297339
+ }
297340
+ }, async ({ sigmaOrigin, handoffJSON }) => {
297341
+ try {
297342
+ if (!ctx)
297343
+ throw new RevelationError("wallet_unavailable", "Connect the agent's existing wallet first.");
297344
+ const data = await revealDelegation(ctx.wallet, sigmaOrigin, handoffJSON);
297345
+ return { ...createSuccessResponse(data), structuredContent: data };
297346
+ } catch (error53) {
297347
+ const failure = error53 instanceof RevelationError ? error53 : new RevelationError("revelation_failed", "Delegation revelation failed. Check wallet and owner status before retrying.");
297348
+ const data = {
297349
+ error: failure.code,
297350
+ message: failure.message,
297351
+ ...failure.status === undefined ? {} : { status: failure.status }
297352
+ };
297353
+ return {
297354
+ ...createSuccessResponse(data),
297355
+ structuredContent: data,
297356
+ isError: true
297357
+ };
297358
+ }
297359
+ });
297360
+ }
297361
+
297161
297362
  // tools/wallet/sendAllBsv.ts
297162
297363
  var sendAllBsvArgsSchema = exports_external.object({
297163
297364
  destination: exports_external.string().describe("Destination P2PKH address to send all funds to")
@@ -297661,6 +297862,7 @@ function registerWalletTools(server, wallet5, config2) {
297661
297862
  registerRefreshUtxosTool(server, config2.ctx);
297662
297863
  registerWalletGetBalanceTool(server, config2.ctx);
297663
297864
  registerBrc100Tools(server, config2.ctx);
297865
+ registerRevealDelegationTool(server, config2.ctx);
297664
297866
  if (config2.enableA2bTools && wallet5 && config2.identityPk) {
297665
297867
  registerA2bPublishMcpTool(server, wallet5, config2.identityPk, {
297666
297868
  disableBroadcasting: config2.disableBroadcasting
@@ -297846,9 +298048,9 @@ class Wallet {
297846
298048
  if (!currentPaymentKey) {
297847
298049
  throw new Error("No payment key available to derive public key.");
297848
298050
  }
297849
- const publicKey = currentPaymentKey.toPublicKey();
298051
+ const publicKey2 = currentPaymentKey.toPublicKey();
297850
298052
  return {
297851
- publicKey: publicKey.toDER("hex")
298053
+ publicKey: publicKey2.toDER("hex")
297852
298054
  };
297853
298055
  }
297854
298056
  async sendToAddress(address, amountSatoshis) {
@@ -298089,9 +298291,9 @@ class Wallet2 {
298089
298291
  if (!currentPaymentKey) {
298090
298292
  throw new Error("No payment key available to derive public key.");
298091
298293
  }
298092
- const publicKey = currentPaymentKey.toPublicKey();
298294
+ const publicKey2 = currentPaymentKey.toPublicKey();
298093
298295
  return {
298094
- publicKey: publicKey.toDER("hex")
298296
+ publicKey: publicKey2.toDER("hex")
298095
298297
  };
298096
298298
  }
298097
298299
  async sendToAddress(address, amountSatoshis) {
@@ -298161,12 +298363,25 @@ init_mod2();
298161
298363
  function readDroplitSponsorConfig(env = process.env) {
298162
298364
  const apiUrl = env.DROPLIT_API_URL;
298163
298365
  const faucetName = env.DROPLIT_FAUCET_NAME;
298164
- if (apiUrl === undefined && faucetName === undefined)
298366
+ const siteUrl = env.DROPLIT_SITE_URL;
298367
+ if (apiUrl === undefined && faucetName === undefined && siteUrl === undefined)
298165
298368
  return;
298166
298369
  if (!apiUrl?.trim() || !faucetName?.trim()) {
298167
298370
  throw new Error("DROPLIT_API_URL and DROPLIT_FAUCET_NAME must both be explicitly configured");
298168
298371
  }
298169
- return { apiUrl, faucetName };
298372
+ return {
298373
+ apiUrl,
298374
+ faucetName,
298375
+ ...siteUrl === undefined ? {} : { siteUrl: approvalSiteOrigin2(siteUrl) }
298376
+ };
298377
+ }
298378
+ function approvalSiteOrigin2(value2 = "https://droplit.dev") {
298379
+ const url3 = new URL(value2);
298380
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
298381
+ if (url3.username || url3.password || url3.pathname !== "/" || url3.href.includes("?") || url3.href.includes("#") || value2.includes("\\") || !(url3.protocol === "https:" || url3.protocol === "http:" && loopback)) {
298382
+ throw new Error("DROPLIT_SITE_URL requires an HTTPS or HTTP loopback origin without credentials, path, query or fragment");
298383
+ }
298384
+ return url3.origin;
298170
298385
  }
298171
298386
 
298172
298387
  class DroplitError2 extends Error {
@@ -298218,9 +298433,11 @@ function httpFailure2(status, details = {}) {
298218
298433
  class DroplitClient2 {
298219
298434
  config;
298220
298435
  authFetch;
298436
+ siteOrigin;
298221
298437
  wallet;
298222
298438
  constructor(config2) {
298223
298439
  this.config = config2;
298440
+ this.siteOrigin = approvalSiteOrigin2(config2.siteUrl);
298224
298441
  if (config2.wallet && config2.authKey)
298225
298442
  throw new Error("Configure Droplit wallet or authKey, not both");
298226
298443
  const url3 = new URL(config2.apiUrl);
@@ -298252,8 +298469,8 @@ class DroplitClient2 {
298252
298469
  async getIdentityKey() {
298253
298470
  if (!this.wallet)
298254
298471
  throw new DroplitError2("authentication_required", "Configure a Droplit wallet identity.");
298255
- const { publicKey } = await this.wallet.getPublicKey({ identityKey: true });
298256
- return publicKey;
298472
+ const { publicKey: publicKey2 } = await this.wallet.getPublicKey({ identityKey: true });
298473
+ return publicKey2;
298257
298474
  }
298258
298475
  get faucetPath() {
298259
298476
  return `/faucet/${encodeURIComponent(this.config.faucetName)}`;
@@ -298315,6 +298532,7 @@ class DroplitClient2 {
298315
298532
  throw new DroplitError2("invalid_response", "Sponsor access response did not match the authenticated wallet.");
298316
298533
  }
298317
298534
  access.approval_path = `/droplit/${encodeURIComponent(this.config.faucetName)}?tab=api&request_key=${encodeURIComponent(identity6)}`;
298535
+ access.approval_url = new URL(access.approval_path, this.siteOrigin).href;
298318
298536
  return access;
298319
298537
  }
298320
298538
  async fund(rawtx) {
@@ -301203,13 +301421,13 @@ class StorageBunSqlite extends import_StorageProvider.StorageProvider {
301203
301421
  e2.certificateId = undefined;
301204
301422
  if (e2.logger)
301205
301423
  e2.logger = undefined;
301206
- const fields = e2.fields;
301424
+ const fields2 = e2.fields;
301207
301425
  if (e2.fields)
301208
301426
  e2.fields = undefined;
301209
301427
  const id = this.insertRow("certificates", e2);
301210
301428
  certificate.certificateId = id;
301211
- if (fields) {
301212
- for (const field of fields) {
301429
+ if (fields2) {
301430
+ for (const field of fields2) {
301213
301431
  field.certificateId = id;
301214
301432
  field.userId = certificate.userId;
301215
301433
  await this.insertCertificateField(field, trx);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "bsv-mcp",
3
3
  "module": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.3.1",
5
+ "version": "0.3.3",
6
6
  "license": "MIT",
7
7
  "author": "satchmo",
8
8
  "description": "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
@@ -53,7 +53,7 @@ export function registerDroplitTools(
53
53
  error: "approval_required",
54
54
  message:
55
55
  "Ask the sponsor owner to approve this wallet. Copy the approval URL for manual review; do not auto-open or submit approval.",
56
- approval_url: `https://droplit.dev${access.approval_path}`,
56
+ approval_url: access.approval_url,
57
57
  }
58
58
  : {}),
59
59
  };
@@ -0,0 +1,74 @@
1
+ import type { OneSatContext } from "@1sat/actions";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import {
5
+ RevelationError,
6
+ revealDelegation,
7
+ } from "../../utils/revealDelegation";
8
+ import { createSuccessResponse } from "../utils/errorHandler";
9
+
10
+ export function registerRevealDelegationTool(
11
+ server: McpServer,
12
+ ctx?: OneSatContext,
13
+ ) {
14
+ server.registerTool(
15
+ "wallet_revealDelegation",
16
+ {
17
+ description:
18
+ "Receive the human owner's BRC-169 certificate handoff, acquire and prove it with this connected agent wallet, and reveal its restrictions to the specified Sigma verifier. Imports a certificate and activates its existing delegation. Never pays, broadcasts, follows redirects, or retries an ambiguous POST. The subject keyring stays with the wallet.",
19
+ inputSchema: {
20
+ sigmaOrigin: z
21
+ .string()
22
+ .max(2048)
23
+ .describe(
24
+ "Explicit Sigma HTTPS origin, or HTTP loopback for local testing",
25
+ ),
26
+ handoffJSON: z
27
+ .string()
28
+ .max(131072)
29
+ .describe(
30
+ "JSON package copied by the owner: certificate, subjectKeyring, revealTo, revelationPath",
31
+ ),
32
+ },
33
+ annotations: {
34
+ readOnlyHint: false,
35
+ destructiveHint: false,
36
+ idempotentHint: false,
37
+ openWorldHint: true,
38
+ },
39
+ },
40
+ async ({ sigmaOrigin, handoffJSON }) => {
41
+ try {
42
+ if (!ctx)
43
+ throw new RevelationError(
44
+ "wallet_unavailable",
45
+ "Connect the agent's existing wallet first.",
46
+ );
47
+ const data = await revealDelegation(
48
+ ctx.wallet,
49
+ sigmaOrigin,
50
+ handoffJSON,
51
+ );
52
+ return { ...createSuccessResponse(data), structuredContent: data };
53
+ } catch (error) {
54
+ const failure =
55
+ error instanceof RevelationError
56
+ ? error
57
+ : new RevelationError(
58
+ "revelation_failed",
59
+ "Delegation revelation failed. Check wallet and owner status before retrying.",
60
+ );
61
+ const data = {
62
+ error: failure.code,
63
+ message: failure.message,
64
+ ...(failure.status === undefined ? {} : { status: failure.status }),
65
+ };
66
+ return {
67
+ ...createSuccessResponse(data),
68
+ structuredContent: data,
69
+ isError: true,
70
+ };
71
+ }
72
+ },
73
+ );
74
+ }
@@ -19,6 +19,7 @@ import { registerOpnsDeregisterTool } from "./opnsDeregister";
19
19
  import { registerOpnsRegisterTool } from "./opnsRegister";
20
20
  import { registerPurchaseListingTool } from "./purchaseListing";
21
21
  import { registerRefreshUtxosTool } from "./refreshUtxos";
22
+ import { registerRevealDelegationTool } from "./revealDelegation";
22
23
  import { registerSendAllBsvTool } from "./sendAllBsv";
23
24
  import { registerSendBsvTool } from "./sendBsv";
24
25
  import { registerSignBsmTool } from "./signBsm";
@@ -58,6 +59,7 @@ export function registerWalletTools(
58
59
 
59
60
  // Register full BRC-100 wallet interface
60
61
  registerBrc100Tools(server, config.ctx);
62
+ registerRevealDelegationTool(server, config.ctx);
61
63
 
62
64
  // A2B tools have to be explicitly enabled
63
65
  if (config.enableA2bTools && wallet && config.identityPk) {