WebSocket-first
Elixir Web Framework
Dialup はサーバー上の GenServer がブラウザの状態を持ち続ける WebSocket ファーストの Elixir フレームワークです。
defmodule Dialup.App.Counter.Page do
use Dialup.Page
declare_action name: :increment, desc: "Increment counter", params: %{}
def mount(_params, assigns), do: {:ok, Map.put(assigns, :count, 0)}
def agent_state(assigns), do: %{count: assigns.count}
def handle_event(:increment, _, assigns) do
{:update, Map.update!(assigns, :count, &(&1 + 1))}
end
def render(assigns) do
~H"""
<p>Count: {@count}</p>
<.dialup_action name={:increment}>+1</.dialup_action>
"""
end
end
Web UI と MCP Tools が自動で対応
<.dialup_action> で Web UI を宣言すると、同じ操作が MCP ツールとして
tools/list に掲載されます。別途 REST を設計する必要はありません。
Web UI を宣言する
<.dialup_action command={...} desc="Add a line item">
Add item
</.dialup_action>
MCP ツールが自動生成される
POST /mcp Authorization: Bearer TOKEN
{"method":"tools/list"}
→ add_item, read_scene, …
同じセッションを更新する
{"method":"tools/call",
"name":"add_item",
"arguments":{"sku":"SKU-1","_version":3}}
Dialup の特徴
1 tab = 1 process で、全てのページにまたがってセッションが継続。
状態はサーバー上に存在し、WebSocket で同期される。
UI → HTTP MCP
action / region 宣言から tools/list と tools/call を自動生成。人間は WebSocket、AI は JSON-RPC。
1 tab = 1 process
各セッションが独立した GenServer を持つ。状態はサーバー上に存在し、WebSocket で同期される。
Command 駆動の DDD
<.dialup_action command={...}> が
Commanded
の command を dispatch。UI と MCP は read model を投影し、変更は集約コンテキストへ一本化。
session / assigns の分離
session はナビゲーションをまたいで持続。assigns はページ遷移でリセット。ログイン状態が自然に保たれる。
クライアント側 DOM モーフィング
サーバーは HTML を送るだけ。差分適用は idiomorph が担う。プロトコルが単純でデバッグしやすい。
軽量な依存関係
Phoenix 本体は不使用。Bandit + HEEx + idiomorph のみ。クライアント JS は自作 ~100 行 + idiomorph 5KB。
実際に動かしてみる
MCP のライブデモと、カウンター・フォームの UI デモを用意しています。