bsv-mcp 0.0.35 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -1
- package/README.md +233 -4
- package/dist/index.js +111762 -0
- package/index.ts +804 -86
- package/next-env.d.ts +6 -0
- package/package.json +57 -15
- package/prompts/bsvSdk/index.ts +5 -5
- package/prompts/bsvSdk/transaction.ts +2 -2
- package/prompts/bsvSdk/wallet.ts +1 -1
- package/prompts/index.ts +1 -1
- package/resources/bitcom.ts +132 -0
- package/resources/resources.ts +7 -3
- package/smithery.yaml +3 -0
- package/test-mcp-server.ts +157 -0
- package/tools/bap/friend.ts +267 -0
- package/tools/bap/generate.test.ts +350 -0
- package/tools/bap/generate.ts +433 -0
- package/tools/bap/getCurrentAddress.ts +76 -0
- package/tools/bap/getId.test.ts +121 -0
- package/tools/bap/getId.ts +169 -0
- package/tools/bap/index.ts +111 -0
- package/tools/bap/types.ts +66 -0
- package/tools/bap/utils.ts +33 -0
- package/tools/bigblocks/components.ts +304 -0
- package/tools/bigblocks/docs.ts +488 -0
- package/tools/bigblocks/examples.ts +527 -0
- package/tools/bigblocks/generator.ts +485 -0
- package/tools/bigblocks/index.ts +23 -0
- package/tools/bsocial/bigblocksApiClient.ts +189 -0
- package/tools/bsocial/bmapFollow.ts +125 -0
- package/tools/bsocial/bmapLikes.ts +111 -0
- package/tools/bsocial/bmapReadPosts.ts +148 -0
- package/tools/bsocial/createPost.ts +136 -0
- package/tools/bsocial/index.ts +45 -0
- package/tools/bsocial/readPosts.ts +255 -0
- package/tools/bsv/decodeTransaction.ts +5 -1
- package/tools/constants.ts +21 -0
- package/tools/index.ts +103 -17
- package/tools/mnee/index.ts +8 -0
- package/tools/mnee/parseTx.ts +1 -1
- package/tools/mnee/sendMnee.ts +1 -1
- package/tools/utils/aip.ts +46 -0
- package/tools/utils/conversion.ts +1 -0
- package/tools/utils/errorHandler.ts +76 -0
- package/tools/utils/index.ts +45 -1
- package/tools/utils/installAgentMaster.ts +84 -0
- package/tools/utils/logger.ts +20 -0
- package/tools/utils/toolConfig.ts +80 -0
- package/tools/utils/toolRegistration.ts +66 -0
- package/tools/utils/transactionBuilder.ts +196 -0
- package/tools/wallet/a2bPublishAgent.ts +8 -4
- package/tools/wallet/a2bPublishMcp.ts +28 -24
- package/tools/wallet/createOrdinals.ts +4 -2
- package/tools/wallet/fetchPaymentUtxos.ts +44 -43
- package/tools/wallet/gatherCollectionInfo.ts +374 -0
- package/tools/wallet/getAddress.ts +13 -21
- package/tools/wallet/getBalance.ts +66 -0
- package/tools/wallet/getBalanceDroplet.ts +59 -0
- package/tools/wallet/integratedWallet.ts +120 -0
- package/tools/wallet/mintCollection.ts +378 -0
- package/tools/wallet/purchaseListing.ts +4 -4
- package/tools/wallet/schemas.ts +18 -249
- package/tools/wallet/sendOrdinals.ts +4 -2
- package/tools/wallet/sendToAddress.ts +5 -1
- package/tools/wallet/setupDroplet.ts +172 -0
- package/tools/wallet/tools.ts +71 -156
- package/tools/wallet/transactionHelper.ts +121 -0
- package/tools/wallet/transferOrdToken.ts +7 -4
- package/tools/wallet/utxo.ts +62 -0
- package/tools/wallet/wallet.ts +237 -234
- package/tools/a2b/call.ts +0 -32
package/index.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { PrivateKey } from "@bsv/sdk";
|
|
3
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import packageJson from "./package.json";
|
|
5
8
|
import { registerAllPrompts } from "./prompts/index.ts";
|
|
6
9
|
import { registerResources } from "./resources/resources.ts";
|
|
7
|
-
import { registerAllTools } from "./tools/index.ts";
|
|
10
|
+
import { registerAllTools, type ToolsConfig } from "./tools/index.ts";
|
|
8
11
|
import { registerMneeTools } from "./tools/mnee/index.ts";
|
|
9
|
-
import {
|
|
12
|
+
import { IntegratedWallet } from "./tools/wallet/integratedWallet.ts";
|
|
10
13
|
import { Wallet } from "./tools/wallet/wallet.ts";
|
|
14
|
+
import { BunSSEServerTransport } from "./transports/sse.ts";
|
|
15
|
+
import {
|
|
16
|
+
type BSVJWTPayload,
|
|
17
|
+
createMCPJWTValidator,
|
|
18
|
+
generateWWWAuthenticate,
|
|
19
|
+
} from "./utils/jwtValidator.ts";
|
|
20
|
+
import { SecureKeyManager } from "./utils/keyManager.ts";
|
|
21
|
+
import { setServerInstance } from "./utils/passphrasePrompt.ts";
|
|
22
|
+
|
|
23
|
+
// Initialize server variable
|
|
24
|
+
let server: McpServer | undefined;
|
|
11
25
|
|
|
12
26
|
/**
|
|
13
27
|
* Configuration options from environment variables
|
|
@@ -18,119 +32,823 @@ const CONFIG = {
|
|
|
18
32
|
loadResources: process.env.DISABLE_RESOURCES !== "true",
|
|
19
33
|
loadTools: process.env.DISABLE_TOOLS !== "true",
|
|
20
34
|
|
|
21
|
-
// Fine-grained tool category control
|
|
35
|
+
// Fine-grained tool category control (dependent on key availability)
|
|
22
36
|
loadWalletTools: process.env.DISABLE_WALLET_TOOLS !== "true",
|
|
23
37
|
loadMneeTools: process.env.DISABLE_MNEE_TOOLS !== "true",
|
|
24
38
|
loadBsvTools: process.env.DISABLE_BSV_TOOLS !== "true",
|
|
25
39
|
loadOrdinalsTools: process.env.DISABLE_ORDINALS_TOOLS !== "true",
|
|
26
40
|
loadUtilsTools: process.env.DISABLE_UTILS_TOOLS !== "true",
|
|
27
41
|
loadA2bTools: process.env.ENABLE_A2B_TOOLS === "true",
|
|
42
|
+
loadBapTools: process.env.DISABLE_BAP_TOOLS !== "true",
|
|
43
|
+
loadBsocialTools: process.env.DISABLE_BSOCIAL_TOOLS !== "true",
|
|
44
|
+
loadBigBlocksTools: process.env.DISABLE_BIGBLOCKS_TOOLS !== "true",
|
|
28
45
|
|
|
29
46
|
// Transaction broadcasting control
|
|
30
47
|
disableBroadcasting: process.env.DISABLE_BROADCASTING === "true",
|
|
48
|
+
|
|
49
|
+
// --- Transport Mode ---
|
|
50
|
+
transportMode: process.env.TRANSPORT?.toLowerCase() || "http", // 'stdio' or 'http'/default
|
|
51
|
+
port: Number.parseInt(process.env.PORT || "3000", 10),
|
|
52
|
+
|
|
53
|
+
// --- Droplet API Configuration ---
|
|
54
|
+
useDropletApi: process.env.USE_DROPLET_API === "true",
|
|
55
|
+
dropletApiUrl: process.env.DROPLET_API_URL || "http://127.0.0.1:4000",
|
|
56
|
+
dropletFaucetName: process.env.DROPLET_FAUCET_NAME || "",
|
|
57
|
+
|
|
58
|
+
// --- OAuth Configuration ---
|
|
59
|
+
enableOAuth: process.env.ENABLE_OAUTH !== "false", // Enabled by default
|
|
60
|
+
oauthIssuer: process.env.OAUTH_ISSUER || "https://auth.sigmaidentity.com",
|
|
61
|
+
resourceUrl: process.env.RESOURCE_URL || "", // Will be set based on port
|
|
31
62
|
};
|
|
32
63
|
|
|
64
|
+
const logFunc = console.error;
|
|
65
|
+
const KEY_DIR = path.join(os.homedir(), ".bsv-mcp");
|
|
66
|
+
const KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
|
|
67
|
+
|
|
33
68
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
69
|
+
* Initializes payment and identity private keys using SecureKeyManager.
|
|
70
|
+
* Priorities:
|
|
71
|
+
* 1. Valid PRIVATE_KEY_WIF environment variable (provides payPk, identityPk remains undefined).
|
|
72
|
+
* 2. Valid keys found in encrypted ~/.bsv-mcp/keys.bep or legacy ~/.bsv-mcp/keys.json file.
|
|
73
|
+
* 3. Generate only payPk, save it to the file, and log warnings. identityPk/xprv only created via bap_generate.
|
|
74
|
+
* Returns the keys and their source ('env', 'file', 'encrypted', 'generated', or 'none').
|
|
36
75
|
*/
|
|
37
|
-
function
|
|
38
|
-
|
|
76
|
+
async function initializeKeys(): Promise<{
|
|
77
|
+
payPk?: PrivateKey;
|
|
78
|
+
identityPk?: PrivateKey;
|
|
79
|
+
xprv?: string;
|
|
80
|
+
source: "env" | "file" | "encrypted" | "generated" | "none";
|
|
81
|
+
}> {
|
|
82
|
+
const keyManager = new SecureKeyManager({ keyDir: KEY_DIR });
|
|
39
83
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
84
|
+
const privateKeyWifEnv = process.env.PRIVATE_KEY_WIF;
|
|
85
|
+
|
|
86
|
+
// 1. Try environment variable for payPk
|
|
87
|
+
if (privateKeyWifEnv) {
|
|
88
|
+
try {
|
|
89
|
+
const payPk = PrivateKey.fromWif(privateKeyWifEnv);
|
|
90
|
+
console.error(
|
|
91
|
+
"\x1b[32mINFO: Using valid PRIVATE_KEY_WIF from environment for payment key.\x1b[0m",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Also check for encrypted/legacy keys to get xprv if available
|
|
95
|
+
let xprvFromFile: string | undefined;
|
|
96
|
+
try {
|
|
97
|
+
const { keys } = await keyManager.loadKeys();
|
|
98
|
+
xprvFromFile = keys.xprv;
|
|
99
|
+
if (xprvFromFile) {
|
|
100
|
+
console.error(
|
|
101
|
+
"\x1b[32mINFO: Loaded BAP HD Master Key (xprv) from secure storage alongside ENV payPk.\x1b[0m",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
} catch (_e) {
|
|
105
|
+
/* Ignore file read errors here */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!xprvFromFile) {
|
|
109
|
+
console.error(
|
|
110
|
+
"\x1b[33mWARN: No identity key (identityPk or xprv) found. BAP/A2B tools needing identity will be limited.\x1b[0m",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
payPk,
|
|
116
|
+
identityPk: undefined,
|
|
117
|
+
xprv: xprvFromFile,
|
|
118
|
+
source: "env",
|
|
119
|
+
};
|
|
120
|
+
} catch (_error) {
|
|
121
|
+
console.error(
|
|
122
|
+
"\x1b[33mWARN: Invalid PRIVATE_KEY_WIF format in environment variable. Checking secure storage next.\x1b[0m",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
55
125
|
}
|
|
56
126
|
|
|
57
|
-
//
|
|
127
|
+
// 2. Try reading from secure storage (encrypted or legacy)
|
|
58
128
|
try {
|
|
59
|
-
|
|
129
|
+
const { keys, source } = await keyManager.loadKeys();
|
|
130
|
+
|
|
131
|
+
if (keys.payPk) {
|
|
132
|
+
if (source === "encrypted") {
|
|
133
|
+
console.error(
|
|
134
|
+
`\x1b[32mINFO: Using encrypted keys from: ${KEY_DIR}/keys.bep\x1b[0m`,
|
|
135
|
+
);
|
|
136
|
+
} else if (source === "legacy") {
|
|
137
|
+
console.error(
|
|
138
|
+
`\x1b[32mINFO: Using legacy keys from: ${KEY_FILE_PATH}\x1b[0m`,
|
|
139
|
+
);
|
|
140
|
+
console.error(
|
|
141
|
+
"\x1b[33mWARN: Using unencrypted keys. Run the server again to encrypt them.\x1b[0m",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (keys.identityPk) {
|
|
146
|
+
console.error(
|
|
147
|
+
"\x1b[32mINFO: Loaded Identity Key (identityPk) from secure storage.\x1b[0m",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (keys.xprv) {
|
|
151
|
+
console.error(
|
|
152
|
+
"\x1b[32mINFO: Loaded BAP HD Master Key (xprv) from secure storage.\x1b[0m",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
payPk: keys.payPk,
|
|
158
|
+
identityPk: keys.identityPk,
|
|
159
|
+
xprv: keys.xprv,
|
|
160
|
+
source: source === "encrypted" ? "encrypted" : "file",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.error("\x1b[33mINFO: No keys found in secure storage.\x1b[0m");
|
|
60
165
|
} catch (error) {
|
|
61
|
-
console.
|
|
62
|
-
|
|
63
|
-
|
|
166
|
+
console.error(
|
|
167
|
+
`\x1b[33mWARN: Error reading keys from secure storage: ${error}\x1b[0m`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 3. Generate new payment key (but not identityPk/xprv)
|
|
172
|
+
console.error("\x1b[33mINFO: Generating new payment key ONLY.\x1b[0m");
|
|
173
|
+
const payPk = PrivateKey.fromRandom();
|
|
174
|
+
const keyStore = {
|
|
175
|
+
payPk,
|
|
176
|
+
identityPk: undefined,
|
|
177
|
+
xprv: undefined,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Save unencrypted for now - user can encrypt on next run
|
|
182
|
+
await keyManager.saveKeys(keyStore, { forceUnencrypted: true });
|
|
183
|
+
const status = keyManager.getStatus();
|
|
184
|
+
|
|
185
|
+
if (status.hasEncrypted) {
|
|
186
|
+
console.error(
|
|
187
|
+
`\x1b[33mWARN: Saved newly generated payment key (encrypted) to: ${KEY_DIR}/keys.bep\x1b[0m`,
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
console.error(
|
|
191
|
+
`\x1b[33mWARN: Saved newly generated payment key (unencrypted) to: ${KEY_FILE_PATH}\x1b[0m`,
|
|
192
|
+
);
|
|
193
|
+
console.error(
|
|
194
|
+
"\x1b[33mWARN: Run the server again to encrypt your keys.\x1b[0m",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
console.error(
|
|
198
|
+
"\x1b[33mWARN: Identity key (identityPk/xprv) not found or generated. Use 'bap_generate' tool to create one.\x1b[0m",
|
|
199
|
+
);
|
|
200
|
+
} catch (writeError) {
|
|
201
|
+
console.error(
|
|
202
|
+
`\x1b[31mERROR: Failed to save newly generated payment key: ${writeError}\x1b[0m`,
|
|
64
203
|
);
|
|
65
|
-
console.
|
|
66
|
-
"
|
|
204
|
+
console.error(
|
|
205
|
+
"\x1b[31mERROR: Using the generated payment key for this session only. No identity key available.\x1b[0m",
|
|
67
206
|
);
|
|
68
|
-
return undefined;
|
|
69
207
|
}
|
|
208
|
+
|
|
209
|
+
return { payPk, identityPk: undefined, xprv: undefined, source: "generated" };
|
|
70
210
|
}
|
|
71
211
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
212
|
+
// --- Main Server Setup ---
|
|
213
|
+
async function main() {
|
|
214
|
+
// Check for help or info commands that don't need authentication
|
|
215
|
+
const args = process.argv.slice(2);
|
|
216
|
+
if (args.includes("--help") || args.includes("-h") || args.includes("help")) {
|
|
217
|
+
console.log(`
|
|
218
|
+
BSV MCP Server v${packageJson.version}
|
|
219
|
+
|
|
220
|
+
Usage: bun run index.ts [options]
|
|
221
|
+
|
|
222
|
+
Options:
|
|
223
|
+
--help, -h Show this help message
|
|
224
|
+
--version, -v Show version information
|
|
225
|
+
|
|
226
|
+
Environment Variables:
|
|
227
|
+
TRANSPORT Transport mode: 'stdio' or 'http' (default: http)
|
|
228
|
+
PORT HTTP server port (default: 3000)
|
|
229
|
+
PRIVATE_KEY_WIF Payment private key in WIF format
|
|
230
|
+
DISABLE_TOOLS Disable all tools (default: false)
|
|
231
|
+
DISABLE_WALLET_TOOLS Disable wallet tools (default: false)
|
|
232
|
+
DISABLE_BSV_TOOLS Disable BSV tools (default: false)
|
|
233
|
+
DISABLE_ORDINALS_TOOLS Disable ordinals tools (default: false)
|
|
234
|
+
DISABLE_UTILS_TOOLS Disable utility tools (default: false)
|
|
235
|
+
DISABLE_BAP_TOOLS Disable BAP tools (default: false)
|
|
236
|
+
DISABLE_BSOCIAL_TOOLS Disable BSocial tools (default: false)
|
|
237
|
+
DISABLE_BIGBLOCKS_TOOLS Disable BigBlocks tools (default: false)
|
|
238
|
+
ENABLE_A2B_TOOLS Enable A2B tools (default: false)
|
|
239
|
+
DISABLE_BROADCASTING Disable transaction broadcasting (default: false)
|
|
240
|
+
USE_DROPLET_API Use Droplet API for transactions (default: false)
|
|
241
|
+
|
|
242
|
+
Tool Categories:
|
|
243
|
+
BSV Tools: Price lookup, transaction decoding, validation
|
|
244
|
+
Wallet Tools: Send payments, manage UTXOs (requires payment key)
|
|
245
|
+
Ordinals Tools: Search listings, market data
|
|
246
|
+
Utils Tools: General utilities, conversions
|
|
247
|
+
BAP Tools: Identity management (requires identity key)
|
|
248
|
+
BSocial Tools: Social posts, likes, follows
|
|
249
|
+
BigBlocks Tools: React component registry, code generation, examples
|
|
250
|
+
A2B Tools: Advanced BSV operations (requires identity key)
|
|
251
|
+
|
|
252
|
+
Authentication:
|
|
253
|
+
- Most tools work without authentication
|
|
254
|
+
- Wallet operations require PRIVATE_KEY_WIF or generated keys
|
|
255
|
+
- BAP/A2B tools require identity keys (generated via bap_generate tool)
|
|
256
|
+
`);
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
261
|
+
console.log(`${packageJson.name} v${packageJson.version}`);
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --- Initialize Keys ---
|
|
266
|
+
const { payPk, identityPk, xprv, source: keySource } = await initializeKeys();
|
|
267
|
+
|
|
268
|
+
// Define persistence based on source
|
|
269
|
+
const hasPersistentPayKey =
|
|
270
|
+
keySource === "env" || keySource === "file" || keySource === "encrypted";
|
|
271
|
+
const hasPersistentIdentityKey =
|
|
272
|
+
!!identityPk && (keySource === "file" || keySource === "encrypted");
|
|
273
|
+
const hasXprv =
|
|
274
|
+
!!xprv &&
|
|
275
|
+
(keySource === "file" || keySource === "env" || keySource === "encrypted");
|
|
276
|
+
|
|
277
|
+
const effectiveConfig = { ...CONFIG };
|
|
278
|
+
|
|
279
|
+
// --- Configuration Logging ---
|
|
280
|
+
logFunc("\n--- BSV MCP Server Configuration ---");
|
|
281
|
+
logFunc(`Server Version: ${packageJson.version}`);
|
|
282
|
+
logFunc(`Server Name: ${packageJson.name}`);
|
|
283
|
+
logFunc("\nEnvironment Variables:");
|
|
284
|
+
logFunc(
|
|
285
|
+
` TRANSPORT: ${process.env.TRANSPORT || "Not Set (http default)"}`,
|
|
286
|
+
);
|
|
287
|
+
if (CONFIG.transportMode === "http") {
|
|
288
|
+
logFunc(
|
|
289
|
+
` PORT: ${process.env.PORT || "Not Set (3000 default)"}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
logFunc(
|
|
293
|
+
` PRIVATE_KEY_WIF: ${process.env.PRIVATE_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
|
|
294
|
+
);
|
|
295
|
+
logFunc(
|
|
296
|
+
` IDENTITY_KEY_WIF: ${process.env.IDENTITY_KEY_WIF ? "Set (using env key)" : "Not Set (using file/generating)"}`,
|
|
297
|
+
);
|
|
298
|
+
if (process.env.BSV_MCP_PASSPHRASE) {
|
|
299
|
+
logFunc(" BSV_MCP_PASSPHRASE: \x1b[31mDEPRECATED - Remove this!\x1b[0m");
|
|
300
|
+
}
|
|
301
|
+
logFunc(
|
|
302
|
+
` DISABLE_PROMPTS: ${process.env.DISABLE_PROMPTS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
303
|
+
);
|
|
304
|
+
logFunc(
|
|
305
|
+
` DISABLE_RESOURCES: ${process.env.DISABLE_RESOURCES === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
306
|
+
);
|
|
307
|
+
logFunc(
|
|
308
|
+
` DISABLE_TOOLS: ${process.env.DISABLE_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
309
|
+
);
|
|
310
|
+
logFunc(
|
|
311
|
+
` DISABLE_WALLET_TOOLS: ${process.env.DISABLE_WALLET_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
312
|
+
);
|
|
313
|
+
logFunc(
|
|
314
|
+
` DISABLE_MNEE_TOOLS: ${process.env.DISABLE_MNEE_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
315
|
+
);
|
|
316
|
+
logFunc(
|
|
317
|
+
` DISABLE_BSV_TOOLS: ${process.env.DISABLE_BSV_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
318
|
+
);
|
|
319
|
+
logFunc(
|
|
320
|
+
` DISABLE_ORDINALS_TOOLS: ${process.env.DISABLE_ORDINALS_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
321
|
+
);
|
|
322
|
+
logFunc(
|
|
323
|
+
` DISABLE_UTILS_TOOLS: ${process.env.DISABLE_UTILS_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
324
|
+
);
|
|
325
|
+
logFunc(
|
|
326
|
+
` ENABLE_A2B_TOOLS: ${process.env.ENABLE_A2B_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
327
|
+
);
|
|
328
|
+
logFunc(
|
|
329
|
+
` DISABLE_BAP_TOOLS: ${process.env.DISABLE_BAP_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
330
|
+
);
|
|
331
|
+
logFunc(
|
|
332
|
+
` DISABLE_BIGBLOCKS_TOOLS: ${process.env.DISABLE_BIGBLOCKS_TOOLS === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
333
|
+
);
|
|
334
|
+
logFunc(
|
|
335
|
+
` DISABLE_BROADCASTING: ${process.env.DISABLE_BROADCASTING === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
336
|
+
);
|
|
337
|
+
logFunc(
|
|
338
|
+
` USE_DROPLET_API: ${CONFIG.useDropletApi ? "Set (true)" : "Not Set/false"}`,
|
|
339
|
+
);
|
|
340
|
+
if (CONFIG.useDropletApi) {
|
|
341
|
+
logFunc(` DROPLET_API_URL: ${CONFIG.dropletApiUrl}`);
|
|
342
|
+
logFunc(` DROPLET_FAUCET_NAME: ${CONFIG.dropletFaucetName || "Not Set"}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
logFunc("\nKey Source:");
|
|
346
|
+
let payKeySourceInfo = `Payment Key (payPk): ${keySource}`;
|
|
347
|
+
if (keySource === "env") payKeySourceInfo = "Payment Key (payPk): env";
|
|
348
|
+
if (keySource === "file")
|
|
349
|
+
payKeySourceInfo += ` (Loaded from ${KEY_FILE_PATH})`;
|
|
350
|
+
if (keySource === "encrypted")
|
|
351
|
+
payKeySourceInfo += ` (Loaded from ${KEY_DIR}/keys.bep)`;
|
|
352
|
+
if (keySource === "generated") payKeySourceInfo += " (Generated & saved)";
|
|
353
|
+
|
|
354
|
+
let identityKeySourceInfo = "Identity Key (identityPk): Not Loaded";
|
|
355
|
+
if (identityPk && (keySource === "file" || keySource === "encrypted")) {
|
|
356
|
+
identityKeySourceInfo = `Identity Key (identityPk): ${keySource} (Loaded from secure storage)`;
|
|
357
|
+
} else if (identityPk) {
|
|
358
|
+
identityKeySourceInfo = `Identity Key (identityPk): ${keySource} (Unexpected Source)`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let xprvSourceInfo = "BAP Master Key (xprv): Not found";
|
|
362
|
+
if (hasXprv) {
|
|
363
|
+
xprvSourceInfo = "BAP Master Key (xprv): Loaded from secure storage";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
logFunc(` ${payKeySourceInfo}`);
|
|
367
|
+
logFunc(` ${identityKeySourceInfo}`);
|
|
368
|
+
logFunc(` ${xprvSourceInfo}`);
|
|
369
|
+
|
|
370
|
+
logFunc("\nEffective Component Status:");
|
|
371
|
+
logFunc(` Transport Mode: ${CONFIG.transportMode.toUpperCase()}`);
|
|
372
|
+
logFunc(
|
|
373
|
+
` Prompts: ${effectiveConfig.loadPrompts ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
374
|
+
);
|
|
375
|
+
logFunc(
|
|
376
|
+
` Resources: ${effectiveConfig.loadResources ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
377
|
+
);
|
|
378
|
+
logFunc(
|
|
379
|
+
` Tools (Overall): ${effectiveConfig.loadTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
380
|
+
);
|
|
381
|
+
if (effectiveConfig.loadTools) {
|
|
382
|
+
const walletStatus = effectiveConfig.loadWalletTools
|
|
383
|
+
? "\x1b[32mEnabled\x1b[0m"
|
|
384
|
+
: "\x1b[31mDisabled\x1b[0m";
|
|
385
|
+
const mneeStatus = effectiveConfig.loadMneeTools
|
|
386
|
+
? "\x1b[32mEnabled\x1b[0m"
|
|
387
|
+
: "\x1b[31mDisabled\x1b[0m";
|
|
388
|
+
const a2bStatus = effectiveConfig.loadA2bTools
|
|
389
|
+
? "\x1b[32mEnabled\x1b[0m"
|
|
390
|
+
: "\x1b[31mDisabled\x1b[0m";
|
|
391
|
+
const bapStatus = effectiveConfig.loadBapTools
|
|
392
|
+
? "\x1b[32mEnabled\x1b[0m"
|
|
393
|
+
: "\x1b[31mDisabled\x1b[0m";
|
|
394
|
+
|
|
395
|
+
let payKeyNote = "";
|
|
396
|
+
if (!hasPersistentPayKey) {
|
|
397
|
+
payKeyNote = " \x1b[33m(Using generated payPk)\x1b[0m";
|
|
398
|
+
}
|
|
399
|
+
let identityKeyNote = "";
|
|
400
|
+
if (!hasPersistentIdentityKey) {
|
|
401
|
+
identityKeyNote = " \x1b[33m(Using generated identityPk)\x1b[0m";
|
|
103
402
|
}
|
|
104
403
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
404
|
+
logFunc(` Wallet: ${walletStatus}${payKeyNote}`);
|
|
405
|
+
logFunc(` MNEE: ${mneeStatus}${payKeyNote}`);
|
|
406
|
+
logFunc(
|
|
407
|
+
` BSV: ${effectiveConfig.loadBsvTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
408
|
+
);
|
|
409
|
+
logFunc(
|
|
410
|
+
` Ordinals: ${effectiveConfig.loadOrdinalsTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
411
|
+
);
|
|
412
|
+
logFunc(
|
|
413
|
+
` Utils: ${effectiveConfig.loadUtilsTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
414
|
+
);
|
|
415
|
+
logFunc(` A2B: ${a2bStatus}${identityKeyNote}`);
|
|
416
|
+
logFunc(` BAP: ${bapStatus}${identityKeyNote}`);
|
|
417
|
+
logFunc(
|
|
418
|
+
` BSocial: ${effectiveConfig.loadBsocialTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
419
|
+
);
|
|
420
|
+
logFunc(
|
|
421
|
+
` BigBlocks: ${effectiveConfig.loadBigBlocksTools ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
if (effectiveConfig.loadWalletTools) {
|
|
425
|
+
logFunc(
|
|
426
|
+
` Broadcasting: ${!effectiveConfig.disableBroadcasting ? "\x1b[32mEnabled\x1b[0m" : "\x1b[31mDisabled\x1b[0m"}`,
|
|
427
|
+
);
|
|
112
428
|
}
|
|
113
429
|
}
|
|
430
|
+
logFunc("------------------------------------\n");
|
|
431
|
+
// --- End of Logging Block ---
|
|
114
432
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
433
|
+
server = new McpServer(
|
|
434
|
+
{ name: packageJson.name, version: packageJson.version },
|
|
435
|
+
{
|
|
436
|
+
capabilities: {
|
|
437
|
+
prompts: {},
|
|
438
|
+
resources: {},
|
|
439
|
+
tools: {},
|
|
440
|
+
},
|
|
441
|
+
instructions: `
|
|
442
|
+
This server exposes Bitcoin SV helpers.
|
|
443
|
+
Tools are idempotent unless marked destructive.
|
|
444
|
+
`,
|
|
445
|
+
},
|
|
446
|
+
);
|
|
123
447
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
448
|
+
// Set server instance for transport detection
|
|
449
|
+
setServerInstance(server);
|
|
450
|
+
|
|
451
|
+
let wallet: Wallet | undefined;
|
|
452
|
+
let integratedWallet: IntegratedWallet | undefined;
|
|
453
|
+
|
|
454
|
+
if (CONFIG.loadTools) {
|
|
455
|
+
// Check if we should use Droplet API mode
|
|
456
|
+
if (CONFIG.useDropletApi && CONFIG.dropletFaucetName) {
|
|
457
|
+
// Initialize IntegratedWallet in Droplet mode
|
|
458
|
+
if (CONFIG.loadWalletTools) {
|
|
459
|
+
try {
|
|
460
|
+
integratedWallet = new IntegratedWallet({
|
|
461
|
+
useDropletApi: true,
|
|
462
|
+
dropletConfig: {
|
|
463
|
+
apiUrl: CONFIG.dropletApiUrl,
|
|
464
|
+
faucetName: CONFIG.dropletFaucetName,
|
|
465
|
+
// Note: authKey will be set by IntegratedWallet if paymentKey is provided
|
|
466
|
+
},
|
|
467
|
+
paymentKey: payPk, // Pass payment key for auth
|
|
468
|
+
identityKey: identityPk,
|
|
469
|
+
});
|
|
470
|
+
logFunc(
|
|
471
|
+
`\x1b[32mINFO: Droplet API mode initialized successfully (Faucet: ${CONFIG.dropletFaucetName}).\x1b[0m`,
|
|
472
|
+
);
|
|
473
|
+
logFunc(
|
|
474
|
+
`\x1b[33mNOTE: Using Droplet API at ${CONFIG.dropletApiUrl}\x1b[0m`,
|
|
475
|
+
);
|
|
476
|
+
logFunc(
|
|
477
|
+
"\x1b[33mNOTE: Local keys are ignored in Droplet API mode\x1b[0m",
|
|
478
|
+
);
|
|
479
|
+
// Create a compatibility wrapper for existing code
|
|
480
|
+
wallet = integratedWallet.getLocalWallet(); // This will be undefined in Droplet mode
|
|
481
|
+
|
|
482
|
+
// In Droplet mode, we need to disable tools that require local keys
|
|
483
|
+
effectiveConfig.loadMneeTools = false; // MNEE requires local wallet
|
|
484
|
+
effectiveConfig.loadBapTools = false; // BAP requires identity key
|
|
485
|
+
effectiveConfig.loadA2bTools = false; // A2B requires identity key
|
|
486
|
+
effectiveConfig.loadBsocialTools = false; // BSocial requires local wallet
|
|
487
|
+
} catch (e) {
|
|
488
|
+
logFunc(
|
|
489
|
+
`\x1b[31mERROR: Failed to initialize Droplet API mode: ${e instanceof Error ? e.message : String(e)}. Wallet-dependent tools will be unavailable.\x1b[0m`,
|
|
490
|
+
);
|
|
491
|
+
integratedWallet = undefined;
|
|
492
|
+
effectiveConfig.loadWalletTools = false;
|
|
493
|
+
effectiveConfig.loadMneeTools = false;
|
|
494
|
+
effectiveConfig.loadBapTools = false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} else if (payPk) {
|
|
498
|
+
// Initialize wallet with the payPk if wallet tools are enabled
|
|
499
|
+
if (CONFIG.loadWalletTools) {
|
|
500
|
+
try {
|
|
501
|
+
wallet = new Wallet(payPk, identityPk);
|
|
502
|
+
integratedWallet = new IntegratedWallet({
|
|
503
|
+
paymentKey: payPk,
|
|
504
|
+
identityKey: identityPk,
|
|
505
|
+
});
|
|
506
|
+
logFunc(
|
|
507
|
+
"\x1b[32mINFO: Custom Wallet initialized successfully.\x1b[0m",
|
|
508
|
+
);
|
|
509
|
+
if (!hasPersistentPayKey) {
|
|
510
|
+
logFunc(
|
|
511
|
+
"\x1b[33mWARN: Wallet is using a generated payment key for this session only. It will not persist.\x1b[0m",
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
if (identityPk && !hasPersistentIdentityKey && !hasXprv) {
|
|
515
|
+
logFunc(
|
|
516
|
+
"\x1b[33mWARN: Wallet is using an identity key that might not be from a persistent file source (keys.json xprv or identityPk field) for BAP operations.\x1b[0m",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
} catch (e) {
|
|
520
|
+
logFunc(
|
|
521
|
+
`\x1b[31mERROR: Failed to initialize custom wallet: ${e instanceof Error ? e.message : String(e)}. Wallet-dependent tools will be unavailable.\x1b[0m`,
|
|
522
|
+
);
|
|
523
|
+
wallet = undefined;
|
|
524
|
+
integratedWallet = undefined;
|
|
525
|
+
effectiveConfig.loadWalletTools = false;
|
|
526
|
+
effectiveConfig.loadMneeTools = false;
|
|
527
|
+
effectiveConfig.loadBapTools = false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Register MNEE tools if enabled and wallet is available
|
|
532
|
+
if (effectiveConfig.loadMneeTools && wallet) {
|
|
533
|
+
registerMneeTools(server);
|
|
534
|
+
} else if (
|
|
535
|
+
effectiveConfig.loadMneeTools &&
|
|
536
|
+
!wallet &&
|
|
537
|
+
CONFIG.loadWalletTools
|
|
538
|
+
) {
|
|
539
|
+
logFunc(
|
|
540
|
+
"\x1b[33mWARN: MNEE tools require a wallet but wallet initialization failed. MNEE tools disabled.\x1b[0m",
|
|
541
|
+
);
|
|
542
|
+
effectiveConfig.loadMneeTools = false;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Register all other tools based on configuration
|
|
547
|
+
const toolsConfig: ToolsConfig = {
|
|
548
|
+
enableBsvTools: effectiveConfig.loadBsvTools,
|
|
549
|
+
enableOrdinalsTools: effectiveConfig.loadOrdinalsTools,
|
|
550
|
+
enableUtilsTools: effectiveConfig.loadUtilsTools,
|
|
551
|
+
enableA2bTools: effectiveConfig.loadA2bTools,
|
|
552
|
+
enableBapTools: effectiveConfig.loadBapTools,
|
|
553
|
+
enableBsocialTools: effectiveConfig.loadBsocialTools,
|
|
554
|
+
enableBigBlocksTools: effectiveConfig.loadBigBlocksTools,
|
|
555
|
+
enableWalletTools: effectiveConfig.loadWalletTools,
|
|
556
|
+
enableMneeTools: effectiveConfig.loadMneeTools,
|
|
557
|
+
identityPk,
|
|
558
|
+
payPk,
|
|
559
|
+
xprv,
|
|
560
|
+
wallet,
|
|
561
|
+
integratedWallet,
|
|
562
|
+
disableBroadcasting: effectiveConfig.disableBroadcasting,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
registerAllTools(server, toolsConfig);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Register prompts if enabled
|
|
569
|
+
if (CONFIG.loadPrompts) {
|
|
570
|
+
registerAllPrompts(server);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Register resources if enabled
|
|
574
|
+
if (CONFIG.loadResources) {
|
|
575
|
+
registerResources(server);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Start the server based on transport mode
|
|
579
|
+
if (CONFIG.transportMode === "stdio") {
|
|
580
|
+
const transport = new StdioServerTransport();
|
|
581
|
+
await server.connect(transport);
|
|
582
|
+
logFunc("BSV MCP Server running on stdio");
|
|
583
|
+
} else {
|
|
584
|
+
const port = CONFIG.port;
|
|
585
|
+
const messageEndpoint = "/messages";
|
|
586
|
+
const activeTransports = new Map<string, BunSSEServerTransport>();
|
|
587
|
+
|
|
588
|
+
// Set up resource URL for OAuth
|
|
589
|
+
const resourceUrl = CONFIG.resourceUrl || `http://localhost:${port}`;
|
|
128
590
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
591
|
+
// Initialize JWT validator if OAuth is enabled
|
|
592
|
+
const jwtValidator = CONFIG.enableOAuth
|
|
593
|
+
? createMCPJWTValidator(resourceUrl)
|
|
594
|
+
: null;
|
|
595
|
+
|
|
596
|
+
logFunc(`Starting BSV MCP Server in HTTP/SSE mode on port ${port}...`);
|
|
597
|
+
if (CONFIG.enableOAuth) {
|
|
598
|
+
logFunc(`OAuth 2.1 authentication enabled`);
|
|
599
|
+
logFunc(` Issuer: ${CONFIG.oauthIssuer}`);
|
|
600
|
+
logFunc(` Resource: ${resourceUrl}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
Bun.serve({
|
|
604
|
+
port: port,
|
|
605
|
+
async fetch(req: Request): Promise<Response> {
|
|
606
|
+
const url = new URL(req.url);
|
|
607
|
+
|
|
608
|
+
// Skip auth for discovery endpoints
|
|
609
|
+
const isDiscoveryEndpoint =
|
|
610
|
+
url.pathname === "/.well-known/oauth-protected-resource" ||
|
|
611
|
+
url.pathname === "/.well-known/oauth-authorization-server";
|
|
612
|
+
|
|
613
|
+
// Validate JWT for protected endpoints (if OAuth enabled)
|
|
614
|
+
let userContext: BSVJWTPayload | null = null;
|
|
615
|
+
if (CONFIG.enableOAuth && jwtValidator && !isDiscoveryEndpoint) {
|
|
616
|
+
try {
|
|
617
|
+
userContext = await jwtValidator.validateFromRequest(req);
|
|
618
|
+
|
|
619
|
+
if (!userContext) {
|
|
620
|
+
// No token provided - return 401 with WWW-Authenticate
|
|
621
|
+
return new Response(
|
|
622
|
+
JSON.stringify({
|
|
623
|
+
error: "unauthorized",
|
|
624
|
+
message: "Authentication required",
|
|
625
|
+
}),
|
|
626
|
+
{
|
|
627
|
+
status: 401,
|
|
628
|
+
headers: {
|
|
629
|
+
"Content-Type": "application/json",
|
|
630
|
+
"WWW-Authenticate": generateWWWAuthenticate(
|
|
631
|
+
resourceUrl,
|
|
632
|
+
"invalid_token",
|
|
633
|
+
"Authentication required",
|
|
634
|
+
),
|
|
635
|
+
"Access-Control-Expose-Headers": "WWW-Authenticate",
|
|
636
|
+
"Access-Control-Allow-Origin": "*",
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Token validated successfully
|
|
643
|
+
logFunc(
|
|
644
|
+
`Authenticated request from user: ${userContext.sub} (pubkey: ${userContext.pubkey?.substring(0, 20)}...)`,
|
|
645
|
+
);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
// Token provided but invalid
|
|
648
|
+
const errorMessage =
|
|
649
|
+
error instanceof Error
|
|
650
|
+
? error.message
|
|
651
|
+
: "Token validation failed";
|
|
652
|
+
|
|
653
|
+
logFunc(`JWT validation error: ${errorMessage}`);
|
|
654
|
+
|
|
655
|
+
return new Response(
|
|
656
|
+
JSON.stringify({
|
|
657
|
+
error: "invalid_token",
|
|
658
|
+
message: errorMessage,
|
|
659
|
+
}),
|
|
660
|
+
{
|
|
661
|
+
status: 401,
|
|
662
|
+
headers: {
|
|
663
|
+
"Content-Type": "application/json",
|
|
664
|
+
"WWW-Authenticate": generateWWWAuthenticate(
|
|
665
|
+
resourceUrl,
|
|
666
|
+
"invalid_token",
|
|
667
|
+
errorMessage,
|
|
668
|
+
),
|
|
669
|
+
"Access-Control-Expose-Headers": "WWW-Authenticate",
|
|
670
|
+
"Access-Control-Allow-Origin": "*",
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Handle SSE connection endpoint
|
|
678
|
+
if (req.headers.get("accept") === "text/event-stream") {
|
|
679
|
+
const transport = new BunSSEServerTransport(messageEndpoint);
|
|
680
|
+
activeTransports.set(transport.sessionId, transport);
|
|
681
|
+
logFunc(`New SSE connection: ${transport.sessionId}`);
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
// Connect server instance to THIS transport
|
|
685
|
+
if (!server) throw new Error("Server not initialized");
|
|
686
|
+
await server.connect(transport);
|
|
687
|
+
logFunc(
|
|
688
|
+
`Server connected to transport for session: ${transport.sessionId}`,
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
return await transport.createResponse();
|
|
692
|
+
} catch (connectError) {
|
|
693
|
+
logFunc(
|
|
694
|
+
`Error connecting server to transport ${transport.sessionId}: ${connectError}`,
|
|
695
|
+
);
|
|
696
|
+
activeTransports.delete(transport.sessionId); // Clean up if connect fails
|
|
697
|
+
return new Response("Failed to establish SSE connection", {
|
|
698
|
+
status: 500,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Handle message POST endpoint
|
|
704
|
+
if (req.method === "POST" && url.pathname === messageEndpoint) {
|
|
705
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
706
|
+
if (!sessionId) {
|
|
707
|
+
return new Response("Missing sessionId query parameter", {
|
|
708
|
+
status: 400,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const transport = activeTransports.get(sessionId);
|
|
712
|
+
if (!transport) {
|
|
713
|
+
return new Response(`Invalid or expired sessionId: ${sessionId}`, {
|
|
714
|
+
status: 404,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
// Let the specific transport instance handle the message
|
|
718
|
+
return await transport.handlePostMessage(req);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Handle OAuth 2.1 Authorization Server Metadata (MCP spec requirement)
|
|
722
|
+
// MCP clients will request this to discover sigma-auth endpoints
|
|
723
|
+
if (
|
|
724
|
+
req.method === "GET" &&
|
|
725
|
+
url.pathname === "/.well-known/oauth-authorization-server"
|
|
726
|
+
) {
|
|
727
|
+
const authServer =
|
|
728
|
+
process.env.OAUTH_ISSUER || "https://auth.sigmaidentity.com";
|
|
729
|
+
|
|
730
|
+
return new Response(
|
|
731
|
+
JSON.stringify(
|
|
732
|
+
{
|
|
733
|
+
issuer: authServer,
|
|
734
|
+
authorization_endpoint: `${authServer}/api/oauth/authorize`,
|
|
735
|
+
token_endpoint: `${authServer}/api/oauth/token`,
|
|
736
|
+
userinfo_endpoint: `${authServer}/api/oauth/userinfo`,
|
|
737
|
+
jwks_uri: `${authServer}/.well-known/jwks.json`,
|
|
738
|
+
registration_endpoint: `${authServer}/api/oauth/register`,
|
|
739
|
+
scopes_supported: [
|
|
740
|
+
"openid",
|
|
741
|
+
"profile",
|
|
742
|
+
"email",
|
|
743
|
+
"offline_access",
|
|
744
|
+
"bsv:tools",
|
|
745
|
+
"bsv:wallet",
|
|
746
|
+
"bsv:ordinals",
|
|
747
|
+
"bsv:tokens",
|
|
748
|
+
],
|
|
749
|
+
response_types_supported: ["code"],
|
|
750
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
751
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
752
|
+
code_challenge_methods_supported: ["S256"],
|
|
753
|
+
},
|
|
754
|
+
null,
|
|
755
|
+
2,
|
|
756
|
+
),
|
|
757
|
+
{
|
|
758
|
+
status: 200,
|
|
759
|
+
headers: {
|
|
760
|
+
"Content-Type": "application/json",
|
|
761
|
+
"Access-Control-Allow-Origin": "*",
|
|
762
|
+
"Cache-Control": "public, max-age=3600",
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Handle OAuth 2.1 Protected Resource Metadata (RFC 9728)
|
|
769
|
+
if (
|
|
770
|
+
req.method === "GET" &&
|
|
771
|
+
url.pathname === "/.well-known/oauth-protected-resource"
|
|
772
|
+
) {
|
|
773
|
+
const resourceUrl =
|
|
774
|
+
process.env.RESOURCE_URL || `http://localhost:${port}`;
|
|
775
|
+
const authServer =
|
|
776
|
+
process.env.OAUTH_ISSUER || "https://auth.sigmaidentity.com";
|
|
777
|
+
|
|
778
|
+
return new Response(
|
|
779
|
+
JSON.stringify(
|
|
780
|
+
{
|
|
781
|
+
resource: resourceUrl,
|
|
782
|
+
authorization_servers: [authServer],
|
|
783
|
+
scopes_supported: [
|
|
784
|
+
"openid",
|
|
785
|
+
"profile",
|
|
786
|
+
"email",
|
|
787
|
+
"bsv:tools",
|
|
788
|
+
"bsv:wallet",
|
|
789
|
+
"bsv:ordinals",
|
|
790
|
+
"bsv:tokens",
|
|
791
|
+
],
|
|
792
|
+
bearer_methods_supported: ["header"],
|
|
793
|
+
resource_signing_alg_values_supported: ["RS256", "ES256"],
|
|
794
|
+
},
|
|
795
|
+
null,
|
|
796
|
+
2,
|
|
797
|
+
),
|
|
798
|
+
{
|
|
799
|
+
status: 200,
|
|
800
|
+
headers: {
|
|
801
|
+
"Content-Type": "application/json",
|
|
802
|
+
"Access-Control-Allow-Origin": "*",
|
|
803
|
+
"Access-Control-Allow-Methods": "GET",
|
|
804
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
805
|
+
"Access-Control-Expose-Headers": "WWW-Authenticate",
|
|
806
|
+
"Cache-Control": "public, max-age=3600",
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Handle CORS preflight for OAuth discovery
|
|
813
|
+
if (
|
|
814
|
+
req.method === "OPTIONS" &&
|
|
815
|
+
(url.pathname === "/.well-known/oauth-protected-resource" ||
|
|
816
|
+
url.pathname === "/.well-known/oauth-authorization-server")
|
|
817
|
+
) {
|
|
818
|
+
return new Response(null, {
|
|
819
|
+
status: 204,
|
|
820
|
+
headers: {
|
|
821
|
+
"Access-Control-Allow-Origin": "*",
|
|
822
|
+
"Access-Control-Allow-Methods": "GET",
|
|
823
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Default response for other paths/methods
|
|
829
|
+
return new Response("Not Found", { status: 404 });
|
|
830
|
+
},
|
|
831
|
+
error(error: Error): Response {
|
|
832
|
+
logFunc(`Bun server error: ${error}\n${error.stack}`);
|
|
833
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
logFunc(
|
|
837
|
+
` Bun server started successfully. Listening on http://localhost:${port} `,
|
|
838
|
+
);
|
|
839
|
+
logFunc(" SSE Endpoint: /sse");
|
|
840
|
+
logFunc(
|
|
841
|
+
` Message Endpoint: ${messageEndpoint} (POST with ?sessionId=...)`,
|
|
842
|
+
);
|
|
843
|
+
}
|
|
132
844
|
}
|
|
133
845
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
846
|
+
main().catch((error) => {
|
|
847
|
+
logFunc(`\x1b[31mFATAL: Server initialization failed: ${error}\x1b[0m`);
|
|
848
|
+
if (error instanceof Error && error.stack) {
|
|
849
|
+
logFunc(error.stack);
|
|
850
|
+
}
|
|
851
|
+
process.exit(1);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
export { server };
|