bsv-mcp 0.0.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.
@@ -0,0 +1,94 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for get BSV20 by ID arguments
6
+ export const getBsv20ByIdArgsSchema = z.object({
7
+ id: z.string().describe("BSV20 token ID in outpoint format (txid_vout)"),
8
+ });
9
+
10
+ export type GetBsv20ByIdArgs = z.infer<typeof getBsv20ByIdArgsSchema>;
11
+
12
+ // BSV20 token response type
13
+ interface Bsv20TokenResponse {
14
+ id: string;
15
+ tick?: string;
16
+ sym?: string;
17
+ max?: string;
18
+ lim?: string;
19
+ dec?: number;
20
+ supply?: string;
21
+ amt?: string;
22
+ status?: number;
23
+ icon?: string;
24
+ height?: number;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ /**
29
+ * Register the BSV20 token lookup tool
30
+ */
31
+ export function registerGetBsv20ByIdTool(server: McpServer): void {
32
+ server.tool(
33
+ "ordinals_getBsv20ById",
34
+ {
35
+ args: getBsv20ByIdArgsSchema,
36
+ },
37
+ async (
38
+ { args }: { args: GetBsv20ByIdArgs },
39
+ extra: RequestHandlerExtra,
40
+ ) => {
41
+ try {
42
+ const { id } = args;
43
+
44
+ // Validate ID format (should be in outpoint format)
45
+ if (!/^[0-9a-f]{64}_\d+$/i.test(id)) {
46
+ throw new Error("Invalid BSV20 ID format. Expected 'txid_vout'");
47
+ }
48
+
49
+ // Fetch BSV20 token data from GorillaPool API
50
+ const response = await fetch(
51
+ `https://ordinals.gorillapool.io/api/bsv20/id/${id}`,
52
+ );
53
+
54
+ if (response.status === 404) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: JSON.stringify({ error: "BSV20 token not found" }),
60
+ },
61
+ ],
62
+ };
63
+ }
64
+
65
+ if (!response.ok) {
66
+ throw new Error(
67
+ `API error: ${response.status} ${response.statusText}`,
68
+ );
69
+ }
70
+
71
+ const data = (await response.json()) as Bsv20TokenResponse;
72
+
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: JSON.stringify(data, null, 2),
78
+ },
79
+ ],
80
+ };
81
+ } catch (error) {
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: error instanceof Error ? error.message : String(error),
87
+ },
88
+ ],
89
+ isError: true,
90
+ };
91
+ }
92
+ },
93
+ );
94
+ }
@@ -0,0 +1,103 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for get inscription arguments
6
+ export const getInscriptionArgsSchema = z.object({
7
+ outpoint: z.string().describe("Outpoint in format 'txid_vout'"),
8
+ });
9
+
10
+ export type GetInscriptionArgs = z.infer<typeof getInscriptionArgsSchema>;
11
+
12
+ // Inscription API response type
13
+ interface InscriptionResponse {
14
+ outpoint: string;
15
+ origin: {
16
+ outpoint: string;
17
+ data?: {
18
+ insc?: {
19
+ text?: string;
20
+ json?: unknown;
21
+ file?: {
22
+ hash?: string;
23
+ size?: number;
24
+ type?: string;
25
+ };
26
+ };
27
+ };
28
+ };
29
+ height?: number;
30
+ idx?: number;
31
+ satoshis?: number;
32
+ script?: string;
33
+ spend?: string;
34
+ [key: string]: unknown;
35
+ }
36
+
37
+ /**
38
+ * Register the Ordinals inscription lookup tool
39
+ */
40
+ export function registerGetInscriptionTool(server: McpServer): void {
41
+ server.tool(
42
+ "ordinals_getInscription",
43
+ {
44
+ args: getInscriptionArgsSchema,
45
+ },
46
+ async (
47
+ { args }: { args: GetInscriptionArgs },
48
+ extra: RequestHandlerExtra,
49
+ ) => {
50
+ try {
51
+ const { outpoint } = args;
52
+
53
+ // Validate outpoint format
54
+ if (!/^[0-9a-f]{64}_\d+$/i.test(outpoint)) {
55
+ throw new Error("Invalid outpoint format. Expected 'txid_vout'");
56
+ }
57
+
58
+ // Fetch inscription data from GorillaPool API
59
+ const response = await fetch(
60
+ `https://ordinals.gorillapool.io/api/inscriptions/${outpoint}`,
61
+ );
62
+
63
+ if (response.status === 404) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: JSON.stringify({ error: "Inscription not found" }),
69
+ },
70
+ ],
71
+ };
72
+ }
73
+
74
+ if (!response.ok) {
75
+ throw new Error(
76
+ `API error: ${response.status} ${response.statusText}`,
77
+ );
78
+ }
79
+
80
+ const data = (await response.json()) as InscriptionResponse;
81
+
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: JSON.stringify(data, null, 2),
87
+ },
88
+ ],
89
+ };
90
+ } catch (error) {
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: error instanceof Error ? error.message : String(error),
96
+ },
97
+ ],
98
+ isError: true,
99
+ };
100
+ }
101
+ },
102
+ );
103
+ }
@@ -0,0 +1,19 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerBsv20MarketSalesTool } from "./bsv20MarketSales";
3
+ import { registerGetBsv20ByIdTool } from "./getBsv20ById";
4
+ import { registerGetInscriptionTool } from "./getInscription";
5
+ import { registerMarketListingsTool } from "./marketListings";
6
+ import { registerSearchInscriptionsTool } from "./searchInscriptions";
7
+
8
+ /**
9
+ * Register all Ordinals tools with the MCP server
10
+ * @param server The MCP server instance
11
+ */
12
+ export function registerOrdinalsTools(server: McpServer): void {
13
+ // Register Ordinals-related tools
14
+ registerGetInscriptionTool(server);
15
+ registerSearchInscriptionsTool(server);
16
+ registerMarketListingsTool(server);
17
+ registerBsv20MarketSalesTool(server);
18
+ registerGetBsv20ByIdTool(server);
19
+ }
@@ -0,0 +1,137 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for market listings arguments
6
+ export const marketListingsArgsSchema = z.object({
7
+ limit: z
8
+ .number()
9
+ .int()
10
+ .min(1)
11
+ .max(100)
12
+ .default(20)
13
+ .describe("Number of results (1-100, default 20)"),
14
+ offset: z.number().int().min(0).default(0).describe("Pagination offset"),
15
+ sort: z
16
+ .enum(["recent", "price", "num"])
17
+ .default("recent")
18
+ .describe("Sort method (recent, price, or num)"),
19
+ dir: z
20
+ .enum(["asc", "desc"])
21
+ .default("desc")
22
+ .describe("Sort direction (asc or desc)"),
23
+ address: z.string().optional().describe("Bitcoin address"),
24
+ origin: z.string().optional().describe("Origin outpoint"),
25
+ mime: z.string().optional().describe("MIME type filter"),
26
+ num: z.string().optional().describe("Inscription number"),
27
+ minPrice: z.number().optional().describe("Minimum price in satoshis"),
28
+ maxPrice: z.number().optional().describe("Maximum price in satoshis"),
29
+ });
30
+
31
+ export type MarketListingsArgs = z.infer<typeof marketListingsArgsSchema>;
32
+
33
+ // Simplified market listing response type
34
+ interface MarketListingResponse {
35
+ results: Array<{
36
+ outpoint: string;
37
+ origin: {
38
+ outpoint: string;
39
+ data?: {
40
+ insc?: {
41
+ text?: string;
42
+ file?: {
43
+ type?: string;
44
+ size?: number;
45
+ };
46
+ };
47
+ };
48
+ };
49
+ data?: {
50
+ list?: {
51
+ price?: number;
52
+ payout?: string;
53
+ sale?: boolean;
54
+ };
55
+ };
56
+ satoshis?: number;
57
+ [key: string]: unknown;
58
+ }>;
59
+ total: number;
60
+ }
61
+
62
+ /**
63
+ * Register the Ordinals market listings tool
64
+ */
65
+ export function registerMarketListingsTool(server: McpServer): void {
66
+ server.tool(
67
+ "ordinals_marketListings",
68
+ {
69
+ args: marketListingsArgsSchema,
70
+ },
71
+ async (
72
+ { args }: { args: MarketListingsArgs },
73
+ extra: RequestHandlerExtra,
74
+ ) => {
75
+ try {
76
+ const {
77
+ limit,
78
+ offset,
79
+ sort,
80
+ dir,
81
+ address,
82
+ origin,
83
+ mime,
84
+ num,
85
+ minPrice,
86
+ maxPrice,
87
+ } = args;
88
+
89
+ // Build the URL with query parameters
90
+ const url = new URL("https://ordinals.gorillapool.io/api/market");
91
+ url.searchParams.append("limit", limit.toString());
92
+ url.searchParams.append("offset", offset.toString());
93
+ url.searchParams.append("sort", sort);
94
+ url.searchParams.append("dir", dir);
95
+
96
+ if (address) url.searchParams.append("address", address);
97
+ if (origin) url.searchParams.append("origin", origin);
98
+ if (mime) url.searchParams.append("mime", mime);
99
+ if (num) url.searchParams.append("num", num);
100
+ if (minPrice !== undefined)
101
+ url.searchParams.append("min", minPrice.toString());
102
+ if (maxPrice !== undefined)
103
+ url.searchParams.append("max", maxPrice.toString());
104
+
105
+ // Fetch market listings from GorillaPool API
106
+ const response = await fetch(url.toString());
107
+
108
+ if (!response.ok) {
109
+ throw new Error(
110
+ `API error: ${response.status} ${response.statusText}`,
111
+ );
112
+ }
113
+
114
+ const data = (await response.json()) as MarketListingResponse;
115
+
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: JSON.stringify(data, null, 2),
121
+ },
122
+ ],
123
+ };
124
+ } catch (error) {
125
+ return {
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: error instanceof Error ? error.message : String(error),
130
+ },
131
+ ],
132
+ isError: true,
133
+ };
134
+ }
135
+ },
136
+ );
137
+ }
@@ -0,0 +1,118 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for search inscriptions arguments
6
+ export const searchInscriptionsArgsSchema = z.object({
7
+ limit: z
8
+ .number()
9
+ .int()
10
+ .min(1)
11
+ .max(100)
12
+ .default(20)
13
+ .describe("Number of results (1-100, default 20)"),
14
+ offset: z.number().int().min(0).default(0).describe("Pagination offset"),
15
+ dir: z
16
+ .enum(["asc", "desc"])
17
+ .default("desc")
18
+ .describe("Sort direction (asc or desc)"),
19
+ num: z.string().optional().describe("Inscription number"),
20
+ origin: z.string().optional().describe("Origin outpoint"),
21
+ address: z.string().optional().describe("Bitcoin address"),
22
+ map: z.string().optional().describe("Map field"),
23
+ terms: z.string().optional().describe("Search terms"),
24
+ mime: z.string().optional().describe("MIME type filter"),
25
+ });
26
+
27
+ export type SearchInscriptionsArgs = z.infer<
28
+ typeof searchInscriptionsArgsSchema
29
+ >;
30
+
31
+ // Simplified inscription response type
32
+ interface InscriptionSearchResponse {
33
+ results: Array<{
34
+ outpoint: string;
35
+ origin: {
36
+ outpoint: string;
37
+ data?: {
38
+ insc?: {
39
+ text?: string;
40
+ file?: {
41
+ type?: string;
42
+ size?: number;
43
+ };
44
+ };
45
+ };
46
+ };
47
+ height?: number;
48
+ satoshis?: number;
49
+ [key: string]: unknown;
50
+ }>;
51
+ total: number;
52
+ }
53
+
54
+ /**
55
+ * Register the Ordinals search inscriptions tool
56
+ */
57
+ export function registerSearchInscriptionsTool(server: McpServer): void {
58
+ server.tool(
59
+ "ordinals_searchInscriptions",
60
+ {
61
+ args: searchInscriptionsArgsSchema,
62
+ },
63
+ async (
64
+ { args }: { args: SearchInscriptionsArgs },
65
+ extra: RequestHandlerExtra,
66
+ ) => {
67
+ try {
68
+ const { limit, offset, dir, num, origin, address, map, terms, mime } =
69
+ args;
70
+
71
+ // Build the URL with query parameters
72
+ const url = new URL(
73
+ "https://ordinals.gorillapool.io/api/inscriptions/search",
74
+ );
75
+ url.searchParams.append("limit", limit.toString());
76
+ url.searchParams.append("offset", offset.toString());
77
+ url.searchParams.append("dir", dir);
78
+
79
+ if (num) url.searchParams.append("num", num);
80
+ if (origin) url.searchParams.append("origin", origin);
81
+ if (address) url.searchParams.append("address", address);
82
+ if (map) url.searchParams.append("map", map);
83
+ if (terms) url.searchParams.append("terms", terms);
84
+ if (mime) url.searchParams.append("mime", mime);
85
+
86
+ // Fetch inscriptions data from GorillaPool API
87
+ const response = await fetch(url.toString());
88
+
89
+ if (!response.ok) {
90
+ throw new Error(
91
+ `API error: ${response.status} ${response.statusText}`,
92
+ );
93
+ }
94
+
95
+ const data = (await response.json()) as InscriptionSearchResponse;
96
+
97
+ return {
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: JSON.stringify(data, null, 2),
102
+ },
103
+ ],
104
+ };
105
+ } catch (error) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: error instanceof Error ? error.message : String(error),
111
+ },
112
+ ],
113
+ isError: true,
114
+ };
115
+ }
116
+ },
117
+ );
118
+ }
@@ -0,0 +1,58 @@
1
+ import { Utils } from "@bsv/sdk";
2
+ import { z } from "zod";
3
+ const {
4
+ toArray: bsvToArray,
5
+ toBase64: bsvToBase64,
6
+ toHex: bsvToHex,
7
+ toUTF8: bsvToUTF8,
8
+ } = Utils;
9
+
10
+ const encodingSchema = z.enum(["utf8", "hex", "base64", "binary"]);
11
+
12
+ /**
13
+ * Convert data between hex, base64, utf8, and binary (number array) formats.
14
+ * @param data - The input data as a string (hex, base64, utf8, or JSON array string for binary)
15
+ * @param from - The encoding of the input data
16
+ * @param to - The desired encoding of the output data
17
+ * @returns The converted data as a string (except for binary, which is a JSON array string)
18
+ */
19
+ export function convertData({
20
+ data,
21
+ from,
22
+ to,
23
+ }: {
24
+ data: string;
25
+ from: "hex" | "base64" | "utf8" | "binary";
26
+ to: "hex" | "base64" | "utf8" | "binary";
27
+ }): string {
28
+ encodingSchema.parse(from);
29
+ encodingSchema.parse(to);
30
+
31
+ let arr: number[];
32
+ if (from === "binary") {
33
+ try {
34
+ arr = JSON.parse(data);
35
+ if (!Array.isArray(arr) || !arr.every((n) => typeof n === "number")) {
36
+ throw new Error();
37
+ }
38
+ } catch {
39
+ throw new Error("Invalid binary input: must be a JSON array of numbers");
40
+ }
41
+ } else {
42
+ arr = bsvToArray(data, from);
43
+ }
44
+
45
+ if (to === "binary") {
46
+ return JSON.stringify(arr);
47
+ }
48
+ if (to === "hex") {
49
+ return bsvToHex(arr);
50
+ }
51
+ if (to === "base64") {
52
+ return bsvToBase64(arr);
53
+ }
54
+ if (to === "utf8") {
55
+ return bsvToUTF8(arr);
56
+ }
57
+ throw new Error("Invalid 'to' encoding");
58
+ }
@@ -0,0 +1,50 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { convertData } from "./conversion";
4
+
5
+ const encodingSchema = z.enum(["utf8", "hex", "base64", "binary"]);
6
+
7
+ /**
8
+ * Register the unified conversion tool with the MCP server
9
+ * @param server The MCP server instance
10
+ */
11
+ export function registerUtilsTools(server: McpServer): void {
12
+ server.tool(
13
+ "utils_convertData",
14
+ {
15
+ args: z.object({
16
+ data: z.string(),
17
+ from: encodingSchema,
18
+ to: encodingSchema,
19
+ }),
20
+ },
21
+ async ({ args }) => {
22
+ try {
23
+ const result = convertData({
24
+ data: args.data,
25
+ from: args.from,
26
+ to: args.to,
27
+ });
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: result,
33
+ },
34
+ ],
35
+ };
36
+ } catch (err: unknown) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: `Error: ${msg}`,
43
+ },
44
+ ],
45
+ isError: true,
46
+ };
47
+ }
48
+ },
49
+ );
50
+ }
@@ -0,0 +1,38 @@
1
+ import { PrivateKey } from "@bsv/sdk";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+
5
+ /**
6
+ * Register the tool to get the wallet address
7
+ * @param server The MCP server instance
8
+ */
9
+ export function registerGetAddressTool(server: McpServer): void {
10
+ server.tool(
11
+ "wallet_getAddress",
12
+ {
13
+ args: z.object({}).optional(),
14
+ },
15
+ async () => {
16
+ try {
17
+ const wif = process.env.PRIVATE_KEY_WIF;
18
+ if (!wif) throw new Error("PRIVATE_KEY_WIF env var not set");
19
+ const privKey = PrivateKey.fromWif(wif);
20
+ const address = privKey.toAddress();
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: JSON.stringify({ address, status: "ok" }),
26
+ },
27
+ ],
28
+ };
29
+ } catch (err: unknown) {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ return {
32
+ content: [{ type: "text", text: msg }],
33
+ isError: true,
34
+ };
35
+ }
36
+ },
37
+ );
38
+ }