Packages
fnord
0.8.38
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/services/mcp.ex
defmodule Services.MCP do
@moduledoc false
alias MCP.Supervisor, as: MCPSup
alias Settings.MCP, as: MCPSettings
alias Services.Once
alias MCP.Tools
alias UI
@spec start() :: :ok
def start do
# Configure Hermes MCP logging to reduce debug output
Application.put_env(:hermes_mcp, :logging,
client_events: :info,
server_events: :info,
transport_events: :warning,
protocol_messages: :warning
)
servers = MCPSettings.effective_config(Settings.new())
if map_size(servers) > 0 do
ensure_supervisor()
Once.run(:mcp_discovery, fn -> discover_once(servers) end)
end
:ok
end
defp ensure_supervisor do
case Process.whereis(MCPSup) do
nil -> MCPSup.start_link([])
_ -> :ok
end
:ok
end
defp discover_once(servers) when is_map(servers) do
Enum.each(servers, fn {server, cfg} ->
case safe_list_tools(server) do
{:ok, tools} ->
Tools.register_server_tools(server, tools)
{:error, reason} ->
UI.warn(
Jason.encode!(
%{
mcp_discovery_error: %{
server: server,
transport: cfg["transport"],
error: inspect(reason)
}
},
pretty: true
)
)
end
end)
end
# Robustly list tools for a server, supporting both Hermes.Base and stubbed return shapes.
defp safe_list_tools(server) do
instance = MCPSup.instance_name(server)
# Check if the process exists and is alive
case Process.whereis(instance) do
nil ->
{:error, :not_started}
pid when is_pid(pid) ->
if Process.alive?(pid) do
# Process is alive, attempt to discover tools via the client module
# Note: Tools may not be available immediately after handshake
try do
# Give the server a moment to complete initialization and handshake
:timer.sleep(1000)
# The instance is a supervisor, we need to find the client process
children = Supervisor.which_children(instance)
# Find the Hermes.Client.Base child process
case List.keyfind(children, Hermes.Client.Base, 0) do
{Hermes.Client.Base, client_pid, _, _} when is_pid(client_pid) ->
result = Hermes.Client.Base.list_tools(client_pid)
case result do
{:ok, %Hermes.MCP.Response{result: %{"tools" => tools}}} -> {:ok, tools}
{:ok, _response} -> {:ok, []}
{:error, reason} -> {:error, reason}
end
_ ->
{:ok, []}
end
rescue
_ ->
# Tools discovery failed, likely due to timing or client not ready
# Return empty list for now - tools may be discovered later
{:ok, []}
end
else
{:error, :not_alive}
end
end
end
@spec test() :: map()
def test do
servers_cfg = MCPSettings.effective_config(Settings.new())
servers =
servers_cfg
|> Enum.map(fn {server, _} ->
info =
case safe_get_info(server) do
{:ok, server_info} -> %{status: "ok", server_info: server_info}
{:error, reason} -> %{status: "error", error: inspect(reason)}
end
capabilities =
case safe_get_capabilities(server) do
{:ok, caps} -> %{capabilities: caps}
{:error, _} -> %{capabilities: %{}}
end
tools =
case safe_list_tools(server) do
{:ok, tools} ->
%{status: "ok", tools: Enum.map(tools, &tool_blurb/1)}
{:error, reason} ->
%{status: "error", error: inspect(reason)}
end
{server, info |> Map.merge(capabilities) |> Map.merge(tools)}
end)
|> Enum.into(%{})
%{status: "ok", servers: servers}
end
# Retrieve server info; check if process is alive for now
defp safe_get_info(server) do
instance = MCPSup.instance_name(server)
# Check if the process exists and is alive
case Process.whereis(instance) do
nil ->
{:error, :not_started}
pid when is_pid(pid) ->
if Process.alive?(pid) do
{:ok, %{"name" => "#{server}-server", "status" => "running"}}
else
{:error, :not_alive}
end
end
end
# Retrieve server capabilities
defp safe_get_capabilities(server) do
instance = MCPSup.instance_name(server)
case Process.whereis(instance) do
nil ->
{:error, :not_started}
pid when is_pid(pid) ->
if Process.alive?(pid) do
# Try to get capabilities from the client state
try do
children = Supervisor.which_children(instance)
case List.keyfind(children, Hermes.Client.Base, 0) do
{Hermes.Client.Base, client_pid, _, _} when is_pid(client_pid) ->
# Try to get server capabilities from the client
case Hermes.Client.Base.get_server_capabilities(client_pid) do
caps when is_map(caps) -> {:ok, caps}
nil -> {:ok, %{}}
end
_ ->
{:ok, %{}}
end
rescue
_ ->
{:ok, %{}}
end
else
{:error, :not_alive}
end
end
end
@spec tool_blurb(tool :: map()) :: map()
defp tool_blurb(tool) do
%{"name" => tool["name"], "description" => Map.get(tool, "description", "")}
end
end