Toolnexus.Mcp.list_tools
Elixir · package toolnexus · SPEC §2 · elixir/lib/toolnexus/mcp.ex
@spec list_tools(String.t() | map(), keyword()) :: Toolnexus.Mcp.Inventory.t()def list_tools(input, opts \\ [])
# %Toolnexus.Mcp.Inventory{# tools: %{String.t() => [%{"name" => String.t(), "description" => String.t(), "inputSchema" => map()}]},# status: %{String.t() => "connected" | "failed" | "disabled"}# }Connects to every enabled server, calls tools/list, and disconnects everything before
returning — no supervisor to close, no connection left running. Unlike
Toolnexus.Mcp.load/2, the tool names in the result are the servers’
original, unprefixed names, and any per-server "tools" allowlist in the config is
ignored — this is a raw inventory of what a server would expose, not what an agent would
actually see wired up.
When to use it
Section titled “When to use it”- Inspect a config before committing to it — see what each server offers, without holding a process open or filtering by an allowlist you have not written yet.
- Author a
toolsallowlist — calllist_tools/2first to see every tool’s real name, then write the per-server"tools"map thatload/2will apply. - A doctor / CI check — assert a server’s tool surface hasn’t silently changed, with nothing left connected afterward.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. A disabled server contributes nothing, and nothing is left running
Section titled “1. A disabled server contributes nothing, and nothing is left running”alias Toolnexus.Mcp
config = %{ "mcpServers" => %{ "off" => %{"type" => "local", "command" => ["true"], "enabled" => false} }}
inventory = Mcp.list_tools(config)
true = inventory.status == %{"off" => "disabled"}true = inventory.tools == %{}
IO.puts("ok: #{inspect(inventory.status)}")2. Per-server isolation — a failing server doesn’t block the others’ entries
Section titled “2. Per-server isolation — a failing server doesn’t block the others’ entries”alias Toolnexus.Mcp
config = %{ "mcpServers" => %{ "off" => %{"type" => "local", "command" => ["true"], "enabled" => false}, "broken" => %{ "type" => "local", "command" => ["/nonexistent-toolnexus-docs-binary"], "timeout" => 500 } }}
inventory = Mcp.list_tools(config)
true = inventory.status["off"] == "disabled"true = inventory.status["broken"] == "failed"# A failed server has no entry in `tools` at all — not even an empty list.false = Map.has_key?(inventory.tools, "broken")
IO.puts("ok: #{inspect(inventory.status)}")3. Malformed config raises, and a deadline bounds a hung server
Section titled “3. Malformed config raises, and a deadline bounds a hung server”list_tools/2 parses config the same way load/2 does — a JSON error raises before anything
connects. It also accepts the same :deadline/:timeout option, since listing has the identical
“one server can hang” problem as loading does.
alias Toolnexus.Mcp
raised_on_bad_json? = try do Mcp.list_tools(~s({not valid json)) false rescue _ -> true end
true = raised_on_bad_json?
config = %{ "mcpServers" => %{ "slow" => %{"type" => "local", "command" => ["sleep", "5"], "timeout" => 30_000} }}
{message, raised_on_deadline?} = try do Mcp.list_tools(config, deadline: 100) {nil, false} rescue e -> {Exception.message(e), true} end
true = raised_on_deadline?true = String.contains?(message, "deadline")
IO.puts("ok: malformed config raised, deadline exceeded raised (#{message})")Options
Section titled “Options”| Option | Aliases | Default | Effect |
|---|---|---|---|
:deadline |
:timeout |
:infinity |
Overall bound in ms across all servers; on exceed, raises "toolnexus: MCP list deadline exceeded". Same semantics as load/2’s deadline. |
What you get back
Section titled “What you get back”| Field | Type | What it is |
|---|---|---|
tools |
%{server => [%{"name", "description", "inputSchema"}]} |
Original (unprefixed) tool definitions per connected server only. |
status |
%{server => status} |
"connected", "failed", or "disabled" per configured server. |
See also
Section titled “See also”Toolnexus.Mcp.load— connect and get callable, prefixed, filtered toolsToolnexus.Mcp.load(ctx-aware form) —:deadlineand:wait_forin depthToolnexus.Mcp.parse_config— parse and validate config without connectingToolnexus.Mcp.Protocol.elicitation_to_request— the §10 elicitation bridge