接続プール管理
McpService は MCP モジュールのコアサービスであり、すべての MCP サーバーの接続ライフサイクルを管理します。遅延読み込み戦略(初回使用時にのみ接続を確立)でクライアント接続プールを維持し、ヘルスチェックと自動再接続機能を提供します。
ファイルパス: packages/desktop/app/main/services/capabilities/tools/mcp-users/McpService.ts
クラス構造
class McpService extends EventEmitter {
private clients: Map<string, Client>;
private connectionStates: Map<string, ConnectionState>;
private toolsCache: Map<string, { tools: MCPTool[]; timestamp: number }>;
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
constructor(private logger: LoggerService);
}
内部状態
| プロパティ | 型 | 説明 |
|---|---|---|
clients | Map<string, Client> | サーバー ID → MCP Client インスタンスのマッピング |
connectionStates | Map<string, ConnectionState> | サーバー ID → 接続状態のマッピング |
toolsCache | Map<string, { tools, timestamp }> | サーバー ID → ツールリストのキャッシュ(タイムスタンプ付き) |
接続状態
stateDiagram-v2
[*] --> disconnected
disconnected --> connecting: initClient()
connecting --> connected: Connection successful
connecting --> error: Connection failed
connected --> disconnected: disconnect()
error --> connecting: reconnect()
connected --> connecting: reconnect()
| 状態 | 説明 |
|---|---|
disconnected | 未接続、またはすでに切断済み |
connecting | 接続を確立中 |
connected | 接続がアクティブ |
error | 接続に失敗 |
接続ライフサイクル
initClient — クライアントの初期化
initClient(server: McpServerRecord): Promise<Client>
コア接続メソッドの実行フロー:
- 再利用の確認 — その ID のクライアントが
clientsMap にすでに存在する場合、まずヘルスチェックを実行する - ヘルスチェック —
client.listTools()を呼び出して接続が有効かを検証する - 異常時の処理 — ヘルスチェックに失敗した場合、切断して再構築する
- トランスポートの作成 —
server.typeに基づいて対応する Transport を作成する - クライアントの作成 — 設定済みの capabilities を持つ MCP SDK Client インスタンスを作成する
- 接続の確立 —
client.connect(transport)を呼び出す - クライアントのキャッシュ —
clientsMap に保存する - イベントの発行 —
server:connectedイベントを発行する
const client = new Client(
{ name: 'elftia', version: '1.0.0' },
{
capabilities: {
roots: { listChanged: true },
sampling: {}
}
}
);
await client.connect(transport);
disconnect — 切断
disconnect(serverId: string): Promise<void>
- Client インスタンスを取得する
client.close()を呼び出す(エラーハンドリング付き)clientsMap から削除する- 状態を
disconnectedに設定する
reconnect — 再接続
reconnect(serverId: string): Promise<void>
まず disconnect() を呼び出し、その後サーバーが isActive であれば initClient() を呼び出します。
cleanup — すべてクリーンアップ
cleanup(): Promise<void>
すべての接続を切断し、clients、connectionStates、toolsCache の 3 つの Map をクリアします。アプリケーション終了時に使用します。
トランスポート層の実装
StdioClientTransport
ローカルプロセス通信に使用され、標準入出力(stdin/stdout)を介して子プロセスと通信します。
new StdioClientTransport({
command: server.config.command,
args: server.config.args || [],
env: {
...process.env, // Inherit system environment
...server.config.env // Override with custom variables
}
});
特徴:
- 子プロセスを自動起動する
- 環境変数は現在のプロセスから完全に継承され、カスタム設定で上書きされる
- プロセス終了時に接続が自動的に閉じられる
SSEClientTransport
HTTP Server-Sent Events に基づく長寿命のトランスポートで、Node.js ネイティブの fetch の代わりに Electron の net.fetch を使用します。
new SSEClientTransport(new URL(server.config.url), {
fetch: (url, init) => {
return net.fetch(
typeof url === 'string' ? url : url.toString(),
init
);
},
requestInit: {
headers: server.config.headers || {}
}
});
net.fetch を使用する理由:
- Electron の
netモジュールは Chromium のネットワークスタックに従う - SSL/TLS サポートと証明書管理がより優れている
- システムのプロキシ設定を自動的に使用する
StreamableHTTPClientTransport
MCP 2025-03-26 仕様に基づく Streamable HTTP トランスポートです。設定は SSE と似ています:
new StreamableHTTPClientTransport(new URL(server.config.url), {
fetch: (url, init) => {
return net.fetch(
typeof url === 'string' ? url : url.toString(),
init
);
},
requestInit: {
headers: server.config.headers || {}
}
});
設定ストレージ
MCP サーバー設定は Electron Store に保存されます:
const configStore = new Store<{
mcpServers: McpServerRecord[];
}>({
name: 'mcp-config',
defaults: { mcpServers: [] }
});
ファイルの場所: {userData}/mcp-config.json
McpServerRecord の完全なフィールド
| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
id | string | 自動生成 | 一意の識別子。形式は mcp_{timestamp}_{random} |
name | string | - | サーバー名(一意である必要がある) |
type | McpServerTransport | - | stdio / sse / http |
scope | McpServerScope | 'user' | user(グローバル)/ local(プロジェクトレベル) |
config | McpServerConfig | - | 接続設定(command/args/env/url/headers) |
isActive | boolean | true | 有効かどうか |
isTrusted | boolean | false | 信頼済みかどうか |
disabledTools | string[] | [] | 無効化されたツール名のリスト |
disabledAutoApproveTools | string[] | [] | 自動承認を許可しないツール名のリスト |
installSource | string | 'manual' | インストール元: builtin / manual / protocol / unknown |
projectPath | string? | - | 関連するプロジェクトパス(scope が local の場合に使用) |
createdAt | number | Date.now() | 作成タイムスタンプ |
updatedAt | number? | - | 最終更新タイムスタンプ |
コアメソッド
list — サーバー一覧
async list(): Promise<McpServerRecord[]>
configStore からサーバーリストを直接読み取ります。接続は関与しません。
add — サーバーの追加
async add(input: McpServerInput): Promise<McpActionResult>
- 名前の一意性を確認する
McpServerRecordを作成し、一意の ID を生成する- configStore に保存する
isActiveの場合、接続の確立を試みる(接続失敗は追加をブロックしない)servers:changedイベントを発行する
addJson — JSON から一括追加
async addJson(input: McpServerJsonInput): Promise<McpActionResult>
JSON 文字列を解析し、{ [name]: config } オブジェクトを反復処理して、各エントリに対して add() を呼び出します。集約された結果を返します。
update — サーバーの更新
async update(id: string, updates: Partial<McpServerRecord>): Promise<McpActionResult>
- ID でサーバーを検索する
- 更新内容をマージする(ID は変更しない)
- 保存して
updatedAtを設定する configまたはtypeが変更された場合、reconnect()をトリガーするservers:changedイベントを発行する
listServerTools — サーバーツールの取得
async listServerTools(serverId: string): Promise<MCPTool[]>
- キャッシュにヒットし、期限切れでないかを確認する
- ヒットした場合はそのまま返す
- ミスした場合は
initClient()→client.listTools()を実行する MCPTool[]形式に変換し、disabledToolsをフィルタリングする- キャッシュに書き込む
ツール ID の生成ルール: mcp__${cleanServerName}__${cleanToolName}(英数字以外の文字は _ に置換)
callTool — ツールの呼び出し
async callTool(serverId: string, toolName: string, args: any, callId: string): Promise<MCPCallToolResponse>
initClient()で接続を確保するclient.callTool({ name, arguments })- 呼び出し時間を記録する
tool:calledイベントを発行する{ isError, content }またはエラーメッセージを返す
test — 接続テスト
async test(input: McpServerRemoveInput): Promise<McpTestResult>
接続してツールの一覧取得を試み、成功/失敗とツール名のリストを返します。
discover — capabilities の検出
async discover(input: McpServerRemoveInput): Promise<McpDiscoverResult>
接続後、listTools()、listPrompts()、listResources() をそれぞれ呼び出し(後者 2 つはオプションで、部分的な失敗を許容)、完全な capability インベントリを返します。
イベント
McpService は EventEmitter を継承し、以下のイベントを発行します:
| イベント | 引数 | トリガー |
|---|---|---|
server:connected | serverId | サーバーが正常に接続された |
connection:state | serverId, state | 接続状態が変化した |
servers:changed | なし | サーバーリストが変更された(追加/削除/更新) |
tool:called | { serverId, toolName, duration, success } | ツール呼び出しが完了した |
拡張ポイント
- 新しいトランスポート型の追加 —
createTransport()の switch 文に新しい case ブランチを追加する - ヘルスチェックのカスタマイズ —
isClientHealthy()メソッドを変更する - キャッシュ戦略 —
CACHE_TTLを変更するか、より複雑なキャッシュ無効化戦略を実装する - 接続イベント — EventEmitter イベントをリッスンして監視する
関連ファイル
- ツールフォーマットアダプター — ツール形式の変換
- Worker アーキテクチャ — CLI ブリッジ用 Worker
- モジュール概要 — 全体アーキテクチャ