Skip to content

Commit 246e1ab

Browse files
Merge pull request modelcontextprotocol#389 from jonathanhefner/add-polling-to-patterns-guide
Add polling pattern to patterns guide
2 parents 7f617b9 + 818bd71 commit 246e1ab

File tree

2 files changed

+152
-10
lines changed

2 files changed

+152
-10
lines changed

docs/patterns.md

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This document covers common patterns and recipes for building MCP Apps.
88

99
## Tools that are private to Apps
1010

11-
Set {@link types!McpUiToolMeta.visibility `Tool._meta.ui.visibility`} to `["app"]` to make tools only callable by Apps (hidden from the model). This is useful for UI-driven actions like updating quantities, toggling settings, or other interactions that shouldn't appear in the model's tool list.
11+
Set {@link types!McpUiToolMeta.visibility `Tool._meta.ui.visibility`} to `["app"]` to make tools only callable by Apps (hidden from the model). This is useful for UI-driven actions like updating server-side state, polling, or other interactions that shouldn't appear in the model's tool list.
1212

1313
<!-- prettier-ignore -->
1414
```ts source="../src/server/index.examples.ts#registerAppTool_appOnlyVisibility"
@@ -32,7 +32,73 @@ registerAppTool(
3232
);
3333
```
3434

35-
_See [`examples/system-monitor-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) for a full implementation of this pattern._
35+
> [!NOTE]
36+
> For full examples that implement this pattern, see: [`examples/system-monitor-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) and [`examples/pdf-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server).
37+
38+
## Polling for live data
39+
40+
For real-time dashboards or monitoring views, use an app-only tool (with `visibility: ["app"]`) that the App polls at regular intervals.
41+
42+
**Vanilla JS:**
43+
44+
<!-- prettier-ignore -->
45+
```ts source="./patterns.tsx#pollingVanillaJs"
46+
let intervalId: number | null = null;
47+
48+
async function poll() {
49+
const result = await app.callServerTool({
50+
name: "poll-data",
51+
arguments: {},
52+
});
53+
updateUI(result.structuredContent);
54+
}
55+
56+
function startPolling() {
57+
if (intervalId !== null) return;
58+
poll();
59+
intervalId = window.setInterval(poll, 2000);
60+
}
61+
62+
function stopPolling() {
63+
if (intervalId === null) return;
64+
clearInterval(intervalId);
65+
intervalId = null;
66+
}
67+
68+
// Clean up when host tears down the view
69+
app.onteardown = async () => {
70+
stopPolling();
71+
return {};
72+
};
73+
```
74+
75+
**React:**
76+
77+
<!-- prettier-ignore -->
78+
```tsx source="./patterns.tsx#pollingReact"
79+
useEffect(() => {
80+
if (!app) return;
81+
let cancelled = false;
82+
83+
async function poll() {
84+
const result = await app!.callServerTool({
85+
name: "poll-data",
86+
arguments: {},
87+
});
88+
if (!cancelled) setData(result.structuredContent);
89+
}
90+
91+
poll();
92+
const id = setInterval(poll, 2000);
93+
return () => {
94+
cancelled = true;
95+
clearInterval(id);
96+
};
97+
}, [app]);
98+
```
99+
100+
> [!NOTE]
101+
> For a full example that implements this pattern, see: [`examples/system-monitor-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server).
36102
37103
## Reading large amounts of data via chunked tool calls
38104

@@ -156,7 +222,8 @@ loadDataInChunks(resourceId, (loaded, total) => {
156222
});
157223
```
158224

159-
_See [`examples/pdf-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) for a full implementation of this pattern._
225+
> [!NOTE]
226+
> For a full example that implements this pattern, see: [`examples/pdf-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server).
160227
161228
## Giving errors back to the model
162229

@@ -279,7 +346,8 @@ function MyApp() {
279346
}
280347
```
281348

282-
_See [`examples/basic-server-vanillajs/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) and [`examples/basic-server-react/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) for full implementations of this pattern._
349+
> [!NOTE]
350+
> For full examples that implement this pattern, see: [`examples/basic-server-vanillajs/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) and [`examples/basic-server-react/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react).
283351
284352
## Entering / exiting fullscreen
285353

@@ -329,7 +397,8 @@ In fullscreen mode, remove the container's border radius so content extends to t
329397
}
330398
```
331399

332-
_See [`examples/shadertoy-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) for a full implementation of this pattern._
400+
> [!NOTE]
401+
> For full examples that implement this pattern, see: [`examples/shadertoy-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server), [`examples/pdf-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server), and [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server).
333402
334403
## Passing contextual information from the App to the model
335404

