Cutting Claude Code's Initial Context Bloat — MCP, Skills, and Plugin Tactics

The Problem in One Screenshot

Open a fresh Claude Code session, type "hi", and check /context. On a heavy power-user config (22+ MCP servers, 100+ skills, half a dozen plugins) the answer is sobering:

Total: 33.8k / 200k tokens (17%)
- System prompt:           8.4k  (4.2%)
- System tools:            987   (0.5%)
- MCP tools (deferred):    67.6k (33.8%)  ← biggest culprit
- System tools (deferred): 24.9k (12.4%)
- Custom agents:           1.3k  (0.6%)
- Memory files:            8.1k  (4.1%)
- Skills:                  15k   (7.5%)
- Messages:                13 tokens
- Autocompact buffer:      33k   (16.5%)  ← reserved, hardcoded

Sixty-six thousand tokens are gone before the conversation begins. Below is what each of those buckets actually contains, the official docs that describe the loader, and the three settings levers that let me bring the number down without losing functionality.


What Claude Code Loads By Default

The startup payload is the sum of a fixed system prompt plus several dynamically discovered resources. Each discovery path is documented but the costs are not always obvious.

Loading diagram…

The red-tinted nodes are the three levers we will operate. The rest is either fixed (system prompt, built-in tools, autocompact buffer) or marginal (memory, agents).

Where the official sources describe each loader

LoaderBehaviourSource
MCP servers + instructionsEach connected server contributes its tool names, descriptions, and an instructions string — the instructions are not deferred by Tool SearchClaude Code MCP docs, Issue #48680
claude.ai connectorsWhen you sign in with a Pro/Max subscription, account-level connectors (Gmail, Linear, etc.) are auto-attached and load ~100K of toolsIssue #20412, Issue #44112
SkillsEach SKILL.md frontmatter (name + description) is concatenated into a listing that ships in the system promptClaude Code Skills, agentskills.io spec
Skill discovery pathsFour locations are scanned: ~/.<client>/skills/, ~/.agents/skills/, and project-level equivalentsAgent Skills client implementation
PluginsMarketplace plugins drop hooks, agents, skills, and commands into plugins/cache/; plugin skills are not controllable via skillOverridesPlugin docs, Skills override note
Autocompact bufferA 33K reservation at the top of the window; environment overrides only shift the trigger threshold, not the reservation sizeIssue #43928, Issue #44536
Tool Search "auto" modeDefers tool definitions (not instructions) when context pressure exceeds 10%, but has multiple known leak pathsTool Search SDK, API reference, Issue #18370

Two community write-ups did most of the legwork that informed this article: Scott Spence's Optimising MCP Server Context Usage (66K → 5.6K) and atcyrus's MCP Tool Search Context Pollution Guide.


The Three Levers

Loading diagram…

Each lever attacks a discovery path that the loader walks before your first prompt is processed.


Lever 1 · MCP — Isolate Claude Code from claude.ai Connectors

The hidden cost of subscription auth

If you use a Pro or Max subscription, signing in tells Claude Code to fetch the account-level MCP connectors you configured on claude.ai (Gmail, Linear, Notion, Google Drive, Google Calendar, Exa, Figma…). These are convenient on the web but on the CLI they load ~100K tokens of tool definitions whether or not you intend to use them.

There is — as of v2.1.144 — no first-party setting to disable connector loading per-client. Several open issues track the feature request: #20412, #47881, #50062, #56773.

The documented workaround is the --strict-mcp-config flag.

Flow before and after

The sequence diagram below contrasts two startup paths: the default flow (steps 1–5) pulls both account-level claude.ai connectors and local MCP servers into context, while the --strict-mcp-config flow (steps 6–8) reads only the file you pass and ignores the connectors entirely.

Loading diagram…

Setup

  1. Carve out a local-only MCP config. Your canonical MCP server list lives in ~/.claude.json; the carve-out is just mcpServers:

    jq '{mcpServers}' ~/.claude.json > ~/.claude/mcp-local.json
    
  2. Wrap claude so the flag is always applied. Fish makes this clean — note the command prefix to dodge fish's infamous alias recursion:

    # ~/.config/fish/functions/claude.fish
    function claude
        command claude \
            --strict-mcp-config \
            --mcp-config ~/.claude/mcp-local.json $argv
    end
    

    Bash/zsh equivalents work the same way (function claude() { command claude ... }).

  3. Leave the connectors connected on claude.ai. The web Cowork experience and Claude Desktop chat will keep them; only the CLI ignores them.

The claude --help excerpt that authorizes this:

--strict-mcp-config — Only use MCP servers from --mcp-config, ignoring all other MCP configurations.

Watch out: catalog-tool drift

