token.ts•4.93 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
	ReturnTypes,
	toBitcoin,
	toSatoshi,
	toToken,
	toTokenSat,
} from "satoshi-token";
import { z } from "zod";
/**
 * Register token conversion tools for BSV
 * @param server The MCP server instance
 */
export function registerTokenTools(server: McpServer): void {
	// Convert Bitcoin to Satoshis
	server.tool(
		"bsv_toSatoshi",
		{
			args: z.object({
				bitcoin: z.union([z.number(), z.string()]),
				returnType: z.enum(["number", "string", "bigint"]).optional(),
			}),
		},
		async ({ args }) => {
			try {
				const { bitcoin, returnType } = args;
				let result: number | string | bigint;
				switch (returnType) {
					case "bigint":
						result = toSatoshi(bitcoin, ReturnTypes.BigInt);
						return { content: [{ type: "text", text: result.toString() }] };
					case "string":
						result = toSatoshi(bitcoin, ReturnTypes.String);
						return { content: [{ type: "text", text: result }] };
					default:
						result = toSatoshi(bitcoin);
						return { content: [{ type: "text", text: result.toString() }] };
				}
			} catch (err: unknown) {
				const msg = err instanceof Error ? err.message : String(err);
				return { content: [{ type: "text", text: msg }], isError: true };
			}
		},
	);
	// Convert Satoshis to Bitcoin
	server.tool(
		"bsv_toBitcoin",
		{
			args: z.object({
				satoshis: z.union([z.number(), z.string(), z.bigint()]),
				returnType: z.enum(["number", "string", "bigint"]).optional(),
			}),
		},
		async ({ args }) => {
			try {
				const { satoshis, returnType } = args;
				let result: number | string | bigint;
				switch (returnType) {
					case "bigint":
						try {
							result = toBitcoin(satoshis, ReturnTypes.BigInt);
							return { content: [{ type: "text", text: result.toString() }] };
						} catch (e) {
							return {
								content: [
									{
										type: "text",
										text: "Error: Cannot return Bitcoin amount as BigInt if it has decimal part",
									},
								],
								isError: true,
							};
						}
					case "string":
						result = toBitcoin(satoshis, ReturnTypes.String);
						return { content: [{ type: "text", text: result }] };
					default:
						result = toBitcoin(satoshis);
						return { content: [{ type: "text", text: result.toString() }] };
				}
			} catch (err: unknown) {
				const msg = err instanceof Error ? err.message : String(err);
				return { content: [{ type: "text", text: msg }], isError: true };
			}
		},
	);
	// Generic token conversion (for tokens with custom decimal places)
	server.tool(
		"bsv_toTokenSatoshi",
		{
			args: z.object({
				token: z.union([z.number(), z.string(), z.bigint()]),
				decimals: z.number().int().min(0),
				returnType: z.enum(["number", "string", "bigint"]).optional(),
			}),
		},
		async ({ args }) => {
			try {
				const { token, decimals, returnType } = args;
				let result: number | string | bigint;
				switch (returnType) {
					case "bigint":
						result = toTokenSat(token, decimals, ReturnTypes.BigInt);
						return { content: [{ type: "text", text: result.toString() }] };
					case "string":
						result = toTokenSat(token, decimals, ReturnTypes.String);
						return { content: [{ type: "text", text: result }] };
					default:
						result = toTokenSat(token, decimals);
						return { content: [{ type: "text", text: result.toString() }] };
				}
			} catch (err: unknown) {
				const msg = err instanceof Error ? err.message : String(err);
				return { content: [{ type: "text", text: msg }], isError: true };
			}
		},
	);
	// Generic token conversion (for tokens with custom decimal places)
	server.tool(
		"bsv_toToken",
		{
			args: z.object({
				tokenSatoshi: z.union([z.number(), z.string(), z.bigint()]),
				decimals: z.number().int().min(0),
				returnType: z.enum(["number", "string", "bigint"]).optional(),
			}),
		},
		async ({ args }) => {
			try {
				const { tokenSatoshi, decimals, returnType } = args;
				let result: number | string | bigint;
				switch (returnType) {
					case "bigint":
						try {
							result = toToken(tokenSatoshi, decimals, ReturnTypes.BigInt);
							return { content: [{ type: "text", text: result.toString() }] };
						} catch (e) {
							return {
								content: [
									{
										type: "text",
										text: "Error: Cannot return token amount as BigInt if it has decimal part",
									},
								],
								isError: true,
							};
						}
					case "string":
						result = toToken(tokenSatoshi, decimals, ReturnTypes.String);
						return { content: [{ type: "text", text: result }] };
					default:
						result = toToken(tokenSatoshi, decimals);
						return { content: [{ type: "text", text: result.toString() }] };
				}
			} catch (err: unknown) {
				const msg = err instanceof Error ? err.message : String(err);
				return { content: [{ type: "text", text: msg }], isError: true };
			}
		},
	);
}