@@ -352,7 +421,8 @@ await app.updateModelContext({
352421
});
353422
```
354423

355-
_See [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) for a full implementation of this pattern._
424+
> [!NOTE]
425+
> For full examples that implement this pattern, see: [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) and [`examples/transcript-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server).
356426
357427
## Sending large follow-up messages
358428

@@ -377,7 +447,8 @@ await app.sendMessage({
377447
});
378448
```
379449

380-
_See [`examples/transcript-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) for a full implementation of this pattern._
450+
> [!NOTE]
451+
> For a full example that implements this pattern, see: [`examples/transcript-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server).
381452
382453
## Persisting view state
383454

@@ -443,7 +514,8 @@ app.ontoolresult = (result) => {
443514
// e.g., saveState({ currentPage: 5 });
444515
```
445516

446-
_See [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) for a full implementation of this pattern._
517+
> [!NOTE]
518+
> For full examples that implement this pattern, see: [`examples/pdf-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) (persists current page) and [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) (persists camera position).
447519
448520
## Pausing computation-heavy views when offscreen
449521

@@ -471,7 +543,8 @@ app.onteardown = async () => {
471543
};
472544
```
473545

474-
_See [`examples/shadertoy-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) for a full implementation of this pattern._
546+
> [!NOTE]
547+
> For full examples that implement this pattern, see: [`examples/shadertoy-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) and [`examples/threejs-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server).
475548
476549
## Lowering perceived latency
477550

@@ -498,4 +571,5 @@ app.ontoolinput = (params) => {
498571
> [!IMPORTANT]
499572
> Partial arguments are "healed" JSON — the host closes unclosed brackets/braces to produce valid JSON. This means objects may be incomplete (e.g., the last item in an array may be truncated). Don't rely on partial data for critical operations; use it only for preview UI.
500573
501-
_See [`examples/threejs-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server) for a full implementation of this pattern._
574+
> [!NOTE]
575+
> For full examples that implement this pattern, see: [`examples/shadertoy-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) and [`examples/threejs-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server).

docs/patterns.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,72 @@ import { registerAppTool } from "../src/server/index.js";
2222
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2323
import { z } from "zod";
2424

25+
/**
26+
* Example: Polling for live data (Vanilla JS)
27+
*/
28+
function pollingVanillaJs(app: App, updateUI: (data: unknown) => void) {
29+
//#region pollingVanillaJs
30+
let intervalId: number | null = null;
31+
32+
async function poll() {
33+
const result = await app.callServerTool({
34+
name: "poll-data",
35+
arguments: {},
36+
});
37+
updateUI(result.structuredContent);
38+
}
39+
40+
function startPolling() {
41+
if (intervalId !== null) return;
42+
poll();
43+
intervalId = window.setInterval(poll, 2000);
44+
}
45+
46+
function stopPolling() {
47+
if (intervalId === null) return;
48+
clearInterval(intervalId);
49+
intervalId = null;
50+
}
51+
52+
// Clean up when host tears down the view
53+
app.onteardown = async () => {
54+
stopPolling();
55+
return {};
56+
};
57+
//#endregion pollingVanillaJs
58+
}
59+
60+
/**
61+
* Example: Polling for live data (React)
62+
*/
63+
function pollingReact(
64+
app: App | null, // via useApp()
65+
) {
66+
const [data, setData] = useState<unknown>();
67+
68+
//#region pollingReact
69+
useEffect(() => {
70+
if (!app) return;
71+
let cancelled = false;
72+
73+
async function poll() {
74+
const result = await app!.callServerTool({
75+
name: "poll-data",
76+
arguments: {},
77+
});
78+
if (!cancelled) setData(result.structuredContent);
79+
}
80+
81+
poll();
82+
const id = setInterval(poll, 2000);
83+
return () => {
84+
cancelled = true;
85+
clearInterval(id);
86+
};
87+
}, [app]);
88+
//#endregion pollingReact
89+
}
90+
2591
/**
2692
* Example: Server-side chunked data tool (app-only)
2793
*/
@@ -336,6 +402,8 @@ function visibilityBasedPause(
336402
}
337403

338404
// Suppress unused variable warnings
405+
void pollingVanillaJs;
406+
void pollingReact;
339407
void chunkedDataServer;
340408
void chunkedDataClient;
341409
void hostContextVanillaJs;

0 commit comments

Comments
 (0)