If you use any GUI/catalog tool to manage MCP servers, it almost certainly writes to ~/.claude.json (the canonical file), while the strict-mcp-config wrapper now reads from ~/.claude/mcp-local.json. Those two files drift every time the tool adds or removes a server, and a re-jq is needed to resync. If you don't use a catalog tool you'll never notice this.


Lever 2 · Skills — skillOverrides Done Right

What lives in the Skills bucket

Every SKILL.md discovered at startup contributes a name (a few tokens) and a description (up to maxSkillDescriptionChars, default 1536) to the system prompt's skill listing. A user with ~100 skills can easily burn 15K tokens here.

The four states (corrected May 2026)

skillOverrides is a per-skill object keyed by directory name. A previously published recommendation in one of my own research notes used a string form ("skillOverrides": "user-invocable-only") — that form fails JSON schema validation; the object form is the only one supported.

The state diagram below maps the four legal values and the transitions that progressively reduce cost: the default on state, two cost-reduced states (name-only keeps auto-invocation; user-invocable-only requires explicit slash invocation), and the fully hidden off state.

Loading diagram…

The pivotal difference between the two cost-reduced values is auto-invocation. user-invocable-only truly does only what its name says — the skill is reachable through /skill-name. Auto-invocation is off, so the model won't reach for the skill on its own when the task naturally calls for it. name-only keeps the name in the listing and leaves auto-invocation alive, so the model can decide to load and run the skill on its own when the task calls for it.

That distinction is why I picked name-only for everything. A lot of my flow is /goal "do X"-style natural-language direction where the model (or a subagent it spawns) needs to recognize "ah, the qa-team skill fits this" and load it without me typing the slash command. user-invocable-only would kill that and force me to remember and type every skill name. I keep user-invocable-only in reserve for the rare skill I want gated behind explicit invocation, but in practice that bucket is empty for me right now.

Setup

# Apply name-only to every locally-discoverable skill
SKILLS=$(find ~/.claude/skills ~/.agents/skills -maxdepth 2 -name "SKILL.md" 2>/dev/null \
  | sed 's|.*/skills/||; s|/SKILL.md$||' | sort -u)

OVERRIDES=$(echo "$SKILLS" | jq -R . | jq -s 'map({(.): "name-only"}) | add')

jq --argjson o "$OVERRIDES" '
  .skillOverrides = $o |
  .maxSkillDescriptionChars = 0 |
  .skillListingBudgetFraction = 0.005
' ~/.claude/settings.json > /tmp/s.json && mv /tmp/s.json ~/.claude/settings.json

Three settings, three purposes:

SettingDefaultMy valueWhy
skillOverrides{}{ <every-skill>: "name-only" }Drop descriptions, keep auto/subagent invocation
maxSkillDescriptionChars15360Drops descriptions entirely — only names remain, hard safety net against any skill I forget to override
skillListingBudgetFraction0.010.005Hard ceiling on how much of the window the skill listing can consume

Restart Claude Code completely after editing — the skill listing is cached for the current session. Verify with /context and look at the Skills row.

