Packages
claude_code
0.34.0
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.2
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Claude Agent SDK for Elixir – Build AI agents with Claude Code
Current section
Files
Jump to
Current section
Files
lib/claude_code/mcp/backend/anubis.ex
if Code.ensure_loaded?(Anubis.Server) do
defmodule ClaudeCode.MCP.Backend.Anubis do
@moduledoc false
@behaviour ClaudeCode.MCP.Backend
alias ClaudeCode.MCP.Server, as: MCPServer
@impl true
def list_tools(server_module) do
%{tools: tool_modules} = server_module.__tool_server__()
Enum.map(tool_modules, fn module ->
%{
"name" => module.__tool_name__(),
"description" => module.__description__(),
"inputSchema" => module.input_schema()
}
end)
end
@impl true
def call_tool(server_module, tool_name, params, assigns) do
%{tools: tool_modules} = server_module.__tool_server__()
case Enum.find(tool_modules, &(&1.__tool_name__() == tool_name)) do
nil ->
{:error, "Tool '#{tool_name}' not found"}
module ->
atom_params = ClaudeCode.MapUtils.safe_atomize_keys(params)
case validate_params(module, atom_params) do
:ok -> execute_tool(module, atom_params, assigns)
{:error, error_msg} -> {:validation_error, "Invalid params: #{error_msg}"}
end
end
end
@impl true
def server_info(server_module) do
%{name: name} = server_module.__tool_server__()
%{"name" => name, "version" => "1.0.0"}
end
@impl true
def compatible?(module) when is_atom(module) do
Code.ensure_loaded?(module) and
function_exported?(module, :start_link, 1) and
not MCPServer.sdk_server?(module) and
has_behaviour?(module, Anubis.Server)
end
defp has_behaviour?(module, behaviour) do
:attributes
|> module.module_info()
|> Keyword.get_values(:behaviour)
|> List.flatten()
|> Enum.member?(behaviour)
end
defp validate_params(module, params) do
schema = module.input_schema()
properties = schema["properties"] || %{}
required = schema["required"] || []
errors =
Enum.flat_map(required, fn field ->
key = String.to_existing_atom(field)
if Map.has_key?(params, key), do: [], else: ["#{field} is required"]
end) ++
Enum.flat_map(params, fn {key, value} ->
key_str = Atom.to_string(key)
case Map.get(properties, key_str) do
nil -> []
prop_schema -> validate_type(key_str, value, prop_schema)
end
end)
case errors do
[] -> :ok
errors -> {:error, Enum.join(errors, ", ")}
end
end
defp validate_type(name, value, %{"type" => "integer"}) when not is_integer(value),
do: ["#{name}: expected integer, got #{inspect(value)}"]
defp validate_type(name, value, %{"type" => "string"}) when not is_binary(value),
do: ["#{name}: expected string, got #{inspect(value)}"]
defp validate_type(name, value, %{"type" => "number"}) when not is_number(value),
do: ["#{name}: expected number, got #{inspect(value)}"]
defp validate_type(name, value, %{"type" => "boolean"}) when not is_boolean(value),
do: ["#{name}: expected boolean, got #{inspect(value)}"]
defp validate_type(_name, _value, _schema), do: []
defp execute_tool(module, params, assigns) do
case module.execute(params, assigns) do
{:ok, value} when is_binary(value) ->
{:ok, %{"content" => [%{"type" => "text", "text" => value}], "isError" => false}}
{:ok, value} when is_map(value) or is_list(value) ->
{:ok, %{"content" => [%{"type" => "text", "text" => Jason.encode!(value)}], "isError" => false}}
{:ok, value} ->
{:ok, %{"content" => [%{"type" => "text", "text" => to_string(value)}], "isError" => false}}
{:error, message} when is_binary(message) ->
{:ok, %{"content" => [%{"type" => "text", "text" => message}], "isError" => true}}
{:error, message} ->
{:ok, %{"content" => [%{"type" => "text", "text" => to_string(message)}], "isError" => true}}
end
rescue
e ->
{:ok,
%{
"content" => [%{"type" => "text", "text" => "Tool error: #{Exception.message(e)}"}],
"isError" => true
}}
end
end
end