Claude Code added MCP (Model Context Protocol) support back in version 0.8. MCP servers are powerful - they give Claude access to your filesystem, databases, APIs, whatever you connect. But every MCP server you enable eats context window and adds latency to every request.
My initial solution was to add all my MCP configs to the default startup:
| |
That worked for about two weeks. Then I noticed Claude was slower and I was hitting context limits on medium-sized tasks. Turns out I was loading Obsidian, Postgres, and filesystem MCP servers for sessions where I just needed to review a pull request.
I needed a way to enable MCP servers only when I actually needed them.
The Problem
Every MCP server loaded at startup consumes context tokens. When you run:
| |
Claude loads the schema and capabilities for every server in that config file. 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, slower responses, hit limits faster
- Load nothing by default - Type long commands every time I need MCP
- Enable MCP on-demand - Only load what I need for each session
Simple aliases can’t do option 3. They’re just string replacement.
(Looking at this now, option 3 seems obvious. But I genuinely started with option 1 and lived with the slowness for weeks before it annoyed me enough to fix it.)
The Solution
That’s why I built this shell function. It lets me enable MCP servers selectively:
| |
The function:
- Supports
--mcp <config_name>to enable specific servers - Auto-discovers available MCP configurations
- Validates configs exist before launching
- Defaults to no MCP (fast startup, minimal context)
- Works in both Bash and ZSH
About 45 lines of bash to avoid MCP overhead when you don’t need it.
Implementation
Here’s the complete shell function:
| |
(The eval command on line 87 is technically a security risk - it’ll execute whatever string you construct. But since you’re already using --dangerously-skip-permissions and trusting MCP servers with filesystem access, we’re past worrying about that particular horse leaving the barn.)
It turns out this optimization matters more than I expected. Sessions without MCP start noticeably faster - maybe 1-2 seconds. And I’ve stopped hitting context limits on medium-sized refactoring tasks.
How It Works
The function does four things:
Argument parsing: Loops through everything you pass in using while [[ $# -gt 0 ]]
MCP detection: When it sees --mcp, it:
- Grabs the config name that follows
- Builds the full path:
~/.claude/mcp/<name>.json - Checks if the file actually exists
- Adds
--mcp-configto the claude command
Error handling: If a config doesn’t exist:
- Shows which file it looked for
- Lists all available configs in
~/.claude/mcp/ - Returns without launching Claude
Pass-through: Any other arguments (--help, --version, etc.) go straight to Claude unchanged
The key is that MCP is opt-in. Default behavior is no MCP servers loaded.
Usage Examples
Basic Usage (Backward Compatible)
| |
With MCP Configuration
| |
Combined Arguments
| |
Error Handling
| |
Installation
Add the function to your shell configuration file:
For Bash
| |
For ZSH
| |
Universal Installation
Since the function works in both shells, you can create a shared file:
| |
Testing and Validation
First test - did basic usage still work?
| |
Good. Then I tried an MCP config:
| |
Worked. I also tested the error handling:
| |
The validation catches typos and shows you what’s available, which has saved me from several “why isn’t this working” moments.
As for the context savings - I haven’t measured precisely, but sessions without MCP definitely start faster. And I’ve noticed I’m not hitting context limits nearly as often on medium-sized refactoring tasks since making this change.
Why This Approach Works
The big wins are performance and context efficiency:
Faster startup: Sessions without MCP launch noticeably faster Context savings: MCP metadata consumes context tokens - when you don’t need MCP capabilities, why pay the cost? Fewer context limit hits: I’ve stopped running into 200k limits on medium refactoring tasks Smart defaults: Most sessions don’t need MCP - code review, quick explanations, simple refactors work fine without it
Secondary benefits:
Auto-discovery: Lists available configs when you mistype a name Validation: Catches typos before launching Claude Flexibility: Easy to add new MCP configs, just drop a JSON file in ~/.claude/mcp/
The function makes it cheap to have many specialized MCP configs (obsidian, postgres, filesystem, api-tools) and load only what you need.
Directory Structure
Here’s what my MCP setup looks like:
| |
Each config is a separate MCP server or group of related servers. This granularity means I can load exactly what I need:
- Writing blog posts →
cy --mcp obsidian - Database work →
cy --mcp postgres - Quick code review →
cy(no MCP overhead) - Complex integration →
cy --mcp obsidian --mcp postgres(if needed, though the function doesn’t support multiple –mcp yet - that’s a TODO)
Breaking configs into single-purpose files turned out to be more useful than I expected.
Extending the Function
You can easily add more features:
Add Default MCP Config
| |
Add Verbose Mode
| |
Add Config Validation
| |
Troubleshooting
Function Not Found: Make sure you sourced your shell configuration after adding the function
Permission Denied: Check that your MCP config files are readable
JSON Errors: Validate your MCP configuration files with jq
Path Issues: Verify ~/.claude/mcp/ directory exists and contains config files
Further Reading
If you’re setting up MCP servers, the Model Context Protocol spec explains how the protocol works. The Claude Code MCP documentation walks through config file format and available servers.
For more about context window optimization, Simon Willison wrote about managing LLM context budgets (if he hasn’t yet, he should - it’s a real concern as projects get larger).
The bash function documentation is surprisingly readable if you want to extend this pattern.
What I Learned
MCP servers are powerful but not free - they consume context window and add latency. Selective loading makes a real difference:
What I’ve noticed:
- Sessions start noticeably faster without MCP overhead
- I’m not hitting context limits on refactoring tasks like I used to
- Most of my sessions don’t actually need MCP - code review, explanations, simple fixes work fine without it
- When I do need MCP (working with Obsidian notes, database queries), I can enable just that specific capability
Time investment:
- Building the function: couple hours one afternoon
- Breaking monolithic config into separate files: maybe 30 minutes
- Worth it if you use Claude Code regularly and hit context limits
The same pattern works for any CLI tool with expensive startup configuration. I’m using similar functions for docker (load different compose configs), pytest (different marker sets), and terraform (workspace selection).
If you’ve got a tool that has optional-but-costly configuration, selective loading via shell functions is worth trying.