Caveats

  • Plugin skills are not affected by skillOverrides. That is the whole reason Lever 3 exists.
  • Build version matters. skillOverrides was broken until v2.1.129 (Issue #50631). Built-in skill support landed in the same fix (Issue #26838). Confirm with claude --version.
  • The frontmatter equivalents disable-model-invocation: true and user-invocable: false work too, but skillOverrides is faster to bulk-apply.

Lever 3 · Plugin → Local Skill

Marketplace plugins are convenient but they ship a bundle: hooks, agents, skills, and commands all enabled together. The skills they include sit in ~/.claude/plugins/cache/.../skills/ and — per the official docs — cannot be silenced by skillOverrides. So if a plugin contributes five chatty skills that you never auto-invoke, those five descriptions are loaded every session no matter what.

The workaround is to promote the parts you actually use into user space, then disable the plugin.

Loading diagram…

Steps

  1. Identify what the plugin contributes. Each plugin's cache directory mirrors skills/, agents/, hooks/, commands/ from its source.

    ls ~/.claude/plugins/cache/<plugin>@<marketplace>/
    
  2. Copy only what you use into user space.

    cp -r ~/.claude/plugins/cache/codex@openai-codex/skills/codex-cli-runtime \
          ~/.claude/skills/
    
    cp -r ~/.claude/plugins/cache/coderabbit@claude-plugins-official/agents/code-reviewer \
          ~/.claude/agents/
    
  3. Disable the plugin. Either remove its key from enabledPlugins in ~/.claude/settings.json or run claude plugin uninstall <plugin>@<marketplace>.

  4. Add the now-local skills to skillOverrides. They are user skills now, so the lever from §2 applies. name-only or user-invocable-only as appropriate.

  5. When the plugin updates, you have to re-copy. The tradeoff is conscious: you trade automatic updates for skillOverrides control and lower steady-state context cost. For plugins you use rarely, the trade is worth it.

What I localized recently

PluginKeptDisabledTokens reclaimed
codex@openai-codexcodex-rescue agent, codex-cli-runtime / codex-result-handling / gpt-5-4-prompting skillsyes~1.4K (plus regained skillOverrides reach)
coderabbit@claude-plugins-officialcode-reviewer agent, autofix / code-review skillsyes~1K
figma@claude-plugins-officialnothing globally — enabled only inside the zumen-fe project via .claude/settings.local.jsonyes (user-level)~1.4K
claude-md-management@claude-plugins-officialnothing — I never used ityessmall

The figma case demonstrates a fourth pattern worth mentioning: scope a plugin to one project. Disable it at user level, then re-enable in a single project's .claude/settings.local.json:

{ "enabledPlugins": { "figma@claude-plugins-official": true } }

The plugin loads only when you cd into that project.


My Current settings.json (Reduced Form)

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "env": {
    "ENABLE_TOOL_SEARCH": "true",
    "CLAUDE_CODE_ENABLE_TELEMETRY": "0",
    "MAX_MCP_OUTPUT_TOKENS": "200000",
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
    "CLAUDE_CODE_DISABLE_1M_CONTEXT": "1",
    "CLAUDE_CODE_EFFORT_LEVEL": "max"
  },
  "skillOverrides": {
    "<every-skill-name>": "name-only"
  },
  "maxSkillDescriptionChars": 0,
  "skillListingBudgetFraction": 0.005,
  "enabledPlugins": {
    "figma@claude-plugins-official": false,
    "codex@openai-codex": false,
    "coderabbit@claude-plugins-official": false,
    "claude-md-management@claude-plugins-official": false
  }
}

Combined with the --strict-mcp-config wrapper from Lever 1, that is the complete steady-state setup.


Verification

Loading diagram…

Concretely:

# 1. confirm strict-mcp-config is engaged
claude --debug 2>&1 | grep -i "strict\|mcp-config" | head -5

# 2. confirm only your local MCPs are loaded
/mcp

# 3. inspect context split
/context

# 4. if Skills row is still large
claude --version   # must be ≥ v2.1.129
jq '.skillOverrides | length' ~/.claude/settings.json   # should match skill count

# 5. confirm plugins are off
jq '.enabledPlugins' ~/.claude/settings.json

Known Bugs to Watch

IssueStatusWhy it matters
#48680OPENMCP server instructions are not Tool Search-deferred — long-form instructions (Serena's 20+ line guide, DeepWiki's tool catalog) stay resident regardless of settings
#40314CLOSED staleHTTP/Streamable MCP servers do not defer at all in older versions — verify with /mcp and /context after upgrade
#54716OPENBuilt-in deferred tools (~25K) have no opt-out yet
#41809CLOSEDDisabled MCP servers used to remain in the deferred list — verify after disabling
#43928OPENAutocompact buffer (33K) is hardcoded; only the trigger threshold is tunable via CLAUDE_AUTOCOMPACT_PCT_OVERRIDE
#50631FIXED in v2.1.129skillOverrides was a no-op in earlier builds — make sure you upgraded before measuring
#20412, #44112OPENNo per-client toggle for claude.ai connectors — --strict-mcp-config is the only documented escape hatch
#18370OPENTool Search auto mode sometimes fails to engage above its 10% threshold — set ENABLE_TOOL_SEARCH=true explicitly

The deeper takeaway: most of these are leak paths around an architecture that does support deferral. The fix is not "turn deferral off"; it's making sure deferral has nothing left to leak around. That is exactly what the three levers accomplish.


What Did Not Make the Cut

A few options I considered and rejected:

  • Switching to ANTHROPIC_API_KEY auth. Yes, the docs confirm that API-key sessions skip the claude.ai connector fetch. But you lose Max plan benefits. --strict-mcp-config gets the same context savings without the billing change.
  • Editing cachedGrowthBookFeatures.tengu_claudeai_mcp_connectors in ~/.claude.json. The server overwrites it on the next handshake — see #44112.
  • Setting every skill to off. Tempting for a clean /context printout, but you lose all auto-discovery; you have to remember every skill name. name-only is a kinder default.
  • Disabling autocompact. It is structurally hardcoded; the only knob is CLAUDE_AUTOCOMPACT_PCT_OVERRIDE which delays the trigger but does not free the reservation.

References

Official docs

Community write-ups that informed this article

Tools mentioned

If you go through the exercise and end up with a different cost distribution, I would genuinely like to hear about it — the leak paths shift with every Claude Code release, and the next 6 months of changelog entries will likely change which lever pays back the most.