bsv-mcp 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -28,6 +28,11 @@ import { getBsvPriceWithCache } from "./tools/bsv/getPrice.ts";
28
28
  import { registerAllTools, type ToolsConfig } from "./tools/index.ts";
29
29
  import { IntegratedWallet } from "./tools/wallet/integratedWallet.ts";
30
30
  import { Wallet } from "./tools/wallet/wallet.ts";
31
+ import { DroplitClient, readDroplitSponsorConfig } from "./utils/droplit";
32
+ import {
33
+ initializeKeysForWalletMode,
34
+ readExternalWalletConfig,
35
+ } from "./utils/externalWalletConfig";
31
36
  import {
32
37
  type BSVJWTPayload,
33
38
  createMCPJWTValidator,
@@ -37,6 +42,7 @@ import { SecureKeyManager } from "./utils/keyManager.ts";
37
42
  import { setServerInstance } from "./utils/passphrasePrompt.ts";
38
43
  import {
39
44
  destroyWallet,
45
+ initExternalWallet,
40
46
  initWallet,
41
47
  setSpendingApprovalServerInstance,
42
48
  } from "./utils/walletInit.ts";
@@ -1107,6 +1113,8 @@ Options:
1107
1113
  Environment Variables:
1108
1114
  TRANSPORT Transport mode: 'stdio' or 'http' (default: http)
1109
1115
  PORT HTTP server port (default: 3000)
1116
+ BRC100_WALLET_URL Existing SDK HTTPWalletJSON signer RPC URL
1117
+ BRC100_WALLET_ORIGINATOR Signer permission origin (default: bsv-mcp.local)
1110
1118
  PRIVATE_KEY_WIF Payment private key in WIF format
1111
1119
  DISABLE_TOOLS Disable all tools (default: false)
1112
1120
  DISABLE_WALLET_TOOLS Disable wallet tools (default: false)
@@ -1130,7 +1138,7 @@ Tool Categories:
1130
1138
 
1131
1139
  Authentication:
1132
1140
  - Most tools work without authentication
1133
- - Wallet operations require PRIVATE_KEY_WIF or generated keys
1141
+ - Wallet operations use BRC100_WALLET_URL, PRIVATE_KEY_WIF or generated keys
1134
1142
  - BAP/A2B tools require identity keys (generated via bap_generate tool)
1135
1143
  `);
1136
1144
  process.exit(0);
@@ -1142,7 +1150,25 @@ Authentication:
1142
1150
  }
1143
1151
 
1144
1152
  // --- Initialize Keys ---
1145
- const { payPk, identityPk, xprv, source: keySource } = await initializeKeys();
1153
+ const externalWallet = readExternalWalletConfig();
1154
+ const sponsorConfig = CONFIG.useDroplitApi
1155
+ ? undefined
1156
+ : readDroplitSponsorConfig();
1157
+ const keys = await initializeKeysForWalletMode(
1158
+ externalWallet,
1159
+ initializeKeys,
1160
+ );
1161
+ const {
1162
+ payPk,
1163
+ identityPk,
1164
+ xprv,
1165
+ source: keySource,
1166
+ } = keys ?? {
1167
+ payPk: undefined,
1168
+ identityPk: undefined,
1169
+ xprv: undefined,
1170
+ source: "external",
1171
+ };
1146
1172
 
1147
1173
  // Define persistence based on source
1148
1174
  const hasPersistentPayKey =
@@ -1154,6 +1180,15 @@ Authentication:
1154
1180
  (keySource === "file" || keySource === "env" || keySource === "encrypted");
1155
1181
 
1156
1182
  const effectiveConfig = { ...CONFIG };
1183
+ if (externalWallet) {
1184
+ effectiveConfig.loadBapTools = false;
1185
+ effectiveConfig.loadBsocialTools = false;
1186
+ effectiveConfig.loadMneeTools = false;
1187
+ effectiveConfig.loadA2bTools = false;
1188
+ logFunc(
1189
+ "External BRC-100 signer selected; local key loading and generation bypassed.",
1190
+ );
1191
+ }
1157
1192
 
1158
1193
  // --- Configuration Logging ---
1159
1194
  logFunc("\n--- BSV MCP Server Configuration ---");
@@ -1169,10 +1204,10 @@ Authentication:
1169
1204
  );
1170
1205
  }
1171
1206
  logFunc(
1172
- ` PRIVATE_KEY_WIF: ${process.env.PRIVATE_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
1207
+ ` PRIVATE_KEY_WIF: ${externalWallet ? "Unused (external signer)" : process.env.PRIVATE_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
1173
1208
  );
1174
1209
  logFunc(
1175
- ` IDENTITY_KEY_WIF: ${process.env.IDENTITY_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
1210
+ ` IDENTITY_KEY_WIF: ${externalWallet ? "Unused (external signer)" : process.env.IDENTITY_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
1176
1211
  );
1177
1212
  if (process.env.BSV_MCP_PASSPHRASE) {
1178
1213
  logFunc(" BSV_MCP_PASSPHRASE: \x1b[31mDEPRECATED - Remove this!\x1b[0m");
@@ -1269,11 +1304,11 @@ Authentication:
1269
1304
  : "\x1b[31mDisabled\x1b[0m";
1270
1305
 
1271
1306
  let payKeyNote = "";
1272
- if (!hasPersistentPayKey) {
1307
+ if (!externalWallet && !hasPersistentPayKey) {
1273
1308
  payKeyNote = " \x1b[33m(Using generated payPk)\x1b[0m";
1274
1309
  }
1275
1310
  let identityKeyNote = "";
1276
- if (!hasPersistentIdentityKey) {
1311
+ if (!externalWallet && !hasPersistentIdentityKey) {
1277
1312
  identityKeyNote = " \x1b[33m(Using generated identityPk)\x1b[0m";
1278
1313
  }
1279
1314
 
@@ -1310,7 +1345,15 @@ Authentication:
1310
1345
 
1311
1346
  if (CONFIG.loadTools) {
1312
1347
  // Check if we should use Droplit API mode
1313
- if (CONFIG.useDroplitApi && CONFIG.droplitFaucetName) {
1348
+ if (externalWallet) {
1349
+ const chain = process.env.BSV_CHAIN ?? "main";
1350
+ if (chain !== "main" && chain !== "test")
1351
+ throw new Error("BSV_CHAIN must be main or test");
1352
+ const result = await initExternalWallet(externalWallet, chain);
1353
+ remoteCtx = result.ctx;
1354
+ remoteServices = result.services;
1355
+ logFunc(`External BRC-100 signer ready. Identity: ${result.identityKey}`);
1356
+ } else if (CONFIG.useDroplitApi && CONFIG.droplitFaucetName) {
1314
1357
  // Initialize IntegratedWallet in Droplit mode
1315
1358
  if (CONFIG.loadWalletTools) {
1316
1359
  try {
@@ -1405,6 +1448,16 @@ Authentication:
1405
1448
  }
1406
1449
  }
1407
1450
 
1451
+ const droplitClient =
1452
+ sponsorConfig && remoteCtx
1453
+ ? new DroplitClient({ ...sponsorConfig, wallet: remoteCtx.wallet })
1454
+ : undefined;
1455
+ if (sponsorConfig && CONFIG.loadTools && !remoteCtx) {
1456
+ throw new Error(
1457
+ "Configured Droplit sponsor requires an initialized BRC-100 wallet context",
1458
+ );
1459
+ }
1460
+
1408
1461
  // Build the shared tools config (used by server factory for each session)
1409
1462
  const toolsConfig: ToolsConfig = CONFIG.loadTools
1410
1463
  ? {
@@ -1424,6 +1477,7 @@ Authentication:
1424
1477
  disableBroadcasting: effectiveConfig.disableBroadcasting,
1425
1478
  ctx: remoteCtx,
1426
1479
  services: remoteServices,
1480
+ droplitClient,
1427
1481
  }
1428
1482
  : {
1429
1483
  enableBsvTools: false,
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.0",
5
+ "version": "0.3.2",
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",
package/tools/index.ts CHANGED
@@ -2,6 +2,7 @@ import type { OneSatContext } from "@1sat/actions";
2
2
  import type { OneSatServices } from "@1sat/wallet-remote";
3
3
  import type { PrivateKey } from "@bsv/sdk";
4
4
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import type { DroplitClient } from "../utils/droplit";
5
6
  import { registerA2bDiscoverTool } from "./a2b/discover";
6
7
  import { registerBapTools } from "./bap";
7
8
  import { registerBsocialTools } from "./bsocial";
@@ -9,6 +10,7 @@ import { registerBsvTools } from "./bsv";
9
10
  import { registerMneeTools } from "./mnee";
10
11
  import { registerOrdinalsTools } from "./ordinals";
11
12
  import { registerUtilsTools } from "./utils";
13
+ import { registerDroplitTools } from "./wallet/droplit";
12
14
  import { registerWalletGetBalanceDroplitTool } from "./wallet/getBalanceDroplit";
13
15
  import type { IntegratedWallet } from "./wallet/integratedWallet";
14
16
  import { registerSetupDroplitTools } from "./wallet/setupDroplit";
@@ -45,6 +47,7 @@ export interface ToolsConfig {
45
47
  disableBroadcasting?: boolean; // For wallet tools
46
48
  ctx?: OneSatContext;
47
49
  services?: OneSatServices;
50
+ droplitClient?: DroplitClient;
48
51
  }
49
52
 
50
53
  /**
@@ -99,7 +102,7 @@ export function registerAllTools(
99
102
  }
100
103
 
101
104
  // Register BAP tools
102
- if (enableBapTools) {
105
+ if (enableBapTools && (!config.ctx || config.wallet)) {
103
106
  const bapConfig: import("./bap").BapToolsConfig = {
104
107
  disableBroadcasting: config.disableBroadcasting,
105
108
  identityPk: config.identityPk,
@@ -116,6 +119,12 @@ export function registerAllTools(
116
119
 
117
120
  // Register Wallet tools themselves
118
121
  if (enableWalletTools) {
122
+ if (config.droplitClient)
123
+ registerDroplitTools(
124
+ server,
125
+ config.droplitClient,
126
+ config.disableBroadcasting,
127
+ );
119
128
  if (config.integratedWallet?.isDroplitMode) {
120
129
  // Register Droplit-mode wallet tools
121
130
  const droplitClient = config.integratedWallet.getDroplitClient();
@@ -127,7 +136,7 @@ export function registerAllTools(
127
136
  }
128
137
  console.error("Registered Droplit mode wallet tools");
129
138
  }
130
- } else if (config.wallet) {
139
+ } else if (config.wallet || config.ctx) {
131
140
  // Register normal wallet tools
132
141
  const walletToolOptions = {
133
142
  disableBroadcasting: config.disableBroadcasting === true,
@@ -140,7 +149,7 @@ export function registerAllTools(
140
149
  }
141
150
 
142
151
  // Register MNEE tools
143
- if (enableMneeTools) {
152
+ if (enableMneeTools && (!config.ctx || config.wallet)) {
144
153
  registerMneeTools(server);
145
154
  }
146
155
 
@@ -184,8 +184,15 @@ export function registerBrc100Tools(
184
184
  outputsJSON: z.string().describe("JSON array of outputs to internalize"),
185
185
  description: z.string().describe("5-50 char description"),
186
186
  labelsJSON: z.string().optional().describe("JSON array of label strings"),
187
+ seekPermission: z.boolean().optional(),
187
188
  },
188
- async ({ txJSON, outputsJSON, description, labelsJSON }) => {
189
+ async ({
190
+ txJSON,
191
+ outputsJSON,
192
+ description,
193
+ labelsJSON,
194
+ seekPermission,
195
+ }) => {
189
196
  if (!ctx) return noCtx;
190
197
  try {
191
198
  return result(
@@ -193,6 +200,7 @@ export function registerBrc100Tools(
193
200
  tx: parseRequiredJSON("txJSON", txJSON),
194
201
  outputs: parseRequiredJSON("outputsJSON", outputsJSON),
195
202
  description,
203
+ seekPermission,
196
204
  labels: parseJSON("labelsJSON", labelsJSON),
197
205
  }),
198
206
  );
@@ -221,6 +229,7 @@ export function registerBrc100Tools(
221
229
  includeOutputLockingScripts: z.boolean().default(false),
222
230
  limit: z.number().default(25),
223
231
  offset: z.number().default(0),
232
+ seekPermission: z.boolean().optional(),
224
233
  },
225
234
  async ({ labelsJSON, ...rest }) => {
226
235
  if (!ctx) return noCtx;
@@ -250,6 +259,7 @@ export function registerBrc100Tools(
250
259
  includeLabels: z.boolean().default(false),
251
260
  limit: z.number().default(25),
252
261
  offset: z.number().default(0),
262
+ seekPermission: z.boolean().optional(),
253
263
  },
254
264
  async ({ tagsJSON, ...rest }) => {
255
265
  if (!ctx) return noCtx;
@@ -331,6 +341,8 @@ export function registerBrc100Tools(
331
341
  keyID: z.string(),
332
342
  counterparty: z.string().optional(),
333
343
  privileged: z.boolean().optional(),
344
+ privilegedReason: z.string().optional(),
345
+ seekPermission: z.boolean().optional(),
334
346
  },
335
347
  async ({ protocolIDJSON, ...rest }) => {
336
348
  if (!ctx) return noCtx;
@@ -358,6 +370,8 @@ export function registerBrc100Tools(
358
370
  keyID: z.string(),
359
371
  counterparty: z.string().optional(),
360
372
  privileged: z.boolean().optional(),
373
+ privilegedReason: z.string().optional(),
374
+ seekPermission: z.boolean().optional(),
361
375
  },
362
376
  async ({ protocolIDJSON, ...rest }) => {
363
377
  if (!ctx) return noCtx;
@@ -385,6 +399,8 @@ export function registerBrc100Tools(
385
399
  keyID: z.string(),
386
400
  counterparty: z.string().optional(),
387
401
  privileged: z.boolean().optional(),
402
+ privilegedReason: z.string().optional(),
403
+ seekPermission: z.boolean().optional(),
388
404
  },
389
405
  async ({ protocolIDJSON, ...rest }) => {
390
406
  if (!ctx) return noCtx;
@@ -413,6 +429,8 @@ export function registerBrc100Tools(
413
429
  keyID: z.string(),
414
430
  counterparty: z.string().optional(),
415
431
  privileged: z.boolean().optional(),
432
+ privilegedReason: z.string().optional(),
433
+ seekPermission: z.boolean().optional(),
416
434
  },
417
435
  async ({ protocolIDJSON, ...rest }) => {
418
436
  if (!ctx) return noCtx;
@@ -444,6 +462,8 @@ export function registerBrc100Tools(
444
462
  keyID: z.string(),
445
463
  counterparty: z.string().optional(),
446
464
  privileged: z.boolean().optional(),
465
+ privilegedReason: z.string().optional(),
466
+ seekPermission: z.boolean().optional(),
447
467
  },
448
468
  async ({ protocolIDJSON, ...rest }) => {
449
469
  if (!ctx) return noCtx;
@@ -480,6 +500,8 @@ export function registerBrc100Tools(
480
500
  counterparty: z.string().optional(),
481
501
  forSelf: z.boolean().optional(),
482
502
  privileged: z.boolean().optional(),
503
+ privilegedReason: z.string().optional(),
504
+ seekPermission: z.boolean().optional(),
483
505
  },
484
506
  async ({ protocolIDJSON, ...rest }) => {
485
507
  if (!ctx) return noCtx;
@@ -677,6 +699,7 @@ export function registerBrc100Tools(
677
699
  identityKey: z.string().describe("Identity public key hex"),
678
700
  limit: z.number().default(25),
679
701
  offset: z.number().default(0),
702
+ seekPermission: z.boolean().optional(),
680
703
  },
681
704
  async (args) => {
682
705
  if (!ctx) return noCtx;
@@ -697,6 +720,7 @@ export function registerBrc100Tools(
697
720
  .describe("JSON object of attribute key/value pairs to match"),
698
721
  limit: z.number().default(25),
699
722
  offset: z.number().default(0),
723
+ seekPermission: z.boolean().optional(),
700
724
  },
701
725
  async ({ attributesJSON, ...rest }) => {
702
726
  if (!ctx) return noCtx;
@@ -0,0 +1,124 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
4
+ import { type DroplitClient, DroplitError } from "../../utils/droplit";
5
+ import { createSuccessResponse } from "../utils/errorHandler";
6
+
7
+ function errorResult(error: unknown) {
8
+ const failure =
9
+ error instanceof DroplitError
10
+ ? error
11
+ : new DroplitError(
12
+ "request_failed",
13
+ "Droplit operation failed. Check configuration and broadcasting policy.",
14
+ );
15
+ const data = {
16
+ error: failure.code,
17
+ message: failure.message,
18
+ ...(failure.status === undefined ? {} : { status: failure.status }),
19
+ ...failure.details,
20
+ };
21
+ return {
22
+ ...createSuccessResponse(data),
23
+ structuredContent: data,
24
+ isError: true,
25
+ };
26
+ }
27
+
28
+ export function registerDroplitTools(
29
+ server: McpServer,
30
+ client: DroplitClient,
31
+ disableBroadcasting = false,
32
+ ) {
33
+ server.registerTool(
34
+ "droplit_getAccess",
35
+ {
36
+ description:
37
+ "Read this connected wallet's sponsor authorization and quotas. If unauthorized, a human sponsor owner must approve the wallet manually. Does not create, fund, or approve anything.",
38
+ inputSchema: {},
39
+ annotations: {
40
+ readOnlyHint: true,
41
+ destructiveHint: false,
42
+ idempotentHint: true,
43
+ openWorldHint: true,
44
+ },
45
+ },
46
+ async () => {
47
+ try {
48
+ const access = await client.getAccess();
49
+ const data = {
50
+ ...access,
51
+ ...(!access.authorized
52
+ ? {
53
+ error: "approval_required",
54
+ message:
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}`,
57
+ }
58
+ : {}),
59
+ };
60
+ return {
61
+ ...createSuccessResponse(data),
62
+ structuredContent: data,
63
+ isError: !access.authorized,
64
+ };
65
+ } catch (error) {
66
+ return errorResult(error);
67
+ }
68
+ },
69
+ );
70
+ server.registerTool(
71
+ "droplit_push",
72
+ {
73
+ description:
74
+ "Submit one sponsored data transaction. Subject to sponsor approval and quotas. An unknown outcome requires checking transaction history before retrying.",
75
+ inputSchema: {
76
+ data: z.array(z.string()).min(1),
77
+ encoding: z.enum(["hex", "utf8"]),
78
+ },
79
+ annotations: {
80
+ readOnlyHint: false,
81
+ destructiveHint: true,
82
+ idempotentHint: false,
83
+ openWorldHint: true,
84
+ },
85
+ },
86
+ async ({ data, encoding }) => {
87
+ try {
88
+ if (disableBroadcasting) throw new Error("Broadcasting disabled");
89
+ assertBroadcastAllowed("droplit_push");
90
+ return createSuccessResponse(await client.push(data, encoding));
91
+ } catch (error) {
92
+ return errorResult(error);
93
+ }
94
+ },
95
+ );
96
+ server.registerTool(
97
+ "droplit_fund",
98
+ {
99
+ description:
100
+ "Submit one raw transaction for sponsor funding and broadcast. Subject to sponsor approval and quotas. Unknown outcomes must be reconciled before retrying.",
101
+ inputSchema: {
102
+ rawtx: z
103
+ .string()
104
+ .min(2)
105
+ .regex(/^(?:[0-9a-fA-F]{2})+$/, "Expected raw transaction hex"),
106
+ },
107
+ annotations: {
108
+ readOnlyHint: false,
109
+ destructiveHint: true,
110
+ idempotentHint: false,
111
+ openWorldHint: true,
112
+ },
113
+ },
114
+ async ({ rawtx }) => {
115
+ try {
116
+ if (disableBroadcasting) throw new Error("Broadcasting disabled");
117
+ assertBroadcastAllowed("droplit_fund");
118
+ return createSuccessResponse(await client.fund(rawtx));
119
+ } catch (error) {
120
+ return errorResult(error);
121
+ }
122
+ },
123
+ );
124
+ }
@@ -66,9 +66,7 @@ export class IntegratedWallet {
66
66
  satoshis: number,
67
67
  ): Promise<{ txid: string }> {
68
68
  if (this.droplitClient) {
69
- // For Droplit API, we use the tap endpoint which sends the faucet's fixed amount
70
- // Note: Droplit API doesn't support custom amounts, it uses fixed_drop_sats
71
- const response = await this.droplitClient.tap(address);
69
+ const response = await this.droplitClient.tap(address, satoshis);
72
70
  return { txid: response.txid };
73
71
  }
74
72
  if (this.localWallet) {
@@ -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
+ }
@@ -23,11 +23,11 @@ type DroplitClient = NonNullable<
23
23
  * signing key, so resolve both up front rather than failing part-way through a
24
24
  * request with a different message depending on which tool was called.
25
25
  */
26
- function requireDroplit(integratedWallet: IntegratedWallet): {
26
+ async function requireDroplit(integratedWallet: IntegratedWallet): Promise<{
27
27
  apiUrl: string;
28
28
  publicKeyHex: string;
29
29
  client: DroplitClient;
30
- } {
30
+ }> {
31
31
  const client = integratedWallet.getDroplitClient();
32
32
  if (!client) {
33
33
  throw new Error(
@@ -35,14 +35,8 @@ function requireDroplit(integratedWallet: IntegratedWallet): {
35
35
  );
36
36
  }
37
37
 
38
- const { apiUrl, authKey } = client.getConfig();
39
- if (!authKey) {
40
- throw new Error(
41
- "Droplit requests must be signed, but no auth key is configured.",
42
- );
43
- }
44
-
45
- return { apiUrl, publicKeyHex: authKey.toPublicKey().toString(), client };
38
+ const { apiUrl } = client.getConfig();
39
+ return { apiUrl, publicKeyHex: await client.getIdentityKey(), client };
46
40
  }
47
41
 
48
42
  async function failIfNotOk(
@@ -61,11 +55,11 @@ export function registerSetupDroplitTools(
61
55
  ) {
62
56
  server.tool(
63
57
  "wallet_registerDroplitKey",
64
- "Registers this wallet's public key with the Droplit API so it can sign authenticated faucet requests. Run once before creating or operating a faucet.",
58
+ "Registers public authentication material. Registration does not grant sponsor access; a sponsor owner must approve the wallet separately.",
65
59
  {},
66
60
  async () => {
67
61
  try {
68
- const { apiUrl, publicKeyHex } = requireDroplit(integratedWallet);
62
+ const { apiUrl, publicKeyHex } = await requireDroplit(integratedWallet);
69
63
 
70
64
  const response = await fetch(`${apiUrl}/auth/register`, {
71
65
  method: "POST",
@@ -86,7 +80,7 @@ export function registerSetupDroplitTools(
86
80
 
87
81
  server.tool(
88
82
  "wallet_createDroplitFaucet",
89
- "Provisions a new Droplit faucet owned by this wallet. Register the wallet's public key first with wallet_registerDroplitKey.",
83
+ "Creates a faucet owned by this wallet which must be funded before use. This does not provide free credit or approval for another sponsor.",
90
84
  {
91
85
  faucetName: z
92
86
  .string()
@@ -109,7 +103,7 @@ export function registerSetupDroplitTools(
109
103
  },
110
104
  async ({ faucetName, fixedDropSats, maxConsolidationInputs }) => {
111
105
  try {
112
- const { client } = requireDroplit(integratedWallet);
106
+ const { client } = await requireDroplit(integratedWallet);
113
107
 
114
108
  // Ownership comes from the BRC-103/104 identity established by the
115
109
  // handshake, not from the body, so the key is never sent here.
@@ -141,11 +135,11 @@ export function registerSetupDroplitTools(
141
135
 
142
136
  server.tool(
143
137
  "wallet_checkDroplitFaucetStatus",
144
- "Reads the status of the configured Droplit faucet, including balance and payout settings.",
138
+ "Reads public faucet balance and payout settings. This is not proof of caller authorization; use droplit_getAccess for that.",
145
139
  {},
146
140
  async () => {
147
141
  try {
148
- const { client } = requireDroplit(integratedWallet);
142
+ const { client } = await requireDroplit(integratedWallet);
149
143
  return createSuccessResponse({
150
144
  faucetStatus: await client.getFaucetStatus(),
151
145
  });
@@ -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";
@@ -31,7 +32,7 @@ import type { Wallet } from "./wallet";
31
32
 
32
33
  export function registerWalletTools(
33
34
  server: McpServer,
34
- wallet: Wallet,
35
+ wallet: Wallet | undefined,
35
36
  config: {
36
37
  disableBroadcasting: boolean;
37
38
  enableA2bTools: boolean;
@@ -58,9 +59,10 @@ 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
- if (config.enableA2bTools) {
65
+ if (config.enableA2bTools && wallet && config.identityPk) {
64
66
  // Register the wallet_a2bPublishMcp tool
65
67
  registerA2bPublishMcpTool(server, wallet, config.identityPk, {
66
68
  disableBroadcasting: config.disableBroadcasting,
@@ -71,8 +73,10 @@ export function registerWalletTools(
71
73
  registerCreateOrdinalsTool(server, config.ctx);
72
74
 
73
75
  // Register collection tools
74
- registerGatherCollectionInfoTool(server, wallet);
75
- registerMintCollectionTool(server, wallet);
76
+ if (wallet) {
77
+ registerGatherCollectionInfoTool(server, wallet);
78
+ registerMintCollectionTool(server, wallet);
79
+ }
76
80
 
77
81
  // Register read-only wallet tools
78
82
  registerGetOrdinalsTool(server, config.ctx);