Skip to main content

Overview

This guide covers common patterns for building MCP Apps with sunpeak. Each recipe shows both the tool file (server-side) and resource component (client-side) where applicable.

App-only tools

Set visibility: ["app"] in a tool’s _meta.ui to make it callable only from the UI — hidden from the LLM. Use this for polling, pagination, form submissions, and other UI-driven server actions.
The model never sees this tool. The resource component calls it directly with useCallServerTool.

Polling for live data

For real-time dashboards or monitoring, poll an app-only tool at regular intervals.
See the system-monitor example in the MCP Apps SDK for a full implementation.

Chunked data loading

Some hosts have size limits on tool call responses. Use an app-only tool with chunked responses to load large files (PDFs, images) without hitting limits. Server-side — return data in chunks with pagination:
Client-side — loop until all chunks are received:
See the pdf-server example for a full implementation of chunked loading.

Binary resources

Serve binary content (video, PDF) via MCP resources and fetch it with useReadServerResource.
See the video-resource example for a full implementation.

Progressive rendering

Use useToolData’s inputPartial to show a preview while the LLM is still generating tool arguments. This lowers perceived latency for tools with large inputs like code or structured data.
Partial arguments are “healed” JSON — the host closes unclosed brackets to produce valid JSON. Objects may be incomplete (e.g., the last array item may be truncated). Use inputPartial only for preview UI, never for critical operations.

Giving errors to the model

When a runtime error occurs in your resource (API failure, permission denied, resource unavailable), use useUpdateModelContext to inform the model:
The model sees this in its context and can respond helpfully to the user.

Entering and exiting fullscreen

Use useRequestDisplayMode to toggle fullscreen. Always check availableModes first — not all hosts support all modes.
In fullscreen mode, remove border radius so content extends to the viewport edges. Use rounded-none or border-radius: 0.

Passing state to the model

Two approaches depending on your needs:

Automatic with useAppState

useAppState syncs React state to the model context automatically after each update. Best for simple state that the model should always see.

Manual with useUpdateModelContext

useUpdateModelContext gives you full control over what and when to send. Best for large or structured context, or when you want to batch updates.
updateModelContext is deferred — the model sees the updated context on its next turn, not immediately. It does not trigger a model response. Use useSendMessage to trigger a response.

Large follow-up messages

When you need to send more data than fits in a message, set the context first, then trigger with a brief message:

Persisting view state

For recoverable state (current page, camera position, scroll position), use localStorage with a server-provided viewUUID. Server-side — include a viewUUID in the tool result _meta:
Client-side — save and restore state using the UUID as a storage key:

Pausing offscreen views

Views with animations, WebGL, or polling consume resources even when scrolled out of view. Use IntersectionObserver to pause when offscreen.
See the shadertoy and threejs examples for full implementations of visibility-based pausing.

See also

MCP Apps SDK Examples

20+ example apps demonstrating these patterns (maps, video, PDF, 3D, monitoring, and more).

MCP Apps SDK Patterns

Framework-agnostic version of these patterns in the SDK documentation.