back to posts
#18 β€’ Part 4 β€’ 2025-07-22 β€’ 15 min

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

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:

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:

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

  1. One concept, one file
  2. Self-contained implementations
  3. Inline what’s important
  4. Document at point of use
  5. Flat over nested
  6. Explicit over implicit
  7. 300 lines maximum
  8. 2-level import maximum
  9. README in every directory
  10. Examples over explanations

The Payoff

With context-efficient architecture:

Your code structure becomes your documentation.

References


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.


Prerequisites

Next Steps

Deep Dives