Packages

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

Current section

Files

Jump to
backplane_mcp_protocol lib mix interactive ui.ex
Raw

lib/mix/interactive/ui.ex

defmodule Mix.Interactive.UI do
@moduledoc false
alias Backplane.McpProtocol.MCP.Error
alias IO.ANSI
@colors %{
prompt: ANSI.bright() <> ANSI.cyan(),
command: ANSI.green(),
error: ANSI.red(),
success: ANSI.bright() <> ANSI.green(),
info: ANSI.yellow(),
warning: ANSI.bright() <> ANSI.yellow(),
reset: ANSI.reset()
}
@doc """
Returns the color configuration map for styling output.
"""
def colors, do: @colors
@doc """
Creates a styled header for an interactive session.
"""
def header(title) do
"""
#{ANSI.bright()}#{ANSI.cyan()}
┌─────────────────────────────────────────┐
#{String.pad_trailing(title, 34)}
└─────────────────────────────────────────┘
#{ANSI.reset()}
"""
end
@doc """
Formats JSON or other data for pretty-printing with indentation.
"""
def format_output(data) do
data
|> JSON.encode!()
|> String.split("\n")
|> Enum.map_join("\n", fn line -> " " <> line end)
rescue
_ -> " " <> inspect(data, pretty: true, width: 80)
end
@doc """
Formats error messages with appropriate styling.
"""
def print_error(reason) do
message = format_error_message(reason)
IO.puts("#{@colors.error}Error: #{message}#{@colors.reset}")
end
defp format_error_message(%Error{reason: :server_capabilities_not_set}) do
"Server capabilities not available. Connection may not be established."
end
defp format_error_message(%Error{reason: :connection_refused}) do
"Connection refused. Server may be unavailable."
end
defp format_error_message(%Error{reason: :request_timeout}) do
"Request timed out. Server may be busy or unreachable."
end
defp format_error_message(%Error{reason: reason, data: data}) do
"#{reason} #{inspect(data, pretty: true)}"
end
defp format_error_message(other) do
inspect(other, pretty: true)
end
@doc """
Formats lists of tools/prompts/resources with helpful styling.
"""
def print_items(title, [_ | _] = items, key_field) do
IO.puts("#{@colors.success}Found #{length(items)} #{title}#{@colors.reset}")
IO.puts("\n#{@colors.info}Available #{title}:#{@colors.reset}")
Enum.each(items, fn item ->
IO.puts(" #{@colors.command}#{item[key_field]}#{@colors.reset}")
if Map.has_key?(item, "description"), do: IO.puts(" #{item["description"]}")
cond do
title == "tools" && Map.has_key?(item, "inputSchema") ->
print_schema(item["inputSchema"])
title == "prompts" && Map.has_key?(item, "arguments") ->
print_prompt_arguments(item["arguments"])
title == "resources" ->
print_resource_details(item)
title == "resource templates" ->
print_resource_template_details(item)
true ->
nil
end
end)
IO.puts("")
end
def print_items(title, items, _key_field) do
IO.puts("#{@colors.success}Found #{length(items)} #{title}#{@colors.reset}")
IO.puts("")
end
defp print_schema(schema) when is_map(schema) do
if schema["type"] == "object" && Map.has_key?(schema, "properties") do
IO.puts(" #{@colors.info}Arguments:#{@colors.reset}")
print_properties(schema["properties"], Map.get(schema, "required", []))
end
end
defp print_schema(_), do: nil
defp print_properties(properties, required) when is_map(properties) do
Enum.each(properties, fn {prop_name, prop_schema} ->
print_property(prop_name, prop_schema, prop_name in required)
end)
end
defp print_property(name, schema, required) do
req_marker = if required, do: " (required)", else: ""
type = schema |> Map.get("type", "any") |> format_schema_type()
description = Map.get(schema, "description", "")
IO.puts(" #{@colors.command}#{name}#{@colors.reset}#{req_marker}: #{type}")
if description != "", do: IO.puts(" #{description}")
end
defp format_schema_type(types) when is_list(types), do: Enum.join(types, " | ")
defp format_schema_type(type), do: type
defp print_prompt_arguments(arguments) when is_list(arguments) do
IO.puts(" #{@colors.info}Arguments:#{@colors.reset}")
Enum.each(arguments, fn arg ->
req_marker = if Map.get(arg, "required", false), do: " (required)", else: ""
name = Map.get(arg, "name", "")
description = Map.get(arg, "description", "")
IO.puts(" #{@colors.command}#{name}#{@colors.reset}#{req_marker}")
if description != "", do: IO.puts(" #{description}")
end)
end
defp print_prompt_arguments(_), do: nil
defp print_resource_details(resource) when is_map(resource) do
if Map.has_key?(resource, "mimeType") do
IO.puts(" #{@colors.info}Type:#{@colors.reset} #{resource["mimeType"]}")
end
if Map.has_key?(resource, "name") && resource["name"] != resource["uri"] do
IO.puts(" #{@colors.info}Name:#{@colors.reset} #{resource["name"]}")
end
end
defp print_resource_template_details(template) when is_map(template) do
if Map.has_key?(template, "uriTemplate") do
IO.puts(" #{@colors.info}URI Template:#{@colors.reset} #{template["uriTemplate"]}")
end
if Map.has_key?(template, "mimeType") do
IO.puts(" #{@colors.info}Type:#{@colors.reset} #{template["mimeType"]}")
end
# Show title if it differs from the name
if Map.has_key?(template, "title") && template["title"] != template["name"] do
IO.puts(" #{@colors.info}Title:#{@colors.reset} #{template["title"]}")
end
end
end