MCP App Examples: 7 Practical Patterns for ChatGPT Apps and Claude Connectors

Practical MCP App examples start with the user workflow, then map it to tools, resources, app state, and tests.
Developers searching for MCP App examples usually are not looking for another protocol overview. They already understand that a tool can return an interactive UI. The next question is what to build first, which parts should be tools, which parts should be resources, and how to keep the same app working in ChatGPT and Claude.
That is the largest search gap around MCP App-oriented keywords right now: practical examples. There is plenty of content about definitions, app frameworks, and testing. There is less material that maps real workflows to MCP App architecture.
TL;DR: Start with the workflow, not the component. The best MCP App examples are approval queues, dashboards, configuration forms, document viewers, job monitors, review screens, and guided creation flows. Give the model short text and typed structuredContent, render the UI from a linked resource, use app-only tools for UI controls, sync important user choices back to model context, and test the same state in ChatGPT and Claude host modes.
What Makes a Good MCP App Example
A good MCP App does something text alone handles poorly.
Use an MCP App when the user needs to:
- Compare several records side by side.
- Change filters without asking the model again.
- Fill a form with validation and defaults.
- Approve or reject a proposed action.
- Inspect a document, image, map, chart, or code diff.
- Track a job that changes over time.
- Keep conversation context and UI state connected.
Use a normal MCP tool response when the answer is short and the user does not need to click, edit, scan, or inspect anything. A one-line status check does not need an iframe. A report with drill-down filters probably does.
The standard MCP App shape is the same across examples:
- A model-visible tool decides when the app should appear.
- The tool returns concise
contentfor the model andstructuredContentfor the UI. - The tool metadata points to a
ui://resource. - The host renders that resource in a sandboxed iframe.
- The app can call server tools, send messages, update model context, or request host actions through the bridge.
In a sunpeak project, that maps to src/tools/, src/resources/, and tests/simulations/. The same shape works for ChatGPT Apps, Claude Connectors, and other MCP Apps-compatible hosts when you stick to the standard contract first.
Example 1: Approval Queue
An approval queue is often the best first production-grade MCP App because the user needs to inspect details before an action happens.
Use it for:
- Expense approvals.
- Access requests.
- Deployment approvals.
- Draft message review.
- Data deletion confirmations.
The model-visible tool should load the request and explain the proposed action. The UI should show the data, risk, source, and action buttons. The approval and rejection buttons should call app-only tools because those actions belong to the UI after the user has inspected the request.
Tool shape:
// src/tools/review-access-request.ts
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
resource: 'access-review',
title: 'Review Access Request',
description: 'Show an access request for user review and approval.',
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
},
};
export const schema = {
requestId: z.string().describe('The access request ID to review.'),
};
type Args = z.infer<z.ZodObject<typeof schema>>;
export default async function reviewAccessRequest(args: Args, _extra: ToolHandlerExtra) {
const request = await loadAccessRequest(args.requestId);
return {
content: [
{
type: 'text' as const,
text: `Loaded access request ${request.id} for ${request.userName}.`,
},
],
structuredContent: { request },
};
}
App-only action tool:
// src/tools/approve-access-request.ts
import { z } from 'zod';
import type { AppToolConfig } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
title: 'Approve Access Request',
description: 'Approve an access request after the user confirms in the app UI.',
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
_meta: {
ui: { visibility: ['app'] },
},
};
export const schema = {
requestId: z.string().describe('The request ID to approve.'),
reason: z.string().optional().describe('Optional reviewer note.'),
};
Resource behavior:
- Show who requested access, what system it touches, and why.
- Disable buttons while a write is in flight.
- Show a final approved, rejected, or failed state.
- Call
updateModelContextafter the decision so the model can refer to it later.
Tests to add:
- Read-only review tool returns the expected request.
- Approve and reject tools are hidden from the model and visible to the app.
- Button clicks call the right app-only tool.
- Failed approval leaves the request in a retryable state.
- Mobile layout keeps the action buttons visible.
Example 2: Data Exploration Dashboard
Dashboards are a strong MCP App fit because users need to scan, filter, sort, and drill into data. A text answer can summarize the top three rows, but it cannot replace a chart, table, and detail panel.
Use it for:
- Sales by region.
- Incident metrics.
- Product usage.
- Finance summaries.
- Support queues.
The first tool should return the initial dataset and a summary the model can read. The resource should own client-side filters when all data is already loaded. For pagination, search, or permission-bound drill-downs, call app-only server tools.
Recommended structure:
| Part | Pattern |
|---|---|
| Model tool | show_revenue_dashboard returns summary text and dashboard data |
| Resource | Chart, table, filter controls, selected row |
| App-only tools | load_revenue_page, get_revenue_detail, export_revenue_csv |
| Model context | Selected filters, selected row, and user-visible summary |
| Display mode | Inline for a compact chart, fullscreen for dense tables |
The model-visible content should stay short:
return {
content: [
{
type: 'text',
text: `Revenue dashboard loaded for Q2. Total revenue is ${formatCurrency(total)}.`,
},
],
structuredContent: {
period: 'Q2',
total,
segments,
rows,
nextCursor,
},
};
Avoid dumping every row into content. Put data the UI needs in structuredContent, then expose a focused summary to the model.
Tests to add:
- Empty dataset renders a useful empty state.
- Long labels do not break the chart or table.
- Filters update model context when they change the answer the user is discussing.
- App-only pagination tool handles stale cursors.
- The dashboard works in light and dark themes.
Example 3: Configuration Form
Configuration forms are a good MCP App example because the model can suggest defaults, while the user still needs deterministic controls.
Use it for:
- Deployment configuration.
- Report setup.
- Notification rules.
- Data import mapping.
- Connector setup.
The model-visible tool should prepare the starting form state. The resource should render fields, validation messages, and a preview of what will happen. The submit button should call an app-only tool that validates and saves the config on the server.
This is where MCP Apps beat a long chat thread. A form lets the user see every option at once. The model can still explain tradeoffs, but the UI owns the final input.
State rules:
- Keep draft field values in app state.
- Persist only the final submitted config unless autosave is a real product requirement.
- Send a short model context update when the user changes a field that affects later model answers.
- Validate both in the UI and in the server tool.
Testing checklist:
- Required fields show errors before submit.
- Server validation errors attach to the right fields.
- Default values match the tool result.
- Submit is disabled during save.
- The saved config appears in model context or follow-up tool output.
Example 4: Document or Media Viewer
Document viewers are a natural MCP App use case because the user needs visual inspection. The model can summarize a PDF or image, but users often need to pan, zoom, select, copy, compare, or cite a specific section.
Use it for:
- PDFs and contracts.
- Images and design files.
- Audio waveforms.
- Video review.
- Slide decks.
Keep the secure boundary simple. The server should decide which document the user may access and return signed or app-readable resource references. The app should request only the domains it needs in resource metadata. Do not rely on the iframe having broad network access.
Good viewer controls:
- Page or frame navigation.
- Zoom.
- Search.
- Selection.
- Annotation or comment actions when the host supports the workflow.
Data lanes:
| Data | Goes where |
|---|---|
| Short summary | content |
| Page list, bounds, thumbnails, signed asset references | structuredContent |
| Internal trace IDs, temporary URLs, cache keys | _meta when supported |
| User selection | app state and model context |
Tests to add:
- Asset domains are declared in CSP metadata.
- Missing file state is clear.
- Large documents do not freeze initial render.
- Keyboard navigation works.
- User selection is visible to the model when follow-up questions depend on it.
Example 5: Long-Running Job Monitor
Some MCP tools start work that takes longer than a normal chat response. An MCP App can keep progress visible and give the user controls for cancel, retry, or inspect logs.
Use it for:
- Batch imports.
- Report generation.
- Test runs.
- Deployments.
- Data sync jobs.
The model-visible tool should create or load the job. The UI should show status, logs, progress, and next actions. For updates, use an app-only polling tool or a host-supported server resource pattern. Keep the model-visible content focused on milestones, not every log line.
Example action map:
| User action | Tool |
|---|---|
| Refresh status | get_job_status, app-only |
| Cancel job | cancel_job, app-only write |
| Retry failed job | retry_job, app-only write |
| Ask model to explain failure | sendMessage with the selected error |
Testing checklist:
- Pending, running, succeeded, failed, cancelled, and timed-out states.
- Polling stops during teardown.
- Cancel button handles an already-finished job.
- Logs are truncated in the UI and never dumped into model context by default.
- Failed jobs include a next action.
Example 6: Review Screen for Code, Content, or Records
A review screen works well when the user needs to compare before deciding. This pattern is close to an approval queue, but the main job is inspection rather than a one-click decision.
Use it for:
- Code diffs.
- Contract redlines.
- CRM record changes.
- Content edits.
- Policy exceptions.
The tool should return the item, proposed changes, source data, and review metadata. The UI should show before and after states with comments and filters. For large reviews, use app-only tools for pagination or section loading.
Design rules:
- Make changed fields easy to find.
- Keep original and proposed values close together.
- Let the user filter by severity or section.
- Send only selected findings to the model when the user asks for explanation.
- Avoid making the model infer UI state from hidden component state.
Tests to add:
- Diff data renders deterministically.
- Long before and after values wrap.
- Comment and filter state survives display mode changes.
- The app handles no-change reviews gracefully.
- Selected finding updates model context.
Example 7: Guided Creation Flow
Guided creation flows let the model help start a task while the user finishes precise input in UI.
Use it for:
- Creating tickets.
- Drafting invoices.
- Building query filters.
- Scheduling reports.
- Creating campaign briefs.
The model-visible tool should prepare a draft. The app should let users edit, validate, preview, and submit. The submit tool should be app-only unless the model can safely call it without user interaction. Most create flows should require an explicit user action in the UI.
The resource usually needs three states:
- Draft editing.
- Preview or validation.
- Submitted result.
Use sendMessage when the user wants the model to continue the conversation, such as “explain these validation errors.” Use callServerTool when the UI needs backend validation or submit. Use updateModelContext when the user changed fields and the model should know the current draft on a future turn.
Testing checklist:
- Draft generated from tool input.
- Required fields and invalid combinations.
- Submit success and failure.
- Double-submit prevention.
- Final submitted ID appears in the UI and model-visible result.
How to Choose Your First Example
Pick the example with the smallest real workflow, not the flashiest UI.
Use this decision table:
| If your user needs to… | Start with… |
|---|---|
| Confirm a risky action | Approval queue |
| Explore many rows or metrics | Dashboard |
| Set many options | Configuration form |
| Inspect visual content | Document or media viewer |
| Watch work complete | Job monitor |
| Compare proposed changes | Review screen |
| Create a structured object | Guided creation flow |
For a first app, avoid building several patterns at once. A dashboard with editing, approvals, exports, comments, and long-running jobs will slow you down. Build one model-visible tool, one resource, one simulation file, and one E2E test first.
A Minimal sunpeak Project Shape
For any example above, the file structure stays familiar:
src/
resources/
access-review/
access-review.tsx
tools/
review-access-request.ts
approve-access-request.ts
reject-access-request.ts
tests/
simulations/
review-access-request.json
e2e/
access-review.spec.ts
The simulation pins the state so you can test the UI without calling a live backend:
{
"tool": "review-access-request",
"userMessage": "Review access request AR-1042.",
"toolInput": {
"requestId": "AR-1042"
},
"toolResult": {
"structuredContent": {
"request": {
"id": "AR-1042",
"userName": "Maya Chen",
"system": "Billing Admin",
"risk": "high",
"reason": "Needs temporary access for month-end close."
}
}
},
"serverTools": {
"approve-access-request": {
"structuredContent": {
"status": "approved"
}
}
}
}
Then test the rendered app:
import { test, expect } from 'sunpeak/test';
test('approval request can be approved from the app UI', async ({ inspector }) => {
const result = await inspector.renderTool('review-access-request');
const app = result.app();
await expect(app.getByText('Maya Chen')).toBeVisible();
await expect(app.getByText('Billing Admin')).toBeVisible();
await app.getByRole('button', { name: 'Approve' }).click();
await expect(app.getByText('Approved')).toBeVisible();
});
That one test covers the real shape of an MCP App: tool result, resource render, iframe interaction, app-only server tool call, and post-action UI state.
Cross-Host Rules for Every Example
Most example bugs come from assuming the host works like a normal browser tab. MCP Apps run inside host-owned iframes, and hosts differ.
For every example:
- Use host context for theme, platform, locale, viewport, and display mode.
- Request display mode changes, but treat the host as the final decision-maker.
- Keep CSS resilient at narrow widths.
- Feature-detect optional bridge capabilities.
- Declare external domains in resource metadata.
- Keep useful model text in
content, UI data instructuredContent, and UI-only data in_metawhen supported. - Test ChatGPT and Claude host modes before publishing.
That last point is where a framework helps. With sunpeak, you can render the same simulation in local ChatGPT and Claude replicas, toggle themes and display modes, and promote the scenario into CI. You still need live host testing near release, but most defects should be caught before that.
What to Build First
If you are starting today, build an approval or review app. It is small enough to finish, but it exercises the parts that matter: tool metadata, structuredContent, a UI resource, app-only tools, state updates, error handling, and tests.
Run:
npx sunpeak new mcp-app-examples
cd mcp-app-examples
pnpm dev
Then add one example state, one button, one server tool mock, and one E2E test. After that works, add display mode tests, mobile layout, and a live-host pass.
The goal is not to copy a demo exactly. The goal is to learn the repeatable shape: the model starts the workflow, the app renders deterministic UI, the user interacts with that UI, and the host keeps the conversation and app state connected.
Get Started
npx sunpeak newFurther Reading
- What is an MCP App? Architecture, hosts, and how to build one.
- MCP App tutorial - build and test your first MCP App.
- MCP App actions - callServerTool, sendMessage, and updateModelContext.
- Interactive MCP Apps with useAppState.
- Testing MCP App data flow.
- MCP App framework
- ChatGPT App framework
- Claude Connector framework
- sunpeak quickstart
- MCP Apps overview - Model Context Protocol
- MCP Apps compatibility in ChatGPT - OpenAI
Frequently Asked Questions
What are good examples of MCP Apps?
Good MCP App examples include approval queues, analytics dashboards, configuration forms, document viewers, long-running job monitors, review screens, and guided creation flows. Each example works well because the user needs deterministic UI, not only a text answer from the model.
What is the best first MCP App to build?
The best first MCP App is usually a read-only detail or review view backed by one tool. It lets you practice the core contract: a model-visible tool returns concise content, structuredContent for the UI, and a UI resource linked through metadata. Add write actions, app-only tools, and state after the first view works.
How do I choose between an MCP tool and an MCP App UI?
Use a normal MCP tool when the result is short, mostly text, and does not need user interaction. Use an MCP App UI when users need to inspect complex data, compare options, fill forms, approve actions, view media, monitor progress, or keep state visible while the conversation continues.
Should an MCP App fetch data from the browser or from server tools?
Prefer server tools for private data, writes, pagination, validation, and anything that needs credentials. Browser fetches are fine for public assets or explicitly allowed resource domains, but MCP Apps run in host-controlled iframes with CSP, so backend work should usually route through tools.
Can the same MCP App example work in ChatGPT and Claude?
Yes, if it uses MCP Apps standard fields and bridge requests first. Keep host-specific APIs behind capability checks, use host context for theme and viewport differences, and test the same resource in ChatGPT and Claude host modes before shipping.
What should every MCP App example test?
Test the tool contract, the rendered success state, loading and error states, empty data, mobile layout, light and dark themes, supported display modes, and any app-only tool calls. For write flows, test disabled states, confirmation paths, and failed writes.
Where does sunpeak help with MCP App examples?
sunpeak scaffolds tools, resources, simulation files, and Playwright tests for MCP Apps. You can run examples locally in a replicated ChatGPT and Claude inspector, then turn the same examples into CI tests before live host testing.