Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- name: Workflow script tests
run: node --test '.github/scripts/*.test.js'

- name: MCP Apps UI script tests
run: node --test tests/docs_src/test_apps.test.js

- name: Surface types match vendored schema
run: |
uv sync --group codegen --frozen
Expand Down
4 changes: 2 additions & 2 deletions docs/advanced/apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ then come back.

## A clock with a face

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down Expand Up @@ -51,7 +51,7 @@ The model reads `content`; the iframe is for humans. A UI-capable host still fee
the text result to the model, and a text-only client gets *only* that. So the
canonical pattern is one tool, two answers. Look at `get_time` again:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
23 changes: 21 additions & 2 deletions docs_src/apps/tutorial001.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,29 @@
<title>Clock</title>
<h1 id="now">...</h1>
<script>
const parent = window.parent;
let initialized = false;
window.addEventListener("message", (event) => {
const text = event.data?.result?.content?.[0]?.text;
if (text) document.getElementById("now").textContent = text;
if (event.source !== parent || event.data?.jsonrpc !== "2.0") return;
if (event.data.id === 1 && event.data.result?.protocolVersion) {
parent.postMessage({jsonrpc: "2.0", method: "ui/notifications/initialized"}, "*");
initialized = true;
return;
}
if (!initialized || event.data.method !== "ui/notifications/tool-result") return;
const text = event.data.params?.content?.[0]?.text;
if (typeof text === "string") document.getElementById("now").textContent = text;
});
parent.postMessage({
jsonrpc: "2.0",
id: 1,
method: "ui/initialize",
params: {
appInfo: {name: "clock", version: "1.0.0"},
appCapabilities: {},
protocolVersion: "2026-01-26"
}
}, "*");
</script>
"""

Expand Down
23 changes: 21 additions & 2 deletions examples/stories/apps/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,29 @@
<title>Current time</title>
<h1 id="now">…</h1>
<script>
const parent = window.parent;
let initialized = false;
window.addEventListener("message", (event) => {
const text = event.data?.result?.content?.[0]?.text;
if (text) document.getElementById("now").textContent = text;
if (event.source !== parent || event.data?.jsonrpc !== "2.0") return;
if (event.data.id === 1 && event.data.result?.protocolVersion) {
parent.postMessage({jsonrpc: "2.0", method: "ui/notifications/initialized"}, "*");
initialized = true;
return;
}
if (!initialized || event.data.method !== "ui/notifications/tool-result") return;
const text = event.data.params?.content?.[0]?.text;
if (typeof text === "string") document.getElementById("now").textContent = text;
});
parent.postMessage({
jsonrpc: "2.0",
id: 1,
method: "ui/initialize",
params: {
appInfo: {name: "clock", version: "1.0.0"},
appCapabilities: {},
protocolVersion: "2026-01-26"
}
}, "*");
</script>
"""

