3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

3000行ファイルでコンテキスト爆発した話と、MCP 100行で解決した話

3
Posted at

あるとき、Claude Code に 3000 行超の TypeScript ファイルを読ませた。「全部見て、依存関係を教えて」という指示だった。

Claude は丁寧に全文を読んだ。そしてセッションの半分以上のコンテキストを、そのファイル 1 本で消費した。

それ以降「コンテキスト不足」が出るのが早くなった。依存関係を答えてもらう前に、セッションが腐り始めた。これを何度か繰り返して、自分でMCPを作ることにした。


作ったもの: token-guardian MCP

Claude Code のファイル読み取りを 「トークンを意識した読み方」 に変えるMCPサーバー。

ツールは 5 本。

ツール 役割
read_smart 閾値を超えたファイルは自動でスケルトン(定義のみ)で返す
read_fragment 行範囲を指定してピンポイントで読む
map_dir_cost ディレクトリ全体のトークンコストを一覧表示
grep_surgical 正規表現検索をmax_matches制限付きで実行
find_symbol 関数・クラス・型の定義箇所を直接取得

一番使うのは read_smartmap_dir_cost の 2 本。まずこの 2 つで生活が変わった。


read_smart: 1500 トークンを超えたら自動でスケルトンに切り替える

const TOKEN_THRESHOLD = Number(process.env.TOKEN_GUARDIAN_THRESHOLD ?? 1500);

export async function readSmart(input: ReadSmartInput) {
  const content = await readFileContent(input.path);
  const tokenCount = countTokens(content);

  if (input.force_full || tokenCount <= TOKEN_THRESHOLD) {
    // フルで返す
    return { content: [{ type: "text", text: `[full] ${tokenCount} tokens\n\n${content}` }] };
  }

  // スケルトンモード: 関数・クラス・型の定義だけ返す
  const skeleton = extractSkeleton(input.path, content);
  return {
    content: [{
      type: "text",
      text: `[skeleton] ${countTokens(skeleton)} tokens (saved from ${tokenCount})\n\n${skeleton}`
    }]
  };
}

1500 トークン以下なら全文を返す。超えたら extractSkeleton が定義だけを抽出して返す。

「全部見たい」ときは force_full: true を渡せばいい。Claude はスケルトンを見て「ここを詳しく読みたい」と判断したら read_fragment で行範囲を指定する。自然な 2 ステップになった。


map_dir_cost: 読む前に「どのファイルが重いか」を把握する

[map_dir_cost] /Users/guchi/my-project
Files: 47 | Estimated total: ~185,000 tokens
────────────────────────────────────────────────
  src/app/page.tsx                         12.3KB  ~   3,000 tok
  src/lib/utils.ts                          8.1KB  ~   2,000 tok
  src/features/auth/auth-service.ts        52.4KB  ~  13,100 tok ⚠️
  src/features/data/data-processor.ts      61.2KB  ~  15,300 tok ⚠️
  node_modules/...                          (省略)

⚠️ マークが 1500 トークン超えのファイル。セッション開始時にこれを一発で確認できる。

今まで「なんか重いな」と感覚で感じていたものが、数字で見えるようになった。どのファイルを読ませるべきか、どこから攻めるかが決めやすくなった。


grep_surgical と find_symbol: 広域検索を捨てる

従来の Grep は「キーワードにマッチしたファイルを全部返す」。マッチが多いと数万トークンを軽く消費する。

grep_surgicalmax_matches で上限を設定できる。「最初の 5 件だけ見て判断する」という使い方ができる。

find_symbol はさらにシンプル。「getUserById がどこで定義されているか」を知りたいだけなら grep は不要で、シンボル名を渡すだけで定義ブロックが返ってくる。

find_symbol("getUserById") →

// src/lib/user-service.ts:142
export async function getUserById(id: string): Promise<User | null> {
  return db.user.findUnique({ where: { id } });
}

これで「定義を確認するためだけに 1 ファイル全部読む」がなくなった。


実装: MCP は意外と 100 行以下で作れる

「MCP サーバーを自作する」と聞くと難しそうに感じた。実際はそうでもなかった。

エントリポイント全体はこれだけ。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({ name: "token-guardian", version: "1.0.0" });

server.tool("read_smart", "...", readSmartSchema.shape, async (input) => readSmart(input));
server.tool("map_dir_cost", "...", mapDirCostSchema.shape, async (input) => mapDirCost(input));
// ...5ツール登録

const transport = new StdioServerTransport();
await server.connect(transport);

@modelcontextprotocol/sdk を入れて、McpServerserver.tool() で登録するだけ。各ツールの実装は「Zod でスキーマ定義 + 非同期関数」の 2 点セット。

特にハマるところはなかった。SDK が整備されていてドキュメント通りに動いた。


導入後: セッションが長持ちするようになった

導入前は、少し複雑な調査をするとすぐにコンテキストが 70〜80% に達していた。読ませすぎに気づかないまま、セッション後半で「コンテキスト不足」が出ることが多かった。

導入後は、同じ調査をしても消費が明らかに落ちた。スケルトンで全体像を掴んで、必要な部分だけ read_fragment で詳しく読む。この流れが定着してから、長いセッションでも終盤まで集中力が続く感覚がある。

「読む量を減らした」のに「理解の質は落ちていない」というのが想定外の発見だった。スケルトンで把握できることは思っていたより多かった。


動作環境

  • Claude Code(最新版推奨)
  • Node.js 18 以上
  • TypeScript ビルド環境(tscdist/ を生成)

リポジトリが公開され次第、git clonenpm install && npm run builddist/index.js が生成される。


Claude Code への設定

~/.claude/settings.json に追加する。

{
  "mcpServers": {
    "token-guardian": {
      "command": "node",
      "args": ["/path/to/token-guardian-mcp/dist/index.js"]
    }
  }
}

TOKEN_GUARDIAN_THRESHOLD 環境変数でスケルトンに切り替わる閾値を変えられる。デフォルトは 1500 トークン。小さいファイルが多いプロジェクトなら 2000 くらいに上げても良い。


まとめ

コンテキスト爆発の直接の原因は「Claude がファイルをどう読むか」ではなく、「そのファイルを読ませるかどうかの判断」にある。

token-guardian はその判断を補助するツール。map_dir_cost で重さを確認して、read_smart でコストを抑えながら読む。この 2 ステップを入れるだけで、セッションのコンテキスト消費パターンが変わった。

「でかいファイルを読ませてセッションが終わる」という体験を繰り返している人には試してみてほしい。MCP の実装自体も、思っているより難しくなかった。


リポジトリ

近日公開予定。公開でき次第こちらに追記します。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?