ExMCP.Response (ex_mcp v0.9.0)

View Source

Structured response types for MCP operations.

This module provides structured response handling for MCP tool calls, resource reads, and other operations. It represents a key improvement in v2, providing type-safe responses instead of raw maps.

Response Types

  • :text - Simple text responses
  • :json - Structured JSON data
  • :error - Error responses with detailed information
  • :tools - Tool listing responses
  • :resources - Resource listing responses
  • :prompts - Prompt listing responses
  • :server_info - Server information responses
  • :mixed - Responses with multiple content types

Usage

# Create a text response
response = ExMCP.Response.text("Hello, world!", "greeting_tool")

# Create a JSON response
response = ExMCP.Response.json(%{result: 42}, "calculator")

# Create an error response
response = ExMCP.Response.error("Invalid input", "validation_tool")

# Extract content
text = ExMCP.Response.text_content(response)
json = ExMCP.Response.json_content(response)

Protocol Data Handling

Protocol data from MCP servers is kept as string-keyed maps to maintain compatibility with the MCP JSON-RPC protocol and avoid atom exhaustion issues.

# Tools, resources, and prompts use string keys
response.tools
#=> [%{"name" => "hello", "description" => "Says hello", "inputSchema" => %{...}}]

# Use accessor functions for convenience
tool = hd(response.tools)
ExMCP.Response.tool_name(tool)        #=> "hello"
ExMCP.Response.tool_description(tool)  #=> "Says hello"
ExMCP.Response.tool_input_schema(tool) #=> %{"type" => "object", ...}

Design Rationale

The structured response type provides:

  • Type safety and consistency across all MCP operations
  • Clear extraction functions for different content types
  • Metadata support for tracing and debugging
  • Unified error handling
  • Protocol fidelity by keeping MCP data as strings
  • Protection against atom exhaustion attacks

Summary

Functions

Gets all text content from the response as a concatenated string.

Gets completion data from response.

Gets the data content from the response.

Checks if the response represents an error.

Creates a Response struct from a plain map (backward compatibility).

Creates a response from a raw MCP response.

Creates a response with JSON data content.

Gets the resource content from the response.

Gets a property from a schema properties map.

Creates a success response with text content.

Gets the text content from the response.

Converts the response back to raw MCP format.

Converts the response to a map that excludes nil pagination fields.

Gets the tool description from a tool definition.

Gets the input schema from a tool definition.

Gets the tool name from a tool definition.

Types

content_item()

@type content_item() :: %{
  type: String.t(),
  text: String.t() | nil,
  data: any() | nil,
  annotations: map() | nil
}

t()

@type t() :: %ExMCP.Response{
  completion: map() | nil,
  content: [content_item()],
  contents: [map()] | nil,
  description: String.t() | nil,
  is_error: boolean(),
  messages: [map()] | nil,
  meta: map() | nil,
  nextCursor: String.t() | nil,
  prompts: [map()] | nil,
  request_id: String.t() | nil,
  resourceLinks: [map()] | nil,
  resourceTemplates: [map()] | nil,
  resources: [map()] | nil,
  roots: [map()] | nil,
  server_info: map() | nil,
  structuredOutput: any() | nil,
  tool_name: String.t() | nil,
  tools: [map()] | nil
}

Functions

all_text_content(response)

@spec all_text_content(t()) :: String.t()

Gets all text content from the response as a concatenated string.

completion(response)

@spec completion(t()) :: map() | nil

Gets completion data from response.

Handles both direct completion field and structuredOutput.completion.

data_content(response)

@spec data_content(t()) :: any()

Gets the data content from the response.

Returns the first data content item, or nil if none exists.

error(message, tool_name \\ nil, opts \\ [])

@spec error(String.t(), String.t() | nil, keyword()) :: t()

Creates an error response.

Examples

