Qwen Code Companion Plugin: Interface Specification
最終更新: 2025年9月15日
このドキュメントは、Qwen CodeのIDEモードを有効にするためのコンパニオンプラグインを構築するための契約を定義します。VS Codeでは、これらの機能(ネイティブの差分表示、コンテキスト認識)が公式拡張機能(marketplace )によって提供されています。この仕様書は、JetBrains IDEやSublime Textなどの他のエディタに同様の機能を提供したいコントリビューター向けです。
I. 通信インターフェース
Qwen CodeとIDEプラグインは、ローカル通信チャネルを通じて通信します。
1. トランスポート層: HTTP上のMCP
プラグインは、Model Context Protocol (MCP) を実装するローカルHTTPサーバーを実行しなければなりません。
- プロトコル: サーバーは有効なMCPサーバーである必要があります。可能であれば、使用している言語向けの既存のMCP SDKの利用を推奨します。
- エンドポイント: サーバーは、すべてのMCP通信のために単一のエンドポイント(例:
/mcp)を公開する必要があります。 - ポート: サーバーは動的に割り当てられたポート(つまり、ポート
0)でリッスンしなければなりません。
2. 検出メカニズム:ポートファイル
Qwen Code が接続するには、自分がどの IDE インスタンス上で動作しているか、そしてサーバーがどのポートを使用しているかを検出する必要があります。プラグインは「検出ファイル(discovery file)」を作成することで、この情報を提供しなければなりません。
-
CLI がファイルを見つける方法: CLI はプロセスツリーを走査して、自身が実行されている IDE のプロセス ID(PID)を特定します。次に、その PID をファイル名に含む検出ファイルを探します。
-
ファイルの保存場所: ファイルは特定のディレクトリに作成する必要があります:
os.tmpdir()/qwen/ide/。このディレクトリが存在しない場合、プラグイン側で作成してください。 -
ファイル名の命名規則: ファイル名は非常に重要であり、以下のパターンに厳密に従う必要があります:
qwen-code-ide-server-${PID}-${PORT}.json${PID}:親プロセスである IDE のプロセス ID。プラグインはこの PID を取得し、ファイル名に含める必要があります。${PORT}:MCP サーバーが listen しているポート番号。
-
ファイル内容とワークスペースの検証: ファイルには以下の構造を持つ JSON オブジェクトが含まれている必要があります:
{ "port": 12345, "workspacePath": "/path/to/project1:/path/to/project2", "authToken": "a-very-secret-token", "ideInfo": { "name": "vscode", "displayName": "VS Code" } }port(数値、必須):MCP サーバーのポート番号。workspacePath(文字列、必須):現在開いているワークスペースのルートパスを OS 固有の区切り文字で連結したもの(Linux/macOS では:、Windows では;)。CLI はこのパスを使って、自身が実行されているプロジェクトフォルダが IDE 上で開かれているものかどうかを確認します。もし CLI のカレントディレクトリがworkspacePath内のいずれのサブディレクトリにも含まれない場合、接続は拒否されます。プラグインは正しい絶対パスを提供しなければなりません。authToken(文字列、必須):接続を保護するためのシークレットトークン。CLI はすべてのリクエストにおいて、このトークンをAuthorization: Bearer <token>ヘッダーに含めます。ideInfo(オブジェクト、必須):IDE に関する情報。name(文字列、必須):IDE を識別する短い小文字の名前(例:vscode、jetbrains)。displayName(文字列、必須):ユーザーに表示される IDE 名(例:VS Code、JetBrains IDE)。
-
認証: 接続の安全性を確保するために、プラグインは一意かつシークレットなトークンを生成し、それを検出ファイルに含める必要があります。CLI は、MCP サーバーへのすべてのリクエストにおいて、このトークンを
Authorizationヘッダーに含めて送信します(例:Authorization: Bearer a-very-secret-token)。サーバー側では、すべてのリクエストでこのトークンを検証し、不正なアクセスは拒否しなければなりません。 -
環境変数による優先判定(推奨): 最も安定した動作を実現するために、プラグインは検出ファイルの作成に加えて、統合ターミナル内で
QWEN_CODE_IDE_SERVER_PORT環境変数を設定することを推奨します。ファイルは主な検出手段ですが、環境変数は複数の候補から正しいものを選ぶために重要です。ユーザーが同じワークスペースに対して複数の IDE ウィンドウを開いている場合、CLI はQWEN_CODE_IDE_SERVER_PORT変数を使って、どのウィンドウのサーバーに接続すべきかを判断します。
II. Context Interface
コンテキスト認識を有効にするために、プラグインはCLIにIDE内でのユーザーのアクティビティに関するリアルタイム情報を提供してもよい。
ide/contextUpdate Notification
プラグインは、ユーザーのコンテキストが変更されるたびに、CLI に対して ide/contextUpdate notification を送信してもよい(MAY)。
-
トリガーイベント: この通知は以下の場合に送信する必要があります(推奨デバウンス時間:50ms):
- ファイルが開かれた、閉じられた、またはフォーカスされたとき。
- アクティブなファイルでカーソル位置またはテキスト選択範囲が変更されたとき。
-
ペイロード(
IdeContext): 通知のパラメータはIdeContextオブジェクトでなければなりません(MUST):interface IdeContext { workspaceState?: { openFiles?: File[]; isTrusted?: boolean; }; } interface File { // ファイルの絶対パス path: string; // 最後にフォーカスされた Unix タイムスタンプ(順序付け用) timestamp: number; // 現在フォーカスされているファイルであれば true isActive?: boolean; cursor?: { // 1ベースの行番号 line: number; // 1ベースの文字番号 character: number; }; // ユーザーが現在選択しているテキスト selectedText?: string; }注意:
openFilesのリストには、ディスク上に存在するファイルのみを含める必要があります。仮想ファイル(例:パスを持たない未保存ファイルやエディタ設定ページなど)は除外しなければなりません(MUST)。
CLIがこのコンテキストをどのように使用するか
IdeContextオブジェクトを受け取った後、CLIは情報をモデルに送信する前に、いくつかの正規化および切り捨て処理を行います。
- ファイルの順序: CLIは
timestampフィールドを使用して、最も最近使用されたファイルを判断します。この値に基づいてopenFilesリストをソートします。そのため、プラグインではファイルが最後にフォーカスされた時刻の正確なUnixタイムスタンプを提供しなければなりません。 - アクティブファイル: CLIは(ソート後の)最も新しいファイルのみを「アクティブ」ファイルとみなします。他のすべてのファイルの
isActiveフラグは無視され、それらのcursorおよびselectedTextフィールドはクリアされます。プラグインでは、現在フォーカスされているファイルに対してのみisActive: trueを設定し、カーソル/選択範囲の詳細を提供することに集中してください。 - 切り捨て: トークン数の制限に対処するため、CLIはファイルリスト(10ファイルまで)と
selectedText(16KBまで)の両方を切り捨てます。
CLIが最終的な切り捨て処理を行いますが、プラグイン側でも送信するコンテキストの量を制限することを強く推奨します。
III. Diffing インターフェース
インタラクティブなコード変更を可能にするために、プラグインは diffing インターフェースを公開することもできます。これにより、CLI が IDE にファイルへの提案された変更を表示する diff ビューを開くようにリクエストできるようになります。ユーザーはその後、IDE 内で直接これらの変更をレビュー、編集し、最終的に受け入れるか拒否するかを決定できます。
openDiff ツール
プラグインは、その MCP サーバー上で openDiff ツールを必ず登録する必要があります。
-
説明: このツールは、特定のファイルに対して変更可能な diff ビューを開くよう IDE に指示します。
-
リクエスト (
OpenDiffRequest): このツールはtools/callリクエスト経由で呼び出されます。リクエスト内のparamsのargumentsフィールドは、必ずOpenDiffRequestオブジェクトである必要があります。interface OpenDiffRequest { // 差分表示対象のファイルへの絶対パス filePath: string; // ファイルに対する提案された新しい内容 newContent: string; } -
レスポンス (
CallToolResult): ツールは、リクエストを受け取ったことを確認し、diff ビューが正常に開かれたかどうかを報告するために、すぐにCallToolResultを返す必要があります。- 成功時: diff ビューが正常に開かれた場合、レスポンスには空のコンテンツ(つまり
content: [])を含める必要があります。 - 失敗時: エラーにより diff ビューを開けなかった場合、レスポンスでは
isError: trueを設定し、content配列内にエラー内容を記述したTextContentブロックを含める必要があります。
実際の diff の結果(受け入れまたは拒否)については、非同期通知を通じて後から伝えられます。
- 成功時: diff ビューが正常に開かれた場合、レスポンスには空のコンテンツ(つまり
closeDiff ツール
プラグインは、その MCP サーバーに closeDiff ツールを必ず登録する必要があります。
-
説明: このツールは、特定のファイルに対して開いている diff ビューを IDE に閉じるよう指示します。
-
リクエスト (
CloseDiffRequest): このツールはtools/callリクエストを通じて呼び出されます。リクエストのparams内にあるargumentsフィールドは、必ずCloseDiffRequestオブジェクトである必要があります。interface CloseDiffRequest { // diff ビューを閉じる対象のファイルの絶対パス filePath: string; } -
レスポンス (
CallToolResult): このツールは 必ずCallToolResultを返す必要があります。- 成功時: diff ビューが正常に閉じられた場合、レスポンスにはファイルの最終内容を含む単一の TextContent ブロックが
content配列に含まれている必要があります(ビューを閉じる前の内容)。 - 失敗時: エラーにより diff ビューを閉じられなかった場合、レスポンスには
isError: trueが設定され、content配列内にエラー内容を説明するTextContentブロックが含まれている必要があります。
- 成功時: diff ビューが正常に閉じられた場合、レスポンスにはファイルの最終内容を含む単一の TextContent ブロックが
ide/diffAccepted 通知
ユーザーが差分ビューでの変更を承認した場合(例:「Apply」や「Save」ボタンをクリック)、プラグインは CLI に ide/diffAccepted 通知を送信する必要があります。
-
ペイロード: 通知のパラメータには、ファイルパスとそのファイルの最終的な内容を含める必要があります。ユーザーが差分ビューで手動で編集を行った場合、この内容は元の
newContentと異なる可能性があります。{ // 差分表示されたファイルの絶対パス filePath: string; // 承認後のファイルの完全な内容 content: string; }
ide/diffRejected 通知
ユーザーが変更を拒否した場合(例:承認せずに差分ビューを閉じた場合)、プラグインは CLI に ide/diffRejected 通知を送信する必要があります。
-
ペイロード: 通知のパラメータには、拒否された差分のファイルパスを含める必要があります。
{ // 差分表示されたファイルの絶対パス filePath: string; }
IV. ライフサイクルインターフェース
プラグインは、IDEのライフサイクルに応じてリソースとdiscoveryファイルを正しく管理する必要があります。
- アクティブ化時(IDE起動/プラグイン有効化):
- MCPサーバーを起動する。
- discoveryファイルを作成する。
- 非アクティブ化時(IDE終了/プラグイン無効化):
- MCPサーバーを停止する。
- discoveryファイルを削除する。