lsmcp - 汎用的な LSP の MCP サーバーを作った
前回は TS に特化した MCP サーバーを作ったが、今回 LSP (Language Server Protocol) を使って任意の言語に対応できるように一般化するのに成功した。
https://github.com/mizchi/lsmcp
TL;DR
- typescript-mcp の TypeScript 特化機能を LSP ベースに一般化
- Rust、Python、Go、Moonbit など任意の LSP と連携可能
-
npx -y @mizchi/lsmcp --bin="rust-analyzer"
のように LSP プロセスを握って起動するので、任意の言語に対応可能 - (実際にリネームや Auto Import のような各コマンドに対応する操作ができるかは、LSP 側の実装に依存)
-
実際に Rust プロジェクトで動いている例:
# Rust でのシンボルリネーム
● lsmcp:rename_symbol (MCP)(root: "~/rust-project",
filePath: "src/main.rs",
line: 15,
oldName: "calculate",
newName: "compute")
⎿ Successfully renamed symbol "calculate" to "compute"
Updated 3 locations across 2 files
Why
Claude Code は vscode 連携があり、VSCode の Problems や今見てるファイルのコンテキストを取り出せる。
ところで Claude Code を使い潰すには、 tmux や zellij でヘッドレスで並列に動かしたい。このとき、人間は見てないしエディタも起動してないので、ビルトインの ide:*
ツールが使えず、型チェックや Lint が貧弱になる。
というわけで、ヘッドレスでも自律的に LSP の情報にアクセスしたい。その層を作った。
使い方
1. 言語サーバーのインストール
LSP 自体はそれぞれの実装を使うので、インストールしておく必要はある。
# TypeScript
npm add typescript typescript-language-server
# TSGO
npm add @typescript/native-preview
# Rust
rustup component add rust-analyzer
手元で動作確認した言語
- typescirpt
- @typescirpt/native-preview
- deno
- moonbit
- rust
- python
- go
2. プロジェクトの初期化
lsmcp はグローバルな MCP 設定ではなく、プロジェクト固有の設定として使用することを推奨。起動するとプロセスが常駐する。
# TypeScript(内蔵サポート)
claude mcp add typescript npx --scope local -- -y @mizchi/lsmcp --language=typescript
# その他の言語(--bin で LSP コマンドを指定)
claude mcp add rust npx --scope local -- -y @mizchi/lsmcp --bin="rust-analyzer"
claude mcp add python npx --scope local -- -y @mizchi/lsmcp --bin="pylsp"
claude mcp add go npx --scope local -- -y @mizchi/lsmcp --bin="gopls"
claude mcp add deno npx --scope local -- -y @mizchi/lsmcp --bin="deno lsp"
これらのコマンドは、カレントディレクトリの .mcp.json
にサーバー設定を追加する。
3. Claude との連携
$ claude
# MCP サーバーの起動確認
╭─────────────────────────────────────────────────────╮
│ Manage MCP Servers │
│ 1 servers found │
│ │
│ lsmcp · connected │
╰─────────────────────────────────────────────────────╯
主な機能
標準 LSP ツール(全言語共通)
-
lsmcp_get_definitions
- 定義へジャンプ -
lsmcp_find_references
- 参照検索 -
lsmcp_get_hover
- ホバー情報 -
lsmcp_rename_symbol
- シンボルのリネーム -
lsmcp_get_diagnostics
- 診断情報 -
lsmcp_get_code_actions
- コードアクション -
lsmcp_format_document
- コードフォーマット -
lsmcp_get_completion
- コード補完 -
lsmcp_get_signature_help
- シグネチャヘルプ -
lsmcp_get_document_symbols
- ドキュメントシンボル -
lsmcp_get_workspace_symbols
- ワークスペースシンボル
TypeScript/JavaScript 拡張ツール
-
lsmcp_move_file
- ファイル移動(インポート自動更新) -
lsmcp_move_directory
- ディレクトリ移動 -
lsmcp_delete_symbol
- シンボル削除 -
lsmcp_search_symbols
- 高速シンボル検索 -
lsmcp_find_import_candidates
- インポート候補検索 -
lsmcp_get_type_at_symbol
- 型情報取得 -
lsmcp_get_module_symbols
- モジュールエクスポート一覧
今後の展望
現状の課題は、AI がコードベースを理解してタスクを実行するまでに、まだ多くの試行錯誤が必要なこと。定義ジャンプ、参照検索、型チェックなどを何度も繰り返し、ようやく目的のコードに辿り着く。
これをいい感じの操作セットやプロンプトを与えて、手戻りなく高速に操作できるようにしたい。機能があっても、脳が足りてない状態。
まとめ
LSP という既存の標準を活用することで、車輪の再発明を避けつつ、AI エージェントに必要な機能を提供できるようになった。
おまけの考察
LSP という視点でみたときに、これらはプロジェクト、ファイル、カーソル位置におけるアクションという命令セットになっているのだが、実際 AI 向けには非効率なのではという直感を得ている。
ワードカウントが苦手な AI に向けにカーソル位置を計算するのは無駄だし、参照可能なシンボルは事前にインデックスを作っておきたい。ファイルを分割するのは人間の都合でしかなく、コードの書き換えは文字列置換ではなく、その木構造のスワップとして行うべきだと思う。
プログラムのテキスト表現は、人間が閲覧する用の情報が圧縮されたインターフェースであり、内部表現までそうである必要はない。
という発想で言語自体を再発明したほうがいいのではと考えていたところ、Unison というドンピシャな言語を見つけた。めちゃくちゃ面白いので、しばらくこれを調べてみようと思っている。
簡単に紹介すると Unison ではコードはファイルではなくデータベースで管理されている。コードは自己参照ハッシュによって保存され、コードの書き換えは、単に新しいハッシュ値を指すだけ。
手元に Unison MCP という、Unison を操作するエージェント用インターフェースをすでに実装してある。もうちょっと練ったら後で紹介したい。
Discussion