WebSocket transport for Ectomancer via Phoenix.Socket.Transport.
Provides bidirectional JSON-RPC communication over WebSocket, with Anubis session management for tool execution and actor propagation.
Phoenix Router Integration
In your endpoint:
socket "/mcp/ws", Ectomancer.Plug.WebSocket,
server: MyApp.MCP,
websocket: [connect_info: [:x_headers, :uri, :peer_data, :user_agent]]The connect_info option controls what HTTP request metadata is available
during the WebSocket handshake for actor extraction.
Actor Extraction
Actor extraction for WebSocket differs from HTTP — there is no Plug.Conn.
The configured actor_from function receives a map with the following keys
when invoked from a WebSocket connection:
:params— query params from the WebSocket URL:connect_info— configured connection info (:x_headers,:uri, etc.)
Example actor_from that handles both HTTP and WebSocket:
config :ectomancer,
actor_from: fn
%Plug.Conn{} = conn ->
Ectomancer.Plug.extract_bearer_token(conn) |> verify_token()
ws_info when is_map(ws_info) ->
# WebSocket: extract token from query param or x-headers
case ws_info.params["token"] do
nil ->
# Try Authorization header from x_headers
headers = (ws_info.connect_info[:x_headers] || [])
{_, token} = List.keyfind(headers, "authorization", 0, {nil, nil})
String.replace_prefix(token || "", "Bearer ", "") |> verify_token()
token -> verify_token(token)
end
endIf no actor_from is configured, the actor defaults to nil.
Auth via connect return value
Return {:error, reason} from the Phoenix socket connect callback to reject
the WebSocket connection before any MCP messages are processed.