Add polling pattern to patterns guide
Documents the recommended approach for real-time dashboards that need to poll server tools at regular intervals. Includes both Vanilla JS and React examples with proper cleanup handling via `app.onteardown` and `useEffect` cleanup functions respectively. Also updates the "Tools that are private to Apps" section to mention polling as a use case for app-only visibility. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6a2905d17d
commit
818bd71c9f
2 changed files with 134 additions and 1 deletions
|
|
@ -8,7 +8,7 @@ This document covers common patterns and recipes for building MCP Apps.
|
|||
|
||||
## Tools that are private to Apps
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="../src/server/index.examples.ts#registerAppTool_appOnlyVisibility"
|
||||
|
|
@ -35,6 +35,71 @@ registerAppTool(
|
|||
> [!NOTE]
|
||||
> 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).
|
||||
|
||||
## Polling for live data
|
||||
|
||||
For real-time dashboards or monitoring views, use an app-only tool (with `visibility: ["app"]`) that the App polls at regular intervals.
|
||||
|
||||
**Vanilla JS:**
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="./patterns.tsx#pollingVanillaJs"
|
||||
let intervalId: number | null = null;
|
||||
|
||||
async function poll() {
|
||||
const result = await app.callServerTool({
|
||||
name: "poll-data",
|
||||
arguments: {},
|
||||
});
|
||||
updateUI(result.structuredContent);
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (intervalId !== null) return;
|
||||
poll();
|
||||
intervalId = window.setInterval(poll, 2000);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (intervalId === null) return;
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
// Clean up when host tears down the view
|
||||
app.onteardown = async () => {
|
||||
stopPolling();
|
||||
return {};
|
||||
};
|
||||
```
|
||||
|
||||
**React:**
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```tsx source="./patterns.tsx#pollingReact"
|
||||
useEffect(() => {
|
||||
if (!app) return;
|
||||
let cancelled = false;
|
||||
|
||||
async function poll() {
|
||||
const result = await app!.callServerTool({
|
||||
name: "poll-data",
|
||||
arguments: {},
|
||||
});
|
||||
if (!cancelled) setData(result.structuredContent);
|
||||
}
|
||||
|
||||
poll();
|
||||
const id = setInterval(poll, 2000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [app]);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 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).
|
||||
|
||||
## Reading large amounts of data via chunked tool calls
|
||||
|
||||
Some host platforms have size limits on tool call responses, so large files (PDFs, images, etc.) cannot be sent in a single response. Use an app-only tool with chunked responses to bypass these limits while keeping the data out of model context.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,72 @@ import { registerAppTool } from "../src/server/index.js";
|
|||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Example: Polling for live data (Vanilla JS)
|
||||
*/
|
||||
function pollingVanillaJs(app: App, updateUI: (data: unknown) => void) {
|
||||
//#region pollingVanillaJs
|
||||
let intervalId: number | null = null;
|
||||
|
||||
async function poll() {
|
||||
const result = await app.callServerTool({
|
||||
name: "poll-data",
|
||||
arguments: {},
|
||||
});
|
||||
updateUI(result.structuredContent);
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (intervalId !== null) return;
|
||||
poll();
|
||||
intervalId = window.setInterval(poll, 2000);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (intervalId === null) return;
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
// Clean up when host tears down the view
|
||||
app.onteardown = async () => {
|
||||
stopPolling();
|
||||
return {};
|
||||
};
|
||||
//#endregion pollingVanillaJs
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Polling for live data (React)
|
||||
*/
|
||||
function pollingReact(
|
||||
app: App | null, // via useApp()
|
||||
) {
|
||||
const [data, setData] = useState<unknown>();
|
||||
|
||||
//#region pollingReact
|
||||
useEffect(() => {
|
||||
if (!app) return;
|
||||
let cancelled = false;
|
||||
|
||||
async function poll() {
|
||||
const result = await app!.callServerTool({
|
||||
name: "poll-data",
|
||||
arguments: {},
|
||||
});
|
||||
if (!cancelled) setData(result.structuredContent);
|
||||
}
|
||||
|
||||
poll();
|
||||
const id = setInterval(poll, 2000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [app]);
|
||||
//#endregion pollingReact
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Server-side chunked data tool (app-only)
|
||||
*/
|
||||
|
|
@ -336,6 +402,8 @@ function visibilityBasedPause(
|
|||
}
|
||||
|
||||
// Suppress unused variable warnings
|
||||
void pollingVanillaJs;
|
||||
void pollingReact;
|
||||
void chunkedDataServer;
|
||||
void chunkedDataClient;
|
||||
void hostContextVanillaJs;
|
||||
|
|
|
|||
Loading…
Reference in a new issue