Skip to content

Commit 06d2b84

Browse files
committed
docs(talks): add MCP Dev Summit 2026 talk transcript
Adds slide-by-slide transcript and demo index for the "MCP Apps Best Practices: Patterns and Pitfalls" talk at MCP Dev Summit NA 2026. The transcript cross-links each section to the relevant heading in docs/patterns.md and the example servers used in live demos, so the talk material doubles as a guided tour of the patterns guide. Slides export and recording link to follow post-conference.
1 parent 0266171 commit 06d2b84

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# MCP Apps Best Practices: Patterns and Pitfalls
2+
3+
**MCP Dev Summit North America 2026** · Thursday April 2, 2:35–3:00pm EDT · Juliard Complex
4+
**Speakers:** Olivier Chafik & Anton Pidkuiko, Anthropic
5+
6+
> MCP Apps open a new world of possibilities for interactions in AI chats. While [SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) defines how they work, this talk is about how to build them well.
7+
8+
This directory contains the source material for the talk. The patterns referenced here are documented in full at [docs/patterns.md](../../patterns.md).
9+
10+
## Contents
11+
12+
- [`transcript.md`](./transcript.md) — Slide-by-slide walkthrough with speaker notes
13+
- Slides: [Google Slides](https://docs.google.com/presentation/d/1xjtUusG1gl-c0lNZFZr4BfM_3InaxzpL0v1gMxRAeBk/) · [PDF export TODO]
14+
- Recording: TBD (post-conference)
15+
16+
## Demos referenced
17+
18+
| Demo | Example server | What it shows |
19+
| -------------------- | -------------------------------------------------------------------- | ----------------------------------------------------- |
20+
| Excalidraw streaming || `ontoolinputpartial` progressive rendering |
21+
| PDF interaction | [`examples/pdf-server`](../../../examples/pdf-server/) | Chunked loading, app-only tools, `updateModelContext` |
22+
| Shadertoy | [`examples/shadertoy-server`](../../../examples/shadertoy-server/) | `IntersectionObserver` pause, fullscreen |
23+
| Map server | [`examples/map-server`](../../../examples/map-server/) | State persistence via `widgetUUID` |
24+
| Transcript follow-up | [`examples/transcript-server`](../../../examples/transcript-server/) | Large context → `sendMessage` pattern |
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# MCP Apps Best Practices: Patterns and Pitfalls
2+
3+
> Slide-by-slide transcript. Speaker notes prefixed with `>` blocks.
4+
> Slides: https://docs.google.com/presentation/d/1xjtUusG1gl-c0lNZFZr4BfM_3InaxzpL0v1gMxRAeBk/
5+
6+
<!-- TODO: re-export slides as txt and merge inline with `gh pr edit` once gdrive auth restored -->
7+
8+
---
9+
10+
## Intro
11+
12+
**[Anton]** Every agent is driven by tools. MCP Apps are what happens when a tool decides it has something to _show_ you, not just tell you.
13+
14+
**[Olivier]** Views are the tip of the agentic iceberg for user interactions — but they're still part of, and connected to, the underground machinery of tools, resources, and model context. This talk is about that connection.
15+
16+
---
17+
18+
## Streaming
19+
20+
**[Anton]**
21+
22+
Demo: **Excalidraw**. Watch the diagram appear stroke by stroke as the model generates it.
23+
24+
What's happening: the model is producing tool arguments, and we're rendering them _while it's still thinking_. The mechanism is `ontoolinputpartial`:
25+
26+
```typescript
27+
app.ontoolinputpartial = (partial) => {
28+
// partial.arguments is "healed" JSON — always parseable,
29+
// but the last array item may be truncated. Preview only.
30+
renderPreview(partial.arguments);
31+
};
32+
```
33+
34+
> Spreadsheet demo as backup if Excalidraw misbehaves on conference wifi.
35+
36+
See [Lowering perceived latency](../../patterns.md#lowering-perceived-latency).
37+
38+
---
39+
40+
## The Readme tool
41+
42+
**[Anton]**
43+
44+
A skill can ship its own README as a tool. The model calls it once to learn how the server works, then uses that knowledge for the rest of the conversation. Self-documenting servers.
45+
46+
> Candidate for cut if we're tight on time — weakest signal/time ratio.
47+
48+
---
49+
50+
## Interaction with the widget — "view-side tools"
51+
52+
**[Olivier]**
53+
54+
Demo: **PDF server**.
55+
56+
User asks "show me this PDF." Tool fires, PDF renders in an iframe. So far so normal. But now the user scrolls to page 12 and asks "what's this diagram?"
57+
58+
The model knows which page they're on. How? The app told it:
59+
60+
```typescript
61+
app.updateModelContext({
62+
content: [{ type: "text", text: "User is viewing page 12 of 47" }],
63+
});
64+
```
65+
66+
The PDF bytes never went through the model. They went through an **app-only tool** — a tool with `_meta.ui.visibility: ["app"]` that the model doesn't even see in its tool list. The app calls it directly to fetch chunks:
67+
68+
```typescript
69+
const chunk = await app.callServerTool({
70+
name: "pdf_get_chunk",
71+
arguments: { offset, length: 64_000 },
72+
});
73+
```
74+
75+
> If "Tools for Apps" protocol update lands before the talk, hint at it here — otherwise stick to the command-queue pattern (caveat: requires stateful server or stdio).
76+
77+
See [Tools that are private to Apps](../../patterns.md#tools-that-are-private-to-apps) and [Reading large amounts of data via chunked tool calls](../../patterns.md#reading-large-amounts-of-data-via-chunked-tool-calls).
78+
79+
---
80+
81+
## Tool results — where do your bytes go?
82+
83+
**[Olivier]**
84+
85+
A tool result has more shelves than people realize. Diagram slide:
86+
87+
| Field | Goes to | Use for |
88+
| ------------------- | ----------------- | ---------------------------------------- |
89+
| `content[]` | Model **and** App | What you'd say out loud |
90+
| `structuredContent` | Model **and** App | Typed data the model reasons about |
91+
| `_meta` | App **only** | Side-channel: cursors, widgetUUID, blobs |
92+
| `isError: true` | Model | "Something went wrong, here's why" |
93+
94+
And separately, from the _app_ side:
95+
96+
| Method | Goes to | Use for |
97+
| ---------------------- | -------------------- | ------------------------------- |
98+
| `updateModelContext()` | Model | App state the model should know |
99+
| `sendMessage()` | Model (as user turn) | Triggering a follow-up |
100+
101+
> The pitfall: stuffing binary into `structuredContent` bloats model context. Put it in `_meta` or fetch it via app-only tool.
102+
103+
See [Passing contextual information from the App to the model](../../patterns.md#passing-contextual-information-from-the-app-to-the-model) and [Sending large follow-up messages](../../patterns.md#sending-large-follow-up-messages).
104+
105+
---
106+
107+
## Dude, can you even fetch?
108+
109+
**[Olivier]**
110+
111+
Two worlds.
112+
113+
**Non-authenticated data:** business as usual. `fetch()`, WebSockets, all the normal web stuff. You configure CSP via `_meta.ui.csp` to allowlist the origins, and `_meta.ui.domain` gives your iframe a stable origin for CORS.
114+
115+
> Side note: if your app already exists as a web page, you get the MCP App version _for the same price_ — iframe embed plus a thin SDK wrapper. We should document this in the official patterns. <!-- TODO: file issue / add to patterns.md -->
116+
117+
**Authenticated data:** tools. The MCP server already has the user's credentials; the app calls server tools to fetch on its behalf. No cookies in the iframe, no OAuth dance in the sandbox.
118+
119+
See [Polling for live data](../../patterns.md#polling-for-live-data), [Serving binary blobs via resources](../../patterns.md#serving-binary-blobs-via-resources), and [Configuring CSP and CORS](../../patterns.md#configuring-csp-and-cors).
120+
121+
---
122+
123+
## Persisting view state
124+
125+
**[Olivier]**
126+
127+
The server mints a UUID, returns it in the tool result's `_meta`. The app uses it as a `localStorage` key:
128+
129+
```typescript
130+
app.ontoolresult = (result) => {
131+
const key = `view:${result._meta.widgetUUID}`;
132+
const saved = localStorage.getItem(key);
133+
if (saved) restoreState(JSON.parse(saved));
134+
};
135+
```
136+
137+
Reload the conversation, your map camera is where you left it. Works client-side or server-side (the UUID keys server storage too).
138+
139+
> A SEP for first-class persistence is in the works — mention if it's public by talk time.
140+
141+
See [Persisting view state](../../patterns.md#persisting-view-state).
142+
143+
---
144+
145+
## Perceived latency
146+
147+
**[Olivier]**
148+
149+
Callback to streaming: `ontoolinputpartial` lets you show _something_ before the model finishes thinking.
150+
151+
Another trick: tool returns a task ID immediately, app polls for completion. The model moves on; the spinner is the app's problem.
152+
153+
See [Lowering perceived latency](../../patterns.md#lowering-perceived-latency).
154+
155+
---
156+
157+
## Making it look good everywhere
158+
159+
**[Anton]**
160+
161+
One slide, three things:
162+
163+
1. **Theme:** the host sets `[data-theme="dark"]` and CSS vars like `--color-background-primary`, `--font-sans`. Use them.
164+
2. **Safe areas:** `getHostContext().safeAreaInsets` — pad accordingly, especially on mobile.
165+
3. **Fullscreen:** `requestDisplayMode()`, listen on `onhostcontextchanged`, drop your border-radius.
166+
167+
Hosts that support this today: Claude (web/desktop/mobile), VS Code Copilot, Goose, Postman, MCPJam, Cursor.
168+
169+
See [Adapting to host context](../../patterns.md#adapting-to-host-context-theme-styling-fonts-and-safe-areas) and [Entering / exiting fullscreen](../../patterns.md#entering--exiting-fullscreen).
170+
171+
---
172+
173+
## Pitfall speedrun
174+
175+
**[Both]**
176+
177+
Five things that will waste an afternoon:
178+
179+
1. **Handlers after `connect()`** → you miss the initial `ontoolresult`. Register first, connect second.
180+
2. **CSP silently blocking** → external scripts 404, no error in your code, only in the browser console nobody opens.
181+
3. **`_meta.ui.resourceUri` typo** → tool works, no UI renders, no error. Check the URI matches what you registered.
182+
4. **Partial JSON treated as final**`ontoolinputpartial` gives you healed JSON; the last array item may be a fragment. Preview-only.
183+
5. **Relative asset paths inside the iframe** → use `vite-plugin-singlefile` or configure CSP for your CDN. Relative paths resolve against `about:srcdoc`.
184+
185+
---
186+
187+
## Reveal
188+
189+
**[Anton]**
190+
191+
Demo: **Imagine**.
192+
193+
> "This thing you've been using? It's an MCP App." Only on Claude.ai today — but it's the same protocol you can build against. This is what's possible when host and app are co-designed.
194+
195+
---
196+
197+
## Links
198+
199+
- Patterns guide: https://apps.extensions.modelcontextprotocol.io/api/documents/patterns.html
200+
- This talk: https://github.com/modelcontextprotocol/ext-apps/tree/main/docs/talks/mcp-dev-summit-2026
201+
- `/create-mcp-app` skill: scaffolds everything above

0 commit comments

Comments
 (0)