Packages
Model Context Protocol (MCP) implementation in Elixir with Phoenix integration
Current section
Files
Jump to
Current section
Files
lib/backplane/mcp_protocol/client/sampling.ex
defmodule Backplane.McpProtocol.Client.Sampling do
@moduledoc false
use Backplane.McpProtocol.Logging
alias Backplane.McpProtocol.Client.State
alias Backplane.McpProtocol.MCP.Error
alias Backplane.McpProtocol.MCP.Message
alias Backplane.McpProtocol.Telemetry
@spec handle_request(msg :: map, State.t()) :: State.t()
def handle_request(%{"id" => id} = msg, state) do
params = Map.get(msg, "params", %{})
case validate_sampling_capability(state) do
:ok ->
handle_sampling_with_callback(id, params, state)
{:error, reason} ->
send_sampling_error(id, reason, "capability_disabled", %{}, state)
end
end
defp validate_sampling_capability(state) do
if Map.has_key?(state.capabilities, "sampling") do
:ok
else
{:error, "Client does not have sampling capability enabled"}
end
end
defp handle_sampling_with_callback(id, params, state) do
case State.get_sampling_callback(state) do
nil ->
send_sampling_error(
id,
"No sampling callback registered",
"sampling_not_configured",
%{},
state
)
callback when is_function(callback, 1) ->
execute_sampling_callback(id, params, callback, state)
end
end
defp execute_sampling_callback(id, params, callback, state) do
Task.start(fn ->
try do
case callback.(params) do
{:ok, result} ->
handle_sampling_result(id, result, state)
{:error, message} ->
send_sampling_error(id, message, "sampling_error", %{}, state)
end
rescue
e ->
error_message = "Sampling callback error: #{Exception.message(e)}"
send_sampling_error(
id,
error_message,
"sampling_callback_error",
%{},
state
)
end
end)
state
end
defp handle_sampling_result(id, result, state) do
case Message.encode_sampling_response(%{"result" => result}, id) do
{:ok, validated} ->
send_sampling_response(id, validated, state)
{:error, [%Peri.Error{} | _] = errors} ->
error_message = "Invalid sampling response"
send_sampling_error(
id,
error_message,
"invalid_sampling_response",
errors,
state
)
{:error, reason} ->
error_message = "Invalid sampling response: #{reason}"
send_sampling_error(
id,
error_message,
"invalid_sampling_response",
reason,
state
)
end
end
defp send_sampling_response(id, response, state) do
transport = state.transport
:ok = transport.layer.send_message(transport.name, response, timeout: state.timeout)
Telemetry.execute(
Telemetry.event_client_response(),
%{system_time: System.system_time()},
%{id: id, method: "sampling/createMessage"}
)
end
defp send_sampling_error(id, message, code, reason, %{transport: transport} = state) do
error = %Error{code: -1, message: message, data: %{"reason" => reason}}
{:ok, response} = Error.to_json_rpc(error, id)
:ok = transport.layer.send_message(transport.name, response, timeout: state.timeout)
Logging.client_event(
"sampling_error",
%{
id: id,
error_code: code,
error_message: message
},
level: :error
)
Telemetry.execute(
Telemetry.event_client_error(),
%{system_time: System.system_time()},
%{id: id, method: "sampling/createMessage", error_code: code}
)
state
end
end