Featured image of post How I Optimize Claude Code Context with Selective MCP Loading

How I Optimize Claude Code Context with Selective MCP Loading

MCP servers consume context tokens every session. Learn how a simple shell function saves context by enabling MCP servers only when needed - load Obsidian for blog work, Postgres for database queries, or nothing for quick code reviews

My MCP workflow has been evolving. Here’s where it is today: selective loading instead of loading everything.

Claude Code’s MCP servers are powerful - they give Claude access to your filesystem, databases, APIs. But every server you enable eats context window and adds latency. My initial solution was loading all configs by default:

1
alias cy="claude --dangerously-skip-permissions --mcp-config ~/.claude/mcp/all-servers.json"

That worked for two weeks. Then I noticed Claude was slower and I was hitting context limits on medium-sized tasks. I was loading Obsidian, Postgres, and filesystem MCP servers for sessions where I just needed to review a pull request.

The Problem

Every MCP server loaded at startup consumes context tokens. Before you even write your first prompt, you’ve spent maybe 2-5k tokens just on MCP metadata. For quick tasks - “review this function”, “explain this error” - you don’t need Obsidian integration or database access. But with a static config, you pay that context tax every time.

I had three options: always load everything (waste context), load nothing by default (type long commands), or enable MCP on-demand. Simple aliases can’t do option 3 - they’re just string replacement.

The Solution

A shell function that enables MCP servers selectively:

1
2
3
cy                      # No MCP - fast, minimal context
cy --mcp obsidian       # Just Obsidian for blog work
cy --mcp postgres       # Just database for schema queries

The core logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cy() {
    local mcp_config="" claude_args="--dangerously-skip-permissions"
    while [[ $# -gt 0 ]]; do
        case $1 in
            --mcp)
                local mcp_file="$HOME/.claude/mcp/$2.json"
                [[ -f "$mcp_file" ]] && mcp_config="--mcp-config $mcp_file" || return 1
                shift 2 ;;
            *) claude_args="$claude_args $1"; shift ;;
        esac
    done
    eval "claude $claude_args $mcp_config"
}

The full version with error messages and config discovery is in my dotfiles. The key is that MCP is opt-in - default behavior loads no servers.

The function parses --mcp arguments, builds the path to ~/.claude/mcp/<name>.json, validates it exists, and passes everything else through to Claude unchanged.

Setup

Add the function to ~/.bashrc or ~/.zshrc, then organize your MCP configs:

1
2
3
4
~/.claude/mcp/
├── obsidian.json    # Obsidian integration
├── postgres.json    # Database access
└── filesystem.json  # File operations

Each config is a separate MCP server. This granularity means you load exactly what you need:

  • Writing blog posts: cy --mcp obsidian
  • Database work: cy --mcp postgres
  • Quick code review: cy (no MCP overhead)

Breaking configs into single-purpose files turned out to be more useful than I expected. The function makes it cheap to have many specialized configs and load only what you need.

What I Learned

MCP servers are powerful but not free - they consume context window and add latency. Selective loading makes a real difference:

  • Sessions start noticeably faster without MCP overhead
  • I’m not hitting context limits on refactoring tasks like I used to
  • Most sessions don’t actually need MCP - code review, explanations, simple fixes work fine without it

The same pattern works for any CLI tool with expensive startup configuration. I’m using similar functions for docker (different compose configs), pytest (different marker sets), and terraform (workspace selection).

This works for my Claude Code workflow. Adjust the config structure for yours.

uname -a: Human 5.4.0-anxiety #1 SMP Coffee-Deprived x86_64 GNU/Paranoid
Built with Hugo
Theme Stack designed by Jimmy