bsv-mcp 0.0.36 → 0.2.7
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 +124 -0
- package/README.md +233 -4
- package/dist/index.js +120082 -54669
- package/index.ts +1196 -88
- package/next-env.d.ts +6 -0
- package/package.json +58 -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/a2b/discover.ts +13 -13
- package/tools/bap/friend.ts +266 -0
- package/tools/bap/generate.test.ts +350 -0
- package/tools/bap/generate.ts +431 -0
- package/tools/bap/getCurrentAddress.ts +69 -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/bsocial/bmapFollow.ts +121 -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 +7 -6
- package/tools/bsv/explore.ts +2 -3
- package/tools/bsv/getPrice.ts +1 -9
- package/tools/bsv/token.ts +14 -26
- package/tools/constants.ts +21 -0
- package/tools/index.ts +91 -18
- package/tools/mnee/getBalance.ts +2 -4
- package/tools/mnee/index.ts +8 -0
- package/tools/mnee/parseTx.ts +4 -4
- package/tools/mnee/sendMnee.ts +6 -6
- package/tools/ordinals/getInscription.ts +2 -3
- package/tools/ordinals/getTokenByIdOrTicker.ts +6 -3
- package/tools/ordinals/marketListings.ts +2 -18
- package/tools/ordinals/marketSales.ts +2 -4
- package/tools/ordinals/searchInscriptions.ts +2 -4
- 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 +56 -14
- 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 +25 -30
- package/tools/wallet/a2bPublishMcp.ts +43 -44
- package/tools/wallet/createOrdinals.ts +13 -20
- package/tools/wallet/fetchPaymentUtxos.ts +48 -39
- package/tools/wallet/gatherCollectionInfo.ts +370 -0
- package/tools/wallet/getAddress.ts +14 -30
- package/tools/wallet/getBalance.ts +54 -0
- package/tools/wallet/getBalanceDroplet.ts +48 -0
- package/tools/wallet/getPublicKey.ts +3 -16
- package/tools/wallet/integratedWallet.ts +120 -0
- package/tools/wallet/mintCollection.ts +373 -0
- package/tools/wallet/purchaseListing.ts +21 -31
- package/tools/wallet/refreshUtxos.ts +2 -14
- package/tools/wallet/schemas.ts +18 -249
- package/tools/wallet/sendOrdinals.ts +14 -21
- package/tools/wallet/sendToAddress.ts +2 -18
- package/tools/wallet/setupDroplet.ts +161 -0
- package/tools/wallet/tools.ts +55 -210
- package/tools/wallet/transactionHelper.ts +121 -0
- package/tools/wallet/transferOrdToken.ts +19 -24
- package/tools/wallet/utxo.ts +62 -0
- package/tools/wallet/wallet.ts +237 -234
- package/vite.config.ts +15 -0
- package/tools/a2b/call.ts +0 -32
package/tools/utils/index.ts
CHANGED
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { convertData } from "./conversion";
|
|
4
|
+
import { installAgentMasterTool } from "./installAgentMaster";
|
|
4
5
|
|
|
5
6
|
const encodingSchema = z.enum(["utf8", "hex", "base64", "binary"]);
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* Register
|
|
9
|
+
* Register utility tools with the MCP server
|
|
9
10
|
* @param server The MCP server instance
|
|
10
11
|
*/
|
|
11
12
|
export function registerUtilsTools(server: McpServer): void {
|
|
13
|
+
// Register install agent master tool
|
|
14
|
+
server.tool(
|
|
15
|
+
installAgentMasterTool.name,
|
|
16
|
+
installAgentMasterTool.description,
|
|
17
|
+
{ ...installAgentMasterTool.inputSchema.shape },
|
|
18
|
+
async (params) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = await installAgentMasterTool.handler(params);
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text:
|
|
26
|
+
typeof result === "string"
|
|
27
|
+
? result
|
|
28
|
+
: JSON.stringify(result, null, 2),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
} catch (err: unknown) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Error: ${msg}`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Register conversion tool
|
|
12
48
|
server.tool(
|
|
13
49
|
"utils_convertData",
|
|
14
50
|
"Converts data between different encodings (utf8, hex, base64, binary). Useful for transforming data formats when working with blockchain data, encryption, or file processing.\n\n" +
|
|
@@ -26,22 +62,20 @@ export function registerUtilsTools(server: McpServer): void {
|
|
|
26
62
|
"- The tool returns the converted data as a string\n" +
|
|
27
63
|
"- For binary conversion, data is represented as an array of byte values",
|
|
28
64
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
to
|
|
35
|
-
|
|
36
|
-
),
|
|
37
|
-
}),
|
|
65
|
+
data: z.string().describe("The data string to be converted"),
|
|
66
|
+
from: encodingSchema.describe(
|
|
67
|
+
"Source encoding format (utf8, hex, base64, or binary)",
|
|
68
|
+
),
|
|
69
|
+
to: encodingSchema.describe(
|
|
70
|
+
"Target encoding format to convert to (utf8, hex, base64, or binary)",
|
|
71
|
+
),
|
|
38
72
|
},
|
|
39
|
-
async ({
|
|
73
|
+
async ({ data, from, to }) => {
|
|
40
74
|
try {
|
|
41
75
|
const result = convertData({
|
|
42
|
-
data
|
|
43
|
-
from
|
|
44
|
-
to
|
|
76
|
+
data,
|
|
77
|
+
from,
|
|
78
|
+
to,
|
|
45
79
|
});
|
|
46
80
|
return {
|
|
47
81
|
content: [
|
|
@@ -66,3 +100,11 @@ export function registerUtilsTools(server: McpServer): void {
|
|
|
66
100
|
},
|
|
67
101
|
);
|
|
68
102
|
}
|
|
103
|
+
|
|
104
|
+
export * from "./aip";
|
|
105
|
+
// Export all utilities for use by other tools
|
|
106
|
+
export * from "./errorHandler";
|
|
107
|
+
export * from "./logger";
|
|
108
|
+
export * from "./toolConfig";
|
|
109
|
+
export * from "./toolRegistration";
|
|
110
|
+
export * from "./transactionBuilder";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
const installAgentMasterSchema = z.object({
|
|
6
|
+
method: z
|
|
7
|
+
.enum(["go", "source", "auto"])
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Installation method"),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Install the Agent Master CLI tool for managing MCP server configurations
|
|
14
|
+
*/
|
|
15
|
+
export async function installAgentMaster(
|
|
16
|
+
args: z.infer<typeof installAgentMasterSchema>,
|
|
17
|
+
) {
|
|
18
|
+
const method = args.method || "auto";
|
|
19
|
+
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let command: string;
|
|
22
|
+
let commandArgs: string[] = [];
|
|
23
|
+
|
|
24
|
+
if (method === "auto" || method === "go") {
|
|
25
|
+
// Try go install first
|
|
26
|
+
command = "go";
|
|
27
|
+
commandArgs = ["install", "github.com/b-open-io/agent-master-cli@latest"];
|
|
28
|
+
} else if (method === "source") {
|
|
29
|
+
// Install from source
|
|
30
|
+
reject(
|
|
31
|
+
new Error(
|
|
32
|
+
"Source installation requires manual steps. Please run:\ngit clone https://github.com/b-open-io/agent-master-cli.git\ncd agent-master-cli\nmake deps && make build && make install",
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.error(`🔧 Installing Agent Master CLI using ${method} method...`);
|
|
39
|
+
|
|
40
|
+
const install = spawn(command, commandArgs, {
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
shell: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
install.on("error", (error) => {
|
|
46
|
+
if (method === "auto" && command === "go") {
|
|
47
|
+
// If go install fails in auto mode, provide instructions
|
|
48
|
+
reject(
|
|
49
|
+
new Error(
|
|
50
|
+
"Go installation failed. Please ensure Go is installed or try manual installation:\n\n1. Install Go from https://golang.org/dl/\n2. Run: go install github.com/b-open-io/agent-master-cli@latest\n\nOr install from source:\ngit clone https://github.com/b-open-io/agent-master-cli.git\ncd agent-master-cli\nmake deps && make build && make install",
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
reject(error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
install.on("close", (code) => {
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
reject(new Error(`Installation failed with code ${code}`));
|
|
61
|
+
} else {
|
|
62
|
+
resolve({
|
|
63
|
+
success: true,
|
|
64
|
+
message:
|
|
65
|
+
"✅ Agent Master CLI installed successfully!\n\nGet started with:\n agent-master init --with-demo\n agent-master list\n agent-master sync --preset claude",
|
|
66
|
+
nextSteps: [
|
|
67
|
+
"Initialize configuration: agent-master init --with-demo",
|
|
68
|
+
"List servers: agent-master list",
|
|
69
|
+
'Add BSV MCP: agent-master add bsv-mcp --transport stdio --command "bunx" --args "bsv-mcp@latest"',
|
|
70
|
+
"Sync to Claude: agent-master sync --preset claude",
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const installAgentMasterTool = {
|
|
79
|
+
name: "utils_installAgentMaster",
|
|
80
|
+
description:
|
|
81
|
+
"Install the Agent Master CLI tool for managing MCP server configurations across multiple platforms (Claude, VS Code, Cursor, etc.)",
|
|
82
|
+
inputSchema: installAgentMasterSchema,
|
|
83
|
+
handler: installAgentMaster,
|
|
84
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logger utility for consistent output formatting
|
|
3
|
+
*/
|
|
4
|
+
export const logger = {
|
|
5
|
+
info: (message: string) => console.error(`ℹ️ ${message}`),
|
|
6
|
+
success: (message: string) => console.error(`✅ ${message}`),
|
|
7
|
+
warn: (message: string) => console.error(`⚠️ ${message}`),
|
|
8
|
+
error: (message: string, error?: unknown) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
console.error(`❌ ${message}:`, error);
|
|
11
|
+
} else {
|
|
12
|
+
console.error(`❌ ${message}`);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
debug: (message: string, ...args: unknown[]) => {
|
|
16
|
+
if (process.env.DEBUG === "true") {
|
|
17
|
+
console.error(`🐛 ${message}`, ...args);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool category configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface ToolCategory {
|
|
5
|
+
name: string;
|
|
6
|
+
envVar: string;
|
|
7
|
+
defaultEnabled: boolean;
|
|
8
|
+
register: (server: unknown, config?: unknown) => void;
|
|
9
|
+
dependencies?: string[]; // Other categories this depends on
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a tool category is enabled based on environment and config
|
|
14
|
+
*/
|
|
15
|
+
export function isToolEnabled(
|
|
16
|
+
category: ToolCategory,
|
|
17
|
+
config: Record<string, boolean | undefined> = {},
|
|
18
|
+
): boolean {
|
|
19
|
+
const configKey = `enable${category.name}Tools`;
|
|
20
|
+
|
|
21
|
+
// Check config override first
|
|
22
|
+
if (configKey in config && config[configKey] !== undefined) {
|
|
23
|
+
return config[configKey] as boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check environment variable
|
|
27
|
+
const envValue = process.env[category.envVar];
|
|
28
|
+
if (envValue !== undefined) {
|
|
29
|
+
// For ENABLE_* vars, check if === "true"
|
|
30
|
+
// For DISABLE_* vars, check if !== "true"
|
|
31
|
+
if (category.envVar.startsWith("ENABLE_")) {
|
|
32
|
+
return envValue === "true";
|
|
33
|
+
}
|
|
34
|
+
return envValue !== "true";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return default
|
|
38
|
+
return category.defaultEnabled;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register multiple tool categories with their dependencies
|
|
43
|
+
*/
|
|
44
|
+
export function registerToolCategories(
|
|
45
|
+
server: unknown,
|
|
46
|
+
categories: ToolCategory[],
|
|
47
|
+
config: Record<string, boolean | undefined> = {},
|
|
48
|
+
): void {
|
|
49
|
+
const enabled: Set<string> = new Set();
|
|
50
|
+
|
|
51
|
+
// First pass: determine what's enabled
|
|
52
|
+
for (const category of categories) {
|
|
53
|
+
if (isToolEnabled(category, config)) {
|
|
54
|
+
enabled.add(category.name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Second pass: register enabled categories
|
|
59
|
+
for (const category of categories) {
|
|
60
|
+
if (enabled.has(category.name)) {
|
|
61
|
+
// Check dependencies
|
|
62
|
+
const missingDeps =
|
|
63
|
+
category.dependencies?.filter((dep) => !enabled.has(dep)) || [];
|
|
64
|
+
if (missingDeps.length > 0) {
|
|
65
|
+
console.warn(
|
|
66
|
+
`⚠️ ${category.name} tools not registered (missing dependencies: ${missingDeps.join(", ")})`,
|
|
67
|
+
);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Register the category
|
|
72
|
+
try {
|
|
73
|
+
category.register(server, config);
|
|
74
|
+
console.error(`✅ Registered ${category.name} tools`);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`❌ Failed to register ${category.name} tools:`, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common tool registration utilities for the BSV MCP server
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { z } from "zod";
|
|
6
|
+
|
|
7
|
+
export interface ToolConfig<TArgs = Record<string, unknown>> {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
schema: z.ZodSchema<TArgs>;
|
|
11
|
+
handler: (args: TArgs) => Promise<ToolResponse>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ToolResponse {
|
|
15
|
+
content: Array<{
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}>;
|
|
19
|
+
isError?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a tool with standard error handling and response formatting
|
|
24
|
+
*/
|
|
25
|
+
export function registerTool<TArgs = Record<string, unknown>>(
|
|
26
|
+
server: McpServer,
|
|
27
|
+
config: ToolConfig<TArgs>,
|
|
28
|
+
): void {
|
|
29
|
+
server.tool(config.name, config.schema, async (args: TArgs) => {
|
|
30
|
+
try {
|
|
31
|
+
return await config.handler(args);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text" as const,
|
|
37
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper function to create a simple tool registration function
|
|
48
|
+
*/
|
|
49
|
+
export function createToolRegistration<TDeps = undefined>(
|
|
50
|
+
toolName: string,
|
|
51
|
+
description: string,
|
|
52
|
+
schema: z.ZodSchema<Record<string, unknown>>,
|
|
53
|
+
handler: (
|
|
54
|
+
args: Record<string, unknown>,
|
|
55
|
+
deps?: TDeps,
|
|
56
|
+
) => Promise<ToolResponse>,
|
|
57
|
+
) {
|
|
58
|
+
return (server: McpServer, deps?: TDeps) => {
|
|
59
|
+
registerTool(server, {
|
|
60
|
+
name: toolName,
|
|
61
|
+
description,
|
|
62
|
+
schema,
|
|
63
|
+
handler: (args) => handler(args, deps),
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBroadcastFailure,
|
|
3
|
+
isBroadcastResponse,
|
|
4
|
+
P2PKH,
|
|
5
|
+
type PrivateKey,
|
|
6
|
+
Script,
|
|
7
|
+
Transaction,
|
|
8
|
+
Utils,
|
|
9
|
+
} from "@bsv/sdk";
|
|
10
|
+
import { V5Broadcaster } from "../../utils/broadcaster";
|
|
11
|
+
import type { Utxo } from "../wallet/utxo";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SAT_PER_BYTE = 0.05;
|
|
14
|
+
const DUST_LIMIT = 546;
|
|
15
|
+
|
|
16
|
+
interface TransactionBuilderConfig {
|
|
17
|
+
outputs: Array<{
|
|
18
|
+
script?: Script;
|
|
19
|
+
address?: string;
|
|
20
|
+
satoshis: number;
|
|
21
|
+
}>;
|
|
22
|
+
utxos: Utxo[];
|
|
23
|
+
changeAddress: string;
|
|
24
|
+
paymentKey: PrivateKey;
|
|
25
|
+
feePerByte?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TransactionResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
txid?: string;
|
|
31
|
+
rawTx?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
fee?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build and optionally broadcast a transaction with automatic UTXO selection and fee calculation
|
|
38
|
+
*/
|
|
39
|
+
export async function buildAndSendTransaction(
|
|
40
|
+
config: TransactionBuilderConfig,
|
|
41
|
+
broadcast = true,
|
|
42
|
+
): Promise<TransactionResult> {
|
|
43
|
+
const {
|
|
44
|
+
outputs,
|
|
45
|
+
utxos,
|
|
46
|
+
changeAddress,
|
|
47
|
+
paymentKey,
|
|
48
|
+
feePerByte = DEFAULT_SAT_PER_BYTE,
|
|
49
|
+
} = config;
|
|
50
|
+
|
|
51
|
+
if (!utxos || utxos.length === 0) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: "No UTXOs available to fund transaction",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tx = new Transaction();
|
|
59
|
+
const p2pkh = new P2PKH();
|
|
60
|
+
|
|
61
|
+
// Add all outputs
|
|
62
|
+
for (const output of outputs) {
|
|
63
|
+
if (output.script) {
|
|
64
|
+
tx.addOutput({
|
|
65
|
+
lockingScript: output.script,
|
|
66
|
+
satoshis: output.satoshis,
|
|
67
|
+
});
|
|
68
|
+
} else if (output.address) {
|
|
69
|
+
tx.addOutput({
|
|
70
|
+
lockingScript: p2pkh.lock(output.address),
|
|
71
|
+
satoshis: output.satoshis,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Calculate total output amount
|
|
77
|
+
const totalOutputSatoshis = outputs.reduce(
|
|
78
|
+
(sum, out) => sum + out.satoshis,
|
|
79
|
+
0,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Select UTXOs and add inputs
|
|
83
|
+
let totalInputSatoshis = 0;
|
|
84
|
+
const selectedUtxos: Utxo[] = [];
|
|
85
|
+
|
|
86
|
+
// Sort UTXOs by value (ascending) to minimize the number of inputs
|
|
87
|
+
const sortedUtxos = [...utxos].sort((a, b) => a.satoshis - b.satoshis);
|
|
88
|
+
|
|
89
|
+
for (const utxo of sortedUtxos) {
|
|
90
|
+
selectedUtxos.push(utxo);
|
|
91
|
+
totalInputSatoshis += utxo.satoshis;
|
|
92
|
+
|
|
93
|
+
const input = {
|
|
94
|
+
sourceTransaction: utxo.tx,
|
|
95
|
+
sourceOutputIndex: utxo.vout,
|
|
96
|
+
unlockingScriptTemplate: p2pkh.unlock(paymentKey),
|
|
97
|
+
};
|
|
98
|
+
tx.addInput(input);
|
|
99
|
+
|
|
100
|
+
// Estimate fee with current inputs
|
|
101
|
+
const estimatedSize = tx.toHex().length / 2 + 150; // rough estimate for unsigned tx
|
|
102
|
+
const estimatedFee = Math.ceil(estimatedSize * feePerByte);
|
|
103
|
+
|
|
104
|
+
// Check if we have enough to cover outputs + fee + potential change
|
|
105
|
+
if (totalInputSatoshis >= totalOutputSatoshis + estimatedFee + DUST_LIMIT) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Calculate final fee
|
|
111
|
+
const estimatedSize = tx.toHex().length / 2 + 35; // more accurate with all inputs
|
|
112
|
+
const fee = Math.ceil(estimatedSize * feePerByte);
|
|
113
|
+
|
|
114
|
+
// Check if we have enough funds
|
|
115
|
+
if (totalInputSatoshis < totalOutputSatoshis + fee) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: `Insufficient funds. Have ${totalInputSatoshis} sats, need ${totalOutputSatoshis + fee} sats`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Add change output if needed
|
|
123
|
+
const change = totalInputSatoshis - totalOutputSatoshis - fee;
|
|
124
|
+
if (change >= DUST_LIMIT) {
|
|
125
|
+
tx.addOutput({
|
|
126
|
+
lockingScript: p2pkh.lock(changeAddress),
|
|
127
|
+
satoshis: change,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Sign the transaction
|
|
132
|
+
await tx.sign();
|
|
133
|
+
|
|
134
|
+
const rawTx = tx.toHex();
|
|
135
|
+
const txid = tx.id("hex") as string;
|
|
136
|
+
|
|
137
|
+
// Check if broadcasting is disabled
|
|
138
|
+
if (process.env.DISABLE_BROADCASTING === "true" || !broadcast) {
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
txid,
|
|
142
|
+
rawTx,
|
|
143
|
+
fee,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Broadcast the transaction
|
|
148
|
+
try {
|
|
149
|
+
const broadcaster = new V5Broadcaster();
|
|
150
|
+
const broadcastResult = await tx.broadcast(broadcaster);
|
|
151
|
+
|
|
152
|
+
if (isBroadcastResponse(broadcastResult)) {
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
txid: broadcastResult.txid || txid,
|
|
156
|
+
rawTx,
|
|
157
|
+
fee,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (isBroadcastFailure(broadcastResult)) {
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
txid,
|
|
164
|
+
rawTx,
|
|
165
|
+
fee,
|
|
166
|
+
error: `Transaction created but broadcast failed: ${broadcastResult.description}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
txid,
|
|
172
|
+
rawTx,
|
|
173
|
+
fee,
|
|
174
|
+
error: "Transaction created but broadcast status uncertain",
|
|
175
|
+
};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
txid,
|
|
180
|
+
rawTx,
|
|
181
|
+
fee,
|
|
182
|
+
error: `Transaction created but broadcast failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build OP_RETURN script from data arrays
|
|
189
|
+
*/
|
|
190
|
+
export function buildOpReturnScript(dataArrays: Uint8Array[]): Script {
|
|
191
|
+
return new Script([
|
|
192
|
+
{ op: 0 }, // OP_0
|
|
193
|
+
{ op: 106 }, // OP_RETURN
|
|
194
|
+
...dataArrays.map((data) => ({ data })),
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { PrivateKey, Utils } from "@bsv/sdk";
|
|
2
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
|
-
import type {
|
|
5
|
-
ServerNotification,
|
|
6
|
-
ServerRequest,
|
|
7
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { createOrdinals } from "js-1sat-ord";
|
|
9
3
|
import type {
|
|
10
4
|
ChangeResult,
|
|
11
5
|
CreateOrdinalsConfig,
|
|
@@ -14,9 +8,12 @@ import type {
|
|
|
14
8
|
LocalSigner,
|
|
15
9
|
PreMAP,
|
|
16
10
|
} from "js-1sat-ord";
|
|
17
|
-
import {
|
|
11
|
+
import { createOrdinals } from "js-1sat-ord";
|
|
18
12
|
import { z } from "zod";
|
|
13
|
+
import packageJson from "../../package.json";
|
|
14
|
+
import { V5Broadcaster } from "../../utils/broadcaster";
|
|
19
15
|
import type { Wallet } from "./wallet";
|
|
16
|
+
|
|
20
17
|
const { toArray, toBase64 } = Utils;
|
|
21
18
|
|
|
22
19
|
// https://raw.githubusercontent.com/google/A2A/refs/heads/main/specification/json/a2a.json
|
|
@@ -152,19 +149,16 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
|
|
|
152
149
|
server.tool(
|
|
153
150
|
"wallet_a2bPublish",
|
|
154
151
|
"Publish an agent.json record on-chain via Ordinal inscription",
|
|
155
|
-
{
|
|
156
|
-
async (
|
|
157
|
-
{ args }: { args: A2bPublishArgs },
|
|
158
|
-
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
159
|
-
) => {
|
|
152
|
+
{ ...a2bPublishArgsSchema.shape },
|
|
153
|
+
async ({ agentUrl, agentName, description, providerOrganization, providerUrl, version, documentationUrl, streaming, pushNotifications, stateTransitionHistory, defaultInputModes, defaultOutputModes, skills, destinationAddress }) => {
|
|
160
154
|
try {
|
|
161
|
-
const paymentPk = wallet.
|
|
155
|
+
const paymentPk = wallet.getPaymentKey();
|
|
162
156
|
if (!paymentPk) throw new Error("No private key available");
|
|
163
157
|
const { paymentUtxos } = await wallet.getUtxos();
|
|
164
158
|
if (!paymentUtxos?.length)
|
|
165
159
|
throw new Error("No payment UTXOs available to fund inscription");
|
|
166
160
|
|
|
167
|
-
// TODO: Get the skills from actually running the MCP instead of trusting the agent args for
|
|
161
|
+
// TODO: Get the skills from actually running the MCP instead of trusting the agent args for skills
|
|
168
162
|
const walletAddress = paymentPk.toAddress().toString();
|
|
169
163
|
|
|
170
164
|
// Create pricing plans using the new schema
|
|
@@ -209,27 +203,27 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
|
|
|
209
203
|
|
|
210
204
|
// Assemble AgentCard with defaults and user overrides
|
|
211
205
|
const agentCard = {
|
|
212
|
-
name:
|
|
213
|
-
description:
|
|
214
|
-
url:
|
|
206
|
+
name: agentName,
|
|
207
|
+
description: description ?? null,
|
|
208
|
+
url: agentUrl,
|
|
215
209
|
provider:
|
|
216
|
-
|
|
210
|
+
providerOrganization && providerUrl
|
|
217
211
|
? {
|
|
218
|
-
organization:
|
|
219
|
-
url:
|
|
212
|
+
organization: providerOrganization,
|
|
213
|
+
url: providerUrl,
|
|
220
214
|
}
|
|
221
215
|
: null,
|
|
222
|
-
version:
|
|
223
|
-
documentationUrl:
|
|
216
|
+
version: version ?? packageJson.version,
|
|
217
|
+
documentationUrl: documentationUrl ?? null,
|
|
224
218
|
capabilities: {
|
|
225
|
-
streaming
|
|
226
|
-
pushNotifications
|
|
227
|
-
stateTransitionHistory
|
|
219
|
+
streaming,
|
|
220
|
+
pushNotifications,
|
|
221
|
+
stateTransitionHistory,
|
|
228
222
|
},
|
|
229
223
|
authentication: null,
|
|
230
|
-
defaultInputModes
|
|
231
|
-
defaultOutputModes
|
|
232
|
-
skills
|
|
224
|
+
defaultInputModes,
|
|
225
|
+
defaultOutputModes,
|
|
226
|
+
skills,
|
|
233
227
|
"x-payment-config": pricingConfig,
|
|
234
228
|
};
|
|
235
229
|
// Validate compliance
|
|
@@ -242,7 +236,7 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
|
|
|
242
236
|
contentType: "application/json",
|
|
243
237
|
};
|
|
244
238
|
// Destination for the ordinal
|
|
245
|
-
const targetAddress =
|
|
239
|
+
const targetAddress = destinationAddress ?? walletAddress;
|
|
246
240
|
const destinations: Destination[] = [
|
|
247
241
|
{ address: targetAddress, inscription },
|
|
248
242
|
];
|
|
@@ -272,7 +266,8 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
|
|
|
272
266
|
|
|
273
267
|
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
274
268
|
if (!disableBroadcasting) {
|
|
275
|
-
|
|
269
|
+
const broadcaster = new V5Broadcaster();
|
|
270
|
+
await changeResult.tx.broadcast(broadcaster);
|
|
276
271
|
|
|
277
272
|
// Refresh UTXOs
|
|
278
273
|
try {
|