bsv-mcp 0.2.15 → 0.3.1
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 +94 -10
- package/README.md +95 -13
- package/dist/app.html +483 -106
- package/dist/index.js +227724 -117380
- package/index.ts +523 -63
- package/package.json +24 -15
- package/prompts/bsvSdk/auth.ts +1 -1
- package/prompts/bsvSdk/cryptography.ts +1 -1
- package/prompts/bsvSdk/overview.ts +1 -1
- package/prompts/bsvSdk/primitives.ts +1 -1
- package/prompts/bsvSdk/script.ts +1 -1
- package/prompts/bsvSdk/transaction.ts +1 -1
- package/prompts/bsvSdk/wallet.ts +1 -1
- package/prompts/ordinals.ts +1 -1
- package/tools/a2b/discover.ts +2 -2
- package/tools/bap/friend.ts +17 -24
- package/tools/bap/generate.test.ts +34 -32
- package/tools/bap/generate.ts +48 -43
- package/tools/bap/getCurrentAddress.ts +1 -3
- package/tools/bap/getId.test.ts +18 -10
- package/tools/bap/getId.ts +8 -6
- package/tools/bap/utils.ts +1 -1
- package/tools/bsocial/bmapReadPosts.ts +16 -2
- package/tools/bsocial/createPost.ts +21 -10
- package/tools/bsocial/readPosts.ts +10 -4
- package/tools/bsv/decodeTransaction.ts +1 -1
- package/tools/bsv/explore.ts +0 -4
- package/tools/bsv/getPrice.ts +1 -1
- package/tools/bsv/token.ts +14 -6
- package/tools/constants.ts +6 -2
- package/tools/index.ts +22 -13
- package/tools/mnee/getBalance.ts +1 -1
- package/tools/mnee/parseTx.ts +1 -1
- package/tools/mnee/sendMnee.ts +30 -8
- package/tools/ordinals/getInscription.ts +1 -3
- package/tools/ordinals/getTokenByIdOrTicker.ts +1 -3
- package/tools/ordinals/index.ts +4 -1
- package/tools/ordinals/marketListings.ts +1 -1
- package/tools/ordinals/marketSales.ts +1 -1
- package/tools/ordinals/searchInscriptions.ts +1 -1
- package/tools/utils/aip.ts +31 -22
- package/tools/utils/installAgentMaster.ts +8 -9
- package/tools/utils/toolRegistration.ts +21 -22
- package/tools/utils/transactionBuilder.ts +21 -14
- package/tools/wallet/a2bPublishMcp.ts +15 -5
- package/tools/wallet/brc100.ts +567 -312
- package/tools/wallet/cancelListing.ts +29 -37
- package/tools/wallet/createOrdinals.ts +43 -27
- package/tools/wallet/droplit.ts +124 -0
- package/tools/wallet/fetchPaymentUtxos.ts +7 -5
- package/tools/wallet/gatherCollectionInfo.ts +3 -5
- package/tools/wallet/getAddress.ts +1 -4
- package/tools/wallet/getBalance.ts +15 -15
- package/tools/wallet/{getBalanceDroplet.ts → getBalanceDroplit.ts} +6 -6
- package/tools/wallet/getBsv21Balances.ts +20 -14
- package/tools/wallet/getLockData.ts +11 -11
- package/tools/wallet/getOrdinals.ts +26 -20
- package/tools/wallet/integratedWallet.ts +53 -53
- package/tools/wallet/listOrdinal.ts +40 -39
- package/tools/wallet/listTokens.ts +18 -17
- package/tools/wallet/lockBsv.ts +36 -22
- package/tools/wallet/mintCollection.ts +15 -10
- package/tools/wallet/opnsDeregister.ts +29 -39
- package/tools/wallet/opnsRegister.ts +29 -39
- package/tools/wallet/purchaseListing.test.ts +27 -0
- package/tools/wallet/purchaseListing.ts +59 -40
- package/tools/wallet/refreshUtxos.ts +1 -1
- package/tools/wallet/schemas.ts +0 -13
- package/tools/wallet/sendAllBsv.ts +26 -20
- package/tools/wallet/sendBsv.ts +107 -0
- package/tools/wallet/setupDroplit.ts +151 -0
- package/tools/wallet/signBsm.ts +18 -18
- package/tools/wallet/sweepBsv.ts +48 -27
- package/tools/wallet/sweepBsv21.ts +42 -28
- package/tools/wallet/sweepOrdinals.ts +38 -26
- package/tools/wallet/tools.ts +17 -17
- package/tools/wallet/transactionHelper.ts +13 -5
- package/tools/wallet/transferOrdToken.ts +72 -84
- package/tools/wallet/unlockBsv.ts +23 -17
- package/tools/wallet/utxo.ts +1 -0
- package/tools/wallet/wallet.ts +1 -13
- package/test-mcp-server.ts +0 -157
- package/tools/wallet/sendToAddress.ts +0 -74
- package/tools/wallet/setupDroplet.ts +0 -161
package/index.ts
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (_isStdio) {
|
|
10
|
-
const _err = console.error.bind(console);
|
|
11
|
-
console.log = (...a: unknown[]) => _err("[log]", ...a);
|
|
12
|
-
console.warn = (...a: unknown[]) => _err("[warn]", ...a);
|
|
13
|
-
console.info = (...a: unknown[]) => _err("[info]", ...a);
|
|
14
|
-
console.debug = (...a: unknown[]) => _err("[debug]", ...a);
|
|
15
|
-
}
|
|
3
|
+
// Must stay the first import. The stdio transport owns stdout for JSON-RPC, so
|
|
4
|
+
// the console redirect has to be installed before any other module initializes
|
|
5
|
+
// and gets a chance to log. This was previously a statement block below the
|
|
6
|
+
// imports, which cannot work: ES imports are evaluated before any top-level
|
|
7
|
+
// statement, so every dependency had already initialized by the time it ran.
|
|
8
|
+
import "./utils/stdioGuard";
|
|
16
9
|
|
|
17
10
|
import { readFile } from "node:fs/promises";
|
|
18
11
|
import os from "node:os";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
import { dirname, join } from "node:path";
|
|
12
|
+
import path, { dirname, join } from "node:path";
|
|
21
13
|
import { fileURLToPath } from "node:url";
|
|
22
14
|
import { PrivateKey } from "@bsv/sdk";
|
|
23
15
|
import {
|
|
@@ -27,6 +19,7 @@ import {
|
|
|
27
19
|
} from "@modelcontextprotocol/ext-apps/server";
|
|
28
20
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
29
21
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
30
23
|
import { z } from "zod";
|
|
31
24
|
import packageJson from "./package.json";
|
|
32
25
|
import { registerAllPrompts } from "./prompts/index.ts";
|
|
@@ -35,8 +28,11 @@ import { getBsvPriceWithCache } from "./tools/bsv/getPrice.ts";
|
|
|
35
28
|
import { registerAllTools, type ToolsConfig } from "./tools/index.ts";
|
|
36
29
|
import { IntegratedWallet } from "./tools/wallet/integratedWallet.ts";
|
|
37
30
|
import { Wallet } from "./tools/wallet/wallet.ts";
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
31
|
+
import { DroplitClient, readDroplitSponsorConfig } from "./utils/droplit";
|
|
32
|
+
import {
|
|
33
|
+
initializeKeysForWalletMode,
|
|
34
|
+
readExternalWalletConfig,
|
|
35
|
+
} from "./utils/externalWalletConfig";
|
|
40
36
|
import {
|
|
41
37
|
type BSVJWTPayload,
|
|
42
38
|
createMCPJWTValidator,
|
|
@@ -44,6 +40,12 @@ import {
|
|
|
44
40
|
} from "./utils/jwtValidator.ts";
|
|
45
41
|
import { SecureKeyManager } from "./utils/keyManager.ts";
|
|
46
42
|
import { setServerInstance } from "./utils/passphrasePrompt.ts";
|
|
43
|
+
import {
|
|
44
|
+
destroyWallet,
|
|
45
|
+
initExternalWallet,
|
|
46
|
+
initWallet,
|
|
47
|
+
setSpendingApprovalServerInstance,
|
|
48
|
+
} from "./utils/walletInit.ts";
|
|
47
49
|
|
|
48
50
|
// Initialize server variable (used for stdio mode and passphrase detection)
|
|
49
51
|
let server: McpServer | undefined;
|
|
@@ -113,13 +115,13 @@ const CONFIG = {
|
|
|
113
115
|
// --stdio CLI flag takes precedence over TRANSPORT env var (matches neighborhood plugin pattern)
|
|
114
116
|
transportMode: process.argv.includes("--stdio")
|
|
115
117
|
? "stdio"
|
|
116
|
-
:
|
|
118
|
+
: process.env.TRANSPORT?.toLowerCase() || "http", // 'stdio' or 'http'/default
|
|
117
119
|
port: Number.parseInt(process.env.PORT || "3000", 10),
|
|
118
120
|
|
|
119
|
-
// ---
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// --- Droplit API Configuration ---
|
|
122
|
+
useDroplitApi: process.env.USE_DROPLIT_API === "true",
|
|
123
|
+
droplitApiUrl: process.env.DROPLIT_API_URL || "http://127.0.0.1:4000",
|
|
124
|
+
droplitFaucetName: process.env.DROPLIT_FAUCET_NAME || "",
|
|
123
125
|
|
|
124
126
|
// --- OAuth Configuration ---
|
|
125
127
|
enableOAuth: process.env.ENABLE_OAUTH !== "false", // Enabled by default
|
|
@@ -279,7 +281,11 @@ async function initializeKeys(): Promise<{
|
|
|
279
281
|
const APP_RESOURCE_URI = "ui://bsv-mcp/app.html";
|
|
280
282
|
const __appDirname = dirname(fileURLToPath(import.meta.url));
|
|
281
283
|
|
|
282
|
-
function registerMcpAppTools(
|
|
284
|
+
function registerMcpAppTools(
|
|
285
|
+
server: McpServer,
|
|
286
|
+
wallet?: Wallet,
|
|
287
|
+
ctx?: import("@1sat/actions").OneSatContext,
|
|
288
|
+
) {
|
|
283
289
|
// Primary dashboard tool — model calls this to open the UI
|
|
284
290
|
registerAppTool(
|
|
285
291
|
server,
|
|
@@ -333,8 +339,7 @@ function registerMcpAppTools(server: McpServer, wallet?: Wallet, ctx?: import("@
|
|
|
333
339
|
const res = await fetch(
|
|
334
340
|
`https://junglebus.gorillapool.io/v1/transaction/get/${txid}`,
|
|
335
341
|
);
|
|
336
|
-
if (!res.ok)
|
|
337
|
-
throw new Error(`Transaction not found: ${res.status}`);
|
|
342
|
+
if (!res.ok) throw new Error(`Transaction not found: ${res.status}`);
|
|
338
343
|
const jbData = (await res.json()) as Record<string, unknown>;
|
|
339
344
|
|
|
340
345
|
const { Transaction, Utils } = await import("@bsv/sdk");
|
|
@@ -491,7 +496,8 @@ function registerMcpAppTools(server: McpServer, wallet?: Wallet, ctx?: import("@
|
|
|
491
496
|
},
|
|
492
497
|
],
|
|
493
498
|
structuredContent: {
|
|
494
|
-
error:
|
|
499
|
+
error:
|
|
500
|
+
"No wallet configured. Set PRIVATE_KEY_WIF or generate keys.",
|
|
495
501
|
},
|
|
496
502
|
_meta: { viewUUID: crypto.randomUUID() },
|
|
497
503
|
};
|
|
@@ -660,12 +666,410 @@ function registerMcpAppTools(server: McpServer, wallet?: Wallet, ctx?: import("@
|
|
|
660
666
|
},
|
|
661
667
|
);
|
|
662
668
|
|
|
669
|
+
// App-only: scan an address for categorized UTXOs (funding, ordinals, BSV-21 tokens)
|
|
670
|
+
registerAppTool(
|
|
671
|
+
server,
|
|
672
|
+
"app_sweep_scan",
|
|
673
|
+
{
|
|
674
|
+
title: "Sweep Scan",
|
|
675
|
+
description:
|
|
676
|
+
"App-only: scans a Bitcoin address for categorized UTXOs — funding, ordinals, and BSV-21 tokens.",
|
|
677
|
+
inputSchema: {
|
|
678
|
+
address: z.string().describe("Bitcoin address to scan"),
|
|
679
|
+
},
|
|
680
|
+
_meta: {
|
|
681
|
+
ui: { resourceUri: APP_RESOURCE_URI, visibility: ["app"] },
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
async (args) => {
|
|
685
|
+
const { address } = args as { address: string };
|
|
686
|
+
try {
|
|
687
|
+
const res = await fetch(
|
|
688
|
+
`https://ordinals.gorillapool.io/api/txos/address/${address}/unspent?limit=1000`,
|
|
689
|
+
);
|
|
690
|
+
if (!res.ok) throw new Error(`GorillaPool API error: ${res.status}`);
|
|
691
|
+
const utxos = (await res.json()) as Array<Record<string, unknown>>;
|
|
692
|
+
|
|
693
|
+
const funding: Array<{
|
|
694
|
+
outpoint: string;
|
|
695
|
+
satoshis: number;
|
|
696
|
+
lockingScript: string;
|
|
697
|
+
}> = [];
|
|
698
|
+
const ordinals: Array<{
|
|
699
|
+
outpoint: string;
|
|
700
|
+
satoshis: number;
|
|
701
|
+
lockingScript: string;
|
|
702
|
+
}> = [];
|
|
703
|
+
const bsv21Raw: Array<{
|
|
704
|
+
outpoint: string;
|
|
705
|
+
satoshis: number;
|
|
706
|
+
lockingScript: string;
|
|
707
|
+
tokenId: string;
|
|
708
|
+
amount: string;
|
|
709
|
+
sym?: string;
|
|
710
|
+
dec: number;
|
|
711
|
+
}> = [];
|
|
712
|
+
|
|
713
|
+
for (const utxo of utxos) {
|
|
714
|
+
const txid = utxo.txid as string;
|
|
715
|
+
const vout = utxo.vout as number;
|
|
716
|
+
const outpoint = `${txid}_${vout}`;
|
|
717
|
+
const satoshis = (utxo.satoshis as number) || 0;
|
|
718
|
+
const script = (utxo.script as string) || "";
|
|
719
|
+
const origin = utxo.origin as Record<string, unknown> | undefined;
|
|
720
|
+
const originData = origin?.data as
|
|
721
|
+
| Record<string, unknown>
|
|
722
|
+
| undefined;
|
|
723
|
+
|
|
724
|
+
const base = { outpoint, satoshis, lockingScript: script };
|
|
725
|
+
|
|
726
|
+
if (originData?.bsv21) {
|
|
727
|
+
const bsv21 = originData.bsv21 as Record<string, unknown>;
|
|
728
|
+
bsv21Raw.push({
|
|
729
|
+
...base,
|
|
730
|
+
tokenId: (bsv21.id as string) || "",
|
|
731
|
+
amount: (bsv21.amt as string) || "0",
|
|
732
|
+
sym: bsv21.sym as string | undefined,
|
|
733
|
+
dec: (bsv21.dec as number) ?? 0,
|
|
734
|
+
});
|
|
735
|
+
} else if (originData?.insc || origin?.outpoint) {
|
|
736
|
+
ordinals.push(base);
|
|
737
|
+
} else {
|
|
738
|
+
funding.push(base);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Group BSV-21 tokens by tokenId
|
|
743
|
+
const tokenGroups = new Map<
|
|
744
|
+
string,
|
|
745
|
+
{
|
|
746
|
+
inputs: typeof bsv21Raw;
|
|
747
|
+
sym?: string;
|
|
748
|
+
dec: number;
|
|
749
|
+
}
|
|
750
|
+
>();
|
|
751
|
+
for (const item of bsv21Raw) {
|
|
752
|
+
let group = tokenGroups.get(item.tokenId);
|
|
753
|
+
if (!group) {
|
|
754
|
+
group = { inputs: [], sym: item.sym, dec: item.dec };
|
|
755
|
+
tokenGroups.set(item.tokenId, group);
|
|
756
|
+
}
|
|
757
|
+
group.inputs.push(item);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const bsv21Tokens: Array<{
|
|
761
|
+
tokenId: string;
|
|
762
|
+
symbol?: string;
|
|
763
|
+
decimals: number;
|
|
764
|
+
totalAmount: string;
|
|
765
|
+
inputs: typeof bsv21Raw;
|
|
766
|
+
}> = [];
|
|
767
|
+
for (const [tokenId, group] of tokenGroups) {
|
|
768
|
+
let total = BigInt(0);
|
|
769
|
+
for (const inp of group.inputs) {
|
|
770
|
+
total += BigInt(inp.amount);
|
|
771
|
+
}
|
|
772
|
+
bsv21Tokens.push({
|
|
773
|
+
tokenId,
|
|
774
|
+
symbol: group.sym,
|
|
775
|
+
decimals: group.dec,
|
|
776
|
+
totalAmount: total.toString(),
|
|
777
|
+
inputs: group.inputs,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const totalFundingSats = funding.reduce(
|
|
782
|
+
(sum, f) => sum + f.satoshis,
|
|
783
|
+
0,
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
content: [
|
|
788
|
+
{
|
|
789
|
+
type: "text" as const,
|
|
790
|
+
text: `Scanned ${address}: ${funding.length} funding, ${ordinals.length} ordinals, ${bsv21Tokens.length} token types`,
|
|
791
|
+
},
|
|
792
|
+
],
|
|
793
|
+
structuredContent: {
|
|
794
|
+
address,
|
|
795
|
+
funding,
|
|
796
|
+
ordinals,
|
|
797
|
+
bsv21Tokens,
|
|
798
|
+
totalFundingSats,
|
|
799
|
+
},
|
|
800
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
801
|
+
};
|
|
802
|
+
} catch (err) {
|
|
803
|
+
return {
|
|
804
|
+
content: [
|
|
805
|
+
{
|
|
806
|
+
type: "text" as const,
|
|
807
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
808
|
+
},
|
|
809
|
+
],
|
|
810
|
+
structuredContent: { error: String(err) },
|
|
811
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
// App-only: prepare unsigned sweep transaction for client-side signing
|
|
818
|
+
registerAppTool(
|
|
819
|
+
server,
|
|
820
|
+
"app_sweep_prepare",
|
|
821
|
+
{
|
|
822
|
+
title: "Sweep Prepare",
|
|
823
|
+
description:
|
|
824
|
+
"App-only: builds an unsigned sweep transaction. Returns BEEF hex and reference for client-side signing.",
|
|
825
|
+
inputSchema: {
|
|
826
|
+
sweepType: z
|
|
827
|
+
.enum(["bsv", "ordinals", "bsv21"])
|
|
828
|
+
.describe("Type of assets to sweep"),
|
|
829
|
+
inputs: z
|
|
830
|
+
.array(
|
|
831
|
+
z.object({
|
|
832
|
+
outpoint: z.string().describe("Outpoint (txid_vout)"),
|
|
833
|
+
satoshis: z.number().int().describe("Satoshis in output"),
|
|
834
|
+
lockingScript: z.string().describe("Locking script hex"),
|
|
835
|
+
}),
|
|
836
|
+
)
|
|
837
|
+
.describe("UTXOs to sweep"),
|
|
838
|
+
},
|
|
839
|
+
_meta: {
|
|
840
|
+
ui: { resourceUri: APP_RESOURCE_URI, visibility: ["app"] },
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
async (args) => {
|
|
844
|
+
const { sweepType, inputs } = args as {
|
|
845
|
+
sweepType: "bsv" | "ordinals" | "bsv21";
|
|
846
|
+
inputs: Array<{
|
|
847
|
+
outpoint: string;
|
|
848
|
+
satoshis: number;
|
|
849
|
+
lockingScript: string;
|
|
850
|
+
}>;
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
if (!ctx) {
|
|
854
|
+
return {
|
|
855
|
+
content: [
|
|
856
|
+
{
|
|
857
|
+
type: "text" as const,
|
|
858
|
+
text: "BRC-100 wallet context not available",
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
structuredContent: { error: "No wallet context" },
|
|
862
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
try {
|
|
867
|
+
if (!ctx.services) throw new Error("Services not available");
|
|
868
|
+
if (!inputs.length) throw new Error("No inputs provided");
|
|
869
|
+
|
|
870
|
+
// Fetch and merge BEEF for all input transactions
|
|
871
|
+
const txids = [...new Set(inputs.map((i) => i.outpoint.split("_")[0]))];
|
|
872
|
+
const firstBeef = await ctx.services.getBeefForTxid(txids[0]);
|
|
873
|
+
for (let i = 1; i < txids.length; i++) {
|
|
874
|
+
const additionalBeef = await ctx.services.getBeefForTxid(txids[i]);
|
|
875
|
+
firstBeef.mergeBeef(additionalBeef);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Build input descriptors using SDK format (txid.vout)
|
|
879
|
+
const inputDescriptors = inputs.map((input) => {
|
|
880
|
+
const [txid, voutStr] = input.outpoint.split("_");
|
|
881
|
+
return {
|
|
882
|
+
outpoint: `${txid}.${Number(voutStr)}`,
|
|
883
|
+
inputDescription: `Sweep ${sweepType} input`,
|
|
884
|
+
unlockingScriptLength: 108,
|
|
885
|
+
sequenceNumber: 0xffffffff,
|
|
886
|
+
};
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
const inputTotal = inputs.reduce((sum, i) => sum + i.satoshis, 0);
|
|
890
|
+
|
|
891
|
+
const createResult = await ctx.wallet.createAction({
|
|
892
|
+
description: `Sweep ${inputTotal} sats (${sweepType})`,
|
|
893
|
+
inputBEEF: firstBeef.toBinary(),
|
|
894
|
+
inputs: inputDescriptors,
|
|
895
|
+
outputs: [],
|
|
896
|
+
options: {
|
|
897
|
+
signAndProcess: false,
|
|
898
|
+
...(sweepType !== "bsv" && {
|
|
899
|
+
randomizeOutputs: false,
|
|
900
|
+
}),
|
|
901
|
+
},
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
if ("error" in createResult && createResult.error) {
|
|
905
|
+
throw new Error(String(createResult.error));
|
|
906
|
+
}
|
|
907
|
+
if (!createResult.signableTransaction) {
|
|
908
|
+
throw new Error("No signable transaction returned");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Map our inputs to their indices in the transaction
|
|
912
|
+
const { Transaction: TxClass, Utils: SdkUtils } = await import(
|
|
913
|
+
"@bsv/sdk"
|
|
914
|
+
);
|
|
915
|
+
const tx = TxClass.fromBEEF(createResult.signableTransaction.tx);
|
|
916
|
+
const ourOutpoints = new Set(
|
|
917
|
+
inputs.map((i) => {
|
|
918
|
+
const [txid, voutStr] = i.outpoint.split("_");
|
|
919
|
+
return `${txid}.${Number(voutStr)}`;
|
|
920
|
+
}),
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
const inputsToSign: Array<{
|
|
924
|
+
index: number;
|
|
925
|
+
outpoint: string;
|
|
926
|
+
satoshis: number;
|
|
927
|
+
lockingScript: string;
|
|
928
|
+
}> = [];
|
|
929
|
+
for (let idx = 0; idx < tx.inputs.length; idx++) {
|
|
930
|
+
const txInput = tx.inputs[idx];
|
|
931
|
+
const op = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
|
|
932
|
+
if (ourOutpoints.has(op)) {
|
|
933
|
+
const match = inputs.find((i) => {
|
|
934
|
+
const [t, v] = i.outpoint.split("_");
|
|
935
|
+
return `${t}.${Number(v)}` === op;
|
|
936
|
+
});
|
|
937
|
+
if (match) {
|
|
938
|
+
inputsToSign.push({
|
|
939
|
+
index: idx,
|
|
940
|
+
outpoint: match.outpoint,
|
|
941
|
+
satoshis: match.satoshis,
|
|
942
|
+
lockingScript: match.lockingScript,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const txHex = SdkUtils.toHex(createResult.signableTransaction.tx);
|
|
949
|
+
|
|
950
|
+
return {
|
|
951
|
+
content: [
|
|
952
|
+
{
|
|
953
|
+
type: "text" as const,
|
|
954
|
+
text: `Prepared ${sweepType} sweep: ${inputsToSign.length} inputs to sign`,
|
|
955
|
+
},
|
|
956
|
+
],
|
|
957
|
+
structuredContent: {
|
|
958
|
+
txHex,
|
|
959
|
+
reference: createResult.signableTransaction.reference,
|
|
960
|
+
inputsToSign,
|
|
961
|
+
},
|
|
962
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
963
|
+
};
|
|
964
|
+
} catch (err) {
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text" as const,
|
|
969
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
970
|
+
},
|
|
971
|
+
],
|
|
972
|
+
structuredContent: { error: String(err) },
|
|
973
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
// App-only: complete a sweep by broadcasting with client-signed spends
|
|
980
|
+
registerAppTool(
|
|
981
|
+
server,
|
|
982
|
+
"app_sweep_complete",
|
|
983
|
+
{
|
|
984
|
+
title: "Sweep Complete",
|
|
985
|
+
description:
|
|
986
|
+
"App-only: completes a sweep by broadcasting the transaction with client-signed unlocking scripts.",
|
|
987
|
+
inputSchema: {
|
|
988
|
+
reference: z
|
|
989
|
+
.string()
|
|
990
|
+
.describe("Opaque reference from app_sweep_prepare"),
|
|
991
|
+
spends: z
|
|
992
|
+
.record(
|
|
993
|
+
z.string(),
|
|
994
|
+
z.object({
|
|
995
|
+
unlockingScript: z
|
|
996
|
+
.string()
|
|
997
|
+
.describe("Signed unlocking script hex"),
|
|
998
|
+
}),
|
|
999
|
+
)
|
|
1000
|
+
.describe("Map of input index to signed unlocking script"),
|
|
1001
|
+
},
|
|
1002
|
+
_meta: {
|
|
1003
|
+
ui: { resourceUri: APP_RESOURCE_URI, visibility: ["app"] },
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
async (args) => {
|
|
1007
|
+
const { reference, spends } = args as {
|
|
1008
|
+
reference: string;
|
|
1009
|
+
spends: Record<number, { unlockingScript: string }>;
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
if (!ctx) {
|
|
1013
|
+
return {
|
|
1014
|
+
content: [
|
|
1015
|
+
{
|
|
1016
|
+
type: "text" as const,
|
|
1017
|
+
text: "BRC-100 wallet context not available",
|
|
1018
|
+
},
|
|
1019
|
+
],
|
|
1020
|
+
structuredContent: { error: "No wallet context" },
|
|
1021
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
const signResult = await ctx.wallet.signAction({
|
|
1027
|
+
reference,
|
|
1028
|
+
spends,
|
|
1029
|
+
options: { acceptDelayedBroadcast: false },
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
if ("error" in signResult) {
|
|
1033
|
+
throw new Error(String(signResult.error));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
content: [
|
|
1038
|
+
{
|
|
1039
|
+
type: "text" as const,
|
|
1040
|
+
text: `Sweep broadcast: ${signResult.txid}`,
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
structuredContent: {
|
|
1044
|
+
txid: signResult.txid,
|
|
1045
|
+
success: true,
|
|
1046
|
+
},
|
|
1047
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
1048
|
+
};
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
return {
|
|
1051
|
+
content: [
|
|
1052
|
+
{
|
|
1053
|
+
type: "text" as const,
|
|
1054
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
structuredContent: { error: String(err) },
|
|
1058
|
+
_meta: { viewUUID: crypto.randomUUID() },
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
);
|
|
1063
|
+
|
|
663
1064
|
// Register the HTML resource
|
|
664
1065
|
registerAppResource(
|
|
665
1066
|
server,
|
|
666
1067
|
"BSV Dashboard",
|
|
667
1068
|
APP_RESOURCE_URI,
|
|
668
|
-
{
|
|
1069
|
+
{
|
|
1070
|
+
description:
|
|
1071
|
+
"Interactive BSV dashboard with Explorer, Wallet, and Ordinals tabs",
|
|
1072
|
+
},
|
|
669
1073
|
async () => {
|
|
670
1074
|
// When running from bundle (dist/index.js), app.html is a sibling.
|
|
671
1075
|
// When running from source (index.ts), it's in dist/.
|
|
@@ -676,7 +1080,8 @@ function registerMcpAppTools(server: McpServer, wallet?: Wallet, ctx?: import("@
|
|
|
676
1080
|
try {
|
|
677
1081
|
html = await readFile(distPath, "utf-8");
|
|
678
1082
|
} catch {
|
|
679
|
-
html =
|
|
1083
|
+
html =
|
|
1084
|
+
"<html><body><p>Dashboard not built. Run <code>bun run build:view</code> to enable it.</p></body></html>";
|
|
680
1085
|
}
|
|
681
1086
|
return {
|
|
682
1087
|
contents: [
|
|
@@ -708,6 +1113,8 @@ Options:
|
|
|
708
1113
|
Environment Variables:
|
|
709
1114
|
TRANSPORT Transport mode: 'stdio' or 'http' (default: http)
|
|
710
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)
|
|
711
1118
|
PRIVATE_KEY_WIF Payment private key in WIF format
|
|
712
1119
|
DISABLE_TOOLS Disable all tools (default: false)
|
|
713
1120
|
DISABLE_WALLET_TOOLS Disable wallet tools (default: false)
|
|
@@ -718,7 +1125,7 @@ Environment Variables:
|
|
|
718
1125
|
DISABLE_BSOCIAL_TOOLS Disable BSocial tools (default: false)
|
|
719
1126
|
ENABLE_A2B_TOOLS Enable A2B tools (default: false)
|
|
720
1127
|
DISABLE_BROADCASTING Disable transaction broadcasting (default: false)
|
|
721
|
-
|
|
1128
|
+
USE_DROPLIT_API Use Droplit API for transactions (default: false)
|
|
722
1129
|
|
|
723
1130
|
Tool Categories:
|
|
724
1131
|
BSV Tools: Price lookup, transaction decoding, validation
|
|
@@ -731,7 +1138,7 @@ Tool Categories:
|
|
|
731
1138
|
|
|
732
1139
|
Authentication:
|
|
733
1140
|
- Most tools work without authentication
|
|
734
|
-
- Wallet operations
|
|
1141
|
+
- Wallet operations use BRC100_WALLET_URL, PRIVATE_KEY_WIF or generated keys
|
|
735
1142
|
- BAP/A2B tools require identity keys (generated via bap_generate tool)
|
|
736
1143
|
`);
|
|
737
1144
|
process.exit(0);
|
|
@@ -743,7 +1150,25 @@ Authentication:
|
|
|
743
1150
|
}
|
|
744
1151
|
|
|
745
1152
|
// --- Initialize Keys ---
|
|
746
|
-
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
|
+
};
|
|
747
1172
|
|
|
748
1173
|
// Define persistence based on source
|
|
749
1174
|
const hasPersistentPayKey =
|
|
@@ -755,6 +1180,15 @@ Authentication:
|
|
|
755
1180
|
(keySource === "file" || keySource === "env" || keySource === "encrypted");
|
|
756
1181
|
|
|
757
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
|
+
}
|
|
758
1192
|
|
|
759
1193
|
// --- Configuration Logging ---
|
|
760
1194
|
logFunc("\n--- BSV MCP Server Configuration ---");
|
|
@@ -770,10 +1204,10 @@ Authentication:
|
|
|
770
1204
|
);
|
|
771
1205
|
}
|
|
772
1206
|
logFunc(
|
|
773
|
-
` 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)"}`,
|
|
774
1208
|
);
|
|
775
1209
|
logFunc(
|
|
776
|
-
` 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)"}`,
|
|
777
1211
|
);
|
|
778
1212
|
if (process.env.BSV_MCP_PASSPHRASE) {
|
|
779
1213
|
logFunc(" BSV_MCP_PASSPHRASE: \x1b[31mDEPRECATED - Remove this!\x1b[0m");
|
|
@@ -812,11 +1246,11 @@ Authentication:
|
|
|
812
1246
|
` DISABLE_BROADCASTING: ${process.env.DISABLE_BROADCASTING === "true" ? "Set (true)" : "Not Set/false"}`,
|
|
813
1247
|
);
|
|
814
1248
|
logFunc(
|
|
815
|
-
`
|
|
1249
|
+
` USE_DROPLIT_API: ${CONFIG.useDroplitApi ? "Set (true)" : "Not Set/false"}`,
|
|
816
1250
|
);
|
|
817
|
-
if (CONFIG.
|
|
818
|
-
logFunc(`
|
|
819
|
-
logFunc(`
|
|
1251
|
+
if (CONFIG.useDroplitApi) {
|
|
1252
|
+
logFunc(` DROPLIT_API_URL: ${CONFIG.droplitApiUrl}`);
|
|
1253
|
+
logFunc(` DROPLIT_FAUCET_NAME: ${CONFIG.droplitFaucetName || "Not Set"}`);
|
|
820
1254
|
}
|
|
821
1255
|
|
|
822
1256
|
logFunc("\nKey Source:");
|
|
@@ -870,11 +1304,11 @@ Authentication:
|
|
|
870
1304
|
: "\x1b[31mDisabled\x1b[0m";
|
|
871
1305
|
|
|
872
1306
|
let payKeyNote = "";
|
|
873
|
-
if (!hasPersistentPayKey) {
|
|
1307
|
+
if (!externalWallet && !hasPersistentPayKey) {
|
|
874
1308
|
payKeyNote = " \x1b[33m(Using generated payPk)\x1b[0m";
|
|
875
1309
|
}
|
|
876
1310
|
let identityKeyNote = "";
|
|
877
|
-
if (!hasPersistentIdentityKey) {
|
|
1311
|
+
if (!externalWallet && !hasPersistentIdentityKey) {
|
|
878
1312
|
identityKeyNote = " \x1b[33m(Using generated identityPk)\x1b[0m";
|
|
879
1313
|
}
|
|
880
1314
|
|
|
@@ -910,28 +1344,36 @@ Authentication:
|
|
|
910
1344
|
let remoteServices: import("@1sat/wallet-remote").OneSatServices | undefined;
|
|
911
1345
|
|
|
912
1346
|
if (CONFIG.loadTools) {
|
|
913
|
-
// Check if we should use
|
|
914
|
-
if (
|
|
915
|
-
|
|
1347
|
+
// Check if we should use Droplit API mode
|
|
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) {
|
|
1357
|
+
// Initialize IntegratedWallet in Droplit mode
|
|
916
1358
|
if (CONFIG.loadWalletTools) {
|
|
917
1359
|
try {
|
|
918
1360
|
integratedWallet = new IntegratedWallet({
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
apiUrl: CONFIG.
|
|
922
|
-
faucetName: CONFIG.
|
|
1361
|
+
useDroplitApi: true,
|
|
1362
|
+
droplitConfig: {
|
|
1363
|
+
apiUrl: CONFIG.droplitApiUrl,
|
|
1364
|
+
faucetName: CONFIG.droplitFaucetName,
|
|
923
1365
|
},
|
|
924
1366
|
paymentKey: payPk,
|
|
925
1367
|
identityKey: identityPk,
|
|
926
1368
|
});
|
|
927
1369
|
logFunc(
|
|
928
|
-
`\x1b[32mINFO:
|
|
1370
|
+
`\x1b[32mINFO: Droplit API mode initialized successfully (Faucet: ${CONFIG.droplitFaucetName}).\x1b[0m`,
|
|
929
1371
|
);
|
|
930
1372
|
logFunc(
|
|
931
|
-
`\x1b[33mNOTE: Using
|
|
1373
|
+
`\x1b[33mNOTE: Using Droplit API at ${CONFIG.droplitApiUrl}\x1b[0m`,
|
|
932
1374
|
);
|
|
933
1375
|
logFunc(
|
|
934
|
-
"\x1b[33mNOTE: Local keys are ignored in
|
|
1376
|
+
"\x1b[33mNOTE: Local keys are ignored in Droplit API mode\x1b[0m",
|
|
935
1377
|
);
|
|
936
1378
|
wallet = integratedWallet.getLocalWallet();
|
|
937
1379
|
|
|
@@ -941,7 +1383,7 @@ Authentication:
|
|
|
941
1383
|
effectiveConfig.loadBsocialTools = false;
|
|
942
1384
|
} catch (e) {
|
|
943
1385
|
logFunc(
|
|
944
|
-
`\x1b[31mERROR: Failed to initialize
|
|
1386
|
+
`\x1b[31mERROR: Failed to initialize Droplit API mode: ${e instanceof Error ? e.message : String(e)}. Wallet-dependent tools will be unavailable.\x1b[0m`,
|
|
945
1387
|
);
|
|
946
1388
|
integratedWallet = undefined;
|
|
947
1389
|
effectiveConfig.loadWalletTools = false;
|
|
@@ -1006,6 +1448,16 @@ Authentication:
|
|
|
1006
1448
|
}
|
|
1007
1449
|
}
|
|
1008
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
|
+
|
|
1009
1461
|
// Build the shared tools config (used by server factory for each session)
|
|
1010
1462
|
const toolsConfig: ToolsConfig = CONFIG.loadTools
|
|
1011
1463
|
? {
|
|
@@ -1025,6 +1477,7 @@ Authentication:
|
|
|
1025
1477
|
disableBroadcasting: effectiveConfig.disableBroadcasting,
|
|
1026
1478
|
ctx: remoteCtx,
|
|
1027
1479
|
services: remoteServices,
|
|
1480
|
+
droplitClient,
|
|
1028
1481
|
}
|
|
1029
1482
|
: {
|
|
1030
1483
|
enableBsvTools: false,
|
|
@@ -1058,6 +1511,7 @@ Authentication:
|
|
|
1058
1511
|
// Stdio: single server instance, single transport
|
|
1059
1512
|
server = createConfiguredServer(serverFactoryOpts);
|
|
1060
1513
|
setServerInstance(server);
|
|
1514
|
+
setSpendingApprovalServerInstance(server);
|
|
1061
1515
|
const transport = new StdioServerTransport();
|
|
1062
1516
|
await server.connect(transport);
|
|
1063
1517
|
logFunc("BSV MCP Server running on stdio");
|
|
@@ -1080,7 +1534,9 @@ Authentication:
|
|
|
1080
1534
|
}
|
|
1081
1535
|
>();
|
|
1082
1536
|
|
|
1083
|
-
logFunc(
|
|
1537
|
+
logFunc(
|
|
1538
|
+
`Starting BSV MCP Server in Streamable HTTP mode on port ${port}...`,
|
|
1539
|
+
);
|
|
1084
1540
|
if (CONFIG.enableOAuth) {
|
|
1085
1541
|
logFunc(`OAuth 2.1 authentication enabled`);
|
|
1086
1542
|
logFunc(` Issuer: ${CONFIG.oauthIssuer}`);
|
|
@@ -1096,8 +1552,7 @@ Authentication:
|
|
|
1096
1552
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
1097
1553
|
"Access-Control-Allow-Headers":
|
|
1098
1554
|
"Content-Type, Authorization, mcp-session-id, Last-Event-ID, mcp-protocol-version",
|
|
1099
|
-
"Access-Control-Expose-Headers":
|
|
1100
|
-
"mcp-session-id, mcp-protocol-version",
|
|
1555
|
+
"Access-Control-Expose-Headers": "mcp-session-id, mcp-protocol-version",
|
|
1101
1556
|
} as const;
|
|
1102
1557
|
|
|
1103
1558
|
/**
|
|
@@ -1154,10 +1609,7 @@ Authentication:
|
|
|
1154
1609
|
"bsv:tokens",
|
|
1155
1610
|
],
|
|
1156
1611
|
response_types_supported: ["code"],
|
|
1157
|
-
grant_types_supported: [
|
|
1158
|
-
"authorization_code",
|
|
1159
|
-
"refresh_token",
|
|
1160
|
-
],
|
|
1612
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
1161
1613
|
token_endpoint_auth_methods_supported: ["none"],
|
|
1162
1614
|
code_challenge_methods_supported: ["S256"],
|
|
1163
1615
|
},
|
|
@@ -1203,7 +1655,9 @@ Authentication:
|
|
|
1203
1655
|
// --- MCP Streamable HTTP endpoint ---
|
|
1204
1656
|
if (url.pathname === "/mcp") {
|
|
1205
1657
|
// Validate OAuth
|
|
1206
|
-
let authInfo:
|
|
1658
|
+
let authInfo:
|
|
1659
|
+
| { token: string; clientId: string; scopes: string[] }
|
|
1660
|
+
| undefined;
|
|
1207
1661
|
try {
|
|
1208
1662
|
const userCtx = await validateAuth(req);
|
|
1209
1663
|
if (userCtx) {
|
|
@@ -1247,10 +1701,18 @@ Authentication:
|
|
|
1247
1701
|
error: { code: -32001, message: "Session not found" },
|
|
1248
1702
|
id: null,
|
|
1249
1703
|
}),
|
|
1250
|
-
{
|
|
1704
|
+
{
|
|
1705
|
+
status: 404,
|
|
1706
|
+
headers: {
|
|
1707
|
+
"Content-Type": "application/json",
|
|
1708
|
+
...corsHeaders,
|
|
1709
|
+
},
|
|
1710
|
+
},
|
|
1251
1711
|
);
|
|
1252
1712
|
}
|
|
1253
|
-
const response = await session.transport.handleRequest(req, {
|
|
1713
|
+
const response = await session.transport.handleRequest(req, {
|
|
1714
|
+
authInfo,
|
|
1715
|
+
});
|
|
1254
1716
|
// Add CORS headers to transport response
|
|
1255
1717
|
for (const [k, v] of Object.entries(corsHeaders)) {
|
|
1256
1718
|
if (!response.headers.has(k)) response.headers.set(k, v);
|
|
@@ -1290,9 +1752,7 @@ Authentication:
|
|
|
1290
1752
|
},
|
|
1291
1753
|
});
|
|
1292
1754
|
|
|
1293
|
-
logFunc(
|
|
1294
|
-
`Bun server listening on http://localhost:${port}`,
|
|
1295
|
-
);
|
|
1755
|
+
logFunc(`Bun server listening on http://localhost:${port}`);
|
|
1296
1756
|
logFunc(" MCP Endpoint: /mcp (Streamable HTTP)");
|
|
1297
1757
|
logFunc(" OAuth Discovery: /.well-known/oauth-protected-resource");
|
|
1298
1758
|
}
|