Algorand MCP
by GoPlausible
import algosdk from 'algosdk';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
// Tool schemas
export const utilityToolSchemas = {
validateAddress: {
type: 'object',
properties: {
address: { type: 'string' }
},
required: ['address']
},
encodeAddress: {
type: 'object',
properties: {
publicKey: { type: 'string' }
},
required: ['publicKey']
},
decodeAddress: {
type: 'object',
properties: {
address: { type: 'string' }
},
required: ['address']
},
getApplicationAddress: {
type: 'object',
properties: {
appId: { type: 'integer' }
},
required: ['appId']
},
bytesToBigint: {
type: 'object',
properties: {
bytes: { type: 'string' }
},
required: ['bytes']
},
bigintToBytes: {
type: 'object',
properties: {
value: { type: 'string' },
size: { type: 'integer' }
},
required: ['value', 'size']
},
encodeUint64: {
type: 'object',
properties: {
value: { type: 'string' }
},
required: ['value']
},
decodeUint64: {
type: 'object',
properties: {
bytes: { type: 'string' }
},
required: ['bytes']
}
};
export class UtilityManager {
static readonly utilityTools = [
{
name: 'validate_address',
description: 'Check if an Algorand address is valid',
inputSchema: utilityToolSchemas.validateAddress,
},
{
name: 'encode_address',
description: 'Encode a public key to an Algorand address',
inputSchema: utilityToolSchemas.encodeAddress,
},
{
name: 'decode_address',
description: 'Decode an Algorand address to a public key',
inputSchema: utilityToolSchemas.decodeAddress,
},
{
name: 'get_application_address',
description: 'Get the address for a given application ID',
inputSchema: utilityToolSchemas.getApplicationAddress,
},
{
name: 'bytes_to_bigint',
description: 'Convert bytes to a BigInt',
inputSchema: utilityToolSchemas.bytesToBigint,
},
{
name: 'bigint_to_bytes',
description: 'Convert a BigInt to bytes',
inputSchema: utilityToolSchemas.bigintToBytes,
},
{
name: 'encode_uint64',
description: 'Encode a uint64 to bytes',
inputSchema: utilityToolSchemas.encodeUint64,
},
{
name: 'decode_uint64',
description: 'Decode bytes to a uint64',
inputSchema: utilityToolSchemas.decodeUint64,
}
];
// Tool handlers
static async handleTool(name: string, args: Record<string, unknown>) {
try {
switch (name) {
case 'validate_address':
if (!args.address || typeof args.address !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Address is required');
}
const isValid = UtilityManager.isValidAddress(args.address);
return {
content: [{
type: 'text',
text: JSON.stringify({ isValid }, null, 2),
}],
};
case 'encode_address':
if (!args.publicKey || typeof args.publicKey !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Public key is required');
}
const encodedAddress = UtilityManager.encodeAddress(Buffer.from(args.publicKey, 'hex'));
return {
content: [{
type: 'text',
text: JSON.stringify({ address: encodedAddress }, null, 2),
}],
};
case 'decode_address':
if (!args.address || typeof args.address !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Address is required');
}
const decodedPublicKey = UtilityManager.decodeAddress(args.address);
return {
content: [{
type: 'text',
text: JSON.stringify({ publicKey: Buffer.from(decodedPublicKey).toString('hex') }, null, 2),
}],
};
case 'get_application_address':
if (!args.appId || typeof args.appId !== 'number') {
throw new McpError(ErrorCode.InvalidParams, 'Application ID is required');
}
const appAddress = UtilityManager.getApplicationAddress(args.appId);
return {
content: [{
type: 'text',
text: JSON.stringify({ address: appAddress }, null, 2),
}],
};
case 'bytes_to_bigint':
if (!args.bytes || typeof args.bytes !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Bytes are required');
}
const bigInt = UtilityManager.bytesToBigInt(Buffer.from(args.bytes, 'hex'));
return {
content: [{
type: 'text',
text: JSON.stringify({ value: bigInt.toString() }, null, 2),
}],
};
case 'bigint_to_bytes':
if (!args.value || typeof args.value !== 'string' || !args.size || typeof args.size !== 'number') {
throw new McpError(ErrorCode.InvalidParams, 'Value and size are required');
}
const bytes = UtilityManager.bigIntToBytes(BigInt(args.value), args.size);
return {
content: [{
type: 'text',
text: JSON.stringify({ bytes: Buffer.from(bytes).toString('hex') }, null, 2),
}],
};
case 'encode_uint64':
if (!args.value || typeof args.value !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Value is required');
}
const encodedUint64 = UtilityManager.encodeUint64(BigInt(args.value));
return {
content: [{
type: 'text',
text: JSON.stringify({ bytes: Buffer.from(encodedUint64).toString('hex') }, null, 2),
}],
};
case 'decode_uint64':
if (!args.bytes || typeof args.bytes !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Bytes are required');
}
const decodedUint64 = UtilityManager.decodeUint64(Buffer.from(args.bytes, 'hex'));
return {
content: [{
type: 'text',
text: JSON.stringify({ value: decodedUint64.toString() }, null, 2),
}],
};
default:
console.error(`[MCP Error] Unknown tool requested: ${name}`);
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error) {
if (error instanceof McpError) {
console.error(`[MCP Error] ${error.code}: ${error.message}`);
throw error;
}
console.error('[MCP Error] Unexpected error:', error);
throw new McpError(
ErrorCode.InternalError,
`Operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Checks if an address is valid
* @param address The address to validate
* @returns True if the address is valid, false otherwise
*/
static isValidAddress(address: string): boolean {
try {
return algosdk.isValidAddress(address);
} catch (error) {
console.error('[MCP Error] Failed to validate address:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid address format: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Encodes a public key to an Algorand address
* @param publicKey The public key to encode
* @returns The encoded address
*/
static encodeAddress(publicKey: Uint8Array): string {
try {
return algosdk.encodeAddress(publicKey);
} catch (error) {
console.error('[MCP Error] Failed to encode address:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid public key: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Decodes an Algorand address to a public key
* @param address The address to decode
* @returns The decoded public key
*/
static decodeAddress(address: string): Uint8Array {
try {
return algosdk.decodeAddress(address).publicKey;
} catch (error) {
console.error('[MCP Error] Failed to decode address:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid address: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Gets the application address for a given application ID
* @param appId The application ID
* @returns The application address
*/
static getApplicationAddress(appId: number): string {
try {
return algosdk.getApplicationAddress(appId);
} catch (error) {
console.error('[MCP Error] Failed to get application address:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid application ID: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Converts bytes to a BigInt
* @param bytes The bytes to convert
* @returns The BigInt value
*/
static bytesToBigInt(bytes: Uint8Array): bigint {
try {
return BigInt('0x' + Buffer.from(bytes).toString('hex'));
} catch (error) {
console.error('[MCP Error] Failed to convert bytes to BigInt:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid bytes format: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Converts a BigInt to bytes
* @param value The BigInt value to convert
* @param size The size of the resulting byte array
* @returns The bytes representation
*/
static bigIntToBytes(value: bigint, size: number): Uint8Array {
try {
const hex = value.toString(16).padStart(size * 2, '0');
return Buffer.from(hex, 'hex');
} catch (error) {
console.error('[MCP Error] Failed to convert BigInt to bytes:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid BigInt value or size: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Encodes a uint64 to bytes
* @param value The uint64 value to encode
* @returns The encoded bytes
*/
static encodeUint64(value: bigint): Uint8Array {
try {
return this.bigIntToBytes(value, 8);
} catch (error) {
console.error('[MCP Error] Failed to encode uint64:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid uint64 value: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Decodes bytes to a uint64
* @param bytes The bytes to decode
* @returns The decoded uint64 value
*/
static decodeUint64(bytes: Uint8Array): bigint {
try {
return this.bytesToBigInt(bytes);
} catch (error) {
console.error('[MCP Error] Failed to decode uint64:', error);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid uint64 bytes: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
}