Packages
fnord
0.9.40
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/mcp/client/default.ex
defmodule MCP.Client.Default do
@moduledoc """
Real implementation of `MCP.Client`: talks to the Hermes client GenServers
registered by `MCP.Supervisor`. All the defensive plumbing for a runtime we
do not control lives here - registration/aliveness checks before every
call, rescue/catch around Hermes invocations, and response unwrapping.
"""
@behaviour MCP.Client
alias MCP.Supervisor, as: MCPSup
@impl MCP.Client
def start_supervisor() do
# The supervisor is VM-global: any process (the CLI phase, an agent, a
# tool call) may be first to want MCP, but the stack must survive its
# initiator. start_link links to the caller, so unlink immediately.
case Process.whereis(MCPSup) do
nil ->
case MCPSup.start_link([]) do
{:ok, pid} ->
Process.unlink(pid)
:ok
{:error, {:already_started, pid}} ->
Process.unlink(pid)
:ok
{:error, reason} ->
{:error, reason}
end
_pid ->
:ok
end
end
@impl MCP.Client
def connected?(server) do
case Process.whereis(MCPSup.instance_name(server)) do
nil -> false
pid -> Process.alive?(pid)
end
end
@impl MCP.Client
def list_tools(server) do
with_connected_client(server, fn client ->
# Give the server a moment to complete initialization and handshake
:timer.sleep(1000)
case Hermes.Client.Base.list_tools(client) do
{:ok, %Hermes.MCP.Response{result: %{"tools" => tools}}} -> {:ok, tools}
{:ok, _response} -> {:ok, []}
{:error, reason} -> {:error, reason}
end
end)
end
@impl MCP.Client
def get_server_capabilities(server) do
with_connected_client(server, fn client ->
case Hermes.Client.Base.get_server_capabilities(client) do
caps when is_map(caps) -> {:ok, caps}
nil -> {:ok, %{}}
end
end)
end
@impl MCP.Client
def call_tool(server, tool, args, opts) do
client = MCPSup.instance_name(server)
try do
case Hermes.Client.Base.call_tool(client, tool, args, opts) do
{:ok, %Hermes.MCP.Response{result: result}} -> {:ok, result}
{:error, reason} -> {:error, reason}
end
catch
:exit, _ -> {:error, "MCP client not available"}
end
end
# Runs `fun` against the server's client atom only when the process is
# registered and alive; shields the caller from Hermes raising or exiting.
defp with_connected_client(server, fun) do
client = MCPSup.instance_name(server)
case Process.whereis(client) do
nil ->
{:error, :not_started}
pid when is_pid(pid) ->
if Process.alive?(pid) do
try do
fun.(client)
rescue
error -> {:error, {:rescue, error}}
catch
:exit, reason -> {:error, {:exit, reason}}
end
else
{:error, :not_alive}
end
end
end
end