メインコンテンツまでスキップ

接続プール管理

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);
}

内部状態

プロパティ説明
clientsMap<string, Client>サーバー ID → MCP Client インスタンスのマッピング
connectionStatesMap<string, ConnectionState>サーバー ID → 接続状態のマッピング
toolsCacheMap<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>

コア接続メソッドの実行フロー:

  1. 再利用の確認 — その ID のクライアントが clients Map にすでに存在する場合、まずヘルスチェックを実行する
  2. ヘルスチェックclient.listTools() を呼び出して接続が有効かを検証する
  3. 異常時の処理 — ヘルスチェックに失敗した場合、切断して再構築する
  4. トランスポートの作成server.type に基づいて対応する Transport を作成する
  5. クライアントの作成 — 設定済みの capabilities を持つ MCP SDK Client インスタンスを作成する
  6. 接続の確立client.connect(transport) を呼び出す
  7. クライアントのキャッシュclients Map に保存する
  8. イベントの発行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>
  1. Client インスタンスを取得する
  2. client.close() を呼び出す(エラーハンドリング付き)
  3. clients Map から削除する
  4. 状態を disconnected に設定する

reconnect — 再接続

reconnect(serverId: string): Promise<void>

まず disconnect() を呼び出し、その後サーバーが isActive であれば initClient() を呼び出します。

cleanup — すべてクリーンアップ

cleanup(): Promise<void>

すべての接続を切断し、clientsconnectionStatestoolsCache の 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 の完全なフィールド

フィールドデフォルト説明
idstring自動生成一意の識別子。形式は mcp_{timestamp}_{random}
namestring-サーバー名(一意である必要がある)
typeMcpServerTransport-stdio / sse / http
scopeMcpServerScope'user'user(グローバル)/ local(プロジェクトレベル)
configMcpServerConfig-接続設定(command/args/env/url/headers)
isActivebooleantrue有効かどうか
isTrustedbooleanfalse信頼済みかどうか
disabledToolsstring[][]無効化されたツール名のリスト
disabledAutoApproveToolsstring[][]自動承認を許可しないツール名のリスト
installSourcestring'manual'インストール元: builtin / manual / protocol / unknown
projectPathstring?-関連するプロジェクトパス(scope が local の場合に使用)
createdAtnumberDate.now()作成タイムスタンプ
updatedAtnumber?-最終更新タイムスタンプ

コアメソッド

list — サーバー一覧

async list(): Promise<McpServerRecord[]>

configStore からサーバーリストを直接読み取ります。接続は関与しません。

add — サーバーの追加

async add(input: McpServerInput): Promise<McpActionResult>
  1. 名前の一意性を確認する
  2. McpServerRecord を作成し、一意の ID を生成する
  3. configStore に保存する
  4. isActive の場合、接続の確立を試みる(接続失敗は追加をブロックしない)
  5. servers:changed イベントを発行する

addJson — JSON から一括追加

async addJson(input: McpServerJsonInput): Promise<McpActionResult>

JSON 文字列を解析し、{ [name]: config } オブジェクトを反復処理して、各エントリに対して add() を呼び出します。集約された結果を返します。

update — サーバーの更新

async update(id: string, updates: Partial<McpServerRecord>): Promise<McpActionResult>
  1. ID でサーバーを検索する
  2. 更新内容をマージする(ID は変更しない)
  3. 保存して updatedAt を設定する
  4. config または type が変更された場合、reconnect() をトリガーする
  5. servers:changed イベントを発行する

listServerTools — サーバーツールの取得

async listServerTools(serverId: string): Promise<MCPTool[]>
  1. キャッシュにヒットし、期限切れでないかを確認する
  2. ヒットした場合はそのまま返す
  3. ミスした場合は initClient()client.listTools() を実行する
  4. MCPTool[] 形式に変換し、disabledTools をフィルタリングする
  5. キャッシュに書き込む

ツール ID の生成ルール: mcp__${cleanServerName}__${cleanToolName}(英数字以外の文字は _ に置換)

callTool — ツールの呼び出し

async callTool(serverId: string, toolName: string, args: any, callId: string): Promise<MCPCallToolResponse>
  1. initClient() で接続を確保する
  2. client.callTool({ name, arguments })
  3. 呼び出し時間を記録する
  4. tool:called イベントを発行する
  5. { 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:connectedserverIdサーバーが正常に接続された
connection:stateserverId, state接続状態が変化した
servers:changedなしサーバーリストが変更された(追加/削除/更新)
tool:called{ serverId, toolName, duration, success }ツール呼び出しが完了した

拡張ポイント

  • 新しいトランスポート型の追加createTransport() の switch 文に新しい case ブランチを追加する
  • ヘルスチェックのカスタマイズisClientHealthy() メソッドを変更する
  • キャッシュ戦略CACHE_TTL を変更するか、より複雑なキャッシュ無効化戦略を実装する
  • 接続イベント — EventEmitter イベントをリッスンして監視する

関連ファイル