Current section
Files
Jump to
Current section
Files
lib/cmdc/mcp/supervisor.ex
defmodule CMDC.MCP.Supervisor do
@moduledoc """
MCP 客户端进程树监督者。
为每个配置的 MCP server 启动一个 `CMDC.MCP.Client` 子进程。
使用 `DynamicSupervisor` —— 逐个启动 server,单个启动失败只记日志,不影响其他。
## 进程树位置
SessionServer (:one_for_one)
├── CMDC.MCP.Supervisor ← 此模块(可选)
│ ├── Client :server_a
│ ├── Client :server_b
│ └── ...
└── CMDC.Agent
当 Session 关闭时,此 Supervisor 级联终止所有 Anubis client 进程,
干净地关闭各 MCP 连接。
"""
use DynamicSupervisor
require Logger
alias CMDC.MCP.Client
# ==========================================================================
# Public API
# ==========================================================================
@doc """
启动 MCP Supervisor 并逐个添加 server children。
## 参数
* `opts` — 关键字列表:
* `:servers` — `[%{name: String.t(), transport: tuple}]` 列表
* `:name` — 可选进程名(atom 或 via-tuple)
"""
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts) do
servers = Keyword.get(opts, :servers, [])
name = Keyword.get(opts, :name)
start_opts = if name, do: [name: name], else: []
case DynamicSupervisor.start_link(__MODULE__, [], start_opts) do
{:ok, pid} ->
start_server_children(pid, servers)
{:ok, pid}
error ->
error
end
end
@doc "返回此 Supervisor 下正在运行的 MCP client 数量。"
@spec running_count(pid()) :: non_neg_integer()
def running_count(supervisor) do
DynamicSupervisor.count_children(supervisor).active
end
@doc "返回此 Supervisor 下正在运行的 MCP client pid 列表。"
@spec running_clients(pid()) :: [pid()]
def running_clients(supervisor) do
DynamicSupervisor.which_children(supervisor)
|> Enum.flat_map(fn
{_, pid, :worker, _} when is_pid(pid) -> [pid]
_ -> []
end)
end
# ==========================================================================
# DynamicSupervisor Callbacks
# ==========================================================================
@impl true
def init(_args) do
DynamicSupervisor.init(strategy: :one_for_one)
end
# ==========================================================================
# 私有辅助 — 逐个启动 server(容错)
# ==========================================================================
defp start_server_children(supervisor, servers) do
Enum.each(servers, fn server_config ->
spec = Client.child_spec(server_config)
case DynamicSupervisor.start_child(supervisor, spec) do
{:ok, _pid} ->
Logger.debug("[MCP.Supervisor] started #{server_config.name}")
{:error, reason} ->
Logger.warning(
"[MCP.Supervisor] failed to start #{server_config.name}: #{inspect(reason)}"
)
end
end)
end
end