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/CHANGELOG.md +17 -0
- package/README.md +123 -0
- package/dist/index.js +894 -146
- package/index.ts +61 -7
- package/package.json +1 -1
- package/tools/index.ts +12 -3
- package/tools/wallet/brc100.ts +25 -1
- package/tools/wallet/droplit.ts +124 -0
- package/tools/wallet/integratedWallet.ts +1 -3
- package/tools/wallet/revealDelegation.ts +74 -0
- package/tools/wallet/setupDroplit.ts +10 -16
- package/tools/wallet/tools.ts +8 -4
- package/test-mcp-server.ts +0 -179
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
|
|
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
|
|
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 (
|
|
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
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
|
|
package/tools/wallet/brc100.ts
CHANGED
|
@@ -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 ({
|
|
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
|
-
|
|
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
|
|
39
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
});
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
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);
|