Packages
Model Context Protocol (MCP) implementation in Elixir with Phoenix integration
Current section
Files
Jump to
Current section
Files
lib/backplane/mcp_protocol/server/handlers/prompts.ex
defmodule Backplane.McpProtocol.Server.Handlers.Prompts do
@moduledoc false
alias Backplane.McpProtocol.MCP.Error
alias Backplane.McpProtocol.Server.Component.Prompt
alias Backplane.McpProtocol.Server.Component.Schema
alias Backplane.McpProtocol.Server.Frame
alias Backplane.McpProtocol.Server.Handlers
alias Backplane.McpProtocol.Server.Response
@spec handle_list(map, Frame.t(), module()) ::
{:reply, map(), Frame.t()} | {:error, Error.t(), Frame.t()}
def handle_list(request, frame, server_module) do
prompts =
server_module
|> Handlers.get_server_prompts(frame)
|> Enum.filter(&visible?(&1, frame))
limit = frame.pagination_limit
{prompts, cursor} = Handlers.maybe_paginate(request, prompts, limit)
{:reply,
then(
%{"prompts" => prompts},
&if(cursor, do: Map.put(&1, "nextCursor", cursor), else: &1)
), frame}
end
@spec handle_get(map(), Frame.t(), module()) ::
{:reply, map(), Frame.t()} | {:error, Error.t(), Frame.t()}
def handle_get(%{"params" => %{"name" => prompt_name, "arguments" => params}}, frame, server) do
registered_prompts = Handlers.get_server_prompts(server, frame)
if prompt = find_prompt_module(registered_prompts, prompt_name) do
with :ok <- check_scopes(prompt, frame),
{:ok, params} <- validate_params(params, prompt, frame),
do: forward_to(server, prompt, params, frame)
else
payload = %{message: "Prompt not found: #{prompt_name}"}
{:error, Error.protocol(:invalid_params, payload), frame}
end
end
def handle_get(%{"params" => %{"name" => prompt_name}}, frame, server) do
registered_prompts = Handlers.get_server_prompts(server, frame)
if prompt = find_prompt_module(registered_prompts, prompt_name) do
with :ok <- check_scopes(prompt, frame),
{:ok, params} <- validate_params(%{}, prompt, frame),
do: forward_to(server, prompt, params, frame)
else
payload = %{message: "Prompt not found: #{prompt_name}"}
{:error, Error.protocol(:invalid_params, payload), frame}
end
end
# Private functions
defp check_scopes(%Prompt{scopes: []}, _frame), do: :ok
defp check_scopes(%Prompt{scopes: required}, frame) do
granted = Frame.scopes(frame)
missing = Enum.reject(required, &(&1 in granted))
if missing == [] do
:ok
else
{:error, Error.execution("insufficient_scope", %{required: required, granted: granted}), frame}
end
end
defp visible?(%Prompt{scopes: []}, _frame), do: true
defp visible?(%Prompt{scopes: required}, frame), do: Frame.has_all_scopes?(frame, required)
defp find_prompt_module(prompts, name), do: Enum.find(prompts, &(&1.name == name))
defp validate_params(_, %Prompt{validate_input: nil}, _), do: {:ok, %{}}
defp validate_params(params, %Prompt{} = prompt, frame) do
with {:error, errors} <- prompt.validate_input.(params) do
message = Schema.format_errors(errors)
{:error, Error.protocol(:invalid_params, %{message: message}), frame}
end
end
defp forward_to(server, %Prompt{handler: nil} = prompt, params, frame) do
case server.handle_prompt_get(prompt.name, params, frame) do
{:reply, %Response{} = response, frame} ->
{:reply, Response.to_protocol(response), frame}
{:noreply, frame} ->
{:reply, %{"content" => [], "isError" => false}, frame}
{:error, %Error{} = error, frame} ->
{:error, error, frame}
end
end
defp forward_to(_server, %Prompt{handler: handler}, params, frame) do
case handler.get_messages(params, frame) do
{:reply, %Response{} = response, frame} ->
{:reply, Response.to_protocol(response), frame}
{:noreply, frame} ->
{:reply, %{"content" => [], "isError" => false}, frame}
{:error, %Error{} = error, frame} ->
{:error, error, frame}
end
end
end