Dialup

WebSocket-first のライブ UI自動生成される HTTP MCP API を備えた、ファイルベースルーティングの Elixir フレームワーク。

English

リンク

概要

Dialup は Next.js のような開発体験でライブアプリを構築できる Elixir フレームワークです。 中核は二つあります。人間向け UI は最初から WebSocket-first で動き、HTTP MCP API は同じページ宣言から自動生成されます。 各ページは 1 つの監視されたサーバー側アクターです。UI を <.dialup_action><.dialup_region> で書くと、Dialup が tools/listtools/callread_scene を導出します。 REST API の二重実装は不要です。

人間ブラウザ ──WebSocket──► UserSessionProcess ◄──HTTP JSON-RPC── AI エージェント
command / set / navigate / handle_event
declare_action / dialup_action

特徴

クイックスタート

1. ジェネレータのインストール

mix archive.install hex dialup_new

2. 新規プロジェクト作成

mix dialup.new my_app
cd my_app
mix deps.get
mix run --no-halt

http://localhost:4000 にアクセス

エージェント対応ページの最小例

defmodule Dialup.App.Page do
use Dialup.Page
declare_action name: :increment, desc: "カウンタを増やす", 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

同じ <.dialup_action> が、ブラウザでは WebSocket 経由のボタンになり、エージェントには生成された MCP ツールとして見えます。 ブラウザで操作することも、トークンを取得して API を呼び出すこともできます(詳細は HTTP MCP API):

curl -X POST http://localhost:4000/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer TOKEN' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_scene","arguments":{}}}'

ドキュメント

mix docs --open で全文を参照できます。

その他は guides/ を参照(ルーティング、状態管理、デプロイなど)。

アーキテクチャ

1 タブにつき 1 つの UserSessionProcess が動きます。サーバー側セッションアクターがページ状態・ 宣言済み action・エージェント grant の single source of truth です。人間と AI エージェントは 異なるトランスポート(ブラウザは WebSocket、エージェントは HTTP JSON-RPC)から同じアクターに 到達します。

全体像

flowchart LR
humanBrowser["Browser (human)"] -->|"WebSocket via dialup.js"| userSessionProcess["UserSessionProcess"]
aiAgent["AI agent (MCP client)"] -->|"HTTP JSON-RPC (POST /mcp)"| userSessionProcess
dialupAction["dialup_action / declare_action"] --> toolsList["tools/list"]
dialupRegion["dialup_region / declare_region"] --> toolsList
toolsList --> toolsCall["tools/call"]
toolsCall --> userSessionProcess
userSessionProcess --> render["render/1 + agent_state/1"]
render --> humanUI["idiomorph DOM update"]
render --> agentJSON["JSON response (no push)"]

POST /agent/:token は同じハンドラへの legacy な path-token トランスポートです。

人間操作(WebSocket)

sequenceDiagram
participant Browser as ユーザー / ブラウザ
participant JS as dialup.js
participant USP as UserSessionProcess
Browser->>JS: click / input
JS->>USP: WebSocket event
USP->>USP: command / set / navigate / handle_event
USP->>USP: version++
USP->>JS: render diff
JS->>Browser: idiomorph morph(DOM 更新)

エージェント操作(HTTP MCP)

sequenceDiagram
participant Agent as AI agent
participant MCP as POST /mcp
participant USP as UserSessionProcess
Agent->>MCP: initialize
MCP->>Agent: server capabilities
Agent->>MCP: tools/list
MCP->>Agent: declared actions + built-ins
Agent->>MCP: tools/call read_scene
MCP->>USP: read actor state
USP->>Agent: structuredContent + currentVersion
Agent->>MCP: tools/call mutation + _version
alt version stale
MCP->>Agent: isError
Agent->>MCP: tools/call read_scene
Note over Agent: 再取得してから fresh _version で retry
else version current
MCP->>USP: apply mutation
USP->>Agent: updated structuredContent
end

エージェントには WebSocket トランスポートも server push もありませんread_scene で ポーリングします。

Agent-first browser handoff

エージェントが headless セッションを開始し、後から人間が参加する場合、join は 3 フェーズの ハンドシェイクです。ブラウザ URL を開いただけでは join は完了しません。

sequenceDiagram
participant Agent as AI agent
participant API as Dialup HTTP
participant USP as UserSessionProcess
participant Browser as 人間ブラウザ
Agent->>API: POST /_dialup/agent-session
API->>USP: start session + grant token
Agent->>API: tools/call issue_browser_url
API->>Agent: browserUrl
Browser->>API: WebSocket attach (tab_id + join_token)
Note over Browser,USP: cookie 未設定
Browser->>API: POST /_dialup/finalize-join
Note over Browser,USP: cookie 設定、join token 消費
Browser->>USP: __reconnect
Note over Agent,Browser: 人間と agent が同一 UserSessionProcess を共有

トークン発行パターンは セッショントークン を参照してください。

宣言境界(declaration boundary)

ライセンス

MIT