# Dialup
**WebSocket-first のライブ UI** と **自動生成される HTTP MCP API** を備えた、ファイルベースルーティングの Elixir フレームワーク。
[English](./README.en.md)
## リンク
- [公式サイト](https://dialup-framework.org/)
- [Getting Started ガイド](https://dialup.hexdocs.pm/getting-started.html)
## 概要
Dialup は Next.js のような開発体験でライブアプリを構築できる Elixir フレームワークです。
中核は二つあります。人間向け UI は最初から WebSocket-first で動き、HTTP MCP API は同じページ宣言から自動生成されます。
各ページは 1 つの監視されたサーバー側アクターです。UI を `<.dialup_action>` と
`<.dialup_region>` で書くと、Dialup が `tools/list`、`tools/call`、`read_scene` を導出します。
REST API の二重実装は不要です。
```
人間ブラウザ ──WebSocket──► UserSessionProcess ◄──HTTP JSON-RPC── AI エージェント
│
command / set / navigate / handle_event
│
declare_action / dialup_action
```
### 特徴
- **WebSocket-first の人間 UI** — `/ws` と idiomorph によるライブ DOM 更新
- **自動生成される HTTP MCP API** — action / region 宣言がそのままエージェントツールになる
- **同じイベント経路** — ブラウザ操作とエージェントの `tools/call` が同一 `UserSessionProcess` で直列化(`command` / `set` / `navigate` / `handle_event`)
- **HTTP MCP** — `POST /mcp`(Bearer token)で `initialize` / `tools/list` / `tools/call`(`POST /agent/:token` は legacy)
- **エージェント向け discovery** — `/.well-known/dialup-agent`、ページ埋め込み、`/llms.txt`
- **スコープ付きセッショントークン** — 有効期限・投影・ capability を細かく制御
- **ファイルベースルーティング** — ファイル配置がそのまま URL になる
- **サーバーサイドステート** — 1 タブ = 1 `UserSessionProcess`
- **コロケーション CSS** — `.ex` の隣に `.css`、コンパイル時に自動スコープ化
## クイックスタート
### 1. ジェネレータのインストール
```bash
mix archive.install hex dialup_new
```
### 2. 新規プロジェクト作成
```bash
mix dialup.new my_app
cd my_app
mix deps.get
mix run --no-halt
```
http://localhost:4000 にアクセス
### エージェント対応ページの最小例
```elixir
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](./guides/mcp-api.md)):
```bash
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` で全文を参照できます。
- [Events](./guides/events.md) — **WebSocket-first UI のイベントと更新**
- [HTTP MCP API](./guides/mcp-api.md) — **ツール自動生成・discovery・バージョニング**
- [Agent-native アプリ開発](./guides/agent-native-app-development.md) — 実装ワークフロー
- [セッショントークン](./guides/agent-handoff.md) — ライブセッションへの接続
- [Authentication](./guides/authentication.md) — HTTP 認証・MCP 権限([シーケンス図](./guides/authentication.md#sequence-diagrams))
- [Getting Started](./guides/getting-started.md) — インストールと基本
その他は `guides/` を参照(ルーティング、状態管理、デプロイなど)。
## アーキテクチャ
1 タブにつき 1 つの `UserSessionProcess` が動きます。サーバー側セッションアクターがページ状態・
宣言済み action・エージェント grant の **single source of truth** です。人間と AI エージェントは
異なるトランスポート(ブラウザは WebSocket、エージェントは HTTP JSON-RPC)から同じアクターに
到達します。
### 全体像
```mermaid
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)
```mermaid
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)
```mermaid
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 は完了しません。
```mermaid
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 を共有
```
トークン発行パターンは [セッショントークン](./guides/agent-handoff.md) を参照してください。
### 宣言境界(declaration boundary)
- MCP tool になるのは `<.dialup_action>` / `declare_action/1` と `<.dialup_region>` /
`declare_region/1` のみです。生の `ws-event` や `ws-href` などの ad-hoc WebSocket フックは
エージェントへ **自動公開されません**。
- `confirm: :human` の action は人間 UI のみで利用可能です。HTTP MCP から呼ぶと isError tool
result が返ります。
- エージェントが見える state は `agent_state/1` による allowlist 投影であり、assigns 全体の
dump ではありません。
## ライセンス
MIT