|
| 1 | +#!/usr/bin/env node |
| 2 | +// Test all MCP servers to ensure they can be launched via npx and list tools |
| 3 | + |
| 4 | +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; |
| 5 | +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; |
| 6 | +import { readFileSync } from 'fs'; |
| 7 | +import { join, dirname } from 'path'; |
| 8 | +import { fileURLToPath } from 'url'; |
| 9 | + |
| 10 | +const __filename = fileURLToPath(import.meta.url); |
| 11 | +const __dirname = dirname(__filename); |
| 12 | + |
| 13 | +// Load mcps.json configuration |
| 14 | +const mcpsConfigPath = join(__dirname, '../../mcps.json'); |
| 15 | +const mcpsConfig = JSON.parse(readFileSync(mcpsConfigPath, 'utf-8')); |
| 16 | + |
| 17 | +// Simple queries for each MCP (read-only operations that don't require private keys) |
| 18 | +const testQueries = { |
| 19 | + argent: { |
| 20 | + tool: 'create_new_argent_account', |
| 21 | + description: 'Create Argent account', |
| 22 | + }, |
| 23 | + erc20: { |
| 24 | + tool: 'erc20_get_balance', |
| 25 | + description: 'Get ERC20 balance', |
| 26 | + args: { |
| 27 | + token_address: |
| 28 | + '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', |
| 29 | + account_address: '0x1', |
| 30 | + }, |
| 31 | + }, |
| 32 | + braavos: { |
| 33 | + tool: 'create_new_braavos_account', |
| 34 | + description: 'Create Braavos account', |
| 35 | + }, |
| 36 | + avnu: { |
| 37 | + tool: 'avnu_get_route', |
| 38 | + description: 'Get AVNU route', |
| 39 | + args: { |
| 40 | + sell_token_address: |
| 41 | + '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', |
| 42 | + buy_token_address: |
| 43 | + '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8', |
| 44 | + sell_amount: '1000000000000000000', |
| 45 | + }, |
| 46 | + }, |
| 47 | + erc721: { |
| 48 | + tool: 'erc721_get_balance', |
| 49 | + description: 'Get ERC721 balance', |
| 50 | + args: { contract_address: '0x1', owner: '0x1' }, |
| 51 | + }, |
| 52 | + transaction: { |
| 53 | + tool: 'simulate_transaction', |
| 54 | + description: 'Simulate transaction', |
| 55 | + }, |
| 56 | + artpeace: { tool: 'place_pixel', description: 'Place pixel' }, |
| 57 | + contract: { |
| 58 | + tool: 'get_constructor_params', |
| 59 | + description: 'Get constructor params', |
| 60 | + args: { contract_path: './test.cairo' }, |
| 61 | + }, |
| 62 | + fibrous: { |
| 63 | + tool: 'fibrous_get_route', |
| 64 | + description: 'Get Fibrous route', |
| 65 | + args: { |
| 66 | + token_in_address: |
| 67 | + '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', |
| 68 | + token_out_address: |
| 69 | + '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8', |
| 70 | + amount: '1000000000000000000', |
| 71 | + }, |
| 72 | + }, |
| 73 | + okx: { tool: 'create_new_okx_account', description: 'Create OKX account' }, |
| 74 | + openzeppelin: { |
| 75 | + tool: 'create_new_openzeppelin_account', |
| 76 | + description: 'Create OpenZeppelin account', |
| 77 | + }, |
| 78 | + opus: { |
| 79 | + tool: 'get_user_troves', |
| 80 | + description: 'Get user troves', |
| 81 | + args: { user_address: '0x1' }, |
| 82 | + }, |
| 83 | + 'starknet-rpc': { tool: 'get_chain_id', description: 'Get chain ID' }, |
| 84 | + scarb: { tool: 'install_scarb', description: 'Install Scarb' }, |
| 85 | + unruggable: { |
| 86 | + tool: 'is_memecoin', |
| 87 | + description: 'Check if memecoin', |
| 88 | + args: { token_address: '0x1' }, |
| 89 | + }, |
| 90 | + vesu: { tool: 'vesu_deposit_earn', description: 'Deposit on Vesu' }, |
| 91 | + ekubo: { |
| 92 | + tool: 'get_pool_info', |
| 93 | + description: 'Get pool info', |
| 94 | + args: { |
| 95 | + token0: |
| 96 | + '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', |
| 97 | + token1: |
| 98 | + '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8', |
| 99 | + fee: '170141183460469235273462165868118016', |
| 100 | + }, |
| 101 | + }, |
| 102 | + extended: { tool: 'extended_get_markets', description: 'Get markets' }, |
| 103 | + endurfi: { |
| 104 | + tool: 'get_total_staked', |
| 105 | + description: 'Get total staked', |
| 106 | + args: { pool: 'xstrk' }, |
| 107 | + }, |
| 108 | + 'cairo-coder': { |
| 109 | + tool: 'starknet_general_knowledge', |
| 110 | + description: 'Starknet knowledge', |
| 111 | + args: { question: 'What is Starknet?' }, |
| 112 | + }, |
| 113 | +}; |
| 114 | + |
| 115 | +async function testMCP(mcpName, mcpConfig) { |
| 116 | + console.log(`\n🧪 Testing ${mcpName}...`); |
| 117 | + |
| 118 | + const client = new Client({ |
| 119 | + name: 'test-client', |
| 120 | + version: '1.0.0', |
| 121 | + }); |
| 122 | + |
| 123 | + try { |
| 124 | + // Create transport using npx with -y flag |
| 125 | + const transport = new StdioClientTransport({ |
| 126 | + command: mcpConfig.client.command, |
| 127 | + args: mcpConfig.client.args, |
| 128 | + env: { |
| 129 | + ...process.env, |
| 130 | + // Set dummy values for required env vars to avoid errors during connection |
| 131 | + STARKNET_RPC_URL: |
| 132 | + process.env.STARKNET_RPC_URL || |
| 133 | + 'https://starknet-mainnet.public.blastapi.io', |
| 134 | + STARKNET_ACCOUNT_ADDRESS: process.env.STARKNET_ACCOUNT_ADDRESS || '0x1', |
| 135 | + STARKNET_PRIVATE_KEY: process.env.STARKNET_PRIVATE_KEY || '0x1', |
| 136 | + EXTENDED_API_KEY: process.env.EXTENDED_API_KEY || 'test', |
| 137 | + EXTENDED_API_URL: |
| 138 | + process.env.EXTENDED_API_URL || |
| 139 | + 'https://api.starknet.extended.exchange', |
| 140 | + EXTENDED_PRIVATE_KEY: process.env.EXTENDED_PRIVATE_KEY || '0x1', |
| 141 | + CAIRO_CODER_API_KEY: process.env.CAIRO_CODER_API_KEY || 'test', |
| 142 | + PATH_UPLOAD_DIR: process.env.PATH_UPLOAD_DIR || '/tmp', |
| 143 | + SECRET_PHRASE: process.env.SECRET_PHRASE || 'test', |
| 144 | + }, |
| 145 | + }); |
| 146 | + |
| 147 | + // Connect to the MCP server |
| 148 | + await client.connect(transport); |
| 149 | + console.log(` ✅ Connection established`); |
| 150 | + |
| 151 | + // List available tools |
| 152 | + const tools = await client.listTools(); |
| 153 | + console.log(` ✅ Tools available: ${tools.tools.length} tools`); |
| 154 | + |
| 155 | + // Show first few tool names |
| 156 | + const toolNames = tools.tools |
| 157 | + .slice(0, 3) |
| 158 | + .map((t) => t.name) |
| 159 | + .join(', '); |
| 160 | + console.log( |
| 161 | + ` 📋 Sample tools: ${toolNames}${tools.tools.length > 3 ? '...' : ''}` |
| 162 | + ); |
| 163 | + |
| 164 | + // Verify expected tool exists (if defined in testQueries) |
| 165 | + const testConfig = testQueries[mcpName]; |
| 166 | + if (testConfig && testConfig.tool) { |
| 167 | + const hasExpectedTool = tools.tools.some( |
| 168 | + (t) => t.name === testConfig.tool |
| 169 | + ); |
| 170 | + if (hasExpectedTool) { |
| 171 | + console.log(` ✅ Expected tool "${testConfig.tool}" found`); |
| 172 | + } else { |
| 173 | + console.log(` ⚠️ Expected tool "${testConfig.tool}" not found`); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + await client.close(); |
| 178 | + return { success: true, toolCount: tools.tools.length }; |
| 179 | + } catch (error) { |
| 180 | + console.log(` ❌ Error: ${error.message}`); |
| 181 | + try { |
| 182 | + await client.close(); |
| 183 | + } catch (closeError) { |
| 184 | + // Ignore close errors |
| 185 | + } |
| 186 | + return { success: false, error: error.message }; |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +async function main() { |
| 191 | + console.log('🚀 Testing all MCP servers via npx...\n'); |
| 192 | + console.log('This will download and test each MCP package from npm.\n'); |
| 193 | + |
| 194 | + const results = {}; |
| 195 | + const mcpNames = Object.keys(mcpsConfig); |
| 196 | + |
| 197 | + for (const mcpName of mcpNames) { |
| 198 | + const mcpConfig = mcpsConfig[mcpName]; |
| 199 | + const result = await testMCP(mcpName, mcpConfig); |
| 200 | + results[mcpName] = result; |
| 201 | + |
| 202 | + // Small delay between tests to avoid overwhelming npm |
| 203 | + await new Promise((resolve) => setTimeout(resolve, 1000)); |
| 204 | + } |
| 205 | + |
| 206 | + // Summary |
| 207 | + console.log('\n' + '='.repeat(60)); |
| 208 | + console.log('📊 Test Summary\n'); |
| 209 | + |
| 210 | + const successful = Object.entries(results).filter(([_, r]) => r.success); |
| 211 | + const failed = Object.entries(results).filter(([_, r]) => !r.success); |
| 212 | + |
| 213 | + console.log(`✅ Successful: ${successful.length}/${mcpNames.length}`); |
| 214 | + successful.forEach(([name, result]) => { |
| 215 | + console.log(` - ${name} (${result.toolCount} tools)`); |
| 216 | + }); |
| 217 | + |
| 218 | + if (failed.length > 0) { |
| 219 | + console.log(`\n❌ Failed: ${failed.length}/${mcpNames.length}`); |
| 220 | + failed.forEach(([name, result]) => { |
| 221 | + console.log(` - ${name}: ${result.error}`); |
| 222 | + }); |
| 223 | + } |
| 224 | + |
| 225 | + console.log('\n' + '='.repeat(60)); |
| 226 | + |
| 227 | + if (failed.length > 0) { |
| 228 | + console.log('\n⚠️ Some MCP servers failed to load!'); |
| 229 | + process.exit(1); |
| 230 | + } else { |
| 231 | + console.log('\n✅ All MCP servers are working correctly!'); |
| 232 | + process.exit(0); |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +main().catch((error) => { |
| 237 | + console.error('Fatal error:', error); |
| 238 | + process.exit(1); |
| 239 | +}); |
0 commit comments