Part 4 of the Journey: Advanced Topics & Deep Dives Previous: From Keywords to Meaning | Next: Rate Limiting Without State
Context Window Management: Building AI-Friendly Code at Scale
How to structure code so AI assistants can actually help when your codebase is 67,000 lines
Date: July 22, 2025 Author: Myron Koch & Claude Code Category: AI-Assisted Development
The Hidden Problem
Claude has ~200k token context window. Sounds huge, right?
Our blockchain MCP ecosystem:
- 17 servers
- 1,247 files
- 67,234 lines of code
- ~2.5 million tokens
Claude can see less than 10% of our codebase at once.
The Failure Modes
Mode 1: The Context Explosion
You: "Add NFT support to all servers"
Claude: "I'll need to see the current implementation..."
*Loads 15 files*
*Context: 50% full*
Claude: "Now I need to see the test files..."
*Loads 10 more files*
*Context: 80% full*
Claude: "Let me check the configuration..."
*Loads 5 more files*
π₯ CONTEXT OVERFLOW
Mode 2: The Lost Context
You: "Continue implementing the feature we discussed"
Claude: "I don't see any previous discussion about a feature..."
You: *Copies 10,000 lines of previous conversation*
Claude: "Now I can't see the current code..."
Mode 3: The Wrong Context
You: "Fix the bug in getBalance"
Claude: *Looks at ethereum-mcp-server/getBalance*
You: "No, in the Solana server!"
Claude: *Has to drop Ethereum context, load Solana*
You: "Actually, compare both implementations"
Claude: π₯ Cannot hold both in context
The Solution: Context-Efficient Architecture
Principle 1: One File Should Tell the Whole Story
// BAD: Context fragmented across files
// src/tools/core/eth-get-balance.ts
import { validateAddress } from '../../utils/validation.js';
import { formatBalance } from '../../utils/formatters.js';
import { ETH_DECIMALS } from '../../constants.js';
import type { Balance } from '../../types.js';
// GOOD: Self-contained with clear boundaries
// src/tools/core/eth-get-balance.ts
import { BlockchainClient } from '../../client.js';
const ETH_DECIMALS = 18; // Local constant
export async function handleEthGetBalance(
args: { address: string },
client: BlockchainClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
// Validation inline or in same file
if (!args.address?.match(/^0x[a-fA-F0-9]{40}$/)) {
throw new Error('Invalid Ethereum address');
}
const balance = await client.getBalance(args.address);
// Formatting inline
const formatted = `${Number(balance) / Math.pow(10, ETH_DECIMALS)} ETH`;
return {
content: [{
type: 'text',
text: JSON.stringify({
address: args.address,
balance: balance.toString(),
formatted
}, null, 2)
}]
};
}
AI can now understand and modify this tool without loading 5 other files.
Principle 2: Directory Naming as Documentation
# BAD: Generic names that require exploration
src/
βββ utils/
βββ helpers/
βββ lib/
βββ modules/
βββ stuff/
# GOOD: Self-documenting structure
src/
βββ tools/
β βββ core/ # Required MCP tools
β βββ wallet/ # Wallet management
β βββ tokens/ # ERC-20/SPL operations
β βββ nft/ # NFT operations
β βββ defi/ # DeFi protocols
βββ client.ts # THE blockchain interface
βββ index.ts # THE entry point
AI knows exactly where to look without asking.
Principle 3: The Sacred Index Pattern
Every index.ts should be a complete map:
// src/index.ts - The ONLY file AI needs to understand the server
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
// All tools imported with clear names
import { handleEthGetBalance } from './tools/core/eth-get-balance.js';
import { handleEthGetTransaction } from './tools/core/eth-get-transaction.js';
import { handleEthSendTransaction } from './tools/wallet/eth-send-transaction.js';
// ... all other imports
// Complete tool registry in one place
const toolHandlers: Record<string, Function> = {
'eth_get_balance': handleEthGetBalance,
'eth_get_transaction': handleEthGetTransaction,
'eth_send_transaction': handleEthSendTransaction,
// ... all 47 tools listed here
};
// Tool definitions visible at a glance
const tools = {
'eth_get_balance': {
description: 'Get ETH balance for an address',
inputSchema: {
type: 'object',
properties: {
address: { type: 'string', description: 'Ethereum address' }
},
required: ['address']
}
},
// ... all tool definitions
};
From this ONE file, AI understands the entire server structure.
Context-Efficient Patterns
Pattern 1: The Context Header
Start every file with context:
/**
* @file Ethereum Balance Tool
* @module ethereum-mcp-server
* @description Gets ETH balance for any Ethereum address
*
* Dependencies:
* - client.ts: Blockchain connection
* - No other dependencies
*
* Used by:
* - index.ts: Registered as 'eth_get_balance'
*
* Related tools:
* - eth-get-token-balance.ts: For ERC-20 balances
*/
Pattern 2: Inline Documentation
Donβt make AI hunt for documentation:
export async function handleEthGetBalance(
args: {
address: string; // Ethereum address (0x...)
},
client: BlockchainClient // Injected by index.ts
): Promise<{
content: Array<{
type: 'text'; // Always 'text' for MCP
text: string; // JSON stringified response
}>
}> {
// Implementation with inline comments
const balance = await client.getBalance(args.address); // Returns BigInt
return {
content: [{
type: 'text',
text: JSON.stringify({
address: args.address,
balance: balance.toString(), // BigInt to string for JSON
formatted: `${Number(balance) / 1e18} ETH`
}, null, 2)
}]
};
}
Pattern 3: Error Messages as Documentation
if (!args.address) {
throw new Error(
'Address is required. ' +
'Expected format: 0x followed by 40 hex characters. ' +
'Example: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3'
);
}
Errors tell AI what the correct format is.
The File Size Sweet Spot
We discovered optimal file sizes for AI assistance:
Perfect: 50-150 lines (1 tool, complete implementation)
Good: 150-300 lines (Related tools or complex logic)
Problematic: 300-500 lines (Getting hard to hold context)
Impossible: 500+ lines (AI can't see it all)
This is why we enforce:
index.ts< 300 lines- Tool files < 150 lines
- Client files < 500 lines
Context-Efficient Testing
Tests that donβt require loading the entire implementation:
// BAD: Test requires understanding implementation
describe('getBalance', () => {
it('should call web3.eth.getBalance with correct parameters', () => {
// Requires knowing internal implementation details
});
});
// GOOD: Test is self-documenting
describe('eth_get_balance tool', () => {
it('returns balance for valid Ethereum address', async () => {
const mockClient = {
getBalance: jest.fn().mockResolvedValue('1000000000000000000')
};
const result = await handleEthGetBalance(
{ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3' },
mockClient
);
expect(JSON.parse(result.content[0].text)).toEqual({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3',
balance: '1000000000000000000',
formatted: '1 ETH'
});
});
it('rejects invalid Ethereum addresses', async () => {
await expect(
handleEthGetBalance({ address: 'invalid' }, mockClient)
).rejects.toThrow('Invalid Ethereum address');
});
});
AI can understand the test without seeing implementation.
The Directory Summary Pattern
We add README.md files that give AI instant context:
# /src/tools/core/
Core MCP tools required by MBPS v2.1 standard.
## Files (5 tools)
- `eth-get-balance.ts` - Get ETH balance for address
- `eth-get-transaction.ts` - Get transaction by hash
- `eth-get-block.ts` - Get block by number/hash
- `eth-validate-address.ts` - Validate Ethereum address
- `eth-get-chain-info.ts` - Get network information
## All tools:
- Accept `args` object and `client` instance
- Return MCP-formatted response
- Handle errors with descriptive messages
- Include inline validation
## To add a new core tool:
1. Create file: `eth-{action}-{resource}.ts`
2. Export function: `handleEth{Action}{Resource}`
3. Register in `/src/index.ts`
4. Add test in `/tests/core.test.ts`
AI instantly knows whatβs in the directory without loading files.
The Import Graph Strategy
Keep imports shallow:
// BAD: Deep import chains
// a.ts imports b.ts imports c.ts imports d.ts imports e.ts
// AI needs to load 5 files to understand 'a'
// GOOD: Flat imports
// a.ts imports only what it directly needs
// client.ts provides all blockchain operations
// index.ts wires everything together
Maximum import depth: 2 levels.
Context-Efficient Commands
Structure commands to minimize context:
# BAD: Vague command requiring exploration
"Add a new feature to the server"
# GOOD: Specific command with context
"Add tool 'eth_get_gas_price' to ethereum-mcp-server following the pattern in eth-get-balance.ts"
The Context Budget
Think of context as a budget:
Total Budget: 200k tokens
File viewing: -5k tokens per file
Conversation history: -20k tokens
AI response buffer: -20k tokens
Working space: 155k tokens
Maximum files visible: ~30-40 files
Design your architecture to fit in 30 files.
Anti-Patterns That Kill Context
1. Circular Dependencies
// file-a.ts imports file-b.ts
// file-b.ts imports file-a.ts
// AI must load both to understand either
2. Scattered Configuration
// Config in 10 different files
// AI needs all 10 to understand system config
3. Inheritance Hierarchies
class Base { }
class Extended extends Base { }
class MoreExtended extends Extended { }
// AI needs entire chain to understand MoreExtended
4. External Type Dependencies
import type {
ComplexType1,
ComplexType2,
ComplexType3
} from 'massive-external-library';
// AI needs to understand external library
The Golden Rules
- One concept, one file
- Self-contained implementations
- Inline whatβs important
- Document at point of use
- Flat over nested
- Explicit over implicit
- 300 lines maximum
- 2-level import maximum
- README in every directory
- Examples over explanations
The Payoff
With context-efficient architecture:
- AI can implement features without asking for files
- AI can debug without seeing entire codebase
- AI can extend patterns without examples
- AI can refactor without breaking things
- You can work on 17 servers without context switching
Your code structure becomes your documentation.
References
- Context patterns:
/docs/ai-context-patterns.md - File size analysis:
/scripts/analyze-file-sizes.js - Import depth checker:
/scripts/check-import-depth.js - README templates:
/templates/readme-templates/
This is part of our ongoing series documenting architectural patterns and insights from building the Blockchain MCP Server Ecosystem. In the age of AI, context efficiency is a core architectural concern.
Related Reading
Prerequisites
- From Keywords to Meaning: Building a Semantic Memory System with Ollama - Understanding how AI uses context is key to managing it.
Next Steps
- Rate Limiting Without State: The MCP Paradox - Another example of building for a constrained environment.
Deep Dives
- Multi-Agent Orchestration: When 6 AIs Build Your Codebase - See how critical context management is when coordinating multiple AIs.