Packages

Model Context Protocol (MCP) implementation in Elixir with Phoenix integration

Retired package: Release invalid - Accidental version. Use 0.3.0 instead.

Current section

Files

Jump to
backplane_mcp_protocol lib backplane mcp_protocol server component schema.ex
Raw

lib/backplane/mcp_protocol/server/component/schema.ex

defmodule Backplane.McpProtocol.Server.Component.Schema do
@moduledoc false
alias Backplane.McpProtocol.Server.Component
@type schema :: map() | list()
@type field_type :: atom() | tuple()
@type json_schema :: map()
@type prompt_argument :: map()
@spec to_json_schema(schema() | nil) :: json_schema()
def to_json_schema(nil), do: %{"type" => "object"}
def to_json_schema(schema) when is_list(schema) do
schema |> Map.new() |> to_json_schema()
end
def to_json_schema(schema) when is_map(schema) do
# Defaults live in `{type, {:default, v}}` after `__build_field__/2`. Strip
# them from JSON Schema output so consumer schemas don't surface internal
# defaults — prompt docs and validation still use them.
schema
|> Component.__expand_user_input__()
|> Peri.to_json_schema(exclude_meta_keys: [:default])
end
@spec to_prompt_arguments(schema() | nil) :: [prompt_argument()]
def to_prompt_arguments(nil), do: []
def to_prompt_arguments(schema) when is_list(schema) do
schema |> Map.new() |> to_prompt_arguments()
end
def to_prompt_arguments(schema) when is_map(schema) do
expanded = Component.__expand_user_input__(schema)
Enum.map(expanded, fn {key, type} ->
%{
"name" => to_string(key),
"description" => describe_type(type),
"required" => required?(type)
}
end)
end
@spec format_errors([map()] | [binary()]) :: binary()
def format_errors(errors) when is_list(errors) do
Enum.map_join(errors, "; ", &format_error/1)
end
defp format_error(%{path: path, message: message}) do
path_str = Enum.join(path, ".")
if path_str == "", do: message, else: "#{path_str}: #{message}"
end
defp format_error(error) when is_binary(error), do: error
defp format_error(error), do: inspect(error, pretty: true)
defp required?({:required, _}), do: true
defp required?({:meta, type, _}), do: required?(type)
defp required?(_), do: false
defp describe_type({:required, {:meta, type, opts}}) do
Keyword.get(opts, :description) || "Required " <> describe_base_type(type)
end
defp describe_type({:required, type}), do: "Required " <> describe_base_type(type)
defp describe_type({:meta, type, opts}) do
Keyword.get(opts, :description) || describe_type(type)
end
defp describe_type({type, {:default, default}}) do
"Optional " <> describe_base_type(type) <> " (default: #{to_string(default)})"
end
defp describe_type(type), do: "Optional " <> describe_base_type(type)
defp describe_base_type(:string), do: "string parameter"
defp describe_base_type(:integer), do: "integer parameter"
defp describe_base_type(:float), do: "number parameter"
defp describe_base_type(:boolean), do: "boolean parameter"
defp describe_base_type({:enum, values}), do: "one of: #{inspect(values, pretty: true)}"
defp describe_base_type({:enum, values, _opts}), do: "one of: #{inspect(values, pretty: true)}"
defp describe_base_type({:list, {type, _}}), do: "array of #{describe_base_type(type)} elements parameter"
defp describe_base_type({:list, type}), do: "array of #{describe_base_type(type)} elements parameter"
defp describe_base_type({:list, type, _opts}), do: "array of #{describe_base_type(type)} elements parameter"
defp describe_base_type({:map, _}), do: "object parameter"
defp describe_base_type({:meta, type, _}), do: describe_base_type(type)
defp describe_base_type({type, _}), do: "#{to_string(type)} parameter"
defp describe_base_type(schema) when is_map(schema), do: "nested object"
defp describe_base_type(_), do: "parameter"
@spec validator(schema()) :: (map() ->
{:ok, map()} | {:error, list(Peri.Error.t())})
def validator(schema) when is_list(schema) do
schema |> Map.new() |> validator()
end
def validator(schema) do
peri_schema = Component.__clean_schema_for_peri__(schema)
fn params -> Peri.validate(peri_schema, params) end
end
end