はじめに
Claude Code や Cursor でコーディングエージェントを使っていると、grep(ripgrep)の検索結果がノイズだらけでトークンを浪費する場面に遭遇します。Serena MCP という LSP ベースの MCP サーバーを導入してみたところ、シンボルレベルの検索で grep との差がかなり出たので、比較結果を共有します。
想定読者
- Claude Code / Cursor を日常的に使っているエンジニア
- MCP サーバーの導入を検討している方
- コーディングエージェントのトークン効率や精度を改善したい方
参考:公式ドキュメント
https://github.com/oraios/serena
https://oraios.github.io/serena/
grep で困ること
Claude Code のビルトインツール(Grep, Glob, Read, Edit)は小規模プロジェクトなら十分に使えます。ripgrep は速いですし、正規表現も効きます。
ただ、コードベースが大きくなってくると困ることがあります。TypeScript プロジェクトで User を検索するとこうなります。
src/models/user.ts:3: export interface User {
src/models/user.ts:15: export interface UserProfile extends User {
src/services/userService.ts:8: async getUser(id: string): Promise<User> {
src/services/userService.ts:22: // User validation logic
src/controllers/auth.ts:5: import { User } from '../models/user';
src/controllers/auth.ts:31: const user: User = await userService.getUser(req.params.id);
src/tests/user.test.ts:12: const mockUser: User = { id: '1', name: 'Test User' };
src/tests/user.test.ts:15: // Test User creation
src/config/strings.ts:8: USER_NOT_FOUND: 'User not found',
9件ヒット。でも LLM が欲しいのは User インターフェースの定義だけだったりします。残りの8件はノイズで、それぞれがトークンを消費します。
grep はテキストを検索するツールなので、「どれが定義で、どれが参照で、どれがコメントか」の区別はできません。LLM がそこを推測するわけですが、推測にはトークンが要りますし、間違えることもあります。
Serena MCP とは
この問題に対して「LSP を使えばいい」というアプローチを取った MCP サーバーです。MIT ライセンスで完全無料、GitHub Stars は2026年3月時点で約21,900。
Microsoft の multilspy をベースに独自開発した Solid-LSP ライブラリで言語サーバーを統合し、MCP プロトコル経由でシンボル操作を LLM に公開しています。対応言語は30以上。
公式の設計思想はこう書かれています。
エージェントはファイル全体を読んだり、grep ライクな検索や基本的な文字列置換をする必要がなくなる
中核となるツールはこの4つです。
find_symbol: シンボル名パスで検索。型情報・hover 情報付きfind_referencing_symbols: 指定シンボルを参照している全シンボルを追跡get_symbols_overview: ファイル内のシンボル構造を俯瞰rename_symbol: LSP rename でコードベース全体をリネーム
特徴的なのが name_path というアドレッシング方式です。ファイルパスでもクラス名でもなく、シンボルツリー上のパスでコード要素を特定します。
"UserService/getUser" → UserService クラスの getUser メソッド
"User" → User という名前の全シンボル
"/models/User" → models 配下の User(完全パス指定)
grep と Serena を比較してみる
TypeScript プロジェクトを想定した具体例で、grep と Serena の違いを見ていきます。以下の MCP 呼び出し例は Serena のツール仕様に基づく想定シナリオです(実際のレスポンス形式はプロジェクトや言語サーバーによって若干異なります)。
ケース1: User インターフェースの定義を探す
grep の場合:
rg "User" --type ts
先ほどの9件がヒットします。コメント、文字列リテラル、変数名、型参照が全部混ざっています。
Serena の場合:
Claude Code で「User インターフェースの定義を探して」と指示すると、Serena は内部でこういう MCP 呼び出しを実行します。
{
"name": "find_symbol",
"arguments": {
"name_path_pattern": "User",
"include_kinds": [11],
"depth": 1,
"include_body": false,
"include_info": true
}
}
include_kinds=[11] は LSP の SymbolKind で Interface を指定しています(Class=5, Method=6, Property=7, Interface=11 — LSP 仕様参照)。レスポンスはこんな感じ。
{
"symbol": "User",
"kind": "Interface",
"location": "src/models/user.ts:3-7",
"info": "interface User { id: string; name: string; email: string }",
"children": [
{"name": "id", "kind": "Property", "type": "string"},
{"name": "name", "kind": "Property", "type": "string"},
{"name": "email", "kind": "Property", "type": "string"}
]
}
grep が9件返すところを1件で済んでいます。トークン消費の差はかなりのものです。
ケース2: getUser の呼び出し元を追跡する
grep の場合:
rg "getUser\(" --type ts
getUser( を含む行が返ります。ここで問題になるのが同名メソッドの存在です。UserService.getUser と AdminService.getUser が両方ヒットしますが、grep ではどちらの呼び出しか区別できません。
Serena の場合:
Claude Code に「UserService の getUser を呼んでいる箇所を全部見せて」と指示するとこうなります。
{
"name": "find_referencing_symbols",
"arguments": {
"name_path": "UserService/getUser"
}
}
{
"referencing_symbols": [
{"name": "AuthController/login", "location": "src/controllers/auth.ts:31"},
{"name": "UserController/getProfile", "location": "src/controllers/user.ts:15"}
]
}
LSP の参照解析が走って、型解決済みの参照だけが返ります。AdminService.getUser の呼び出し元は含まれません。これは grep では原理的に不可能な操作です。
ケース3: getUser を fetchUser にリネームする
grep + sed の場合:
rg "getUser" --type ts -l | xargs sed -i 's/getUser/fetchUser/g'
getUser という文字列を全ファイルで置換します。コメント内の // getUser is deprecated も変わりますし、getUserResult のような部分一致も壊れます。
Serena の場合:
rename_symbol("UserService/getUser", new_name="fetchUser")
LSP の rename 機能が全参照箇所を正確に特定して、意味的に正しいリネームを実行します。コメントや文字列リテラルは変えません。部分一致も壊しません。
リファクタリングに関しては差が大きいです。grep + sed は壊れない保証がないので。
トークン効率はどれくらい変わるのか
正直なところ、Serena 公式は定量ベンチマークを公開していません。トークン消費量・正答率・所要時間の3軸で自前計測するのが理想ですが、公開データはトークン消費量の参考値に限られます。正答率と所要時間はプロジェクトの性質に大きく依存するので、各自の環境で計測するのがよさそうです。
参考になるデータを並べておきます。
| 計測元 | 手法 | 結果 |
|---|---|---|
| azukiazusa.dev 実測 | リファクタリングタスクで Serena あり/なし比較 | 27,453 → 約9,151トークン(約66%削減) |
| jCodeMunch ベンチマーク(※tree-sitter ベース、参考値) | 50回 A/B テスト | 成功率 72%→80%、コスト6%削減、トークン10.5%削減 |
ただし、見落としがちなトレードオフがあります。
Serena のツール定義自体が約23,878トークンを消費します。 29個のツールスキーマがコンテキストウィンドウを占有するんですよね。serena-slim というラッパーを使えば50.3%削減(約12,000トークン)できますが、無視はできない量です。
つまりこういう構図です。
- 大規模プロジェクト: 検索結果の圧縮効果がツール定義のオーバーヘッドを上回る → Serena が有利
- 小規模プロジェクト: ファイル全体を Read しても大したトークン量にならない → grep で十分
個人的には、境界線は「ファイル数50〜100」あたりかなという感覚です。
Claude Code へのセットアップ
前提条件は uv(Astral 製の Python パッケージマネージャー)だけです。未導入の場合は以下でインストールできます。
curl -LsSf https://astral.sh/uv/install.sh | sh
なお、Serena は Python 3.11 が必要ですが(3.12 以上は非対応)、uvx 経由で実行すれば自動的にハンドリングされます。
プロジェクト単位で追加:
claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)"
グローバルに追加する場合:
claude mcp add --scope user serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context=claude-code --project-from-cwd
--context claude-code は必ず付けてください。 これを指定すると、Claude Code の組み込みツールと重複する Serena のツール5つ(read_file, create_text_file, execute_shell_command, replace_content, prepare_for_new_conversation)が自動的に除外され、LSP シンボルツールに特化した構成になります。
さらに、このコンテキストには LLM への行動指示も埋め込まれています。
ファイル全体を読むことは厳に避けよ。コードの探索と読み取りには Serena のシンボルツールを優先せよ。
これ結構大事で、--context claude-code を付けないと重複ツールが有効化されて、LLM が Read と read_file のどっちを使えばいいか迷って非効率になったりします。
接続確認は claude mcp list で。serena の Status が connected になっていれば OK です。
接続できたら、Claude Code でこう指示してみてください。
> このプロジェクトの主要なクラスを find_symbol で一覧して
Serena がシンボル一覧を返してくれれば、セットアップ完了です。
初回起動時に Serena がプロジェクト構造を解析し、.serena/project.yml を自動生成します。TypeScript や Python のプロジェクトであれば、基本的にデフォルト設定のまま使えます。言語サーバーの個別設定が必要な場合は 公式の設定ドキュメント を参照してください。
ハマりポイント
実際に使ってみて気づいた注意点をいくつか挙げておきます。
コンテキストコンパクション問題。 Claude Code は長いセッションでコンテキストを自動圧縮しますが、この過程で Serena ツールの存在を「忘れる」ことがあります。突然 grep に戻り始めたら、「Serena の find_symbol を使って」と明示的に指示するか、セッションを区切るのがよいです。
stdout が ERROR 表示される。 MCP の仕様上、サーバーの stdout 出力がすべて ERROR レベルで表示されます。初めて見ると焦りますが、実際にはエラーではないケースがほとんどです。
find_referencing_symbols の信頼性。 言語サーバーの実装品質に依存します。Java(Eclipse LSP)で参照が見つからないケースが報告されています。TypeScript(tsserver)や Python(Pylsp)は比較的安定している印象です。
静的解析の限界。 動的な型生成、リフレクション、メタプログラミングで生成されるシンボルは追跡できません。ここは grep の方が強いです。
じゃあいつ Serena を使うのか
Serena は grep の上位互換ではなく、補完です。Serena 自身も search_for_pattern という grep 相当のツールを内蔵しています。テキスト検索が要らなくなるわけではありません。
使い分けの目安はこんな感じです。
grep で十分なケース: * ファイル数50以下、新規開発 * 設定値・ログメッセージの検索 * 正規表現でのパターンマッチ
Serena が向いているケース: * 大規模コードベースのリファクタリング * 同名シンボルの区別が必要な場面 * 参照追跡・依存関係分析 * API 課金でトークンコスト最適化が重要な場合
Serena の開発チームが lessons_learned.md にこう書いています。
LLM は行カウントが苦手で、編集後に行番号が変わることも理解しにくい。我々はシンボル名ベースの編集に転換した。
grep が返す「行」ではなく、LSP が返す「シンボル」で考える。興味がある方は、まず手元のプロジェクトで試してみてください。
手順をまとめるとこうなります。
uvをインストールclaude mcp addで Serena を追加(--context claude-codeを忘れずに)claude mcp listで接続確認find_symbolで主要クラスを検索してみる- 同じ検索を grep でもやって、ヒット数やトークン消費の差を確認する
自分のプロジェクトでトークン削減効果を実測してみると、導入するかどうかの判断がしやすくなります。
関連情報
- oraios/serena — GitHub — 公式リポジトリ
- Serena 公式ドキュメント — セットアップガイド・ツールリファレンス
- serena-slim — ツール定義のトークン50.3%削減ラッパー
- Serena を試してみた(azukiazusa.dev) — トークン効率の実測レポート
- jCodeMunch ベンチマーク — シンボルレベル検索 vs grep の定量比較(tree-sitter ベース)