Part 2 of the Journey: Discovering the Patterns - Learning Through Pain Previous: BigInt Testing Hell | Next: The MBPS v2.1 Standard
The Cross-Chain Tool Naming Registry: Universal Standards
How we made 17 blockchains speak the same language
Historical Context (November 2025): Developed September 2025 after manually building 17 servers revealed naming chaos. This work directly led to the MBPS v2.1 standard—pattern discovery through repetition.
Date: September 8, 2025 Author: Myron Koch & Claude Code Category: Standards & Architecture
The Tower of Babel
Before standardization, each blockchain server was its own universe:
// Ethereum server
'getBalance'
'check_balance'
'eth.getBalance'
'ethereum_balance'
// Solana server
'balance'
'getAccountBalance'
'sol_balance'
'solana.balance'
// Bitcoin server
'btc_get_balance'
'bitcoin_balance'
'get_wallet_balance'
Same operation. 12 different names. Total chaos.
The Naming Crisis
Real conversation we had:
User: "Check my balance on Ethereum and Solana"
Claude (using old servers):
- Ethereum: Calling 'eth.getBalance'
- Solana: Error - No tool 'sol.getBalance' found
- Found: 'getAccountBalance'
User: "Why doesn't it just work?"
Us: "Because we're idiots who didn't standardize."
The MBPS v2.1 Naming Convention
The solution: {prefix}_{action}_{resource}
The Prefix Registry
Every blockchain gets ONE canonical prefix:
// The Sacred Registry
export const BLOCKCHAIN_PREFIXES = {
// Mainnet prefixes
'ethereum': 'eth',
'bitcoin': 'btc',
'solana': 'sol',
'polygon': 'matic',
'avalanche': 'avax',
'binance-smart-chain': 'bsc',
'arbitrum': 'arb',
'optimism': 'op',
'base': 'base',
'near': 'near',
'sui': 'sui',
'aptos': 'apt',
'cosmos-hub': 'atom',
'osmosis': 'osmo',
'xrp-ledger': 'xrp',
'tron': 'tron',
'bnb-chain': 'bnb',
// Testnet suffixes
'ethereum-sepolia': 'eth', // Same prefix!
'solana-devnet': 'sol', // Same prefix!
'bitcoin-testnet': 'btc', // Same prefix!
// ... all testnets use mainnet prefix
};
Why same prefix for testnet/mainnet?
- User doesn’t care which network
- Claude shouldn’t care which network
- Only the server configuration cares
The Action Registry
Standard verbs for all blockchains:
export const STANDARD_ACTIONS = {
// Read operations
'get': 'Retrieve data',
'list': 'Get multiple items',
'search': 'Find by criteria',
'validate': 'Check validity',
'estimate': 'Calculate cost',
'simulate': 'Test without executing',
// Write operations
'send': 'Transfer native token',
'transfer': 'Move tokens/assets',
'create': 'Generate new entity',
'deploy': 'Deploy contract',
'mint': 'Create tokens/NFTs',
'burn': 'Destroy tokens',
// DeFi operations
'swap': 'Exchange tokens',
'add': 'Add to pool/stake',
'remove': 'Withdraw from pool',
'claim': 'Collect rewards',
'stake': 'Lock for staking',
'unstake': 'Unlock staked',
// Smart contract operations
'call': 'Execute contract function',
'approve': 'Grant permission',
'revoke': 'Remove permission',
};
The Resource Registry
Standard nouns for blockchain entities:
export const STANDARD_RESOURCES = {
// Core blockchain resources
'chain_info': 'Network information',
'balance': 'Native token balance',
'transaction': 'Transaction details',
'block': 'Block information',
'account': 'Account/address data',
// Wallet resources
'wallet': 'Wallet entity',
'address': 'Address validation/generation',
// Token resources
'token_info': 'Token metadata',
'token_balance': 'Token holdings',
'allowance': 'Token permissions',
// Contract resources
'contract': 'Smart contract',
'logs': 'Event logs',
// NFT resources
'nft': 'NFT metadata',
'nft_collection': 'NFT collection',
// DeFi resources
'pool_info': 'Liquidity pool data',
'rewards': 'Staking/farming rewards',
// Network resources
'gas_price': 'Transaction fees',
'network_info': 'Network status',
'validators': 'Validator information',
};
The Universal Tool Names
Result: Every blockchain has identical tool names for identical operations:
// All 17 servers have these exact tools:
'{prefix}_get_chain_info'
'{prefix}_get_balance'
'{prefix}_get_transaction'
'{prefix}_get_block'
'{prefix}_create_wallet'
'{prefix}_send_transaction'
'{prefix}_get_token_balance'
'{prefix}_transfer_token'
// ... 25 mandatory tools with identical patterns
Real Examples Across Chains
Getting Balance
// Ethereum
'eth_get_balance'
// Solana
'sol_get_balance'
// Bitcoin
'btc_get_balance'
// Cosmos Hub
'atom_get_balance'
// All follow same pattern!
Token Operations
// Ethereum ERC-20
'eth_transfer_token'
// Solana SPL
'sol_transfer_token'
// Polygon ERC-20
'matic_transfer_token'
// Same semantic meaning, same tool name pattern
DeFi Operations
// Ethereum Uniswap
'eth_swap_tokens'
// Solana Jupiter
'sol_swap_tokens'
// Osmosis DEX
'osmo_swap_tokens'
// Universal operation = universal name
The Naming Enforcement
We built validators to prevent deviation:
// scripts/validate-tool-names.ts
function validateToolName(toolName: string, serverPrefix: string): ValidationResult {
const parts = toolName.split('_');
if (parts.length < 3) {
return {
valid: false,
error: `Tool name must have format: {prefix}_{action}_{resource}`,
example: `${serverPrefix}_get_balance`
};
}
const [prefix, action, ...resourceParts] = parts;
// Validate prefix matches server
if (prefix !== serverPrefix) {
return {
valid: false,
error: `Prefix '${prefix}' doesn't match server prefix '${serverPrefix}'`,
suggestion: `Use '${serverPrefix}_${action}_${resourceParts.join('_')}'`
};
}
// Validate action is standard
if (!STANDARD_ACTIONS[action]) {
return {
valid: false,
error: `Non-standard action '${action}'`,
standardActions: Object.keys(STANDARD_ACTIONS),
suggestion: `Did you mean: ${findClosestAction(action)}?`
};
}
const resource = resourceParts.join('_');
// Validate resource is standard (or chain-specific)
if (!STANDARD_RESOURCES[resource] && !isChainSpecificResource(resource, serverPrefix)) {
return {
valid: false,
error: `Non-standard resource '${resource}'`,
standardResources: Object.keys(STANDARD_RESOURCES),
suggestion: `Did you mean: ${findClosestResource(resource)}?`
};
}
return { valid: true };
}
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
echo "Validating tool names..."
# Get list of all tools across all servers
node scripts/extract-all-tool-names.js > /tmp/all-tools.json
# Validate each tool name
node scripts/validate-tool-names.js /tmp/all-tools.json
if [ $? -ne 0 ]; then
echo "❌ Tool naming validation failed!"
echo "Fix tool names before committing."
exit 1
fi
echo "✅ Tool naming validation passed"
The Migration Hell
Converting existing servers to standard naming:
Step 1: Inventory Current Names
// scripts/inventory-tools.ts
import fs from 'fs';
import path from 'path';
interface ToolInventory {
server: string;
currentName: string;
suggestedName: string;
references: string[];
}
function inventoryServer(serverPath: string): ToolInventory[] {
const tools: ToolInventory[] = [];
const indexPath = path.join(serverPath, 'src/index.ts');
const content = fs.readFileSync(indexPath, 'utf8');
// Extract tool registrations
const toolRegex = /['"]([^'"]+)['"]\s*:\s*handle/g;
let match;
while ((match = toolRegex.exec(content)) !== null) {
const currentName = match[1];
const suggestedName = generateStandardName(currentName, serverPath);
tools.push({
server: path.basename(serverPath),
currentName,
suggestedName,
references: findReferences(currentName, serverPath)
});
}
return tools;
}
Step 2: Generate Migration Plan
// Output migration instructions
const inventory = inventoryServer('servers/testnet/ethereum-sepolia-mcp-server');
inventory.forEach(tool => {
if (tool.currentName !== tool.suggestedName) {
console.log(`
RENAME: ${tool.currentName} → ${tool.suggestedName}
Files to update:
${tool.references.map(ref => ` - ${ref}`).join('\n')}
Commands:
# Rename handler function
sed -i 's/handle${capitalizeFirst(tool.currentName)}/handle${capitalizeFirst(tool.suggestedName)}/g' ${tool.references.join(' ')}
# Update tool registration
sed -i "s/'${tool.currentName}'/'${tool.suggestedName}'/g" src/index.ts
`);
}
});
Step 3: Automated Renaming
// scripts/rename-tools.ts
async function renameTool(
serverPath: string,
oldName: string,
newName: string
): Promise<void> {
console.log(`Renaming ${oldName} → ${newName}`);
// 1. Rename handler function
const handlerOldName = `handle${toPascalCase(oldName)}`;
const handlerNewName = `handle${toPascalCase(newName)}`;
await replaceInFiles(serverPath, handlerOldName, handlerNewName);
// 2. Rename tool file
const oldFile = `src/tools/*/${toKebabCase(oldName)}.ts`;
const newFile = `src/tools/*/${toKebabCase(newName)}.ts`;
await renameFile(oldFile, newFile);
// 3. Update tool registration
await replaceInFile(
`${serverPath}/src/index.ts`,
`'${oldName}'`,
`'${newName}'`
);
// 4. Update tests
await replaceInFiles(
`${serverPath}/tests`,
`'${oldName}'`,
`'${newName}'`
);
// 5. Update documentation
await replaceInFiles(
`${serverPath}`,
oldName,
newName,
['*.md', '*.json']
);
console.log(`✅ Renamed ${oldName} → ${newName}`);
}
The Alias Pattern
For backward compatibility during migration:
// src/index.ts
const toolHandlers: Record<string, Function> = {
// New standard name
'eth_get_balance': handleEthGetBalance,
// Legacy aliases (deprecated)
'getBalance': handleEthGetBalance,
'eth.getBalance': handleEthGetBalance,
'ethereum_balance': handleEthGetBalance,
};
// Warn on legacy usage
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params;
if (isLegacyName(name)) {
logger.warn('Legacy tool name used', {
legacy: name,
standard: getStandardName(name),
message: 'Please update to standard naming'
});
}
return await toolHandlers[name](request.params.arguments, client);
});
The Documentation Standard
Every server’s README must include the tool naming reference:
## Tool Naming Convention
All tools follow MBPS v2.1 standard: `{prefix}_{action}_{resource}`
### Prefix
- **eth**: All Ethereum tools (mainnet and testnet)
### Available Tools
#### Core Operations
- `eth_get_chain_info` - Get network information
- `eth_get_balance` - Get ETH balance for address
- `eth_get_transaction` - Get transaction details
- `eth_get_block` - Get block information
#### Wallet Management
- `eth_create_wallet` - Generate new wallet
- `eth_import_wallet` - Import existing wallet
- `eth_validate_address` - Validate address format
[... complete tool listing]
The Benefits We Saw
1. Claude Can Generalize
User: "Check my balance on Ethereum and Solana"
Claude: I'll use {prefix}_get_balance for both chains.
- eth_get_balance for Ethereum
- sol_get_balance for Solana
[It just works!]
2. Tool Discovery
// Claude can infer tool names without documentation
User: "Can you swap tokens on Polygon?"
Claude: Looking for 'matic_swap_tokens'...
- Found! Available DeFi tool.
3. Cross-Chain Workflows
// Claude understands cross-chain operations
User: "Bridge USDC from Ethereum to Polygon"
Claude: I'll need:
1. eth_approve_token (approve bridge)
2. eth_call_contract (initiate bridge)
3. matic_get_transaction (check receipt)
4. Reduced Documentation
With standard naming, we don’t need to explain every tool:
{prefix}_get_balance→ Obviously gets balance{prefix}_transfer_token→ Obviously transfers tokens{prefix}_swap_tokens→ Obviously swaps on DEX
The Exceptions
Some chains needed special resources:
// Solana-specific (Account-based)
'sol_get_account_info' // Solana accounts != Ethereum accounts
'sol_create_token_account' // SPL tokens need dedicated accounts
// Bitcoin-specific (UTXO-based)
'btc_get_utxos' // Bitcoin uses UTXOs not account balances
'btc_select_utxos' // UTXO selection algorithm
// Cosmos-specific (IBC)
'atom_ibc_transfer' // Inter-blockchain communication
'osmo_get_ibc_channels' // IBC channel management
// XRP-specific
'xrp_create_escrow' // XRP's native escrow
'xrp_create_trustline' // XRP's trust system
Rule: Chain-specific features use standard actions with chain-specific resources.
The Testing Pattern
// tests/naming-convention.test.ts
describe('Tool Naming Convention', () => {
const tools = getAllToolNames();
const serverPrefix = getServerPrefix();
it('all tools use correct prefix', () => {
tools.forEach(tool => {
expect(tool).toMatch(new RegExp(`^${serverPrefix}_`));
});
});
it('all tools follow {prefix}_{action}_{resource} pattern', () => {
tools.forEach(tool => {
const parts = tool.split('_');
expect(parts.length).toBeGreaterThanOrEqual(3);
const [prefix, action, ...resourceParts] = parts;
expect(prefix).toBe(serverPrefix);
expect(STANDARD_ACTIONS).toHaveProperty(action);
});
});
it('all actions are standard verbs', () => {
tools.forEach(tool => {
const action = tool.split('_')[1];
expect(Object.keys(STANDARD_ACTIONS)).toContain(action);
});
});
});
The Numbers
Before standardization:
- 247 unique tool names
- 67 naming patterns
- ∞ confusion
After standardization:
- 17 blockchains
- 1 naming convention
- Universal understanding
The Checklist
When adding a new tool:
- Use server’s canonical prefix
- Use standard action verb
- Use standard resource noun
- If chain-specific, document why
- Add to tool registry
- Update README with tool listing
- Run naming validation tests
- Verify in MCP inspector
The Future
The naming registry is now used for:
- Automatic tool discovery
- Cross-chain comparison tools
- Documentation generation
- Claude’s inference engine
- API documentation
- Client SDKs
Standardization unlocked capabilities we didn’t anticipate.
References
- Naming registry:
/docs/tool-naming-registry.md - Validation scripts:
/scripts/validate-tool-names.ts - Migration tools:
/scripts/rename-tools.ts - Standard actions:
/docs/standard-actions.md - Standard resources:
/docs/standard-resources.md
This is part of our ongoing series documenting architectural patterns and insights from building the Blockchain MCP Server Ecosystem. Standards enable scale.
Related Reading
Prerequisites
- BigInt Testing Hell: Mocking Blockchain Numbers in Jest - Understanding the data types is crucial before standardizing how to access them.
Next Steps
- The MBPS v2.1 Standard: How Chaos Became Order - See how this naming convention became a cornerstone of our formal standard.
Deep Dives
- Context Window Management: Building AI-Friendly Code - Explains why predictable, standardized tool names are critical for AI agents.