Claude Codeプラグインに独自のLSPサーバーを組み込む
公式マーケットプレイスに無い言語向けに、.lsp.jsonでLSPサーバーを自作プラグインへ組み込む手順と、バージョン依存の落とし穴を解説します。
TypeScript・Python・Rustなど主要言語なら、公式マーケットプレイスの/pluginから検索するだけでLSPプラグインが手に入ります。ですが対応言語に無いマイナー言語や社内DSLでコード補完や型チェックをClaudeに使わせたいなら、自分のプラグインに.lsp.jsonを1つ足すだけで対応できます。バイナリの手配とバージョンごとの挙動差だけ押さえておけば、仕組み自体はシンプルです。
LSPサーバーをプラグインに組み込むとは何か
LSP(Language Server Protocol)は、VS Codeのコード補完を支えているのと同じプロトコルです。プラグインがLSPサーバーを提供すると、Claude Codeはそのプロジェクトを編集している間ずっと言語サーバーと接続し続け、Claudeに実時間のコードインテリジェンスを与えます。
得られる能力は2つに分かれます。自動診断は、Claudeが編集するたびに言語サーバーがエラーや警告を返す仕組みで、コンパイラやリンターを手動で走らせなくても型エラーやimportの欠落にClaudeが気づけます。コード操作は、定義へのジャンプ・参照の検索・ホバーでの型情報取得・シンボル一覧・実装の検索・呼び出し階層の追跡で、grepベースの検索より正確な移動ができます。診断結果を自分の目で見たいときは、Found 3 new diagnostic issues in 2 filesのような表示が出たタイミングでCtrl+Oを押します。
まず公式マーケットプレイスを確認する
自作する前に、対応言語がすでに公式マーケットプレイスにないか確認します。/pluginのDiscoverタブで「lsp」と検索すると、次のような主要言語がヒットします。
| 言語 | プラグイン | 必要なバイナリ |
|---|---|---|
| Python | プラグインpyright-lsp | 必要なバイナリpyright-langserver |
| TypeScript | プラグインtypescript-lsp | 必要なバイナリtypescript-language-server |
| Rust | プラグインrust-analyzer-lsp | 必要なバイナリrust-analyzer |
| Go | プラグインgopls-lsp | 必要なバイナリgopls |
| Java | プラグインjdtls-lsp | 必要なバイナリjdtls |
| C/C++ | プラグインclangd-lsp | 必要なバイナリclangd |
| C# | プラグインcsharp-lsp | 必要なバイナリcsharp-ls |
| Kotlin | プラグインkotlin-lsp | 必要なバイナリkotlin-language-server |
| Lua | プラグインlua-lsp | 必要なバイナリlua-language-server |
| PHP | プラグインphp-lsp | 必要なバイナリintelephense |
| Swift | プラグインswift-lsp | 必要なバイナリsourcekit-lsp |
プラグインは言語サーバーへの接続方法を設定するだけで、サーバー本体は含みません。表のバイナリを先にインストールしてからプラグインを入れます。すでにバイナリが入っている状態でプロジェクトを開くと、Claudeが対応プラグインのインストールを提案してくることもあります。
独自のLSPサーバーを追加する
対応言語に無い場合だけ、自分のプラグインに.lsp.jsonを追加します。
ステップ1 — .lsp.jsonを書く
プラグインのルートに.lsp.jsonを置き、言語名をキーにした設定を書きます。
{
"go": {
"command": "gopls",
"args": ["serve"],
"extensionToLanguage": {
".go": "go"
}
}
}command(PATHに存在する実行ファイル名)とextensionToLanguage(拡張子から言語識別子へのマップ)が必須フィールドです。単体のプラグインしか作らないなら、.lsp.jsonを別ファイルにせずplugin.jsonにlspServersキーとしてインラインで書くこともできます。
ステップ2 — オプションフィールドで挙動を調整する
必須の2つに加えて、次のフィールドで細かい挙動を制御できます。
| フィールド | 役割 |
|---|---|
args | 役割サーバー起動時のコマンドライン引数 |
env | 役割起動時に設定する環境変数 |
initializationOptions | 役割初期化時にサーバーへ渡すオプション |
settings | 役割workspace/didChangeConfiguration経由で渡す設定 |
startupTimeout | 役割起動待ちの上限時間(ミリ秒) |
shutdownTimeout | 役割正常終了待ちの上限時間。超えるとClaude Codeがプロセスを強制終了する |
restartOnCrash | 役割クラッシュ後に再起動するか。既定はtrue |
maxRestarts | 役割再起動を試みる上限回数 |
diagnostics | 役割編集後の診断をClaudeの文脈へ自動投入するか。既定はtrue |
workspaceFolder | 役割サーバーに渡すワークスペースのルートパス |
transport | 役割サーバーとの通信方式の指定(下記参照) |
transportフィールドはstdio(既定)かsocketを選べますが、Claude Codeはsocketを受け付けても実際には全サーバーをstdio経由で動かします。指定を変えても通信方式そのものは変わりません。
restartOnCrashとshutdownTimeoutはClaude Code v2.1.205以降が必須です。それより前のバージョンでは、この2つのどちらかを設定した瞬間、設定スキーマ自体は受け付けるものの、起動時にそのLSPサーバーがまるごとスキップされます。理由はclaude --debugの出力でしか確認できず、通常の/plugin Errorsタブにも出ません。古いバージョンとの互換性を気にする配布物では、この2つのフィールドを外しておくのが安全です。
ステップ3 — 動作確認する
プラグインを有効にした状態でClaude Codeを起動し、/pluginのErrorsタブを確認します。バイナリが見つからない場合はExecutable not found in $PATHが表示され、commandやextensionToLanguageが欠けているような無効な設定は、エラーを出さずそのサーバーだけスキップされます。スキップされた理由を知りたいときはclaude --debugで起動し直します。
プラグインが実際にどのLSPサーバーを提供しているか一覧で確認したいときは、claude plugin details <name>を実行します。Skills・Agents・Hooks・MCPサーバーと並んでLSP serversの数が表示され、セッションに常時追加されるトークン数の見積もりも併せて出ます。設定した.lsp.jsonが意図通り認識されているかどうかは、まずこの棚卸しで確認すると早く気づけます。
複数サーバーが同じ拡張子を取り合ったらどうなるか
同じ拡張子を複数の有効なLSPサーバーがextensionToLanguageで宣言している場合、1つのプラグインからでも複数のプラグインにまたがっていても、最初に登録されたサーバーだけがその拡張子のファイルを処理し、残りは起動すらしません。/pluginの画面には、どのプラグインのサーバーが有効になっているかを示す警告が出ます。設定が無効でスキップされたサーバーはこの競合に参加しないため、同じ拡張子を宣言する別の(同じプラグインでも別プラグインでも)有効なサーバーがあれば、そちらが引き継いで処理します。
ログは標準エラー出力に出す
Claude Codeはサーバーの標準出力をプロトコルメッセージ専用の経路として読みます。メッセージヘッダーは64KiB、本文は32MiBまでを受け付け、どちらかを超えるか標準出力にプロトコル以外の出力を書き込むと、Claude Codeはそのサーバーとの接続を切断します。この切断はrestartOnCrashとmaxRestartsの判定上もクラッシュ扱いです。デバッグ用のログは標準エラー出力に書き、--debugを付けて起動すれば原因を特定するエラーがデバッグログに残ります。
変数とユーザー設定値を起動コマンドに渡す
.lsp.jsonのcommand・args・env・workspaceFolderには、Claude Codeが用意する3つのパス変数をそのまま埋め込めます。プラグインに同梱したバイナリを指すなら${CLAUDE_PLUGIN_ROOT}(プラグインのインストール先)、node_modulesのような依存関係を永続化したいなら更新をまたいで残る${CLAUDE_PLUGIN_DATA}、プロジェクト側のパスを参照したいなら${CLAUDE_PROJECT_DIR}を使います。
利用者ごとに変わる値(社内APIのエンドポイントや認証トークンなど)をハードコードしたくない場合は、plugin.jsonのuserConfigでフィールドを定義しておくと、プラグイン有効化時にClaude Codeが入力ダイアログを出してくれます。定義した値は${user_config.KEY}という形で.lsp.jsonの設定にそのまま埋め込めるので、envにトークンを渡すような使い方ができます。sensitive: trueを付けた値はmacOSのKeychain(無ければ~/.claude/.credentials.json)に保存され、ダイアログの入力時にはマスクされます。
プロジェクトスコープのプラグイン(リポジトリにチェックインして共同開発者にも配るタイプ)の場合、LSPサーバーはワークスペースの信頼ダイアログを承認するまで起動しません。個人スコープ(~/.claude/skills/相当の置き場所)のプラグインにはこの制限がないため、社内で配布するプラグインを設計するときは、想定するスコープによって初回起動のタイミングが変わる点を踏まえておきます。
使い分け早見表 — 既存プラグインか自作か
| 状況 | 選択肢 |
|---|---|
| 主要言語(Python/TypeScript/Rust/Go等)を使っている | 選択肢公式マーケットプレイスの既製プラグインをインストール |
| マイナー言語だがLSP準拠のサーバーが存在する | 選択肢自分のプラグインに.lsp.jsonを追加する |
| その言語のLSPサーバー自体が存在しない | 選択肢現状この機能では対応できない。サーバーが登場するまで待つ必要がある |
自作プラグインにLSPサーバー以外の要素(Skills・Hooks・MCPサーバー)も含めたい場合の全体構成は、Claude Codeプラグイン完全ガイドにまとまっています。
よくあるつまずき
Executable not found in $PATHが出る: プラグインは接続方法しか設定しません。表のバイナリを別途インストールしますrestartOnCrashを設定したらサーバーごと起動しなくなった: Claude Code v2.1.205より前では、このフィールドの存在自体がサーバー全体のスキップを招きます。claude --debugで確認します- 狙った拡張子が別のプラグインに取られる: 最初に登録されたサーバーが優先されます。
/pluginの警告でどちらが有効か確認します - ログを仕込んだのに反映されない、むしろ接続が切れる: 標準出力へのログ出力はプロトコル違反として切断されます。標準エラー出力に切り替えます
- プラグインを更新してもLSPサーバーが古いバージョンのまま: セッション中の更新は、
/reload-pluginsを実行するまでhooks・MCPサーバーと同様に旧バージョンのパスを使い続けます
LSPサーバー固有の問題以外に、プラグイン自体が読み込めない事故に遭遇したら「archive integrity check」エラーの対処も合わせて確認します。
よくある質問
対応言語がすでにマーケットプレイスにある場合でも自作すべきですか
その必要はありません。公式ドキュメントも、既製プラグインが無い言語のためだけに自作を勧めています。主要言語は先にマーケットプレイスを確認します。
LSPサーバー本体をプラグインに同梱すればユーザーの手間を減らせますか
.lsp.jsonはサーバーへの接続方法を設定するだけで、サーバー本体を配布する仕組みではありません。利用者は言語サーバーのバイナリを自分の環境に別途インストールする必要があります。
transportをsocketにすればソケット通信に切り替わりますか
切り替わりません。Claude Codeはsocketという値を受け付けはしますが、実際にはすべてのLSPサーバーをstdio経由で動かします。標準出力をプロトコル専用に保つルールは、この設定に関わらず常に適用されます。
まとめ
独自のLSPサーバーを組み込む作業は、.lsp.jsonにcommandとextensionToLanguageを書くだけなら数分で終わります。手間がかかるのは、バージョンごとの挙動差(restartOnCrashのv2.1.205境界)、同じ拡張子を争う競合、標準出力をログに使ってしまう事故といった、公式の一覧には表として並んでいない落とし穴の側です。先に公式マーケットプレイスを確認し、無ければ.lsp.json、というシンプルな判断順で進めれば手戻りは最小限で済みます。プラグイン化した機能をコマンドとして呼び出したい場合は、Claude Code Skills完全ガイドでSkillsとの組み合わせ方も確認できます。