In-process MCP (Model Context Protocol) support for the BeamAgent SDK.
MCP is an open protocol that lets AI sessions discover and call structured tools, read resources, and retrieve prompt templates. Instead of embedding tool logic inside prompts, you define tools as Elixir functions, group them into a named server, and pass the server to the session at startup — the backend calls them in-process over a well-defined JSON-RPC 2.0 wire format.
When to use directly vs through BeamAgent
Pass an MCP server via :sdk_mcp_servers in BeamAgent.start_session/1 for
normal usage. Use this module directly when you need to:
- Build and inspect registries programmatically
- Implement the full MCP server or client state machine (e.g. for a custom transport layer)
- Toggle or reconnect servers at runtime
Quick example — defining and registering tools
# 1. Define a tool
greet_tool = BeamAgent.MCP.tool(
"greet",
"Greet a user by name",
%{"type" => "object", "properties" => %{"name" => %{"type" => "string"}}},
fn input ->
name = Map.get(input, "name", "world")
{:ok, [%{type: :text, text: "Hello, #{name}!"}]}
end
)
# 2. Group into a named MCP server
my_server = BeamAgent.MCP.server("my-tools", [greet_tool])
# 3. Register at session start
{:ok, session} = BeamAgent.start_session(%{
backend: :claude,
sdk_mcp_servers: [my_server]
})The 4 subsystems
This module re-exports from four underlying core modules:
1. Tool registry (beam_agent_tool_registry)
Manages named MCP servers and their tools within a session. Key functions:
tool/4, server/2,3, new_registry/0, build_registry/1,
register_server/2, call_tool_by_name/3,4, all_tool_definitions/1,
handle_mcp_message/3,4.
Runtime management: toggle_server/3, reconnect_server/2,
unregister_server/2, set_servers/2.
Session-scoped ETS registry: register_session_registry/2,
get_session_registry/1, update_session_registry/2,
unregister_session_registry/1, ensure_registry_table/0.
2. Protocol (beam_agent_mcp_protocol)
Pure functions for MCP spec 2025-06-18. protocol_version/0 returns the
version string.
3. Server-side dispatch (beam_agent_mcp_dispatch)
Full MCP server state machine. Use new_dispatch/3 to create server state,
dispatch_message/2 to process incoming JSON-RPC messages.
state0 = BeamAgent.MCP.new_dispatch(
%{name: "my-server", version: "1.0.0"},
%{tools: %{}},
%{}
)
{reply, state1} = BeamAgent.MCP.dispatch_message(init_msg, state0)4. Client-side dispatch (beam_agent_mcp_client_dispatch)
Full MCP client state machine. Drive the handshake and method calls:
state0 = BeamAgent.MCP.new_client(
%{name: "beam-agent-client", version: "1.0.0"},
%{roots: %{}},
%{}
)
{init_msg, state1} = BeamAgent.MCP.client_send_initialize(state0)
# ... send init_msg over transport, receive response ...
{:ok, state2} = BeamAgent.MCP.client_handle_message(server_response, state1)
{ack_msg, state3} = BeamAgent.MCP.client_send_initialized(state2)Architecture: session-scoped ETS registries
Each session process registers its mcp_registry() in a global ETS table via
register_session_registry/2. This allows other processes (e.g. the transport
layer, universal fallback handlers) to look up and atomically update the session
registry without holding a reference to the session process state directly. The
table is initialised once via ensure_registry_table/0 during application
startup.
Summary
Types
Result type from client_handle_message/2.
Opaque state record for the MCP client-side dispatch state machine.
MCP completion reference.
Content result returned by a tool handler.
Result type from dispatch_message/2.
Opaque state record for the MCP server-side dispatch state machine.
MCP implementation info identifying a client or server.
MCP log level.
A registry mapping server name binaries to their sdk_mcp_server() definitions.
A pending request awaiting a server response.
MCP progress token.
MCP request identifier per JSON-RPC 2.0 spec.
An SDK MCP server grouping one or more tools under a name.
Describes a request that timed out in the client pending-request queue.
A complete tool definition map as produced by tool/4.
Handler function type for in-process MCP tools.
Functions
Register a new MCP tool server with a live session.
Return the flat list of all tool definitions across all servers in the registry.
Build an mcp_registry() from a list of servers, or return nil.
Call a tool by name across all servers in the registry.
Call a tool by name with additional options.
Remove all globally registered MCP servers.
Check for timed-out pending requests and purge them from the client state.
Return the error reason from the client state.
Process an incoming JSON-RPC message from the MCP server through the client state machine.
Return true if the client is in the :ready state.
Return the current lifecycle state of the MCP client dispatch state machine.
Transition the client state to :disconnected.
Transition the client state to :error.
Transition the client state to :shutting_down.
Return the number of pending (in-flight) requests in the client state.
Reset the client from :error or :disconnected back to :uninitialized.
Build and return a cancelled notification for a pending request (no reason).
Build and return a cancelled notification with a human-readable reason.
Build and return a completion/complete request (no context).
Build and return a completion/complete request with context.
Build and return an MCP initialize request message.
Build and return an MCP initialized notification message.
Build and return a logging/setLevel request.
Build and return an MCP ping request message.
Build and return a progress notification (progress value only).
Build and return a progress notification with a total value.
Build and return a progress notification with total and a status message.
Build and return a prompts/get request by prompt name (no arguments).
Build and return a prompts/get request with template arguments.
Build and return a prompts/list request (no cursor).
Build and return a prompts/list request with a pagination cursor.
Build and return an arbitrary MCP request by method name.
Build and return a resources/list request (no cursor, first page).
Build and return a resources/list request with a pagination cursor.
Build and return a resources/read request for a specific resource URI.
Build and return a resources/subscribe request for a resource URI.
Build and return a resources/templates/list request (no cursor).
Build and return a resources/templates/list request with a pagination cursor.
Build and return a resources/unsubscribe request for a resource URI.
Build and return a roots/list_changed notification.
Build and return a tools/call request.
Build and return a tools/list request (no cursor, first page).
Build and return a tools/list request with a pagination cursor.
Return the server capabilities advertised by the MCP server during the handshake.
Return the negotiated session capabilities from the client state.
Return the error reason from the server dispatch state.
Return true if the server dispatch is in the :ready state.
Return the current lifecycle state of the server dispatch state machine.
Transition the server dispatch state to :error.
Transition the server dispatch state to :shutting_down.
Process an incoming JSON-RPC message through the MCP server dispatch state machine.
Reset the server dispatch from :error back to :uninitialized.
Return the negotiated session capabilities from the server dispatch state.
Create the global MCP servers ETS table. Idempotent.
Ensure the global ETS session-registry table exists, creating it if necessary.
Fetch a single global MCP server by name.
Retrieve the MCP registry for a session from the global ETS table.
Dispatch an incoming MCP JSON-RPC message to the named server in the registry.
Dispatch an MCP JSON-RPC message with additional call options.
List all globally registered MCP servers.
Create a new MCP client-side dispatch state machine.
Create a new MCP server-side dispatch state machine.
Create a new empty MCP registry.
Return the MCP protocol version string this SDK implements.
Mark a named server as reconnected in the registry.
Register an MCP server definition globally (shared across all sessions).
Add a server to a registry, returning the updated registry.
Store a session's MCP registry in the global ETS session registry table.
Create a named MCP server containing a list of tool definitions.
Create a named MCP server with an explicit version string.
Return the list of server name binaries registered in a registry.
Initiate an OAuth login flow for an MCP server.
Reload all MCP server configurations for a live session.
Return a status map for every server in the registry.
Project the registry into the CLI-integration format expected by backend adapters.
Return the list of server name binaries to advertise in the MCP initialize handshake.
Reconnect a disconnected MCP server on a live session.
Get the status of all MCP servers for a live session.
Replace all MCP servers for a live session.
Enable or disable an MCP server on a live session.
Replace the full set of servers in a registry.
Get the MCP status for a live session.
List the status of all MCP servers as a single response.
Enable or disable a named server in the registry at runtime.
Create a tool definition for use in an in-process MCP server.
Unregister a global MCP server by name. Idempotent.
Remove a named server from the registry.
Remove the MCP registry entry for a session from the global ETS table.
Atomically update the MCP registry for a session in the ETS table.
Types
@type client_result() :: {:response, request_id(), term(), client_state()} | {:error_response, request_id(), integer(), binary(), client_state()} | {:server_request, map(), client_state()} | {:notification, binary(), map(), client_state()} | {:noreply, client_state()}
Result type from client_handle_message/2.
Tagged tuple variants for different server message types:
{:response, id, result, state}— successful response{:error_response, id, code, message, state}— error response{:server_request, msg, state}— server-initiated request{:notification, method, params, state}— server notification{:noreply, state}— no reply needed
@type client_state() :: %{ :lifecycle => :uninitialized | :initializing | :ready | :error | :disconnected | :shutting_down, :client_info => implementation_info(), :client_capabilities => map(), optional(:server_capabilities) => map(), optional(:session_capabilities) => map(), :next_id => pos_integer(), :pending => %{required(request_id()) => pending_request()}, :default_timeout => pos_integer(), optional(:handler) => module(), optional(:handler_state) => term(), optional(:error_info) => term() }
Opaque state record for the MCP client-side dispatch state machine.
MCP completion reference.
@type content_result() :: %{type: :text, text: binary()} | %{type: :image, data: binary(), mime_type: binary()}
Content result returned by a tool handler.
Either a text result %{type: :text, text: binary()} or an image result
%{type: :image, data: binary(), mime_type: binary()}.
@type dispatch_result() :: {map() | :noreply, dispatch_state()}
Result type from dispatch_message/2.
Typically {response_map, new_state} where response_map is a JSON-RPC map
to send back over the transport, or {:noreply, new_state} for notifications
that require no response.
@type dispatch_state() :: %{ :lifecycle => :uninitialized | :initializing | :ready | :error | :shutting_down, :server_info => implementation_info(), :server_capabilities => map(), optional(:session_capabilities) => map(), optional(:tool_registry) => mcp_registry(), optional(:handler_timeout) => pos_integer(), optional(:provider) => module(), optional(:provider_state) => term(), optional(:error_info) => term() }
Opaque state record for the MCP server-side dispatch state machine.
@type implementation_info() :: %{ :name => binary(), :version => binary(), optional(:title) => binary() }
MCP implementation info identifying a client or server.
@type log_level() ::
:debug | :info | :notice | :warning | :error | :critical | :alert | :emergency
MCP log level.
@type mcp_registry() :: %{required(binary()) => sdk_mcp_server()}
A registry mapping server name binaries to their sdk_mcp_server() definitions.
Constructed via new_registry/0 or build_registry/1.
A pending request awaiting a server response.
MCP progress token.
MCP request identifier per JSON-RPC 2.0 spec.
In the Elixir API, nil represents a null request ID. The Erlang layer
uses the atom :null. Request IDs are always generated by the Erlang layer
and returned to Elixir callers; nil in this type reflects how a null ID
appears on the Elixir side and is not a valid input to any Elixir API
function.
@type sdk_mcp_server() :: %{ :name => binary(), optional(:version) => binary(), tools: [tool_def()] }
An SDK MCP server grouping one or more tools under a name.
Contains :name, :tools, and optionally :version.
@type timed_out_request() :: %{id: request_id(), method: binary(), sent_at: integer()}
Describes a request that timed out in the client pending-request queue.
Contains at minimum the request ID and the method name. Returned from
client_check_timeouts/2.
@type tool_def() :: %{ name: binary(), description: binary(), input_schema: map(), handler: tool_handler() }
A complete tool definition map as produced by tool/4.
Contains :name, :description, :input_schema (a JSON Schema map), and
:handler (the tool_handler() function).
@type tool_handler() :: (map() -> {:ok, [content_result()]} | {:error, binary()})
Handler function type for in-process MCP tools.
A 1-arity function that receives the tool's input arguments as a map and
returns {:ok, [content_result()]} on success or {:error, reason} on
failure.
handler = fn input ->
value = Map.get(input, "x", 0)
{:ok, [%{type: :text, text: Integer.to_string(value * 2)}]}
end
Functions
Register a new MCP tool server with a live session.
body describes the server to add. It must contain a :name (binary)
and a :tools list (list of tool definition maps).
Parameters
session-- pid of a running session.body-- server definition map.
Returns
{:ok, result}or{:error, reason}.
Examples
server = %{name: "my-tools", tools: [%{name: "greet", description: "Say hello"}]}
{:ok, _} = BeamAgent.MCP.add_server(session, server)
@spec all_tool_definitions(mcp_registry()) :: [tool_def()]
Return the flat list of all tool definitions across all servers in the registry.
Useful for building a tools/list MCP response or inspecting what tools are
available in a given session.
@spec build_registry(nil | [sdk_mcp_server()]) :: mcp_registry() | nil
Build an mcp_registry() from a list of servers, or return nil.
Accepts either a list of sdk_mcp_server() values or nil. This is the
canonical way to construct a registry from the :sdk_mcp_servers session
option.
Example
registry = BeamAgent.MCP.build_registry([server1, server2])
@spec call_tool_by_name(binary(), map(), mcp_registry()) :: {:ok, [content_result()]} | {:error, binary()}
Call a tool by name across all servers in the registry.
Searches all registered servers for a tool matching tool_name and invokes its
handler with arguments. Returns {:ok, [content_result()]} on success, or
{:error, "tool not found"} if no server in the registry has a tool with that
name.
@spec call_tool_by_name(binary(), map(), mcp_registry(), map()) :: {:ok, [content_result()]} | {:error, binary()}
Call a tool by name with additional options.
Same as call_tool_by_name/3 but accepts an opts map (e.g.
%{timeout: 5000}).
@spec clear_global_servers() :: :ok
Remove all globally registered MCP servers.
@spec client_check_timeouts(integer(), client_state()) :: {[timed_out_request()], client_state()}
Check for timed-out pending requests and purge them from the client state.
now is a monotonic timestamp (e.g. from :erlang.monotonic_time(:millisecond)).
Returns {timed_out_list, new_state} where timed_out_list is a list of
timed_out_request() values for requests that exceeded their deadline.
Call this periodically (e.g. from a gen_statem timeout event) to clean up
stale pending requests.
@spec client_error_info(client_state()) :: term() | nil
Return the error reason from the client state.
Returns nil if not in :error or :disconnected state.
@spec client_handle_message(map(), client_state()) :: client_result()
Process an incoming JSON-RPC message from the MCP server through the client state machine.
msg is a decoded map received from the server (a response or notification).
Returns {:ok, new_state} on success. Thread new_state into the next call.
@spec client_is_operational(client_state()) :: boolean()
Return true if the client is in the :ready state.
@spec client_lifecycle_state(client_state()) :: :uninitialized | :initializing | :ready | :error | :disconnected | :shutting_down
Return the current lifecycle state of the MCP client dispatch state machine.
Returns one of :uninitialized, :initializing, :ready, :error,
:disconnected, or :shutting_down.
@spec client_mark_disconnected(term(), client_state()) :: client_state()
Transition the client state to :disconnected.
Call when the transport signals loss of connection. Only valid from :ready
or :initializing state. Raises on invalid state.
@spec client_mark_error(term(), client_state()) :: client_state()
Transition the client state to :error.
Stores reason for later retrieval via client_error_info/1. All send_*
calls will raise until client_reset/1 is called.
@spec client_mark_shutting_down(client_state()) :: client_state()
Transition the client state to :shutting_down.
This is a terminal state. All send_* calls will raise.
@spec client_pending_count(client_state()) :: non_neg_integer()
Return the number of pending (in-flight) requests in the client state.
A non-zero count means there are requests awaiting responses from the server.
@spec client_reset(client_state()) :: client_state()
Reset the client from :error or :disconnected back to :uninitialized.
Clears error info, pending requests, and server/session capabilities. Raises on invalid state.
@spec client_send_cancelled(request_id(), client_state()) :: {map(), client_state()}
Build and return a cancelled notification for a pending request (no reason).
@spec client_send_cancelled(request_id(), binary(), client_state()) :: {map(), client_state()}
Build and return a cancelled notification with a human-readable reason.
@spec client_send_completion_complete(completion_ref(), map(), client_state()) :: {map(), client_state()}
Build and return a completion/complete request (no context).
ref identifies the completion reference (prompt or resource URI).
argument is the argument map with the partial value to complete.
@spec client_send_completion_complete(completion_ref(), map(), map(), client_state()) :: {map(), client_state()}
Build and return a completion/complete request with context.
context is an optional map of additional context for the completion.
@spec client_send_initialize(client_state()) :: {map(), client_state()}
Build and return an MCP initialize request message.
This is the first message to send in the MCP handshake. Returns
{msg, new_state} where msg is the JSON-RPC map to send over the transport.
@spec client_send_initialized(client_state()) :: {map(), client_state()}
Build and return an MCP initialized notification message.
Send this after receiving a successful initialize response from the server.
Returns {msg, new_state}.
@spec client_send_logging_set_level(log_level(), client_state()) :: {map(), client_state()}
Build and return a logging/setLevel request.
level is one of the MCP log level atoms such as :debug, :info,
:warning, or :error.
@spec client_send_ping(client_state()) :: {map(), client_state()}
Build and return an MCP ping request message.
Use to verify the server connection is alive. Returns {msg, new_state}.
@spec client_send_progress(progress_token(), number(), client_state()) :: {map(), client_state()}
Build and return a progress notification (progress value only).
token is the progress token from the original request.
progress is the current progress value (0.0–1.0 or an absolute count).
@spec client_send_progress(progress_token(), number(), number(), client_state()) :: {map(), client_state()}
Build and return a progress notification with a total value.
total is the total work units, allowing clients to display a percentage.
@spec client_send_progress( progress_token(), number(), number(), binary(), client_state() ) :: {map(), client_state()}
Build and return a progress notification with total and a status message.
message is a human-readable binary describing the current step.
@spec client_send_prompts_get(binary(), client_state()) :: {map(), client_state()}
Build and return a prompts/get request by prompt name (no arguments).
@spec client_send_prompts_get(binary(), map(), client_state()) :: {map(), client_state()}
Build and return a prompts/get request with template arguments.
arguments is a map of variable bindings for the prompt template.
@spec client_send_prompts_list(client_state()) :: {map(), client_state()}
Build and return a prompts/list request (no cursor).
@spec client_send_prompts_list(binary(), client_state()) :: {map(), client_state()}
Build and return a prompts/list request with a pagination cursor.
@spec client_send_request(binary(), map(), client_state()) :: {map(), client_state()}
Build and return an arbitrary MCP request by method name.
Use this for MCP methods not covered by the typed send functions. method is
the JSON-RPC method string and params is the params map.
@spec client_send_resources_list(client_state()) :: {map(), client_state()}
Build and return a resources/list request (no cursor, first page).
@spec client_send_resources_list(binary(), client_state()) :: {map(), client_state()}
Build and return a resources/list request with a pagination cursor.
@spec client_send_resources_read(binary(), client_state()) :: {map(), client_state()}
Build and return a resources/read request for a specific resource URI.
@spec client_send_resources_subscribe(binary(), client_state()) :: {map(), client_state()}
Build and return a resources/subscribe request for a resource URI.
Subscribe to change notifications for the resource at uri.
@spec client_send_resources_templates_list(client_state()) :: {map(), client_state()}
Build and return a resources/templates/list request (no cursor).
@spec client_send_resources_templates_list(binary(), client_state()) :: {map(), client_state()}
Build and return a resources/templates/list request with a pagination cursor.
@spec client_send_resources_unsubscribe(binary(), client_state()) :: {map(), client_state()}
Build and return a resources/unsubscribe request for a resource URI.
@spec client_send_roots_list_changed(client_state()) :: {map(), client_state()}
Build and return a roots/list_changed notification.
Send this when the client's root list has changed so the server can re-fetch it.
@spec client_send_tools_call(binary(), map(), client_state()) :: {map(), client_state()}
Build and return a tools/call request.
Parameters:
tool_name— name of the tool to invokearguments— map of input arguments matching the tool's JSON Schema
@spec client_send_tools_list(client_state()) :: {map(), client_state()}
Build and return a tools/list request (no cursor, first page).
@spec client_send_tools_list(binary(), client_state()) :: {map(), client_state()}
Build and return a tools/list request with a pagination cursor.
Pass cursor from a previous tools/list response to fetch the next page.
@spec client_server_capabilities(client_state()) :: map()
Return the server capabilities advertised by the MCP server during the handshake.
Only meaningful after the initialize/initialized handshake completes.
@spec client_session_capabilities(client_state()) :: map()
Return the negotiated session capabilities from the client state.
@spec dispatch_error_info(dispatch_state()) :: term() | nil
Return the error reason from the server dispatch state.
Returns nil if not in :error state.
@spec dispatch_is_operational(dispatch_state()) :: boolean()
Return true if the server dispatch is in the :ready state.
@spec dispatch_lifecycle_state(dispatch_state()) :: :uninitialized | :initializing | :ready | :error | :shutting_down
Return the current lifecycle state of the server dispatch state machine.
Returns one of :uninitialized, :initializing, :ready, :error, or
:shutting_down.
@spec dispatch_mark_error(term(), dispatch_state()) :: dispatch_state()
Transition the server dispatch state to :error.
Stores reason for later retrieval via dispatch_error_info/1. In :error
state, only ping and initialize (re-init) requests are accepted.
@spec dispatch_mark_shutting_down(dispatch_state()) :: dispatch_state()
Transition the server dispatch state to :shutting_down.
This is a terminal state. Only ping requests are accepted.
@spec dispatch_message(map(), dispatch_state()) :: dispatch_result()
Process an incoming JSON-RPC message through the MCP server dispatch state machine.
msg is a decoded JSON-RPC map received from the client. Returns a
dispatch_result() — typically {response_map, new_state} for requests or
{:noreply, new_state} for notifications.
Thread new_state into the next dispatch_message/2 call.
@spec dispatch_reset(dispatch_state()) :: dispatch_state()
Reset the server dispatch from :error back to :uninitialized.
Clears error info and session capabilities, allowing a fresh MCP handshake. Raises on invalid state.
@spec dispatch_session_capabilities(dispatch_state()) :: map()
Return the negotiated session capabilities from the server dispatch state.
Only meaningful after the MCP initialize handshake completes (lifecycle :ready).
@spec ensure_global_table() :: :ok
Create the global MCP servers ETS table. Idempotent.
@spec ensure_registry_table() :: :ok
Ensure the global ETS session-registry table exists, creating it if necessary.
This is idempotent and safe to call multiple times. Call it once during application startup (or OTP supervisor init) before any sessions are started.
@spec get_global_server(binary()) :: {:ok, sdk_mcp_server()} | {:error, :not_found}
Fetch a single global MCP server by name.
@spec get_session_registry(pid()) :: {:ok, mcp_registry()} | {:error, :not_found}
Retrieve the MCP registry for a session from the global ETS table.
Returns {:ok, registry} if the session is registered, or
{:error, :not_found} if no entry exists for pid.
@spec handle_mcp_message(binary(), map(), mcp_registry()) :: {:ok, map()} | {:error, binary()}
Dispatch an incoming MCP JSON-RPC message to the named server in the registry.
server_name identifies which in-process server should handle the message.
message is a decoded JSON-RPC map (e.g. a tools/call request).
Returns {:ok, response_map} or {:error, reason}.
@spec handle_mcp_message(binary(), map(), mcp_registry(), map()) :: {:ok, map()} | {:error, binary()}
Dispatch an MCP JSON-RPC message with additional call options.
opts may include a :timeout key controlling the maximum handler execution
time in milliseconds (default: 30_000 ms).
@spec list_global_servers() :: [sdk_mcp_server()]
List all globally registered MCP servers.
@spec new_client(implementation_info(), map(), map()) :: client_state()
Create a new MCP client-side dispatch state machine.
Parameters:
client_info— implementation info map, e.g.%{name: "my-client", version: "1.0.0"}client_caps— client capabilities map, e.g.%{roots: %{listChanged: true}}opts— additional options (pass%{}for defaults)
Returns an opaque client_state(). Drive the MCP handshake by calling
client_send_initialize/1 next.
Example
state = BeamAgent.MCP.new_client(
%{name: "beam-agent-client", version: "1.0.0"},
%{roots: %{}},
%{}
)
@spec new_dispatch(implementation_info(), map(), map()) :: dispatch_state()
Create a new MCP server-side dispatch state machine.
Parameters:
server_info— implementation info map, e.g.%{name: "srv", version: "1.0"}server_caps— server capabilities map, e.g.%{tools: %{}, resources: %{}}opts— additional options (pass%{}for defaults)
Returns an opaque dispatch_state() to be threaded through dispatch_message/2.
Example
state = BeamAgent.MCP.new_dispatch(
%{name: "my-server", version: "1.0.0"},
%{tools: %{}},
%{}
)
@spec new_registry() :: %{}
Create a new empty MCP registry.
An mcp_registry() is a map from server-name binary to sdk_mcp_server().
Use register_server/2 to add servers, or build_registry/1 to construct one
directly from a list of servers.
Example
registry = BeamAgent.MCP.new_registry()
@spec protocol_version() :: <<_::80>>
Return the MCP protocol version string this SDK implements.
Returns a binary such as "2025-06-18". Used during the MCP initialize
handshake to advertise protocol compatibility.
@spec reconnect_server(binary(), mcp_registry() | :undefined) :: {:ok, mcp_registry()} | {:error, :not_found}
Mark a named server as reconnected in the registry.
Resets any error state on the server entry.
Returns {:ok, updated_registry} or {:error, :not_found}.
@spec register_global_server(binary(), sdk_mcp_server()) :: :ok
Register an MCP server definition globally (shared across all sessions).
Parameters
@spec register_server(sdk_mcp_server(), mcp_registry()) :: mcp_registry()
Add a server to a registry, returning the updated registry.
If a server with the same name already exists it is replaced.
Example
registry =
BeamAgent.MCP.new_registry()
|> BeamAgent.MCP.register_server(my_server)
@spec register_session_registry(pid(), mcp_registry() | :undefined) :: :ok
Store a session's MCP registry in the global ETS session registry table.
Associates registry (or nil) with the session pid so that other processes
can retrieve it via get_session_registry/1 without holding a reference to the
session process state. Called by session handlers during initialisation.
@spec server(binary(), [tool_def()]) :: sdk_mcp_server()
Create a named MCP server containing a list of tool definitions.
Uses default version "1.0.0". Pass the returned sdk_mcp_server() in the
:sdk_mcp_servers option when starting a session, or register it at runtime
with register_server/2.
Example
server = BeamAgent.MCP.server("file-tools", [read_file_tool, list_dir_tool])
@spec server(binary(), [tool_def()], binary()) :: sdk_mcp_server()
Create a named MCP server with an explicit version string.
Use this variant when the backend or client requires a specific server version for capability negotiation.
Example
server = BeamAgent.MCP.server("file-tools", [tool], "2.1.0")
@spec server_names(mcp_registry()) :: [binary()]
Return the list of server name binaries registered in a registry.
Initiate an OAuth login flow for an MCP server.
This operation requires native backend support; the universal fallback
returns a status: :not_supported result.
Parameters
session-- pid of a running session.opts-- OAuth parameters map (server name, client_id, redirect_uri, scopes).
Returns
{:ok, result}or{:error, reason}.
Reload all MCP server configurations for a live session.
Forces a refresh of tool definitions from all registered servers.
Parameters
session-- pid of a running session.
Returns
{:ok, result}or{:error, reason}.
@spec server_status(mcp_registry() | :undefined) :: {:ok, %{required(binary()) => map()}}
Return a status map for every server in the registry.
Returns {:ok, %{server_name => status_map}} where each status_map
describes the server's current state (e.g. enabled/disabled, tool count).
Pass nil to get {:ok, %{}}.
@spec servers_for_cli(mcp_registry()) :: map()
Project the registry into the CLI-integration format expected by backend adapters.
Returns a map suitable for passing as the mcp_servers field in CLI invocation
opts. Internal use by session handlers.
@spec servers_for_init(mcp_registry()) :: [binary()]
Return the list of server name binaries to advertise in the MCP initialize handshake.
Internal use by session handlers during the MCP initialization sequence.
Reconnect a disconnected MCP server on a live session.
Session-scoped variant — takes a session pid.
Use reconnect_server/2 for registry-scoped operations.
Parameters
session-- pid of a running session.server_name-- binary server name.
Returns
{:ok, result}or{:error, {:server_not_found, server_name}}.
Get the status of all MCP servers for a live session.
Session-scoped variant — takes a session pid instead of a registry.
Use server_status/1 for registry-scoped (pure data) status queries.
Parameters
session-- pid of a running session.
Returns
{:ok, status_map}or{:error, reason}.
Replace all MCP servers for a live session.
Session-scoped variant — takes a session pid and server list.
Use set_servers/2 for registry-scoped (pure data) operations.
Parameters
session-- pid of a running session.servers-- list of server definition maps.
Returns
{:ok, result}or{:error, reason}.
Enable or disable an MCP server on a live session.
Session-scoped variant — takes a session pid.
Use toggle_server/3 for registry-scoped operations.
Parameters
session-- pid of a running session.server_name-- binary server name.enabled-- boolean.
Returns
{:ok, result}or{:error, {:server_not_found, server_name}}.
@spec set_servers([sdk_mcp_server()], mcp_registry() | :undefined) :: mcp_registry()
Replace the full set of servers in a registry.
Merges new servers over the old registry, preserving runtime state (enabled/disabled flags) for servers that existed before.
Get the MCP status for a live session.
Returns the combined status of all MCP servers registered with the session, including connection state and tool availability. This is the session-scoped equivalent of inspecting a registry's server status.
Parameters
session-- pid of a running session.
Returns
{:ok, status_map}or{:error, reason}.
Examples
{:ok, status} = BeamAgent.MCP.status(session)
for {name, info} <- status do
IO.puts("#{name}: #{info[:status]}")
end
List the status of all MCP servers as a single response.
An alias for server_status/1 when called with a session pid,
provided for backends that distinguish between a single-server status
query and a bulk list operation. Returns the same map-of-maps structure.
Parameters
session-- pid of a running session.
Returns
{:ok, status_map}or{:error, reason}.
@spec toggle_server(binary(), boolean(), mcp_registry() | :undefined) :: {:ok, mcp_registry()} | {:error, :not_found}
Enable or disable a named server in the registry at runtime.
enabled is true to enable or false to disable.
Returns {:ok, updated_registry} or {:error, :not_found}.
@spec tool(binary(), binary(), map(), tool_handler()) :: tool_def()
Create a tool definition for use in an in-process MCP server.
Parameters:
name— unique tool name binary, e.g."search_files"description— human-readable description shown to the AIinput_schema— JSON Schema map describing the tool's input objecthandler—tool_handler()function invoked when the AI calls the tool
Example
Always validate AI-supplied file paths against an allowed root.
root_dir = "/path/to/allowed/directory"
tool = BeamAgent.MCP.tool(
"read_file",
"Read the contents of a file",
%{
"type" => "object",
"properties" => %{"path" => %{"type" => "string"}},
"required" => ["path"]
},
fn %{"path" => path} ->
case :filename.safe_relative_path(String.to_charlist(path)) do
:unsafe ->
{:error, "path rejected: must be relative, no traversal allowed"}
safe_path ->
full = Path.join(root_dir, List.to_string(safe_path))
case File.read(full) do
{:ok, bin} -> {:ok, [%{type: :text, text: bin}]}
{:error, reason} -> {:error, inspect(reason)}
end
end
end
)
@spec unregister_global_server(binary()) :: :ok
Unregister a global MCP server by name. Idempotent.
@spec unregister_server(binary(), mcp_registry()) :: mcp_registry()
Remove a named server from the registry.
No-ops if the server is not present. Returns the updated registry.
@spec unregister_session_registry(pid()) :: :ok
Remove the MCP registry entry for a session from the global ETS table.
Called by session handlers during termination to clean up ETS state.
@spec update_session_registry(pid(), (mcp_registry() -> mcp_registry())) :: :ok | {:error, :not_found}
Atomically update the MCP registry for a session in the ETS table.
Applies update_fun to the current registry and stores the result. This is the
safe way to add or modify servers while the session is running.
Returns :ok or {:error, :not_found} if the session is not registered.
Example
:ok = BeamAgent.MCP.update_session_registry(pid, fn reg ->
BeamAgent.MCP.register_server(new_server, reg)
end)