Ectomancer.Plug.WebSocket (Ectomancer v1.6.0)

Copy Markdown View Source

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
  end

If 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.