diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..33b3f0b --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Etherscan API Configuration +# Get your API key from: https://etherscan.io/apis +ETHERSCAN_API_KEY=your_api_key_here \ No newline at end of file diff --git a/README.md b/README.md index 44de6c9..e7ffdec 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A complete Python implementation of the Etherscan Model Context Protocol (MCP) s ## Overview -This server provides 53+ tools for accessing Ethereum blockchain data, including: +This server provides 56 comprehensive tools for accessing Ethereum blockchain data, including: - **Account Tools**: Balance checking, transaction history, internal transactions - **Block Tools**: Block data, rewards, timing information @@ -40,6 +40,12 @@ Set your Etherscan API key as an environment variable: export ETHERSCAN_API_KEY="your_api_key_here" ``` +Or create a `.env` file: +```bash +cp .env.example .env +# Edit .env and add your API key +``` + ## Usage ### Standalone Server @@ -58,6 +64,22 @@ etherscan_mcp = MCPTools( ) ``` +## Testing Tools + +To test all tools and generate recommendations for Agent development: + +```bash +# Setup +cp .env.example .env +# Add your API key to .env file + +# Run comprehensive test (tests all 56 tools) +python test_all_tools.py +``` + +This will test all 56 tools and generate: +- **tool_test_results.json**: Raw test results with performance metrics + ## Complete Tool Reference ### ๐Ÿฆ Account Tools (12 tools) @@ -151,6 +173,123 @@ etherscan_mcp = MCPTools( | `proxy_eth_gasPrice` | Get current gas price | `chainid` | | `proxy_eth_estimateGas` | Estimate gas for transaction | `data`, `to`, `value`, `gas`, `gasPrice` | +## ๐Ÿค– Essential Tools for Building Agents + +*Based on comprehensive testing of all 55 tools using test_all_tools.py* + +### Quick Reference for Agent Development + +| Category | ๐ŸŸข Essential | ๐ŸŸก Situational | ๐Ÿ”ด Other Tools | Total | +|----------|-------------|----------------|-------------------|-------| +| **Account** | 11 tools | 1 tool | 0 tools | 12 tools | +| **Block** | 3 tools | 0 tools | 1 tool | 4 tools | +| **Contract** | 2 tools | 1 tool | 1 tool | 4 tools | +| **Transaction** | 2 tools | 0 tools | 0 tools | 2 tools | +| **Token** | 2 tools | 0 tools | 0 tools | 2 tools | +| **Gas** | 2 tools | 0 tools | 1 tool | 3 tools | +| **Statistics** | 3 tools | 0 tools | 9 tools | 12 tools | +| **Logs** | 0 tools | 3 tools | 0 tools | 3 tools | +| **RPC** | 12 tools | 0 tools | 1 tool | 13 tools | +| **TOTAL** | **37 tools** | **5 tools** | **13 tools** | **55 tools** | + +#### Complete Essential Tools List + +**Account Tools (11/12)** +- โœ… `account_balance` - Single ETH balance +- โœ… `account_balancemulti` - Multiple ETH balances +- โœ… `account_fundedby` - Funding source analysis +- โœ… `account_getminedblocks` - Blocks mined by address +- โœ… `account_token1155tx` - ERC-1155 token transfers +- โœ… `account_tokennfttx` - NFT transfers +- โœ… `account_txlist` - Normal transactions +- โœ… `account_txlistinternal` - Internal transactions +- โœ… `account_txlistinternal_byblock` - Internal tx by block range +- โœ… `account_txlistinternal_byhash` - Internal tx by hash +- โœ… `account_txsBeaconWithdrawal` - Beacon chain withdrawals + +**Block Tools (4/4)** +- โœ… `block_getblocknobytime` - Block number by timestamp +- โœ… `block_getblockreward` - Block mining rewards +- โœ… `block_getblocktxnscount` - Transaction count in block +- โœ… `block_getblockcountdown` - Block countdown + +**Contract Tools (3/4)** +- โœ… `contract_getcontractcreation` - Contract creation info +- โœ… `contract_getsourcecode` - Verified source code +- โœ… `contract_checkverifystatus` - Contract verification status + +**Transaction Tools (2/2)** +- โœ… `transaction_getstatus` - Execution status +- โœ… `transaction_gettxreceiptstatus` - Receipt status + +**Token Tools (2/2)** +- โœ… `account_tokenbalance` - ERC-20 token balance +- โœ… `stats_tokensupply` - Total token supply + +**Gas Tools (2/3)** +- โœ… `gas_gasestimate` - Gas time estimates +- โœ… `gas_gasoracle` - Current gas prices + +**Statistics Tools (3/12)** +- โœ… `stats_chainsize` - Blockchain database size +- โœ… `stats_ethprice` - Current ETH price +- โœ… `stats_nodecount` - Network node count + +**RPC Tools (12/13)** +- โœ… `proxy_eth_blockNumber` - Latest block number +- โœ… `proxy_eth_call` - Contract function call +- โœ… `proxy_eth_estimateGas` - Gas estimation +- โœ… `proxy_eth_gasPrice` - Current gas price +- โœ… `proxy_eth_getBlockByNumber` - Block details +- โœ… `proxy_eth_getBlockTransactionCountByNumber` - Block tx count +- โœ… `proxy_eth_getStorageAt` - Contract storage slot +- โœ… `proxy_eth_getTransactionByBlockNumberAndIndex` - Transaction by index +- โœ… `proxy_eth_getTransactionByHash` - Transaction details +- โœ… `proxy_eth_getTransactionCount` - Address nonce +- โœ… `proxy_eth_getTransactionReceipt` - Transaction receipt +- โœ… `proxy_eth_getUncleByBlockNumberAndIndex` - Uncle block data + +### ๐ŸŸก Situational Tools (5 tools) +*Larger outputs, slower responses, or specific use cases - use carefully* + +**Large Output Tools** +- โš ๏ธ `account_tokentx` - ERC-20 token transfers (use pagination for large datasets) + +**Contract Analysis Tools** +- โš ๏ธ `contract_getabi` - Contract ABI (can be very large for complex contracts) + +**Event Log Analysis Tools** +- โš ๏ธ `logs_getLogsByAddress` - Event logs by address +- โš ๏ธ `logs_getLogsByTopics` - Event logs by topics +- โš ๏ธ `logs_getLogsByAddressAndTopics` - Combined filtering + +**Usage Tips:** +- Use small block ranges for logs tools (1000 blocks max) +- Implement pagination for transaction lists +- Cache results when possible +- Monitor response times and output sizes + +### ๐Ÿ”ด Other Tools (11 tools) +*These tools failed during testing or require Pro accounts* + +**Pro Account Required** +Most daily statistics tools require Etherscan Pro accounts: +- โŒ `stats_dailyavghashrate` - Daily average hashrate +- โŒ `stats_dailyavgnetdifficulty` - Daily mining difficulty +- โŒ `stats_dailynetutilization` - Daily network utilization +- โŒ `stats_dailynewaddress` - Daily new addresses +- โŒ `stats_dailytx` - Daily transaction count +- โŒ `stats_dailytxnfee` - Daily transaction fees +- โŒ `stats_ethdailyprice` - Historical ETH prices +- โŒ `stats_ethsupply` - ETH supply data +- โŒ `stats_ethsupply2` - ETH supply data v2 +- โŒ `stats_dailyavggaslimit` - Daily average gas limit + +**Large Output Tools** +- โŒ `proxy_eth_getCode` - Contract bytecode (can be extremely large) + +**Recommendation**: Use essential tools for reliable agent performance. Pro tools may work with upgraded Etherscan accounts. + ## ๐ŸŽฏ Use Cases & Examples ### Basic Balance Check diff --git a/test_all_tools.py b/test_all_tools.py new file mode 100644 index 0000000..0994283 --- /dev/null +++ b/test_all_tools.py @@ -0,0 +1,919 @@ +#!/usr/bin/env python3 +""" +Comprehensive test script for all Etherscan MCP tools. +Tests all 56 tools to determine which are essential for building Agents. + +Setup: +1. Copy .env.example to .env +2. Add your Etherscan API key to .env file +3. Run: python test_all_tools.py +""" + +import os +import sys +import json +import time +from datetime import datetime, timedelta +import traceback +from pathlib import Path + +# Load environment variables from .env file +from dotenv import load_dotenv + +# Load .env file if it exists +env_file = Path(__file__).parent / '.env' +if env_file.exists(): + load_dotenv(env_file) + print(f"โœ… Loaded environment variables from {env_file}") +else: + print(f"โš ๏ธ No .env file found at {env_file}") + + +# Check if API key is set +if not os.getenv("ETHERSCAN_API_KEY") or os.getenv("ETHERSCAN_API_KEY") == "your_api_key_here": + print("โŒ ETHERSCAN_API_KEY not set in .env file") + print(" Please add your API key to the .env file:") + print(" ETHERSCAN_API_KEY=your_actual_api_key_here") + sys.exit(1) + +# Add the src directory to Python path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +# Import the utils API call function directly +from tools.utils import api_call + +class ToolTester: + def __init__(self): + self.results = {} + self.test_data = self._get_test_data() + + def _get_test_data(self): + """Get test data for different scenarios.""" + return { + # Well-known addresses + 'vitalik_address': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + 'usdt_contract': '0xdac17f958d2ee523a2206206994597c13d831ec7', + 'uniswap_router': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', + + # Recent block numbers (approximate) + 'recent_block': '19000000', + 'older_block': '18000000', + + # Known transaction hashes + 'known_tx': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'recent_tx': '0xa1e8d6d9d1f1b1c1e1f1a1b1c1d1e1f1a1b1c1d1e1f1a1b1c1d1e1f1a1b1c1d1', + + # Time ranges + 'yesterday': (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'week_ago': (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d'), + 'today': datetime.now().strftime('%Y-%m-%d'), + } + + def test_tool(self, tool_name, api_params): + """Test a single tool by making direct API calls.""" + try: + start_time = time.time() + result = api_call(api_params) + end_time = time.time() + + response_time = end_time - start_time + result_str = str(result) + output_length = len(result_str) + + # Truncate very long outputs for analysis + sample_output = result_str[:1000] + "..." if output_length > 1000 else result_str + + return { + 'success': True, + 'output_length': output_length, + 'response_time': round(response_time, 2), + 'sample_output': sample_output, + 'full_output': result_str if output_length < 5000 else "TRUNCATED_DUE_TO_LENGTH", + 'error': None, + 'requires_pro': self._check_pro_requirement(result_str), + 'api_params': api_params + } + + except Exception as e: + error_str = str(e) + traceback_str = traceback.format_exc() + + return { + 'success': False, + 'output_length': 0, + 'response_time': 0, + 'sample_output': None, + 'full_output': None, + 'error': error_str, + 'traceback': traceback_str, + 'requires_pro': self._check_pro_requirement(error_str), + 'api_params': api_params + } + + def _check_pro_requirement(self, text): + """Check if the response indicates a Pro account requirement.""" + pro_indicators = [ + 'Max rate limit reached', + 'upgrade to premium', + 'rate limit exceeded', + 'premium account', + 'pro api' + ] + return any(indicator.lower() in text.lower() for indicator in pro_indicators) + + def run_all_tests(self): + """Run tests for all tool categories.""" + + print("๐Ÿงช Starting comprehensive tool testing...") + if os.environ.get('ETHERSCAN_API_KEY'): + print("Using ETHERSCAN_API_KEY from environment.") + print("=" * 80) + + # Test each category + self._test_account_tools() + self._test_block_tools() + self._test_contract_tools() + self._test_transaction_tools() + self._test_token_tools() + self._test_gas_tools() + self._test_stats_tools() + self._test_logs_tools() + self._test_rpc_tools() + + return self.results + + def _test_account_tools(self): + """Test account-related tools.""" + print("\n๐Ÿฆ Testing Account Tools...") + + account_tests = [ + { + 'name': 'account_balance', + 'params': { + 'module': 'account', + 'action': 'balance', + 'address': self.test_data['vitalik_address'], + 'chainid': '1' + } + }, + { + 'name': 'account_balancemulti', + 'params': { + 'module': 'account', + 'action': 'balancemulti', + 'address': f"{self.test_data['vitalik_address']},{self.test_data['usdt_contract']}", + 'chainid': '1' + } + }, + { + 'name': 'account_txlist', + 'params': { + 'module': 'account', + 'action': 'txlist', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_txlistinternal', + 'params': { + 'module': 'account', + 'action': 'txlistinternal', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_txlistinternal_byhash', + 'params': { + 'module': 'account', + 'action': 'txlistinternal', + 'txhash': self.test_data['known_tx'], + 'chainid': '1' + } + }, + { + 'name': 'account_txlistinternal_byblock', + 'params': { + 'module': 'account', + 'action': 'txlistinternal', + 'startblock': self.test_data['older_block'], + 'endblock': self.test_data['recent_block'], + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_tokentx', + 'params': { + 'module': 'account', + 'action': 'tokentx', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_tokennfttx', + 'params': { + 'module': 'account', + 'action': 'tokennfttx', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_token1155tx', + 'params': { + 'module': 'account', + 'action': 'token1155tx', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '10', + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'account_fundedby', + 'params': { + 'module': 'account', + 'action': 'fundedby', + 'address': self.test_data['vitalik_address'], + 'chainid': '1' + } + }, + { + 'name': 'account_getminedblocks', + 'params': { + 'module': 'account', + 'action': 'getminedblocks', + 'address': self.test_data['vitalik_address'], + 'blocktype': 'blocks', + 'page': '1', + 'offset': '10', + 'chainid': '1' + } + }, + { + 'name': 'account_txsBeaconWithdrawal', + 'params': { + 'module': 'account', + 'action': 'txsBeaconWithdrawal', + 'address': self.test_data['vitalik_address'], + 'startblock': '0', + 'endblock': '99999999', + 'page': '1', + 'offset': '100', + 'sort': 'asc', + 'chainid': '1' + } + } + ] + + for test in account_tests: + self._run_single_test('Account', test) + + def _test_block_tools(self): + """Test block-related tools.""" + print("\n๐Ÿงฑ Testing Block Tools...") + + block_tests = [ + { + 'name': 'block_getblockreward', + 'params': { + 'module': 'block', + 'action': 'getblockreward', + 'blockno': self.test_data['recent_block'], + 'chainid': '1' + } + }, + { + 'name': 'block_getblockcountdown', + 'params': { + 'module': 'block', + 'action': 'getblockcountdown', + 'blockno': str(int(self.test_data['recent_block']) + 1000), + 'chainid': '1' + } + }, + { + 'name': 'block_getblocknobytime', + 'params': { + 'module': 'block', + 'action': 'getblocknobytime', + 'timestamp': str(int(time.time()) - 86400), # 1 day ago + 'closest': 'before', + 'chainid': '1' + } + }, + { + 'name': 'block_getblocktxnscount', + 'params': { + 'module': 'proxy', + 'action': 'eth_getBlockTransactionCountByNumber', + 'tag': hex(int(self.test_data['recent_block'])), + 'chainid': '1' + } + } + ] + + for test in block_tests: + self._run_single_test('Block', test) + + def _test_contract_tools(self): + """Test contract-related tools.""" + print("\n๐Ÿ“„ Testing Contract Tools...") + + contract_tests = [ + { + 'name': 'contract_getabi', + 'params': { + 'module': 'contract', + 'action': 'getabi', + 'address': self.test_data['usdt_contract'], + 'chainid': '1' + } + }, + { + 'name': 'contract_getsourcecode', + 'params': { + 'module': 'contract', + 'action': 'getsourcecode', + 'address': self.test_data['usdt_contract'], + 'chainid': '1' + } + }, + { + 'name': 'contract_getcontractcreation', + 'params': { + 'module': 'contract', + 'action': 'getcontractcreation', + 'contractaddresses': self.test_data['usdt_contract'], + 'chainid': '1' + } + }, + { + 'name': 'contract_checkverifystatus', + 'params': { + 'module': 'contract', + 'action': 'checkverifystatus', + 'guid': 'test-guid-placeholder', # This will likely fail as it needs a real GUID + 'chainid': '1' + } + } + ] + + for test in contract_tests: + self._run_single_test('Contract', test) + + def _test_transaction_tools(self): + """Test transaction-related tools.""" + print("\n๐Ÿ”„ Testing Transaction Tools...") + + tx_tests = [ + { + 'name': 'transaction_getstatus', + 'params': { + 'module': 'transaction', + 'action': 'getstatus', + 'txhash': self.test_data['known_tx'], + 'chainid': '1' + } + }, + { + 'name': 'transaction_gettxreceiptstatus', + 'params': { + 'module': 'transaction', + 'action': 'gettxreceiptstatus', + 'txhash': self.test_data['known_tx'], + 'chainid': '1' + } + } + ] + + for test in tx_tests: + self._run_single_test('Transaction', test) + + def _test_token_tools(self): + """Test token-related tools.""" + print("\n๐Ÿช™ Testing Token Tools...") + + token_tests = [ + { + 'name': 'stats_tokensupply', + 'params': { + 'module': 'stats', + 'action': 'tokensupply', + 'contractaddress': self.test_data['usdt_contract'], + 'chainid': '1' + } + }, + { + 'name': 'account_tokenbalance', + 'params': { + 'module': 'account', + 'action': 'tokenbalance', + 'contractaddress': self.test_data['usdt_contract'], + 'address': self.test_data['vitalik_address'], + 'chainid': '1' + } + } + ] + + for test in token_tests: + self._run_single_test('Token', test) + + def _test_gas_tools(self): + """Test gas-related tools.""" + print("\nโ›ฝ Testing Gas Tools...") + + gas_tests = [ + { + 'name': 'gas_gasoracle', + 'params': { + 'module': 'gastracker', + 'action': 'gasoracle', + 'chainid': '1' + } + }, + { + 'name': 'gas_gasestimate', + 'params': { + 'module': 'gastracker', + 'action': 'gasestimate', + 'gasprice': '20000000000', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailyavggaslimit', + 'params': { + 'module': 'stats', + 'action': 'dailyavggaslimit', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + } + ] + + for test in gas_tests: + self._run_single_test('Gas', test) + + def _test_stats_tools(self): + """Test statistics-related tools.""" + print("\n๐Ÿ“Š Testing Statistics Tools...") + + stats_tests = [ + { + 'name': 'stats_ethsupply', + 'params': { + 'module': 'stats', + 'action': 'ethsupply', + 'chainid': '1' + } + }, + { + 'name': 'stats_ethsupply2', + 'params': { + 'module': 'stats', + 'action': 'ethsupply2', + 'chainid': '1' + } + }, + { + 'name': 'stats_ethprice', + 'params': { + 'module': 'stats', + 'action': 'ethprice', + 'chainid': '1' + } + }, + { + 'name': 'stats_chainsize', + 'params': { + 'module': 'stats', + 'action': 'chainsize', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'clienttype': 'geth', + 'syncmode': 'default', + 'chainid': '1' + } + }, + { + 'name': 'stats_nodecount', + 'params': { + 'module': 'stats', + 'action': 'nodecount', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailytxnfee', + 'params': { + 'module': 'stats', + 'action': 'dailytxnfee', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailynewaddress', + 'params': { + 'module': 'stats', + 'action': 'dailynewaddress', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailynetutilization', + 'params': { + 'module': 'stats', + 'action': 'dailynetutilization', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailyavghashrate', + 'params': { + 'module': 'stats', + 'action': 'dailyavghashrate', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailytx', + 'params': { + 'module': 'stats', + 'action': 'dailytx', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_dailyavgnetdifficulty', + 'params': { + 'module': 'stats', + 'action': 'dailyavgnetdifficulty', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + }, + { + 'name': 'stats_ethdailyprice', + 'params': { + 'module': 'stats', + 'action': 'ethdailyprice', + 'startdate': self.test_data['week_ago'], + 'enddate': self.test_data['yesterday'], + 'sort': 'asc', + 'chainid': '1' + } + } + ] + + for test in stats_tests: + self._run_single_test('Statistics', test) + + def _test_logs_tools(self): + """Test logs-related tools.""" + print("\n๐Ÿ“ Testing Logs Tools...") + + logs_tests = [ + { + 'name': 'logs_getLogsByAddress', + 'params': { + 'module': 'logs', + 'action': 'getLogs', + 'address': self.test_data['usdt_contract'], + 'fromBlock': self.test_data['older_block'], + 'toBlock': self.test_data['recent_block'], + 'page': '1', + 'offset': '10', + 'chainid': '1' + } + }, + { + 'name': 'logs_getLogsByTopics', + 'params': { + 'module': 'logs', + 'action': 'getLogs', + 'fromBlock': self.test_data['older_block'], + 'toBlock': self.test_data['recent_block'], + 'topic0': '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', # Transfer event + 'page': '1', + 'offset': '10', + 'chainid': '1' + } + }, + { + 'name': 'logs_getLogsByAddressAndTopics', + 'params': { + 'module': 'logs', + 'action': 'getLogs', + 'address': self.test_data['usdt_contract'], + 'fromBlock': self.test_data['older_block'], + 'toBlock': self.test_data['recent_block'], + 'topic0': '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', # Transfer event + 'page': '1', + 'offset': '10', + 'chainid': '1' + } + } + ] + + for test in logs_tests: + self._run_single_test('Logs', test) + + def _test_rpc_tools(self): + """Test RPC proxy tools.""" + print("\n๐Ÿ”— Testing RPC Tools...") + + rpc_tests = [ + { + 'name': 'proxy_eth_blockNumber', + 'params': { + 'module': 'proxy', + 'action': 'eth_blockNumber', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_gasPrice', + 'params': { + 'module': 'proxy', + 'action': 'eth_gasPrice', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getTransactionByHash', + 'params': { + 'module': 'proxy', + 'action': 'eth_getTransactionByHash', + 'txhash': self.test_data['known_tx'], + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getBlockByNumber', + 'params': { + 'module': 'proxy', + 'action': 'eth_getBlockByNumber', + 'tag': hex(int(self.test_data['recent_block'])), + 'boolean': 'true', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getTransactionCount', + 'params': { + 'module': 'proxy', + 'action': 'eth_getTransactionCount', + 'address': self.test_data['vitalik_address'], + 'tag': 'latest', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getUncleByBlockNumberAndIndex', + 'params': { + 'module': 'proxy', + 'action': 'eth_getUncleByBlockNumberAndIndex', + 'tag': self.test_data['recent_block'], + 'index': '0x0', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getBlockTransactionCountByNumber', + 'params': { + 'module': 'proxy', + 'action': 'eth_getBlockTransactionCountByNumber', + 'tag': self.test_data['recent_block'], + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getTransactionByBlockNumberAndIndex', + 'params': { + 'module': 'proxy', + 'action': 'eth_getTransactionByBlockNumberAndIndex', + 'tag': self.test_data['recent_block'], + 'index': '0x0', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getTransactionReceipt', + 'params': { + 'module': 'proxy', + 'action': 'eth_getTransactionReceipt', + 'txhash': self.test_data['known_tx'], + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_call', + 'params': { + 'module': 'proxy', + 'action': 'eth_call', + 'to': self.test_data['usdt_contract'], + 'data': '0x18160ddd', # totalSupply() function signature + 'tag': 'latest', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getCode', + 'params': { + 'module': 'proxy', + 'action': 'eth_getCode', + 'address': self.test_data['usdt_contract'], + 'tag': 'latest', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_getStorageAt', + 'params': { + 'module': 'proxy', + 'action': 'eth_getStorageAt', + 'address': self.test_data['usdt_contract'], + 'position': '0x0', + 'tag': 'latest', + 'chainid': '1' + } + }, + { + 'name': 'proxy_eth_estimateGas', + 'params': { + 'module': 'proxy', + 'action': 'eth_estimateGas', + 'data': '0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045000000000000000000000000000000000000000000000000000000000000000a', + 'to': self.test_data['usdt_contract'], + 'from': self.test_data['vitalik_address'], + 'value': '0x0', + 'gasPrice': '0x9184e72a000', + 'gas': '0x76c0', + 'chainid': '1' + } + ] + + for test in rpc_tests: + self._run_single_test('RPC', test) + + def _run_single_test(self, category, test_config): + """Run a single test and record results.""" + tool_name = test_config['name'] + params = test_config['params'] + + print(f" Testing {tool_name}...") + + # Make actual API call + result = self.test_tool(tool_name, params) + + if category not in self.results: + self.results[category] = {} + + self.results[category][tool_name] = { + 'test_params': params, + 'result': result, + 'recommendation': self._categorize_tool(result) + } + + # Print quick result + status = "โœ…" if result['success'] else "โŒ" + print(f" {status} {tool_name} - {result.get('output_length', 0)} chars, {result.get('response_time', 0)}s") + + def _categorize_tool(self, result): + """Categorize tool based on test results.""" + if not result['success']: + if result['requires_pro']: + return '๐Ÿ”ด Not Recommended - Requires Pro Account' + else: + return '๐Ÿ”ด Not Recommended - API Error' + + if result['output_length'] > 10000: + return '๐Ÿ”ด Not Recommended - Output Too Large' + elif result['output_length'] > 5000: + return '๐ŸŸก Situational - Large Output' + elif result['response_time'] > 5.0: + return '๐ŸŸก Situational - Slow Response' + else: + return '๐ŸŸข Essential - Good for Agents' + + def generate_report(self): + """Generate a comprehensive report of all test results.""" + report = { + 'timestamp': datetime.now().isoformat(), + 'total_tools_tested': sum(len(category) for category in self.results.values()), + 'summary': {}, + 'detailed_results': self.results, + 'recommendations': { + 'essential': [], + 'situational': [], + 'not_recommended': [] + } + } + + # Categorize all tools + for category, tools in self.results.items(): + for tool_name, tool_data in tools.items(): + recommendation = tool_data['recommendation'] + tool_info = { + 'name': tool_name, + 'category': category, + 'output_length': tool_data['result']['output_length'], + 'response_time': tool_data['result']['response_time'] + } + + if '๐ŸŸข' in recommendation: + report['recommendations']['essential'].append(tool_info) + elif '๐ŸŸก' in recommendation: + report['recommendations']['situational'].append(tool_info) + else: + report['recommendations']['not_recommended'].append(tool_info) + + # Generate summary + report['summary'] = { + 'essential_count': len(report['recommendations']['essential']), + 'situational_count': len(report['recommendations']['situational']), + 'not_recommended_count': len(report['recommendations']['not_recommended']) + } + + return report + +def main(): + """Main function to run all tests.""" + print("๐Ÿš€ Etherscan MCP Tool Testing Suite") + print("=" * 50) + + tester = ToolTester() + + # Run all tests + results = tester.run_all_tests() + + # Generate report + report = tester.generate_report() + + # Save results to file + with open('tool_test_results.json', 'w') as f: + json.dump(report, f, indent=2) + + # Print summary + print("\n" + "=" * 80) + print("๐Ÿ“Š TEST SUMMARY") + print("=" * 80) + print(f"Total Tools Tested: {report['total_tools_tested']}") + print(f"๐ŸŸข Essential Tools: {report['summary']['essential_count']}") + print(f"๐ŸŸก Situational Tools: {report['summary']['situational_count']}") + print(f"๐Ÿ”ด Not Recommended: {report['summary']['not_recommended_count']}") + + print(f"\n๐Ÿ“ Detailed results saved to: tool_test_results.json") + + return report + +if __name__ == "__main__": + main() \ No newline at end of file