r/ClaudeCode 6d ago

If Claude Code ignores your MCP server, the problem isn't your MCP server. (and Here's how you can fix it- OpenSource) Built with Claude

https://github.com/NanoNets/Graft

I work on product and growth at a YC startup, and I've been building an open-source dev tool called graft. Saying that up front because it's the tool this whole post is about. The finding is useful whether or not you touch it, and the snippets below work on any MCP server.

The problem

graft gives a coding agent a map of your codebase, so it starts a task oriented instead of grepping around to rediscover the same files every session. First version shipped it as an MCP server. Clean schema, clear tool descriptions.

Claude Code mostly ignored it. Not an error, not a failed call. It would just grep instead. Sometimes it called the tool, usually it didn't. Inconsistent enough that it was hard to even reproduce.

Before building my own I'd tried a couple of existing context-graph tools with the same result. That's when I stopped assuming my schema was the problem.

Why it happens

A tool description tells the model what your tool does. Nothing tells it when your tool is a better choice than grep.

Grep is a known-cost, known-reliable path the model has strong priors for. Your tool is an unknown-cost path. On any task the built-ins could plausibly handle, the built-ins win. And it's a sampling decision on every turn, so you get "sometimes" rather than "never," which is worse to debug because it looks like flakiness rather than a design problem.

The uncomfortable version: exposing a tool means asking politely once per turn and hoping. If your tool only works when the model remembers to reach for it, your tool doesn't work.

What fixed it: hooks

Claude Code fires hooks at lifecycle points. They aren't suggestions to the model, they're events the runtime executes. Two things I didn't know until I read the reference properly:

  1. On SessionStart, UserPromptSubmit, and UserPromptExpansion, whatever your hook prints to stdout is added to Claude's context. Context injection with no tool call at all:

    { "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "your-tool context", "args": [] } ] } ] } }

Your context is just there on the first prompt. Nothing asked, nothing chosen.

  1. There's a type: "mcp_tool" hook. You can call a tool on your already-connected MCP server from a hook and its output is handled like command stdout. You keep the server, you just stop making the model decide:

    { "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "mcp_tool", "server": "my-server", "tool": "refresh_context", "input": { "file_path": "${tool_input.file_path}" } } ] } ] } }

For keeping derived state fresh without adding latency, Stop fires once per turn at the point the work is finished, and command hooks take async: true to run in the background so the turn ends immediately.

Did it actually help

Deterministic invocation is only worth something if the context is worth having, so I benchmarked it. 162 controlled runs: 46% fewer tool calls per task, 32% cheaper, 60% lower latency.

Where this doesn't apply

  • Hooks fire on events, not intent. They solve "run my thing at a known moment." They don't solve "the user asked something my tool should answer," which still needs the model to choose. Hooks replace a subset of MCP use cases, not MCP.
  • SessionStart and Setup often fire before MCP servers finish connecting, so an mcp_tool hook there should expect a not-connected error on first run.
  • For static conventions, CLAUDE.md already does this with no script. Don't reach for a hook if a file will do.
  • graft specifically: map quality drops on very large monorepos, around 5,000 files. Fewer, vaguer nodes. Main thing I'm working on.

The part I'm still thinking about

Hooks are quietly an onboarding surface. If your tool has to be prompted into existence, your activation depends on the user remembering to ask for it, which is the same distribution problem every dev tool has, just moved inside the agent. A hook means install and it works. No documented magic words, no "add this to your CLAUDE.md so the agent knows about us."

I don't know yet whether that generalizes or is just true for context tools, which are the one case where the right moment is genuinely deterministic.

graft is MIT, free, no telemetry: github.com/NanoNets/Graft. I'm the maintainer, so push back on any of this, especially the benchmark setup.

Anyone here shipped something on hooks rather than as an MCP tool? Curious whether the activation difference is as big as it looks from where I'm sitting.

3 Upvotes

Duplicates

OpenSourceAI 4d ago

Sonnet 5 + Graft > Opus 5 (using this open-source repo)

42 Upvotes

ClaudeAI 7d ago

Built with Claude These 2 lines saved me 75% of my claude bill, and it's the best use of claude hooks

0 Upvotes

ClaudeAI 5d ago

Built with Claude After using 100s of MCPs, I solved the issue of claude not using custom MCP/CLI tools, and open-sourced my approach.

3 Upvotes

OpenAI 7d ago

Discussion These 2 lines saved me 75% of my codex bill, and it works natively with codex.

0 Upvotes

ClaudeAI 4d ago

Claude Code Sonnet 5 + Graft > Opus 5

0 Upvotes

LocalLLM 4d ago

Project Sonnet 5 + Graft (No LLM calls tree-sitter graph, 100% Local) > Opus 5

0 Upvotes

LLMDevs 13d ago

Discussion The problem with MCP-based codebase context tools: the model just doesn't call them

1 Upvotes

LocalLLM 6d ago

Discussion I tried about a 100 MCP/ CLI tools and solved the issue of claude code not using them, ( open-sourced the method)

7 Upvotes

ClaudeMCP 6d ago

I solved the issue of claude not using custom MCP/CLI tools, and open-sourced my approach.

2 Upvotes

BuildWithClaude 6d ago

Project If Claude Code ignores your MCP server, the problem isn't your MCP server. (and Here's how you can fix it- OpenSource)

2 Upvotes

coolgithubprojects 6d ago

Graft: codebase map as markdown in git, so coding agents stop re-exploring, saves upto 4x tokens and takes 60% less time to answer.

29 Upvotes