Expand Down
6 changes: 3 additions & 3 deletions i18n/de/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ Das SDK liefert das als eingebaute Extension `Apps` (`io.modelcontextprotocol/ui

## Eine Uhr mit Gesicht {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ Nicht jeder Client rendert Apps. Die Spezifikation sagt unverblümt, was das fü

Das Modell liest `content`; der iframe ist für Menschen. Ein UI-fähiger Host füttert das Modell trotzdem mit dem Textergebnis, und ein reiner Text-Client bekommt *nur* das. Das kanonische Muster ist also: ein Tool, zwei Antworten. Sieh dir `get_time` noch einmal an:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/es/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -25,7 +25,7 @@ y luego vuelve.

## Un reloj con cara visible {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down Expand Up @@ -56,7 +56,7 @@ El modelo lee `content`; el iframe es para humanos. Un host capaz de mostrar UI
el resultado en texto al modelo, y un cliente solo de texto recibe *solo* eso. Así que el
patrón canónico es una herramienta, dos respuestas. Mira `get_time` de nuevo:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/fr/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ Le SDK fournit cela sous la forme de l’extension intégrée `Apps` (`io.modelc

## Une horloge avec un cadran {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ Tous les clients n’affichent pas les apps. La spécification dit sans détour

Le modèle lit `content` ; l’iframe est pour les humains. Un hôte capable d’afficher une interface transmet quand même le résultat textuel au modèle, et un client purement textuel ne reçoit *que* cela. Le schéma canonique est donc : un outil, deux réponses. Regardez à nouveau `get_time` :

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/hi/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK इसे built-in `Apps` extension (`io.modelcontextprotocol/ui`) के

## चेहरे वाली घड़ी {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down Expand Up @@ -49,7 +49,7 @@ model `content` पढ़ता है; iframe इंसानों के ल
model को देता है, और text-only client को **सिर्फ़** वही मिलता है। इसलिए मानक
pattern है: एक tool, दो जवाब। `get_time` को फिर से देखें:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/ja/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK はこれを組み込みの `Apps` 拡張(`io.modelcontextprotocol/ui`)

## 見た目のある時計 {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ HTML 自体はホストの `postMessage` を待ち受けて結果を表示しま

モデルが読むのは `content` で、iframe は人間のためのものです。UI に対応したホストでもテキストの結果はモデルに渡されますし、テキスト専用のクライアントはそれ「だけ」を受け取ります。ですから定番のパターンは「1 つのツール、2 つの答え」です。もう一度 `get_time` を見てください。

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/ko/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK는 이를 내장 `Apps` 확장(`io.modelcontextprotocol/ui`)으로 제공합

## 얼굴을 가진 시계 {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ HTML 자체는 호스트의 `postMessage`를 수신하고 결과를 표시합니

모델은 `content`를 읽고, iframe은 사람을 위한 것입니다. UI를 지원하는 호스트도 여전히 텍스트 결과를 모델에 전달하며, 텍스트 전용 클라이언트는 **오직** 그 텍스트만 받습니다. 따라서 표준 패턴은 도구 하나에 답 둘입니다. `get_time`을 다시 살펴보세요.

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/pt/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -25,7 +25,7 @@ e depois volte aqui.

## Um relógio com uma cara {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down Expand Up @@ -56,7 +56,7 @@ O modelo lê `content`; o iframe é para humanos. Um host com suporte a UI ainda
o resultado em texto para o modelo, e um cliente só de texto recebe *apenas* isso. Então o
padrão canônico é uma ferramenta, duas respostas. Olhe `get_time` de novo:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/ru/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ translation:

## Часы с циферблатом {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ translation:

Модель читает `content`; iframe — для людей. Хост с поддержкой UI всё равно передаёт текстовый результат модели, а чисто текстовый клиент получает *только* его. Поэтому канонический паттерн — один инструмент, два ответа. Взгляните на `get_time` ещё раз:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/tr/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK bunu yerleşik `Apps` uzantısı (`io.modelcontextprotocol/ui`) olarak sunar

## Yüzü olan bir saat {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ Her istemci uygulamaları çizmez. Şartname bunun sizin için ne anlama geldiğ

Model `content`'i okur; iframe insanlar içindir. UI destekli bir host yine de metin sonucunu modele iletir, yalnızca metin destekleyen bir istemci ise *sadece* onu alır. Yani kanonik desen tek araç, iki yanıttır. `get_time`'a bir daha bakın:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/uk/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK постачає це як вбудоване розширення `Apps` (`

## Годинник із циферблатом {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ SDK постачає це як вбудоване розширення `Apps` (`

Модель читає `content`; iframe — для людей. Хост із підтримкою UI все одно передає текстовий результат моделі, а суто текстовий клієнт отримує *лише* його. Тож канонічний шаблон — один інструмент, дві відповіді. Погляньте на `get_time` ще раз:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
6 changes: 3 additions & 3 deletions i18n/zh-hant/pages/advanced/apps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, b4ac54f338092ec6, 1419418a05511012, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Expand All @@ -18,7 +18,7 @@ SDK 以內建的 `Apps` 擴充功能(`io.modelcontextprotocol/ui`)提供這

## 有錶面的時鐘 {#a-clock-with-a-face}

```python title="server.py" hl_lines="17 20 28 30"
```python title="server.py" hl_lines="36 39 47 49"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand All @@ -39,7 +39,7 @@ HTML 本身會監聽主機的 `postMessage` 並顯示結果。真正的 app 請

模型讀的是 `content`;iframe 是給人看的。支援 UI 的主機照樣會把文字結果餵給模型,而純文字用戶端**只**會拿到那個。所以標準做法是一個工具,兩種答案。再看一次 `get_time`:

```python title="server.py" hl_lines="21-25"
```python title="server.py" hl_lines="40-44"
--8<-- "docs_src/apps/tutorial001.py"
```

Expand Down
Loading
Loading