Algorand MCP
by GoPlausible
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { UtilityManager } from '../../src/tools/utilityManager';
import algosdk from 'algosdk';
// Rest of the file remains the same...
// Mock algosdk
jest.mock('algosdk', () => ({
isValidAddress: jest.fn(),
encodeAddress: jest.fn(),
decodeAddress: jest.fn(),
getApplicationAddress: jest.fn(),
}));
describe('UtilityManager', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Tool Schemas', () => {
it('should have valid tool schemas', () => {
expect(UtilityManager.utilityTools).toHaveLength(8);
expect(UtilityManager.utilityTools.map((t: { name: string; }) => t.name)).toEqual([
'validate_address',
'encode_address',
'decode_address',
'get_application_address',
'bytes_to_bigint',
'bigint_to_bytes',
'encode_uint64',
'decode_uint64',
]);
});
});
describe('Address Operations', () => {
const testAddress = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const testPublicKey = Buffer.from('0'.repeat(64), 'hex');
it('should validate address', () => {
(algosdk.isValidAddress as jest.Mock).mockReturnValue(true);
const result = UtilityManager.isValidAddress(testAddress);
expect(result).toBe(true);
expect(algosdk.isValidAddress).toHaveBeenCalledWith(testAddress);
});
it('should encode address', () => {
(algosdk.encodeAddress as jest.Mock).mockReturnValue(testAddress);
const result = UtilityManager.encodeAddress(testPublicKey);
expect(result).toBe(testAddress);
expect(algosdk.encodeAddress).toHaveBeenCalledWith(testPublicKey);
});
it('should decode address', () => {
(algosdk.decodeAddress as jest.Mock).mockReturnValue({ publicKey: testPublicKey });
const result = UtilityManager.decodeAddress(testAddress);
expect(result).toBe(testPublicKey);
expect(algosdk.decodeAddress).toHaveBeenCalledWith(testAddress);
});
it('should get application address', () => {
const appId = 123;
const appAddress = 'APP_ADDRESS';
(algosdk.getApplicationAddress as jest.Mock).mockReturnValue(appAddress);
const result = UtilityManager.getApplicationAddress(appId);
expect(result).toBe(appAddress);
expect(algosdk.getApplicationAddress).toHaveBeenCalledWith(appId);
});
});
describe('BigInt Operations', () => {
it('should convert bytes to BigInt', () => {
const bytes = Buffer.from('0001', 'hex');
const result = UtilityManager.bytesToBigInt(bytes);
expect(result.toString()).toBe('1');
});
it('should convert BigInt to bytes', () => {
const value = BigInt(1);
const size = 2;
const result = UtilityManager.bigIntToBytes(value, size);
expect(Buffer.from(result).toString('hex')).toBe('0001');
});
it('should encode uint64', () => {
const value = BigInt(1);
const result = UtilityManager.encodeUint64(value);
expect(Buffer.from(result).toString('hex')).toBe('0000000000000001');
});
it('should decode uint64', () => {
const bytes = Buffer.from('0000000000000001', 'hex');
const result = UtilityManager.decodeUint64(bytes);
expect(result.toString()).toBe('1');
});
});
describe('handleTool', () => {
it('should handle validate_address', async () => {
(algosdk.isValidAddress as jest.Mock).mockReturnValue(true);
const result = await UtilityManager.handleTool('validate_address', {
address: 'test-address',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ isValid: true }, null, 2),
}],
});
});
it('should handle encode_address', async () => {
const testAddress = 'encoded-address';
(algosdk.encodeAddress as jest.Mock).mockReturnValue(testAddress);
const result = await UtilityManager.handleTool('encode_address', {
publicKey: '00',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ address: testAddress }, null, 2),
}],
});
});
it('should handle decode_address', async () => {
const testPublicKey = Buffer.from('00', 'hex');
(algosdk.decodeAddress as jest.Mock).mockReturnValue({ publicKey: testPublicKey });
const result = await UtilityManager.handleTool('decode_address', {
address: 'test-address',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ publicKey: '00' }, null, 2),
}],
});
});
it('should handle get_application_address', async () => {
const appAddress = 'app-address';
(algosdk.getApplicationAddress as jest.Mock).mockReturnValue(appAddress);
const result = await UtilityManager.handleTool('get_application_address', {
appId: 123,
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ address: appAddress }, null, 2),
}],
});
});
it('should handle bytes_to_bigint', async () => {
const result = await UtilityManager.handleTool('bytes_to_bigint', {
bytes: '0001',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ value: '1' }, null, 2),
}],
});
});
it('should handle bigint_to_bytes', async () => {
const result = await UtilityManager.handleTool('bigint_to_bytes', {
value: '1',
size: 2,
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ bytes: '0001' }, null, 2),
}],
});
});
it('should handle encode_uint64', async () => {
const result = await UtilityManager.handleTool('encode_uint64', {
value: '1',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ bytes: '0000000000000001' }, null, 2),
}],
});
});
it('should handle decode_uint64', async () => {
const result = await UtilityManager.handleTool('decode_uint64', {
bytes: '0000000000000001',
});
expect(result).toEqual({
content: [{
type: 'text',
text: JSON.stringify({ value: '1' }, null, 2),
}],
});
});
describe('Error Handling', () => {
it('should throw error for unknown tool', async () => {
await expect(UtilityManager.handleTool('unknown_tool', {}))
.rejects
.toThrow(new McpError(ErrorCode.MethodNotFound, 'Unknown tool: unknown_tool'));
});
it('should throw error for missing address in validate_address', async () => {
await expect(UtilityManager.handleTool('validate_address', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Address is required'));
});
it('should throw error for missing public key in encode_address', async () => {
await expect(UtilityManager.handleTool('encode_address', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Public key is required'));
});
it('should throw error for missing address in decode_address', async () => {
await expect(UtilityManager.handleTool('decode_address', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Address is required'));
});
it('should throw error for missing app ID in get_application_address', async () => {
await expect(UtilityManager.handleTool('get_application_address', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Application ID is required'));
});
it('should throw error for missing bytes in bytes_to_bigint', async () => {
await expect(UtilityManager.handleTool('bytes_to_bigint', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Bytes are required'));
});
it('should throw error for missing value or size in bigint_to_bytes', async () => {
await expect(UtilityManager.handleTool('bigint_to_bytes', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Value and size are required'));
});
it('should throw error for missing value in encode_uint64', async () => {
await expect(UtilityManager.handleTool('encode_uint64', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Value is required'));
});
it('should throw error for missing bytes in decode_uint64', async () => {
await expect(UtilityManager.handleTool('decode_uint64', {}))
.rejects
.toThrow(new McpError(ErrorCode.InvalidParams, 'Bytes are required'));
});
});
});
});