Skip to main content
Glama
mcp-server.test.ts9.83 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { ListToolsRequestSchema, CallToolRequestSchema, type ListToolsRequest, type CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; // Mock ethers before importing our modules vi.mock('ethers', () => ({ ethers: { JsonRpcProvider: vi.fn(() => ({ getNetwork: vi.fn(() => Promise.resolve({ chainId: 8453 })) })), Wallet: vi.fn(() => ({ address: '0x1234567890123456789012345678901234567890' })), Contract: vi.fn(() => global.createMockContract()), Interface: vi.fn(() => ({ encodeFunctionData: vi.fn(() => '0x1234567890abcdef') })) } })); describe('MCP Server Integration', () => { let server: Server; let mockTransport: any; beforeEach(() => { // Reset all mocks vi.clearAllMocks(); // Create mock transport mockTransport = { start: vi.fn(), close: vi.fn(), send: vi.fn() }; // Set up test environment process.env.UNLOCK_ADDRESS = '0x1FF7e338d5E582138C46044dc238543Ce555C963'; process.env.INFURA_API_KEY = 'test-key'; process.env.PRIVATE_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001'; }); afterEach(async () => { if (server) { try { await server.close(); } catch (error) { // Ignore close errors in tests } } }); describe('Server Initialization', () => { it('should create server with correct capabilities', async () => { server = new Server( { name: 'unlock-stdio', version: '1.0.0' }, { capabilities: { tools: {} } } ); expect(server).toBeDefined(); }); it('should handle missing environment variables gracefully', () => { delete process.env.UNLOCK_ADDRESS; expect(() => { // This should throw when trying to import stdio module import('../../src/stdio.js'); }).not.toThrow(); // Module import itself shouldn't throw }); }); describe('ListTools Handler', () => { it('should return all 55 tools', async () => { const { UNLOCK_TOOLS } = await import('../../src/tools.js'); expect(UNLOCK_TOOLS).toHaveLength(55); // Verify tools structure UNLOCK_TOOLS.forEach(tool => { expect(tool).toHaveProperty('name'); expect(tool).toHaveProperty('description'); expect(tool).toHaveProperty('inputSchema'); }); }); it('should include essential function categories', async () => { const { UNLOCK_TOOLS } = await import('../../src/tools.js'); const toolNames = UNLOCK_TOOLS.map(tool => tool.name); // Check Unlock protocol functions expect(toolNames).toContain('createLock'); expect(toolNames).toContain('createUpgradeableLock'); expect(toolNames).toContain('upgradeLock'); expect(toolNames).toContain('unlockVersion'); expect(toolNames).toContain('governanceToken'); // Check PublicLock functions expect(toolNames).toContain('balanceOf'); expect(toolNames).toContain('purchase'); expect(toolNames).toContain('grantKeys'); expect(toolNames).toContain('getHasValidKey'); }); it('should have proper tool schemas', async () => { const { UNLOCK_TOOLS } = await import('../../src/tools.js'); const createLockTool = UNLOCK_TOOLS.find(tool => tool.name === 'createLock'); expect(createLockTool).toBeDefined(); expect(createLockTool!.inputSchema.required).toContain('chainId'); expect(createLockTool!.inputSchema.properties.chainId.enum).toEqual([8453, 84532]); }); }); describe('Function Validation', () => { it('should validate function arguments correctly', async () => { const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); // Test valid balanceOf call const validBalanceOfArgs = { chainId: 8453, _keyOwner: '0x1234567890123456789012345678901234567890' }; expect(() => FUNCTION_SCHEMAS.balanceOf.parse(validBalanceOfArgs)).not.toThrow(); // Test invalid balanceOf call const invalidBalanceOfArgs = { chainId: 1, // unsupported chain _keyOwner: 'invalid-address' }; expect(() => FUNCTION_SCHEMAS.balanceOf.parse(invalidBalanceOfArgs)).toThrow(); }); it('should validate complex function arguments', async () => { const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); // Test valid purchase call const validPurchaseArgs = { chainId: 8453, _values: ['1000000000000000000'], _recipients: ['0x1234567890123456789012345678901234567890'], _referrers: ['0x0000000000000000000000000000000000000000'], _keyManagers: ['0x1234567890123456789012345678901234567890'], _data: ['0x'] }; expect(() => FUNCTION_SCHEMAS.purchase.parse(validPurchaseArgs)).not.toThrow(); }); it('should validate Unlock protocol functions', async () => { const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); // Test createLock const validCreateLockArgs = { chainId: 8453, _lockCreator: '0x1234567890123456789012345678901234567890', _expirationDuration: '31536000', _tokenAddress: '0x0000000000000000000000000000000000000000', _keyPrice: '1000000000000000000', _maxNumberOfKeys: '100', _lockName: 'Test Lock' }; expect(() => FUNCTION_SCHEMAS.createLock.parse(validCreateLockArgs)).not.toThrow(); // Test unlockVersion const validVersionArgs = { chainId: 8453 }; expect(() => FUNCTION_SCHEMAS.unlockVersion.parse(validVersionArgs)).not.toThrow(); }); }); describe('Function Categorization', () => { it('should correctly categorize read and write functions', async () => { const { isReadFunction, isWriteFunction } = await import('../../src/tools.js'); // Test read functions expect(isReadFunction('balanceOf')).toBe(true); expect(isReadFunction('keyPrice')).toBe(true); expect(isReadFunction('unlockVersion')).toBe(true); expect(isReadFunction('governanceToken')).toBe(true); // Test write functions expect(isWriteFunction('purchase')).toBe(true); expect(isWriteFunction('createLock')).toBe(true); expect(isWriteFunction('upgradeLock')).toBe(true); expect(isWriteFunction('grantKeys')).toBe(true); // Test that functions aren't both expect(isReadFunction('purchase')).toBe(false); expect(isWriteFunction('balanceOf')).toBe(false); }); it('should handle unknown functions', async () => { const { isReadFunction, isWriteFunction } = await import('../../src/tools.js'); expect(isReadFunction('unknownFunction')).toBe(false); expect(isWriteFunction('unknownFunction')).toBe(false); }); }); describe('Contract Address Resolution', () => { it('should identify Unlock protocol functions correctly', () => { const unlockFunctions = [ 'createLock', 'createUpgradeableLock', 'upgradeLock', 'chainIdRead', 'unlockVersion', 'governanceToken', 'getGlobalTokenSymbol', 'publicLockLatestVersion' ]; unlockFunctions.forEach(funcName => { // These should be identified as Unlock protocol functions // In actual implementation, this would route to UNLOCK contract address expect(unlockFunctions.includes(funcName)).toBe(true); }); }); it('should handle function name mapping', () => { // Test that chainIdRead maps to chainId for contract calls const actualFunctionName = 'chainIdRead' === 'chainIdRead' ? 'chainId' : 'chainIdRead'; expect(actualFunctionName).toBe('chainId'); }); }); describe('Error Handling', () => { it('should handle validation errors gracefully', async () => { const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); const invalidArgs = { chainId: 'invalid', _keyOwner: 'not-an-address' }; expect(() => FUNCTION_SCHEMAS.balanceOf.parse(invalidArgs)).toThrow(); }); it('should handle missing required fields', async () => { const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); const incompleteArgs = { chainId: 8453 // missing _keyOwner }; expect(() => FUNCTION_SCHEMAS.balanceOf.parse(incompleteArgs)).toThrow(); }); }); describe('Tool Completeness', () => { it('should have schemas for all tools', async () => { const { UNLOCK_TOOLS } = await import('../../src/tools.js'); const { FUNCTION_SCHEMAS } = await import('../../src/schemas.js'); const toolNames = UNLOCK_TOOLS.map(tool => tool.name); const schemaNames = Object.keys(FUNCTION_SCHEMAS); // Most tools should have corresponding schemas const toolsWithSchemas = toolNames.filter(name => schemaNames.includes(name)); expect(toolsWithSchemas.length).toBeGreaterThan(40); // Allow for some tools without schemas }); it('should cover essential blockchain operations', async () => { const { UNLOCK_TOOLS } = await import('../../src/tools.js'); const toolNames = UNLOCK_TOOLS.map(tool => tool.name); // Essential operations should be covered const essentialOps = [ 'balanceOf', 'purchase', 'createLock', 'grantKeys', 'transferFrom', 'withdraw', 'updateKeyPricing' ]; essentialOps.forEach(op => { expect(toolNames).toContain(op); }); }); }); });

Latest Blog Posts

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/blahkheart/unlock-mcp'

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