Skip to main content
Glama
README.md
# sample-mcp-apps-in-claudeai

MCP Apps の機能を、claude.ai をホストにして実際に動かして確かめるためのサンプルです。題材は付箋ボードで、
Claude が書いた付箋を View(claude.ai の会話の中に表示される iframe)に貼り、ユーザーと Claude の両方が付箋を動かせます。

View と Host(claude.ai)の間でやりとりしたものは、すべて View の下のログに出ます。同じ内容は MCP サーバーのターミナルにも
JSON で 1 行ずつ出るので、View が消えたあとに届くもの(`ui/resource-teardown`)も確かめられます。

## 確かめられること

### View→Host

- `ui/open-link`: 付箋の「検索」ボタン。付箋の本文で Web 検索のページを開く
- `ui/message`: 付箋の「掘り下げて」ボタン。「付箋『…』を掘り下げて」を会話に送る
- `ui/request-display-mode`: 「全画面」「戻す」ボタン。`inline` と `fullscreen` を切り替える
- `ui/update-model-context`: 付箋を足す・消す・書き換える・並べ替えると、1 秒おいてボード全体を Claude の文脈に送る
- `ui/download-file`: 「.md で書き出す」ボタン。付箋を Markdown の箇条書きにして書き出す
- `ui/notifications/request-teardown`: 「ボードを閉じる」ボタン。View を閉じるようホストに頼む。
  応じるかどうかはホストが決めるので、3 秒待っても `ui/resource-teardown` が来なければ、そう画面とログに出す
- `ui/notifications/size-changed`: 付箋が増えて View が高くなると、SDK が自動で送る。送った幅と高さをログに出す

### Host→View

- `ui/notifications/tool-input-partial`: Claude が `show_board` の引数を書いている途中で、そこまでの引数が届く。
  届いた回数と、最後の partial の時点で引数がそろっていたかを「tool-input-partial のまとめ」としてログに出す
- `ui/notifications/tool-input`: 引数がそろうと届く
- `ui/notifications/tool-result`: `show_board` の結果が返ると届く
- `ui/notifications/tool-cancelled`: `show_board` の実行中に会話を止めると届く。理由(`reason`)を画面に出す
- `ui/notifications/host-context-changed`: テーマや表示モード、枠の大きさが変わると届く。View のテーマに反映する
- `ui/resource-teardown`: View が破棄される前に届く。サーバーにログを送り終えてから応答する
- あわせて、View が消える瞬間(`pagehide`)もログに残す。iframe が消えるときは通信も止まるので、届かないこともある

### App 提供ツール

View が `app.registerTool` で、次の 2 つのツールを Claude に出します。Claude はユーザーが開いている View を直接操作できます。

- `edit_board`: 付箋を追加・書き換え・削除する
- `read_board`: 今の付箋(id・本文・色・並び順)を読む

claude.ai の App 提供ツールは、2026 年 11 月の時点では public preview で、一部の組織でしか使えません
([公式ドキュメント](https://claude.com/docs/connectors/building/mcp-apps/app-tools.md))。使えるかどうかは試して確かめるしかありません。
次の条件もあります。

- MCP サーバーがリモート(URL で登録するカスタムコネクタ)であること。このサンプルはトンネルで公開するので満たします
- unified experience(Chat と Cowork の統合)が有効であること。入力欄で Chat と Cowork を選べる状態なら無効です。
  Team と Enterprise では、組織の Owner が設定の Capabilities で有効にします
- Claude にツールが渡るのは、View が開いている間だけです。会話で最初に View を開いたメッセージでは渡らず、次のメッセージから渡ります

View が開くと、ログの最初の行(「接続した」)にホストの capabilities が出ます。

## 必要なもの

- Node.js 24 と pnpm
- [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)(`brew install cloudflared`)。外から届く URL を作れれば、ほかのトンネルでもかまいません
- カスタムコネクタを追加できる claude.ai のアカウント

## 動かし方

1. 依存を入れる

   ```sh
   pnpm install
   ```

2. サーバーを起動する。`PATH_TOKEN` は URL に入れる推測しにくい文字列です。固定しておくと、起動し直してもコネクタを登録し直さずに済みます

   ```sh
   PATH_TOKEN=$(openssl rand -hex 16) pnpm start
   ```

   `PATH_TOKEN` を省くと起動のたびに作り、ターミナルに表示します。ポートは既定で 3000 で、`PORT` で変えられます。
   サーバーは 127.0.0.1 だけで待ち受けます。

3. 別のターミナルでトンネルを開く

   ```sh
   cloudflared tunnel --url http://127.0.0.1:3000
   ```

   数秒すると、ターミナルに `https://xxxx-xxxx-xxxx.trycloudflare.com` のような URL が出ます。これがトンネルのホストです。
   ログが流れて見失ったら、`trycloudflare.com` で探してください。ホスト名は cloudflared を起動するたびに変わるので、
   開き直したらコネクタの URL も登録し直します

4. claude.ai の「設定 > コネクタ」で、カスタムコネクタに `https://<トンネルのホスト>/mcp/<PATH_TOKEN>` を追加する

5. 終わったら、トンネルとサーバーを止めて、コネクタを外す

## ログの読み方

- サーバーのターミナルの 1 行が、View で起きた 1 つの出来事です。`view` はどの View(ボード)か、`seq` はその View の中で起きた順の番号です。
  View からの送信は待たずに投げるので、ターミナルでは時刻の順に並ばないことがあります。順番は `seq` で追ってください
- `server` で始まる行は、サーバー自身のログです。`show_board` が呼ばれた・結果を返した・中断された(リクエストが取り消された)ときに出ます

## 試し方

新しいチャットで、上から順に送ると、このアプリの機能を一通り使えます。やりとりは View の下のログと、サーバーのターミナルで確かめます。

1. 「sticky-board で、休日の過ごし方の付箋ボードを作って。付箋は 8 枚」と頼む
2. 付箋を書き換えたり、「←」「→」で並べ替えたり、「付箋を追加」から足したりしてから、「今のボードで一番手軽なのはどれ?」と聞く
3. 付箋の「掘り下げて」「検索」、上の「全画面」「.md で書き出す」を押す
4. 「ボードに『昼寝をする』を足して、一番上の付箋を消して」と頼む(App 提供ツール)
5. 「sticky-board で、新しい趣味の付箋ボードを作って。付箋は 5 枚、delay_seconds は 30」と頼み、ボードが開いたら会話を止める。
   `delay_seconds` は、サーバーが結果を返すまで待つ秒数で、実行中に止めるための検証用の引数です
6. claude.ai のテーマ(ライト / ダーク)を切り替える、ウィンドウの幅を変える
7. 「ボードを閉じる」を押す、または会話を離れる

## 作りについて

- `src/server.ts`: MCP サーバー。View を開く `show_board` と、View だけが呼べる `log_event`(ログをターミナルに出す)を持つ
- `src/ui/main.ts`: View。`@modelcontextprotocol/ext-apps` の `App` で Host とやりとりする
- `scripts/build-ui.mjs`: View のスクリプトを esbuild でまとめ、1 つの HTML(`dist/ui.html`)にする

View で加えた変更は保存しません。会話を開き直すなどして View が作り直されると、`show_board` を呼んだときの付箋に戻ります。

## ライセンス

[MIT](LICENSE)