Part 4 of the Journey: Advanced Topics & Deep Dives Previous: The Token Overflow Crisis | Next: Building Interactive NFT Creation Workflows
The Chain Info Breakthrough: Why Your Blockchain Tools Return Nothing
How we discovered that minimal implementations were hiding rich blockchain data
Date: August 20, 2025 Author: Myron Koch & Claude Code Category: Architecture Insights
The Problem
We had 17 blockchain MCP servers, each with a get_chain_info tool. But they were returning almost nothing:
{
"network": "testnet",
"chainId": 1
}
Meanwhile, users expected comprehensive data like TPS, gas prices, validator counts, network health. We assumed blockchains just didn’t provide this data easily.
We were completely wrong.
The Discovery
On January 16, 2025, while enhancing four testnet servers (Arbitrum, TRON, NEAR, Sui), we made a shocking discovery:
Every blockchain provides rich data. We just weren’t asking for it.
Our implementations were minimal not because of blockchain limitations, but because we’d copy-pasted a minimal template everywhere. The chains had the data all along - we just needed to actually query for it.
The Pattern That Changed Everything
Instead of one lazy API call:
// BEFORE: The lazy way
async function getChainInfo() {
const chainId = await provider.getNetwork();
return { chainId };
}
We started fetching everything available in parallel:
// AFTER: The comprehensive way
async function getChainInfo() {
const [
network,
blockNumber,
block,
gasPrice,
priorityFee,
blocks,
validators,
metrics,
marketData
] = await Promise.all([
provider.getNetwork(),
provider.getBlockNumber(),
provider.getBlock('latest'),
provider.getGasPrice(),
provider.send('eth_maxPriorityFeePerGas', []),
getRecentBlocks(),
getValidators(),
calculateMetrics(),
fetchMarketData()
]);
return {
network: {
chainId: network.chainId,
name: network.name,
ensAddress: network.ensAddress
},
block: {
current: blockNumber,
timestamp: block.timestamp,
transactions: block.transactions.length
},
gas: {
price: formatUnits(gasPrice, 'gwei'),
base: block.baseFeePerGas,
priority: formatUnits(priorityFee, 'gwei')
},
metrics: {
tps: calculateTPS(blocks),
utilization: getNetworkUtilization(),
totalTransactions: await getTotalTransactions()
},
market: {
price: marketData.price,
marketCap: marketData.marketCap,
volume24h: marketData.volume24h
}
};
}
The Transformation
Arbitrum Sepolia - Before:
{
"chainId": 421614
}
Arbitrum Sepolia - After:
{
"network": {
"chainId": 421614,
"name": "arbitrum-sepolia",
"rollup": "optimistic",
"l1": "ethereum-sepolia"
},
"block": {
"l2": 12345678,
"l1": 5678901,
"timestamp": 1737043921
},
"gas": {
"l2Price": "0.1 gwei",
"l1Price": "15 gwei",
"sequencerStatus": "healthy"
},
"metrics": {
"tps": 15.2,
"utilization": "42%",
"totalTransactions": 50000000
},
"market": {
"price": "$0.487",
"marketCap": "$1.2B",
"volume24h": "$500M"
}
}
Chain-Specific Discoveries
Each blockchain had unique data we were missing:
TRON
- Witnesses: 27 super representatives we weren’t showing
- Resources: Energy and bandwidth pricing
- Chain parameters: Hundreds of governance settings
NEAR
- Seat price: Validator staking requirements
- Protocol config: Gas limits, storage costs
- Epoch info: Current epoch, validator rotation
Sui
- Checkpoints: Consensus milestones
- Object model: Unique UTXO-like system
- Move modules: On-chain code statistics
Arbitrum
- L2/L1 blocks: Rollup synchronization status
- Sequencer health: Critical for L2 operations
- Batch info: Transaction batching statistics
The Implementation Pattern
We established a universal pattern for comprehensive chain info:
interface ChainInfo {
// Network basics
network: {
chainId: number;
name: string;
version?: string;
protocol?: string;
};
// Block information
block: {
current: number;
timestamp: number;
transactions?: number;
gasUsed?: string;
gasLimit?: string;
};
// Gas/Fee data
gas: {
price: string;
base?: string;
priority?: string;
unit: string;
};
// Network metrics
metrics: {
tps?: number;
utilization?: string;
totalTransactions?: number;
activeValidators?: number;
};
// Market data (via CoinGecko)
market?: {
price: string;
marketCap: string;
volume24h: string;
priceChange24h: string;
};
// Chain-specific extensions
[key: string]: any;
}
Lessons Learned
1. Never Assume Limitations
We assumed blockchains didn’t provide data. Wrong. We just weren’t asking.
2. Parallel Fetching is Free Performance
Promise.all() turns 6 sequential calls (3 seconds) into 1 parallel call (0.5 seconds).
3. Copy-Paste Propagates Mediocrity
One minimal implementation got copied 17 times. The pattern spread like a virus.
4. Users Expect Bloomberg, Not Basic
People using blockchain tools expect comprehensive data. “Just the chain ID” doesn’t cut it.
5. Rich Data is Always There
Every blockchain has metrics, validators, parameters, and statistics. You just have to look.
The Ripple Effect
This discovery triggered a cascade of improvements:
- All servers enhanced: We retrofitted every blockchain server with comprehensive chain info
- Market data integration: Added CoinGecko to everything
- Standardized structure: One pattern across all chains
- User satisfaction: “Finally, useful information!”
Implementation Checklist
To implement comprehensive chain info:
- List all available RPC methods for your chain
- Identify unique chain features (validators, epochs, rollups)
- Design parallel fetch strategy
- Add market data integration
- Calculate derived metrics (TPS, utilization)
- Handle errors gracefully (some data might fail)
- Cache appropriately (block data: no cache, market data: 60s)
Code Example: The Full Pattern
export async function handleGetChainInfo(
args: any,
client: BlockchainClient
): Promise<any> {
try {
// Parallel fetch everything
const [
networkInfo,
latestBlock,
gasPrice,
validatorInfo,
marketData
] = await Promise.all([
client.getNetwork().catch(() => null),
client.getLatestBlock().catch(() => null),
client.getGasPrice().catch(() => null),
client.getValidators().catch(() => null),
fetchMarketData(client.token).catch(() => null)
]);
// Calculate metrics
const metrics = await calculateMetrics(client, latestBlock);
// Build comprehensive response
return {
content: [{
type: 'text',
text: JSON.stringify({
network: formatNetwork(networkInfo),
block: formatBlock(latestBlock),
gas: formatGas(gasPrice),
validators: formatValidators(validatorInfo),
metrics: metrics,
market: marketData,
timestamp: Date.now()
}, null, 2)
}]
};
} catch (error) {
// Even on error, return what we can
return {
content: [{
type: 'text',
text: JSON.stringify({
error: error.message,
partial: true,
network: { chainId: await client.getChainId() }
}, null, 2)
}]
};
}
}
What’s Next
We’re now auditing all 600+ tools across our ecosystem. How many are returning minimal data when they could be comprehensive?
The pattern is clear:
- Don’t assume limitations
- Fetch everything available
- Use parallel requests
- Provide rich, useful data
The Bigger Lesson
This breakthrough wasn’t about technology. It was about assumptions. We assumed blockchains were limited because our first implementation was limited. That assumption spread through copy-paste until it became “truth.”
Question your assumptions. Especially the ones that seem obviously true.
Sometimes the data is right there. You just have to ask for it.
References
- Enhanced implementations:
/servers/testnet/*/src/tools/core/*-get-chain-info.ts - Original discovery: January 16, 2025 refactoring session
- Pattern library:
/servers/testnet/NEW STANDARDS/07-TOOL-IMPLEMENTATION-TEMPLATES.md
This is part of our ongoing series documenting architectural patterns and insights from building the Blockchain MCP Server Ecosystem.
Related Reading
Prerequisites
- The Token Overflow Crisis - Learn how we managed large data payloads, a lesson needed before fetching more data.
Next Steps
- Building Interactive NFT Creation Workflows in MCP - See how rich, structured data enables more complex user interactions.
Deep Dives
- Copy-Paste Architecture: When Duplication Beats Abstraction - This post is a direct consequence of the dangers of copy-pasting a minimal implementation, as described here.