iex> ExMCP.Response.error("Tool execution failed", "calculate_sum")
%ExMCP.Response{
  content: [%{type: "text", text: "Error: Tool execution failed", data: nil, annotations: nil}],
  meta: nil,
  tool_name: "calculate_sum",
  request_id: nil,
  server_info: nil,
  is_error: true
}

error?(response)

@spec error?(t()) :: boolean()

Checks if the response represents an error.

from_map(response)

@spec from_map(map() | t()) :: t()

Creates a Response struct from a plain map (backward compatibility).

This is useful for tests that expect to work with plain maps. If the input is already a Response struct, returns it unchanged.

Examples

iex> map = %{"tools" => [%{"name" => "test"}], "nextCursor" => "abc"}
iex> response = ExMCP.Response.from_map(map)
iex> response.tools
[%{"name" => "test"}]
iex> response.nextCursor
"abc"

from_raw_response(raw_response, opts \\ [])

@spec from_raw_response(
  map(),
  keyword()
) :: t()

Creates a response from a raw MCP response.

Examples

iex> raw = %{"content" => [%{"type" => "text", "text" => "Hello"}]}
iex> ExMCP.Response.from_raw_response(raw)
%ExMCP.Response{
  content: [%{type: "text", text: "Hello", data: nil, annotations: nil}],
  meta: nil,
  tool_name: nil,
  request_id: nil,
  server_info: nil,
  is_error: false
}

json(data, tool_name \\ nil, opts \\ [])

@spec json(any(), String.t() | nil, keyword()) :: t()

Creates a response with JSON data content.

Examples

iex> data = %{"result" => 42}
iex> ExMCP.Response.json(data, "calculate")
%ExMCP.Response{
  content: [%{type: "text", text: nil, data: %{"result" => 42}, annotations: nil}],
  meta: nil,
  tool_name: "calculate",
  request_id: nil,
  server_info: nil,
  is_error: false
}

resource_content(response)

@spec resource_content(t()) :: String.t() | nil

Gets the resource content from the response.

Returns the text from the first resource content item, or nil if none exists. This is used for resource read responses that have a contents field.

schema_property(arg1, key)

Gets a property from a schema properties map.

Examples

iex> schema = %{"properties" => %{"name" => %{"type" => "string"}}}
iex> ExMCP.Response.schema_property(schema, "name")
%{"type" => "string"}

text(text_content, tool_name \\ nil, opts \\ [])

@spec text(String.t(), String.t() | nil, keyword()) :: t()

Creates a success response with text content.

Examples

iex> ExMCP.Response.text("Hello, World!", "say_hello")
%ExMCP.Response{
  content: [%{type: "text", text: "Hello, World!", data: nil, annotations: nil}],
  meta: nil,
  tool_name: "say_hello",
  request_id: nil,
  server_info: nil,
  is_error: false
}

text_content(response)

@spec text_content(t()) :: String.t() | nil

Gets the text content from the response.

Returns the first text content item, or nil if none exists.

to_raw(response)

@spec to_raw(t()) :: map()

Converts the response back to raw MCP format.

to_test_map(response)

@spec to_test_map(t()) :: map()

Converts the response to a map that excludes nil pagination fields.

This is useful for tests that expect Map.has_key?/2 to return false for pagination fields when they are not present in the original response.

tool_description(arg1)

Gets the tool description from a tool definition.

Examples

iex> tool = %{"name" => "hello", "description" => "Says hello"}
iex> ExMCP.Response.tool_description(tool)
"Says hello"

tool_input_schema(arg1)

Gets the input schema from a tool definition.

Examples

iex> tool = %{"name" => "hello", "inputSchema" => %{"type" => "object"}}
iex> ExMCP.Response.tool_input_schema(tool)
%{"type" => "object"}

tool_name(arg1)

Gets the tool name from a tool definition.

Examples

iex> tool = %{"name" => "hello", "description" => "Says hello"}
iex> ExMCP.Response.tool_name(tool)
"hello"