maidr について
maidr-mcpはChatGPTやClaudeの会話内にアクセシブルなチャートを表示し、視覚障害やロービジョンのある読者がキーボード、スクリーンリーダー、音声、点字で探索できます。モデルによるデータの移動や対応コマンドの実行、チャートの差し替えにも対応します。
主な機能
- 01会話内でのチャート表示と更新
- 02棒・折れ線・散布図などに対応
- 03モデルの依頼によるデータ点の移動
- 04対応するチャートコマンドの実行
- 05読者の現在位置を報告
- 06音声・サウンド・点字で内容を通知
活用シーン
- 01視覚に頼らずチャートを探索
- 02モデルに指定のデータ点への移動を依頼
- 03点字の切り替えやチャート再生を依頼
- 04表示を保ったままチャートを差し替え
MCP 対応
MCPサーバーはモデルに、チャート表示、更新、移動、データ取得、コマンド実行のツールを提供します。チャートビューがモデルのツール呼び出しをmaidrに中継し、結果を返します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
10掲載時のツール一覧 · 1 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| chart | any | はい |
詳細
maidr-mcp
MCP サーバーです。ChatGPT や Claude との会話内に、アクセス可能な maidr チャートを表示します。また、会話のモデルがチャート内で読者を案内し、チャートのコマンドを代わりに実行できます。
モデルがデータを指定して show_chart を呼び出すと、会話内にチャートが MCP App として表示されます。目の見えない方や弱視の方は Tab キーでチャートに移動し、矢印キー、スクリーンリーダー、音声化、点字を使って、通常の maidr チャートと同じように探索できます。続いて、モデルを介して次の3つのことができます。
- モデルが読者を目的の場所へ案内できます。 読者が「最も高い棒」と尋ねると、モデルは
maidr_get_layer_dataを呼び出してその棒を見つけ、maidr_navigateでそこへ移動します。maidr はそのデータ点を音声、点字、サウンドで知らせます。読者がチャート内ではなくモデルへの入力中に尋ねた場合、そこへ案内するよう求めているため、モデルはfocus: trueを渡します。すると maidr はキーボードのフォーカスをチャートに移し、移動します。 - モデルが読者に代わってチャートのキーを押せます。 読者が点字をオフにする、チャートを再生する、最小値へ移動するなどを求めると、モデルは
maidr_list_commandsを呼び出してコマンドと読者の現在のモードを確認し、maidr_run_commandで実行します。maidr は読者がキーを押したかのように結果を知らせ、モデルはどのキーを押したか伝えます。 - モデルは読者がどこにいるか把握できます。 読者が移動すると、チャートはその位置をモデルに伝えるため、「このデータ点は何ですか?」と尋ねてもツールを呼び出す必要はありません。ターン中に
maidr_list_chartsを呼び出すと、現在の位置を取得できます。
実験段階です。 MCP Apps SDK のリファレンスホストでエンドツーエンドの動作を確認しています(以下を参照)。claude.ai または ChatGPT では、まだ確認していません。
動作の仕組み
-
show_chartはサーバー上で matplotlib と py-maidr を使ってグラフを描画するため、モデルが送信するのは SVG ではなく数値です。SVG はツールの結果に含まれる_metaを通じてグラフビューに届きますが、モデルのコンテキストには追加されません。 -
ビューは、CSP で宣言されている唯一のドメインである
cdn.jsdelivr.netから、固定バージョンの maidr.js と MCP Apps SDK を読み込みます。 -
maidr.js は、
document.modelContextに独自の WebMCP ツールを登録します。ホストからビューのツールをモデルに渡す仕組みはまだないため(ext-apps#797)、ビュー自身がdocument.modelContextを提供します。同じ名前の各サーバーツールは、呼び出しを開いているビューに中継します。- モデルがサーバーツールを呼び出します。
- ビューがアプリ専用ツールをポーリングして呼び出しを受け取り、maidr のツールを実行して、maidr の応答を返します。
- その応答がモデルのツール結果になります。
ポーリングはロングポーリングです。呼び出しが届くまでサーバーは最大 20 秒間待機するため、呼び出しはすぐにグラフに届きます。ホストがそのような長時間のリクエストを切断または拒否した場合、ビューはショートポーリングに切り替えます。サーバーはこれに即座に応答し、2 秒ごとに 1 回ずつ処理するため、サーバーがグラフを待つ 10 秒以内に、モデルの呼び出しに引き続き応答できます。ビューは 1 分後に再びロングポーリングを試み、ホストに切断された場合は、試みるたびに待機時間を 2 倍にします。
ext-apps#798 の提案どおり、ホストが MCP Apps 向けの WebMCP を採用すれば、maidr のツールはモデルに直接届くようになり、中継は不要になります。
-
読み上げ利用者がグラフ内でキーを押すたびに、ビューはその位置を
ui/update-model-contextで送信します。 -
update_chartはviewId用の新しいグラフを描画し、そのビューに中継して、既存の場所に差し替えます。maidr は古いグラフを解放し、利用者が操作やコマンドを待っている場合はそれも解除してから、新しいグラフを結び付けます。待機中の操作があった場合、グラフのステータス行にはその操作が解除されたことが表示され、update_chartは、利用者に約束した操作を取り消せるようモデルに伝えます。サーバーは最新のグラフを保持するため、差し替えを見逃したビューも、表示領域外にある場合やホストによって再マウントされた場合も、次回のポーリングで最新の状態になります。 -
ビューが読み上げ利用者のフォーカスを動かすのは、
update_chartが置き換えたグラフに利用者を戻す場合だけです。maidr が利用者をグラフに移動するのは、モデルがfocus: trueを渡した場合に限られます。サーバーの指示とツールの説明では、利用者が今すぐどこかへの移動や何かの実行を求めた場合にのみ指定し、利用者がグラフを離れたことを理由に指定してはならないとモデルに伝えています。それ以外の操作やコマンドは、利用者がグラフの外にいる間は待機します。利用者をグラフに移動できなかったfocus: trueの操作(focused: false)も同様です。利用者がグラフに入るまでは、ビューのステータス行に「アシスタントの操作が待機しています。グラフに Tab で移動して聞いてください。」と表示されます。update_chartの後は、利用者が Tab キーまたはfocus: trueによってグラフに入るまで、同じ行に「グラフを更新しました:」と表示されます。操作やコマンドも待機している場合は、両方が表示されます。
ツール
| ツール | 呼び出す主体 | 内容 |
|---|---|---|
show_chart | モデル | グラフを描画して表示します。他のツールが使用する viewId を返します。 |
update_chart | モデル | 新しいグラフを描画し、その viewId のビューにあるグラフと置き換えます。ビューは追加しません。閲覧者がグラフ内にいる場合は、新しいグラフでもその位置にとどまります。それ以外の人は、現在のフォーカスを保ちます。グラフが変更されたことは、両者に通知されます。 |
maidr_list_charts | モデル | グラフのレイヤー、点の数、および閲覧者がグラフ内にいる場合は現在位置をリアルタイムで返します。通知はありません。 |
maidr_get_layer_data | モデル | レイヤーの点をページ単位で返します。各点には、maidr_navigate が使用する target が含まれます。通知はありません。 |
maidr_navigate | モデル | 閲覧者を点へ移動させ、その点を通知します。閲覧者が移動を依頼していて focus: true の場合、maidr はまずキーボードフォーカスをグラフ内に移し、移動できた場合は focused: true、できなかった場合は focused: false を返し、移動は閲覧者が操作するまで待機します。 |
maidr_list_commands | モデル | 閲覧者が使用できるコマンドを、それぞれの ID、タイトル、キー、モデルが実行可能かどうかとともに返し、閲覧者の現在のモード(テキスト、音声、点字、自動再生など)も返します。通知はありません。 |
maidr_run_command | モデル | toggle_braille、autoplay_forward、go_to_max_value などの閲覧者のコマンドを、キーを押した場合と同様に実行し、結果を通知します。ダイアログやテキストフィールドを開くコマンドは、閲覧者自身が操作します。閲覧者がその時点で実行を依頼していて focus: true の場合、maidr はまずキーボードフォーカスをグラフ内に移し、閲覧者が現在位置を聞いてから 0.5 秒後にコマンドを実行します(applied: "queued"、focused: true)。フォーカスを移せなかった場合は focused: false を返し、コマンドは閲覧者が操作するまで待機します。 |
maidr_view_poll、maidr_view_reply、maidr_view_svg | グラフビューのみ | リレーと、update_chart 用および _meta を破棄するホスト用の SVG を受け渡します。 |
show_chart と update_chart は、次のグラフタイプのいずれかを受け取ります。これらは py-maidr が安定版とするタイプです。それぞれ、以下に示す maidr のレイヤータイプに対応します。
type | フィールド | maidr レイヤー | モデルが閲覧者を移動できるか |
|---|---|---|---|
bar | categories、series(1 系列、複数系列の横並び、または stacked) | bar、dodged_bar、stacked_bar | はい |
line | x(数値またはラベル)、series | line | はい |
step | x、series、where(post、pre、mid のいずれか) | step | はい |
scatter | x、y、最小二乗直線を示す trend | point、トレンドラインには smooth | 点には移動できますが、線には移動できません |
histogram | values、bins | hist | はい |
box | groups | box | いいえ |
violin | groups | violin_box、violin_kde | いいえ |
heatmap | x_labels、y_labels、values、z_label | heat | はい |
pie | categories、values(12 時の位置から時計回りに読み取ります) | pie | いいえ |
candlestick | dates、open、high、low、close | candlestick | いいえ |
すべてのタイプで、title、x_label、y_label も指定できます。円グラフには軸がないため、これらはスライスの内容と値の測定対象を示します。
モデルは maidr_get_layer_data を使って、すべてのレイヤーの点を読み取ります。最後の列に「いいえ」とある場合、maidr は点に target を付けません。閲覧者はキーを使って点を移動し、maidr_navigate は layer not navigable と応答します。
プロンプトの例
サーバーに接続したら、閲覧者は普段の言葉で依頼します。ツール名を指定する必要はありません。以下のプロンプトは、閲覧者の操作内容ごとにまとめられており、モデルが呼び出すことを想定したツールもそれぞれに記載されています。データを会話に貼り付けるか、チャットの前のやり取りですでに得ている数値をモデルに使わせてください。
グラフを表示する
| シナリオ | プロンプト | グラフ |
|---|---|---|
| 売上レポート | 「四半期ごとの収益を棒グラフで表示してください。Q1 は 120、Q2 は 180、Q3 は 150、Q4 は 210(単位:千ドル)です。」 | bar |
| グループの比較 | 「1月から4月まで、2つの店舗を月ごとに並べて比較してください。北店は 40、52、61、58、南店は 35、49、66、70 です。」 | bar、2系列 |
| 全体に占める内訳 | 「今年のエネルギーミックスを四半期ごとに積み上げて表示してください。石炭、ガス、太陽光、風力の数値は以下のとおりです。」 | bar、stacked |
| 時系列の傾向 | 「先週の月曜日から日曜日までの日別気温(18、21、19、24、26、23、20度)を折れ線グラフでプロットしてください。」 | line |
| 複数の傾向 | 「以下の表の2015年から2025年までの米国、韓国、ドイツの失業率を、それぞれ1本の線で示した折れ線グラフにしてください。」 | line、3系列 |
| 段階的に変化する値 | 「年間の料金プランをステップチャートで表示してください。3月までは9ドル、8月までは12ドル、それ以降は15ドルです。」 | step |
| 相関関係 | 「この30人の生徒について、学習時間と試験の点数の散布図を作成し、トレンドラインも追加してください。」 | trend を含む scatter |
| 分布 | 「ミリ秒単位の応答時間が200件あります。ビン数を20にしたヒストグラムを表示してください。」 | histogram |
| ばらつきと外れ値 | 「対照群、低用量群、高用量群の反応時間を箱ひげ図で表示してください。」 | box |
| 分布の形状 | 「同じデータをバイオリンプロットで表示してください。各グループの値がどこに集中しているか分かるようにしてください。」 | violin |
| 値の格子 | 「曜日と時間帯ごとのウェブサイト訪問数をヒートマップで表示してください。色のスケールには『訪問数』とラベルを付けてください。」 | heatmap |
| 全体に占める割合 | 「月々の予算の内訳を円グラフにしてください。家賃45%、食費20%、交通費10%、貯蓄15%、その他10%です。」 | pie |
| 株価 | 「この株の過去10取引日分の始値、高値、安値、終値をローソク足チャートで表示してください。」 | candlestick |
モデルは show_chart を1回呼び出し、他のツールが使う viewId でグラフが表示されます。
モデルを通じてグラフを操作する
| プロンプト | モデルの動作 |
|---|---|
| 「最も高い棒に移動してください。」 | maidr_get_layer_data でその棒を見つけてから、focus: true を指定して maidr_navigate を呼び出します。maidr が音声、点字、サウンドで知らせます。 |
| 「最も気温が低かった月はどれですか?移動せずに、教えてください。」 | maidr_get_layer_data のみを呼び出し、チャットで回答します。 |
| 「スコアが90を超える最初の点に移動してください。」 | レイヤーデータから該当する点を見つけて移動します。 |
| 「この点は何ですか?」(グラフを表示中) | グラフから報告された位置の情報をもとに回答するか、maidr_list_charts でリアルタイムに読み取ります。 |
| 「このグラフには点がいくつあり、私はどこにいますか?」 | maidr_list_charts を呼び出します。 |
| 「南店の系列のQ3に移動してください。」 | maidr_navigate で該当する系列とカテゴリーに移動します。 |
| 「ローソク足チャートの最も低い終値に移動してください。」 | maidr はモデルが読者をその位置に移動できないと伝え、代わりにキー操作を案内します(上の表を参照)。 |
モデルにグラフのキーを押させる
| プロンプト | モデルが実行するコマンド |
|---|---|
| 「点字をオンにしてください。」/「サウンドをオフにしてください。」 | toggle_braille、toggle_sound |
| 「テキストの読み上げを止めて、音だけにしてください。」 | toggle_text |
| 「グラフ全体を左から右へ再生してください。」 | autoplay_forward |
| 「もっと速くしてください。」/「停止してください。」 | speed_up_autoplay、stop_autoplay |
| 「最大値に移動してください。」/「今度は最小値に移動してください。」 | go_to_max_value、go_to_min_value |
| 「最後の点に移動してください。」 | move_to_right_extreme |
| 「トレンドラインに切り替えてください。」 | next_layer |
| 「ハイコントラストをオンにしてください。」 | toggle_high_contrast |
モデルはまず maidr_list_commands を呼び出して、読者が実行できる操作と有効になっているモードを確認し、次に maidr_run_command を呼び出します。その後、次回同じ操作をするためのキーを読者に伝えます。
グラフをその場で変更する
| プロンプト | モデルの動作 |
|---|---|
| 「12月を追加:230。」 | 新しいデータを使って、同じビューでupdate_chartを実行します。 |
| 「今度は棒グラフではなく、折れ線グラフで表示して。」 | type: "line"を指定してupdate_chartを実行します。 |
| 「20ではなく10個のビンを使って。」 | ヒストグラムでupdate_chartを実行します。 |
| 「地域別に分けて。」 | 地域ごとに1つの系列を使ってupdate_chartを実行します。 |
グラフを見ていたユーザーは、新しいグラフ上でもそのままグラフ内にとどまり、変更されたことを音声で聞けます。
会話の全体例
- 宿題のサポート。 「私は目が見えず、統計の試験勉強をしています。この50件のテストの点数のヒストグラムを表示し、形がわかるように左から右へ再生してから、歪んでいるか教えてください。」モデルはヒストグラムを表示し、
autoplay_forwardを実行して、レイヤーのデータに基づいて回答します。 - データセットを手軽に確認する。 「住宅価格と床面積のCSVです。傾向線付きの散布図を表示し、最も高価な住宅に移動して、傾向線からどれくらい離れているか教えてください。」
- 株価を確認する。 「過去10取引日の株価の始値、高値、安値、終値です。ローソク足チャートで表示してください。高値と安値の差が最も大きかった日はどれですか?」続けて、「終値を折れ線グラフで表示し、その日に移動してください。」
- 実験を確認する。 「3つのグループの反応時間を箱ひげ図で表示してください。ばらつきが最も大きいグループはどれですか?点字をオンにして、その箱に移動してください。」モデルはデータに基づいて回答し、
toggle_brailleを実行します。箱ひげ図はモデルが操作できないため、その箱に移動するためのキーをユーザーに伝えます。 - 予算を確認する。 「支出を円グラフで表示し、次に金額の大きい順に並べた棒グラフで表示して、最も大きいカテゴリに移動してください。」
使い方
maidr-mcpは、同じバージョンで3つの方法で公開されています。
- PyPI:
maidr-mcp。uvで実行します。uvx maidr-mcpは127.0.0.1:8000/mcpでHTTPを提供し、uvx maidr-mcp --stdioはホストがサーバー自体を起動する方式で提供します。uvx maidr-mcp@latestを使うと、uvがキャッシュしているバージョンではなく最新リリースが使われます。 - コンテナイメージ:
ghcr.io/xability/maidr-mcp。各バージョンとlatestのタグが付いています。ポート8000の/mcpでHTTPを提供します。 - MCP Registry:
io.github.xability/maidr-mcp。レジストリからサーバーを追加するクライアント向けです。
ホストからサーバーに接続する方法によって、サーバーに必要なものは異なります。
| ホスト | 接続方法 | サーバーに必要なもの |
|---|---|---|
| Claude(Web、Desktop、モバイル) | リモートMCPサーバーを指定するカスタムコネクター | 公開HTTPSアドレス |
| ChatGPT | 開発者モードのアプリ | 公開HTTPSアドレス、または自分のマシンへのSecure MCP Tunnel |
| ChatGPTデスクトップアプリ | STDIO経由で起動する、独自のMCPサーバー | ホスト上での設定不要。テストされていない項目については以下を参照してください |
公開アドレスを使う場合
# Make a token once, and keep it: it is the <token> in each host's URL below.
export MAIDR_MCP_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uvx maidr-mcp --host 0.0.0.0 --port 8000
# or
docker run -p 8000:8000 -e MAIDR_MCP_TOKEN ghcr.io/xability/maidr-mcp
エンドポイントはStreamable HTTP経由の/mcpで、トークンを指定する場合は/mcp/<token>です。
- インスタンスは1つだけ実行してください。 リレーは各グラフのキューをメモリ上に保持します。
- 再起動しても問題ありません。 開いているグラフは、再起動後の次のポーリング時に自身を再登録します。
Claude。 カスタムコネクターを追加します。Customize > Connectors > Add custom connectorで、https://<your-host>/mcp/<token>を指定してください。
- プラン: カスタムコネクターはすべてのプランで利用できます。Freeでは1つまで追加できます。
- TeamおよびEnterprise: Ownerが追加します。
- グラフの表示先: コネクターを追加すると、Web、Desktop、iOS/Androidで表示されます。
ChatGPT。 開発者モードをオンにし、https://<your-host>/mcp/<token>のアプリを作成します。開発者モードは、Plus、Pro、Business、Enterprise、EducationアカウントのWebで利用できます。
アクセストークン
トークンがなければ、サーバーにアクセスできる人なら誰でもチャートを描画できます。トークンを設定すると、OPTIONS(CORS プリフライトやプローブ)以外のすべての HTTP リクエストにトークンが必要になり、トークンがないリクエストには 401 が返されます。MAIDR_MCP_TOKEN、トークンだけを記載したファイルを指定する --token-file または MAIDR_MCP_TOKEN_FILE、あるいは --token で設定できます。STDIO 経由ではトークンは不要です。ホストがサーバーを直接起動します。
リクエストでは、次のいずれかの方法でトークンを渡します。
- URL に指定する場合:
/mcp/<token>とします。Claude のカスタムコネクターと ChatGPT の開発者モードアプリでは、OAuth 以外では URL しか指定できないため、この形式を使用します。 - ヘッダーに指定する場合: トークンを送信できるクライアント向けに、
/mcpにAuthorization: Bearer <token>を指定します。
python3 -c 'import secrets; print(secrets.token_urlsafe(32))' で適切なトークンを生成できます。サーバーは、16 文字未満のトークンや、英字、数字、-._~ 以外の文字を含むトークンを拒否します。
- URL が秘密情報になります。 ホストのコネクターまたはアプリの設定に保存されるため、そこで URL を見た人は誰でもサーバーを利用できます。
- サーバーを新しいトークンで再起動してトークンをローテーションし、各ホストに新しい URL を渡します。古い URL は直ちに使えなくなります。
- これは共有トークンであり、OAuth ではありません。 URL を渡された人は全員それを共有し、特定の人だけからトークンを取り消すことはできません。OAuth は実装されていません。
- ログ。 サーバーのアクセスログでは、URL は
/mcp/<redacted>と表示されます。サーバーの前段にあるプロキシ、ロードバランサー、プラットフォームでは、URL 全体が記録される場合があります。クライアントがヘッダーを送信できる場合は、ヘッダーを使用してください。 --tokenは他のマシンユーザーからプロセス一覧で見えます。 環境変数かファイルを使用してください。Docker secrets を使う場合は、-e MAIDR_MCP_TOKEN_FILE=/run/secrets/<name>とします。- HTTPS で配信してください。 通常の HTTP では、トークンが暗号化されずにネットワークを通過します。
パブリックアドレスを使わずに ChatGPT を利用する
Secure MCP Tunnel を介して自分のマシンを使う方法。 ChatGPT は OpenAI 側からアプリの MCP サーバーを呼び出すため、localhost に直接アクセスできません。OpenAI の Secure MCP Tunnel を使うと、インターネットに公開されていないサーバーに接続できます。
- サーバー:
maidr-mcp(127.0.0.1:8000/mcpで HTTP)またはmaidr-mcp --stdioを、マシン上で実行します。 - トンネルクライアント: OpenAI のオープンソースクライアントをサーバーと同じマシンで実行すると、OpenAI に対して送信方向のみの HTTPS 接続を確立します。Platform のトンネル設定で取得した
tunnel_idとランタイム API キーが必要です。 - アプリ: ガイドの説明に従い、トンネル経由で開発者モードから追加します。
- 稼働時間: チャートの使用中は、マシンを起動したままにする必要があります。
ChatGPT デスクトップアプリ独自の MCP サーバー機能。 デスクトップアプリはローカルサーバーを自分で起動できます。Settings > MCP servers > Add server を開き、STDIO を選択して、uv が必要な次のコマンドを指定します。
uvx maidr-mcp --stdio
デスクトップアプリは、この設定を Codex CLI および IDE 拡張機能と共有します。注意点が 2 つあります。
- この方法で追加したサーバーの MCP App の UI をアプリが描画するかどうかは、ドキュメントに記載されていません。
- この方法は maidr-mcp で試されていないため、チャートが表示されるかどうかは不明です。
maidr skill と併用する
maidr-skill は、AI エージェントが Python、R、JavaScript で作成するグラフをアクセシブルにする方法を教えます。両方を同時にインストールできます。サーバーの指示とスキルではグラフを同じように振り分け、どちらも各グラフの作成方法を 1 つだけモデルに指示します。
| リクエスト | 振り分け先 |
|---|---|
| 会話内で表示または操作する、上記の種類のグラフ | show_chart。変更する場合は update_chart |
| プロット用コード、ファイル、ノートブック、ウェブページ、レポート、その他の種類のグラフ、複数のパネル、またはスタイルを設定したグラフ | スキル |
| ターミナル内、または MCP Apps を表示しない別のホスト内のグラフ | スキル |
ここにある maidr_* ツールが操作できるのは、show_chart で開いたグラフだけです。スキルが作成するページでは maidr.js が動作し、対応するブラウザーでは、viewId なしで同じ名前のツールをページ内のエージェントに提供します。指示によって、モデルはどちらを使うべきか判断できます。ChatGPT デスクトップアプリに追加された MCP サーバーは Codex CLI と IDE 拡張機能からも使えます。この環境ではスキルがインストールされていることが多いため、振り分けが特に重要です。
リファレンスホストで試す
e2e/run.sh は、ChatGPT または Claude のアカウントなしで、一連の処理全体を確認します。次の 3 つを実行します。
- ext-apps(
examples/basic-host、タグv2.0.3)からリファレンスホストをビルドします。 - このサーバーを起動します。
- モデルと閲覧者の両方にならって、Playwright で双方を操作します。
cd e2e && npm install && npx playwright-core install chromium && cd ..
bash e2e/run.sh
MAIDR_MCP_TOKEN を設定すると、サーバーはそのトークンを使って実行され、ホストは /mcp/<token> から接続します。
次の項目を確認します。
- グラフが表示されること。
- モデルからの呼び出しがグラフ内の maidr によって処理されること。
maidr_list_commandsも対象に含まれ、maidr_run_commandが maidr によって実行可能とされているコマンドだけを提示すること。 - 閲覧者がチャット中に行った移動とコマンドが待機し、グラフのステータス行にそのことが表示されること。閲覧者が Tab キーでグラフに移動すると、移動が通知され、コマンドが実行され、通知が消えること。
focus: trueの場合、maidr_navigateによって、ホストページで入力中の閲覧者がグラフに移動すること。フォーカスがグラフに移り、ポイントが通知され、Tab キーで移動した場合と同様にステータス行がクリアされ、その後は矢印キーでグラフ内を移動できること。10 秒以内に maidr が再び閲覧者のフォーカスを移動させないこと。focus: trueを指定した移動とコマンドはfocused: falseを返して待機し、ステータス行に内容が表示されること。その後、focus: trueを指定したコマンドが閲覧者をグラフに移動させ、queuedを返し、待機中の処理の後に実行されること。- 矢印キーで移動が通知され、閲覧者の位置がモデルコンテキストとしてホストに届くこと。
- 閲覧者がグラフ内にいるときに行った移動とコマンドがすぐに反映され、
maidr_list_chartsによって、コマンドで閲覧者が移動した位置がモデルに伝わること。 update_chartによって、ビューが追加されずにグラフがそのビュー内で変更されること。グラフの外にいる閲覧者はフォーカスを保ち、グラフ内にいる閲覧者はそのままグラフ内にとどまり、両者に通知され、maidr が新しいグラフだけを読み取ること。古いグラフ内で閲覧者を待っていた処理が新しいグラフに引き継がれ、そのことがモデルとステータス行の両方に表示され、新しいグラフ内で待機していた移動もステータス行の変更に反映されること。- 更新を受け取れなかったビューが、次のポーリング時に追いつき、ロングポーリングを継続すること。
- コンソールエラーや CSP 違反がないこと。
- ステップ、バイオリン、円、ローソク足の各グラフと、トレンドライン付きの散布図がそれぞれ表示されること。maidr が各グラフを上の表にあるレイヤーとして読み取り、ArrowRight で最初のポイントが通知され、モデルから要求された移動が通知されるか、表に記載されている場合は maidr によって拒否されること。
- ホストがロングポーリングを中断した場合に、ビューがショートポーリングに切り替わり、モデルへの応答(
update_chartを含む)を継続し、グラフが閉じられるとポーリングを停止すること。
MAIDR_JS_FILE=/path/to/maidr/dist/maidr.js bash e2e/run.sh を実行すると、固定リリースの代わりに maidr.js のローカルビルドを使って同じ確認を行います。
制限事項
- チャートが報告する位置がモデルに届くのは、読者の次のメッセージです。 ホストは次のターンで
ui/update-model-contextを適用するため、1 ターンの間はモデル自身の移動やコマンド、そしてモデルが回答している間に読者が移動した内容がモデルコンテキストに反映されません。maidr_list_chartsは位置をリアルタイムで読み取り、コンテキストが古くなっている可能性があるときに読者が「この点」について尋ねたら呼び出すよう、サーバーの指示でモデルに伝えています。残る制約:maidr が位置を把握できるのは読者がチャート内にいる間だけなので、モデルに入力している間は最後に報告された位置のままです。また、呼び出しを省略したモデルはコンテキストだけをもとに回答します。 - 読者がそこへ移動するよう求めていない限り、移動やコマンドは保留されます。
focus: trueがない場合、読者がモデルに入力している間に行われた移動やコマンドは保持され、読者が Tab キーでチャートに戻ったときに実行されます。まず移動し、その後コマンドが半秒間隔で実行されます。ツールはapplied: "on-next-focus"と応答し、モデルにもそのように伝えるよう指示されます。スクリーンリーダーで会話を見ている読者にも分かるよう、チャートのステータス行にも表示されます。モデルに、読者から求められた場合に限って渡すよう指示されているfocus: trueが指定されると、maidr は読者のフォーカスをチャートに移し、その場で実行します。残る制約:- Safari ではできません。 WebKit は、読者自身のキー操作またはクリックなしでは、別のページのフレーム内にあるチャートがフォーカスを取得することを許可しません。そのため、Safari では maidr は
focused: falseと応答し、読者自身が Tab キーでチャートに入り、保留中の移動やコマンドを見つけます。Chromium と Firefox では、チャートがフォーカスを取得できます。 - 10 秒に 1 回までです。 読者がチャートから離れたあと、すぐに引き戻されないように、maidr がモデルのためにフォーカスを移動できるのは、ページごとに 10 秒に 1 回までです。それより早い呼び出しには
focused: falseと応答し、focusがない場合と同様に保留されます。各チャートビューはそれぞれ独立したページなので、この制限はチャート間では共有されません。モデルは読者を同時に 2 つ目のチャートへ移動させることもできますが、それを防ぐのはモデルへの指示だけです。 - ホスト独自のダイアログには対応できません。 チャートはホストが会話の上に表示するダイアログを認識できないため、maidr はそこからフォーカスを奪わないようにできません。ダイアログが開いている間は
focus: trueを渡さないようモデルに指示されていますが、それ以外に防ぐ手段はありません。 - 保留できるコマンドは最大 8 個です。
- Safari ではできません。 WebKit は、読者自身のキー操作またはクリックなしでは、別のページのフレーム内にあるチャートがフォーカスを取得することを許可しません。そのため、Safari では maidr は
show_chartを呼び出すたびに、チャートが追加されます。 Claude は UI を持つツールが呼び出されるたびに新しいビューをマウントし、以前のビューも残します。チャートを変更して同じビューに表示し続けるには、サーバーの指示にあるように、モデルがupdate_chartを呼び出す必要があります。2 回目のshow_chartは別のビューになります。- リレーはポーリングします。 開いている各チャートは 20 秒ごとにリクエストを送信します。リクエストの接続維持時間が短いホストでは 2 秒ごとに送信し、その場合、モデルの呼び出しがチャートに届くまでさらに最大 2 秒かかることがあります。また、1 つのビューからの呼び出しを 1 分あたり 30 回未満に制限するホストでは、モデルの呼び出しの一部に応答が返りません。どのホストがビューからの呼び出しを制限するかは、まだ分かっていません。同じチャートを 2 回開いた場合、たとえば 2 つのタブで開くと、各コピーはもう一方のポーリングを中断と認識するため、両方とも短い間隔でのポーリングに切り替わります。
- アクセスには共通のトークンを 1 つ使い、設定した場合にのみ有効になります。 トークンがなければ、サーバーの URL を知っている人は誰でもチャートを描画できます。
MAIDR_MCP_TOKENを設定すると、すべてのリクエストでトークンが必要になります。Claude と ChatGPT はトークンを URL に含めて送信します。ホストのコネクタ設定に保存されるこの URL が、その後は秘密情報となります。トークンをローテーションするには、新しいトークンでサーバーを再起動し、各ホストの設定を更新します。完全な OAuth は実装されていません。アクセス トークンを参照してください。チャートの読み取りや操作には、そのチャートのviewIdが必要です。これはランダムな 24 文字のトークンです。 - 対応するチャートファミリーは 10 種類で、プロット用コードはありません。 モデルは上記の種類のいずれかのデータを送信し、サーバーが描画します。サーバーの URL を知っている人なら誰でもコードを実行できるため、プロット用コードは意図的に受け取りません。py-maidr の実験的なプロットタイプは、読者と試すまで対象外です。残りの機能は maidr スキルが補います。
show_chartはチャートが表示されたかどうかを判別できません。 ステートレスなリクエストでは、クライアントに何が表示できるかが常に伝わるわけではないため、どのホストでも同じ内容で応答します。ターミナルなど、MCP Apps を表示しないホストでも、チャートは表示中と応答します。指示とツールの説明で、そのような環境では呼び出さないようモデルに伝えていますが、それ以外に防ぐ手段はありません。
ネットワークとデータ
- サーバー: モデルがチャートを描画するために送信するデータを受信します。チャートへのポーリングが15分間行われない状態になるまで、最新の SVG をメモリ上に保持します。HTTP アクセスログ以外にデータに関する情報は記録しません。このログでは、URL 内のトークンは
/mcp/<redacted>として表示されます。 - チャートビュー:
cdn.jsdelivr.netから maidr.js と MCP Apps SDK を読み込みます。チャート内にある maidr 独自の AI チャットは、読者が追加したキーを使い、ほかの場所と同じように動作します。
開発
uv sync
uv run ruff check . && uv run ruff format --check .
uv run pytest
bash e2e/run.sh
maidr のリリースが maidr-mcp に反映される仕組み
- maidr.js: チャートビューで使うバージョンは、
server.pyのMAIDR_JS_VERSIONで指定されています。update-maidr.ymlは、以下のチェックに合格すると、npm のリリースから約1時間以内にこのバージョンを引き上げます。その後、mainから作成されたインストールやイメージでそのバージョンが読み込まれ、次の週次リリースで PyPI と GHCR に反映されます。 - py-maidr: チャートを描画するこのパッケージは、
maidr>=1.26,<2の範囲で、uvxまたは Docker のインストールごとに新たに解決されるため、PyPI にリリースされるとすぐに新規インストールで利用できます。同じワークフローが更新するuv.lockは、CI とチェックアウト時のテストで使う py-maidr のバージョンを固定します。
.github/workflows/update-maidr.yml は、毎時 npm と PyPI を確認します。新しい maidr.js には、maidr のリリースワークフローによる npm provenance と、npm audit signatures で検証できる Sigstore 署名が必要です。その後、新しいバージョンを使って ruff、Python 3.10 と 3.13 での pytest、e2e/run.sh を実行し、すべてに合格した場合に限り、ワークフローが main にコミットします。maidr.js の固定バージョンを上げる場合はユーザーに提供されるため fix(deps):、uv.lock だけを更新する場合は chore(deps): としてコミットします。実行可能なコマンドを変更した maidr.js は、古い RunnableCommand のままリリースされることなく、e2e/run.sh で失敗します。チェックに失敗した場合は何もプッシュされず、バージョン、失敗したステップ、実行へのリンクを記載した Issue が作成されます。ダウンロードやブラウザーのインストールなど、次回は成功する可能性がある失敗は、1日間、毎時再試行されます。新しいバージョンでチェックが失敗するなど、再試行しても同じ結果になる失敗の場合は、Issue がクローズされるか、ワークフローが手動で実行されるまで更新が保留されます。同等以上に新しい更新が main に反映されると、Issue は自動的にクローズされます。更新のたびに、npm の最新 maidr.js と PyPI の最新 py-maidr を組み合わせます。そのため、maidr.js のリリースに失敗している間は、新しい py-maidr もその maidr.js と組み合わせてのみ試され、uv.lock の更新も保留されます。Issue には、現在 main が読み込んでいる maidr.js のバージョンを使ってワークフローを手動実行し、py-maidr だけを更新する方法が記載されます。
設計上、このワークフローはプルリクエストを作成せず、自ら main にコミットします。maidr.js と py-maidr は、それぞれのリポジトリでレビューとテストを経てからリリースされます。このリポジトリで確認する必要があるのは、サーバー、ビュー、そして読者がチャートを利用する手順が、これらの新バージョンでも引き続き動作することです。そのため、告知、フォーカス、点字を e2e/run.sh で確認し、コミット前にワークフローですべてをチェックします。ワークフロー独自のトークンで作成したプルリクエストでは、ci.yml を含め、どのワークフローも実行されません。すでに同じチェックに合格した変更を人がマージするまで待つだけとなり、変更に含まれる maidr.js の修正もその間保留されます。各更新を自分で確認するには、ワークフローを無効にし、scripts/update_maidr.py を使ってプルリクエストでバージョンの固定を更新してください。
別のバージョンを選ぶには、Actions タブから maidr.js のバージョンを指定してワークフローを実行するか、チェックアウトしたリポジトリで uv run --no-project python scripts/update_maidr.py update --maidr-js <version> --allow-lower を実行し、プルリクエストを作成します。次の毎時実行で、より低いバージョンの固定は再び引き上げられます。そのため、maidr.js を古いバージョンに固定するには、maidr が修正版をリリースするまで Actions タブでワークフローを無効にしてください。
ライセンス
maidr のほかの部分と同じく、GPL-3.0-or-later です。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。