Skip to main content
Glama

Neo N3 MCP Server

by r3e-network
tool-handler.test.ts17.5 kB
/** * Comprehensive Unit Tests for Tool Handlers * Testing all MCP tool operations */ import { jest } from '@jest/globals'; import { callTool } from '../src/handlers/tool-handler'; import { NeoService, NeoNetwork } from '../src/services/neo-service'; import { ContractService } from '../src/contracts/contract-service'; import { FAMOUS_CONTRACTS } from '../src/contracts/contracts'; import { ValidationError } from '../src/utils/errors'; // Mock data const mockBlockchainInfo = { height: 12345, network: NeoNetwork.MAINNET, validators: [] }; const mockBalance = { address: 'NMockAddress123', balance: [ { asset_name: 'NEO', asset_hash: '0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5', amount: '100' }, { asset_name: 'GAS', asset_hash: '0xd2a4cff31913016155e38e474a2c06d08be276cf', amount: '50.5' } ] }; const mockTransferResult = { txid: '0xabc123def456' }; const mockWallet = { address: 'NMockAddress123', publicKey: 'mock-public-key', encryptedPrivateKey: 'encrypted-key', WIF: 'mock-wif' }; // Mock services const createMockNeoService = (): jest.Mocked<NeoService> => ({ getBlockchainInfo: jest.fn().mockResolvedValue(mockBlockchainInfo), getBlockCount: jest.fn().mockResolvedValue(12345), getBlock: jest.fn().mockResolvedValue({ hash: '0x123', index: 12344 }), getTransaction: jest.fn().mockResolvedValue({ hash: '0x456', sender: 'NMock' }), getBalance: jest.fn().mockResolvedValue(mockBalance), transferAssets: jest.fn().mockResolvedValue(mockTransferResult), invokeContract: jest.fn().mockResolvedValue(mockTransferResult), createWallet: jest.fn().mockReturnValue(mockWallet), importWallet: jest.fn().mockReturnValue(mockWallet), calculateTransferFee: jest.fn().mockResolvedValue({ networkFee: '0.1', systemFee: '0.05' }), calculateInvokeFee: jest.fn().mockResolvedValue({ networkFee: '0.1', systemFee: '0.05' }), claimGas: jest.fn().mockResolvedValue(mockTransferResult), getNetwork: jest.fn().mockReturnValue(NeoNetwork.MAINNET) } as any); const createMockContractService = (): jest.Mocked<ContractService> => ({ listSupportedContracts: jest.fn().mockReturnValue([ { name: 'NeoFS', description: 'Decentralized storage', available: true, operationCount: 5, network: NeoNetwork.MAINNET } ]), getContractOperations: jest.fn().mockReturnValue({ operations: { transfer: { name: 'transfer', description: 'Transfer tokens' } }, count: 1, contractName: 'NeoFS', network: NeoNetwork.MAINNET, available: true }), queryContract: jest.fn().mockResolvedValue({ state: 'HALT', stack: [{ value: '100' }] }), invokeContract: jest.fn().mockResolvedValue('0xabc123'), getNetwork: jest.fn().mockReturnValue(NeoNetwork.MAINNET) } as any); describe('Tool Handlers', () => { let mockNeoServices: Map<NeoNetwork, NeoService>; let mockContractServices: Map<NeoNetwork, ContractService>; beforeEach(() => { jest.clearAllMocks(); mockNeoServices = new Map(); mockNeoServices.set(NeoNetwork.MAINNET, createMockNeoService()); mockNeoServices.set(NeoNetwork.TESTNET, createMockNeoService()); mockContractServices = new Map(); mockContractServices.set(NeoNetwork.MAINNET, createMockContractService()); mockContractServices.set(NeoNetwork.TESTNET, createMockContractService()); }); describe('get_network_mode', () => { test('should return network mode successfully', async () => { const result = await callTool('get_network_mode', {}, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('networkMode'); }); }); describe('get_blockchain_info', () => { test('should get blockchain info for mainnet', async () => { const input = { network: 'mainnet' }; const result = await callTool('get_blockchain_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('height'); expect(result.result.height).toBe(12345); }); test('should get blockchain info for testnet', async () => { const input = { network: 'testnet' }; const result = await callTool('get_blockchain_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('height'); }); test('should handle missing network parameter', async () => { const result = await callTool('get_blockchain_info', {}, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('get_block_count', () => { test('should get block count successfully', async () => { const input = { network: 'mainnet' }; const result = await callTool('get_block_count', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('blockCount'); expect(result.result.blockCount).toBe(12345); }); }); describe('get_block', () => { test('should get block by height', async () => { const input = { network: 'mainnet', hashOrHeight: 12344 }; const result = await callTool('get_block', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('hash'); }); test('should get block by hash', async () => { const input = { network: 'mainnet', hashOrHeight: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }; const result = await callTool('get_block', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); }); }); describe('get_transaction', () => { test('should get transaction successfully', async () => { const input = { network: 'mainnet', txid: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }; const result = await callTool('get_transaction', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('hash'); }); test('should handle invalid transaction hash', async () => { const input = { network: 'mainnet', txid: 'invalid' }; const result = await callTool('get_transaction', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('get_balance', () => { test('should get balance successfully', async () => { const input = { network: 'mainnet', address: 'NMockAddress123' }; const result = await callTool('get_balance', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('address'); expect(result.result).toHaveProperty('balance'); expect(Array.isArray(result.result.balance)).toBe(true); }); test('should handle invalid address', async () => { const input = { network: 'mainnet', address: 'invalid' }; const result = await callTool('get_balance', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('transfer_assets', () => { test('should transfer assets successfully', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', toAddress: 'NRecipientAddress123', asset: 'NEO', amount: '1', confirm: true }; const result = await callTool('transfer_assets', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('txid'); }); test('should require confirmation', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', toAddress: 'NRecipientAddress123', asset: 'NEO', amount: '1', confirm: false }; const result = await callTool('transfer_assets', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); test('should handle invalid WIF', async () => { const input = { network: 'mainnet', fromWIF: 'invalid-wif', toAddress: 'NRecipientAddress123', asset: 'NEO', amount: '1', confirm: true }; const result = await callTool('transfer_assets', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('create_wallet', () => { test('should create wallet successfully', async () => { const input = { password: 'password123' }; const result = await callTool('create_wallet', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('address'); expect(result.result).toHaveProperty('encryptedWIF'); }); test('should handle invalid password', async () => { const input = { password: '123' }; // too short const result = await callTool('create_wallet', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('import_wallet', () => { test('should import wallet with password', async () => { const input = { key: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', password: 'password123' }; const result = await callTool('import_wallet', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('address'); expect(result.result).toHaveProperty('encryptedWIF'); }); test('should import wallet without password', async () => { const input = { key: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k' }; const result = await callTool('import_wallet', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('address'); expect(result.result).toHaveProperty('WIF'); }); }); describe('invoke_contract', () => { test('should invoke read-only contract', async () => { const input = { network: 'mainnet', scriptHash: '0x1234567890abcdef1234567890abcdef12345678', operation: 'balanceOf', args: [] }; const result = await callTool('invoke_contract', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); }); test('should invoke write contract with WIF', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', scriptHash: '0x1234567890abcdef1234567890abcdef12345678', operation: 'transfer', args: [], confirm: true }; const result = await callTool('invoke_contract', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); }); test('should require confirmation for write operations', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', scriptHash: '0x1234567890abcdef1234567890abcdef12345678', operation: 'transfer', args: [], confirm: false }; const result = await callTool('invoke_contract', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('list_famous_contracts', () => { test('should list contracts successfully', async () => { const input = { network: 'mainnet' }; const result = await callTool('list_famous_contracts', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('contracts'); expect(Array.isArray(result.contracts)).toBe(true); }); }); describe('get_contract_info', () => { test('should get contract info successfully', async () => { const input = { network: 'mainnet', contractName: 'NeoFS' }; const result = await callTool('get_contract_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('contractInfo'); expect(result.contractInfo).toHaveProperty('operations'); expect(result.contractInfo).toHaveProperty('contractName'); }); test('should handle invalid contract name', async () => { const input = { network: 'mainnet', contractName: 'NonExistent' }; const result = await callTool('get_contract_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('estimate_transfer_fees', () => { test('should estimate transfer fees successfully', async () => { const input = { network: 'mainnet', fromAddress: 'NSenderAddress123', toAddress: 'NRecipientAddress123', asset: 'NEO', amount: '1' }; const result = await callTool('estimate_transfer_fees', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('networkFee'); expect(result.result).toHaveProperty('systemFee'); }); }); describe('estimate_invoke_fees', () => { test('should estimate invoke fees successfully', async () => { const input = { network: 'mainnet', signerAddress: 'NSenderAddress123', scriptHash: '0x1234567890abcdef1234567890abcdef12345678', operation: 'transfer', args: [] }; const result = await callTool('estimate_invoke_fees', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('networkFee'); expect(result.result).toHaveProperty('systemFee'); }); test('should require signer address', async () => { const input = { network: 'mainnet', scriptHash: '0x1234567890abcdef1234567890abcdef12345678', operation: 'transfer', args: [] }; const result = await callTool('estimate_invoke_fees', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('claim_gas', () => { test('should claim GAS successfully', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', confirm: true }; const result = await callTool('claim_gas', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('result'); expect(result.result).toHaveProperty('txid'); }); test('should require confirmation', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', confirm: false }; const result = await callTool('claim_gas', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('error handling', () => { test('should handle unknown tool', async () => { const result = await callTool('unknown_tool', {}, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); test('should handle invalid network', async () => { const input = { network: 'invalid' }; const result = await callTool('get_blockchain_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); test('should handle service errors', async () => { const mockService = mockNeoServices.get(NeoNetwork.MAINNET)!; mockService.getBlockchainInfo = jest.fn().mockRejectedValue(new Error('Service error')); const input = { network: 'mainnet' }; const result = await callTool('get_blockchain_info', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); describe('validation', () => { test('should validate addresses', async () => { const input = { network: 'mainnet', address: 'invalid-address' }; const result = await callTool('get_balance', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); test('should validate amounts', async () => { const input = { network: 'mainnet', fromWIF: 'L5yLSKvNBzC9M6XECV6eaTVX5dLKzGCY8wV9wXw8LkUuMbhJE21k', toAddress: 'NRecipientAddress123', asset: 'NEO', amount: '0', // invalid amount confirm: true }; const result = await callTool('transfer_assets', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); test('should validate script hashes', async () => { const input = { network: 'mainnet', scriptHash: 'invalid-hash', operation: 'transfer', args: [] }; const result = await callTool('invoke_contract', input, mockNeoServices, mockContractServices); expect(result).toHaveProperty('error'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/r3e-network/neo-n3-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server