Content
# MCP App Studio Starter
Starter template for building interactive apps for AI assistants with [MCP App Studio](https://github.com/assistant-ui/assistant-ui/tree/main/packages/mcp-app-studio).
> **Note:** This template is automatically downloaded when you run `npx mcp-app-studio`. You don't need to clone this repo directly.
## Supported Platforms
Build once, deploy anywhere:
- **ChatGPT** — as an MCP Apps host (standard `ui/*` bridge)
- **Claude Desktop** — as an MCP Apps host
- **Any MCP Apps host** — compatible with any MCP-supporting AI assistant
## Quick Start
```bash
# npm (default)
npm install
npm run dev
```
Open http://localhost:3002 — you're in the workbench.
This project also works with pnpm/yarn/bun (use the equivalent install + run commands).
If you switch package managers (e.g. `pnpm` → `npm`), delete `node_modules/` first to avoid confusing the installer.
The MCP server (when `server/` exists) runs at `http://localhost:3001/mcp` by default. If 3001 is already in use, it will select the next available port and write it to `server/.mcp-port`.
The workbench simulates an MCP Apps host in an iframe. It also installs a
`window.openai` shim so you can exercise **ChatGPT-only extensions** during
development (optional, non-standard).
## Agent Workflow
Use `.agent/skills/mcp-app-development/SKILL.md` as the default coding-agent workflow for this repo. It defines the 80/20 capability-slice loop:
- Build UI and real MCP tool(s) in one chunk
- Use TDD (`red -> green`) for parity checks
- Never treat mock-only behavior as done
## Commands
| Command | Description |
| ---------------- | ---------------------------------------- |
| `npm run dev` | Start workbench (Next.js + MCP server) |
| `npm run build` | Production build |
| `npm run export` | Generate widget bundle for deployment |
## Project Structure
```
app/ Next.js pages
components/
├── examples/ Example widgets (POI Map)
├── workbench/ Workbench UI components
└── ui/ Shared UI components
lib/
├── sdk/ SDK exports for production
├── workbench/ React hooks + dev environment
└── export/ Production bundler
server/ MCP server (if included)
```
## Building Your Widget
### 1. Create a component
```tsx
// components/my-widget/index.tsx
import {
useToolInput,
useCallTool,
useTheme,
useCapabilities,
useUpdateModelContext,
useWidgetState,
} from "@/lib/sdk";
export function MyWidget() {
const input = useToolInput<{ query: string }>();
const callTool = useCallTool();
const theme = useTheme();
const capabilities = useCapabilities();
const updateModelContext = useUpdateModelContext();
const [widgetState, setWidgetState] = useWidgetState();
const handleSearch = async () => {
const result = await callTool("search", { query: input.query });
console.log(result.structuredContent);
};
return (
<div
data-theme={theme}
className={theme === "dark" ? "dark bg-background text-foreground" : "bg-background text-foreground"}
style={{ colorScheme: theme }}
>
<p>Query: {input.query}</p>
<button onClick={handleSearch}>Search</button>
{/* Platform-specific features */}
{capabilities.modelContext && (
<button
onClick={() =>
updateModelContext({ structuredContent: { query: input.query } })
}
>
Update model context (host-dependent)
</button>
)}
{capabilities.widgetState && (
<button
onClick={() =>
setWidgetState({
...(widgetState ?? {}),
savedAt: Date.now(),
})
}
>
Save widget state (ChatGPT extensions)
</button>
)}
</div>
);
}
```
### 2. Register in the workbench
Add your component to `lib/workbench/component-registry.tsx`.
### 3. Add mock data
Configure mock tool responses in `lib/workbench/mock-config/`.
### React Hooks Reference
Full documentation: [`lib/workbench/README.md`](lib/workbench/README.md)
#### Universal Hooks (recommended)
These hooks work identically across MCP hosts (including ChatGPT):
| Hook | Description |
| ---- | ----------- |
| `useToolInput<T>()` | Get input arguments from tool call |
| `useTheme()` | Get current theme ("light" or "dark") |
| `useCallTool()` | Call backend tools |
| `useDisplayMode()` | Get/set display mode |
| `useSendMessage()` | Send messages to conversation |
#### Platform Detection (when needed)
| Hook | Description |
| ---- | ----------- |
| `useCapabilities()` | Get full capability object |
| `useFeature(name)` | Check if specific feature is available |
#### Host-Dependent / Extensions (advanced)
These hooks only work on specific platforms. Check availability first:
| Hook | Platform | Description |
| ---- | -------- | ----------- |
| `useWidgetState()` | ChatGPT extensions | Optional OpenAI/ChatGPT host-managed state |
| `useUpdateModelContext()` | Host-dependent | Update model-visible context dynamically |
| `useToolInputPartial()` | Host-dependent | Streaming input during generation |
| `useLog()` | Host-dependent | Structured logging to host |
| `openModal()` helper | ChatGPT extensions (fallback-safe) | Use host modal when available, fallback locally |
`useWidgetState()` is not a standard MCP Apps persistence primitive.
For portable MCP Apps, use app-managed persistence such as localStorage or
server-backed tools.
Tool result metadata (`_meta`) is available via
`readToolResponseMetadata()` when the host exposes
`window.openai.toolResponseMetadata`.
## Platform-Specific Features
MCP App Studio is MCP-first: prefer the MCP Apps bridge (`ui/*`) and feature-detect
optional ChatGPT extensions (`window.openai`) when needed.
| Feature | MCP Apps standard | ChatGPT extensions (optional) |
| ------- | ----------------- | ---------------------------- |
| Tool input | Yes | (alias: `window.openai.toolInput`) |
| Tool result | Yes | (alias: `window.openai.toolOutput`) |
| Tool result metadata (`_meta`) | Yes | Yes (alias: `window.openai.toolResponseMetadata`) |
| Call tool | Yes | (alias: `window.openai.callTool`) |
| Send message | Host-dependent | (alias: `window.openai.sendFollowUpMessage`) |
| Update model context | Host-dependent | (extension: `window.openai.setWidgetState`) |
| Host-managed modal | No | Yes (`window.openai.requestModal`) |
| Widget state persistence | No | Yes (OpenAI/ChatGPT host-managed state) |
| File upload/download | No | Yes |
| Open in app link | No | Yes (`window.openai.setOpenInAppUrl`) |
| Instant checkout | No | Yes (`window.openai.requestCheckout`) *(private beta)* |
Use `useCapabilities()` or `useFeature()` to conditionally enable features.
### Modal Guidance
- Prefer local/in-widget modals for cross-host compatibility.
- Use `window.openai.requestModal()` only when you specifically need a ChatGPT-hosted modal template.
- Always feature-detect and provide a fallback:
```ts
if (typeof window !== "undefined" && window.openai?.requestModal) {
await window.openai.requestModal({ title: "Details", params: { id } });
} else {
// Fallback: local modal state or route navigation
}
```
### Checkout Guidance (ChatGPT beta extension)
- `window.openai.requestCheckout(...)` is currently a ChatGPT private beta extension.
- Treat checkout as optional: feature-detect and provide a fallback (for example, external checkout).
- If you expose a companion deep link in ChatGPT menus, use `window.openai.setOpenInAppUrl({ href })`.
```ts
import { requestCheckout, setOpenInAppUrl } from "@/lib/sdk";
setOpenInAppUrl("https://your-app.com/orders/123");
const outcome = await requestCheckout(
{ id: "checkout_123", payment_mode: "test" },
() => window.open("https://your-app.com/checkout/123", "_blank"),
);
if (outcome.mode === "fallback") {
// Non-ChatGPT host or checkout beta unavailable.
}
```
## Exporting for Production
```bash
npm run export
```
Defaults for `--entry` and `--export-name` are read from `mcp-app-studio.config.json` (written by the CLI when you scaffold a project). You can override them via flags.
Generates:
```
export/
├── widget/
│ └── index.html Self-contained widget
├── manifest.json App manifest
└── README.md Deployment instructions
```
The exported widget uses the `mcp-app-studio` SDK which automatically detects the host platform and uses the appropriate bridge.
### MCP Metadata Defaults (Export)
- `ui.*` metadata is canonical in exported server code.
- Legacy metadata keys from older ChatGPT Apps integrations are not supported in exported MCP metadata.
- Visibility defaults to host default (`["model","app"]`) when no visibility keys are set.
- Widget resources are emitted with MCP Apps MIME type `text/html;profile=mcp-app`.
Widget resource CSP must use MCP-standard keys:
```ts
ui: {
csp: {
connectDomains: ["https://api.example.com"],
resourceDomains: ["https://cdn.example.com"],
frameDomains: ["https://www.youtube.com"],
baseUriDomains: ["https://cdn.example.com"],
},
}
```
## Deploying
### Widget
Deploy `export/widget/` to any static host:
```bash
# Vercel
cd export/widget && vercel deploy
# Netlify
netlify deploy --dir=export/widget
# Or any static host (S3, Cloudflare Pages, etc.)
```
### MCP Server
If you have a `server/` directory:
```bash
cd server
npm run build
# Deploy to Vercel, Railway, Fly.io, etc.
```
### Register with Platform
**For ChatGPT:**
1. Update `manifest.json` with your deployed widget URL
2. Go to [ChatGPT Apps dashboard](https://chatgpt.com/apps)
3. Create a new app and connect your MCP server
4. Test in a new ChatGPT conversation
**For Claude Desktop:**
1. Configure your MCP server in Claude Desktop settings
2. The widget will render when tools with UI are invoked
## Configuration
### SDK Guide (Optional)
The workbench includes an AI-powered SDK guide. To enable:
```bash
cp .env.example .env.local
# then set:
# OPENAI_API_KEY="your-key"
```
### MCP Server CORS
For production, restrict CORS to your widget domain:
```bash
cp server/.env.example server/.env
# then set:
# CORS_ORIGIN=https://your-widget-domain.com
```
### Dark Mode
Exported widgets inherit host theme and token variables. Follow the framework-agnostic contract in `lib/workbench/THEMING_CONTRACT.md`.
At minimum, support `data-theme` / `.dark` and semantic tokens:
```css
.my-element {
background: var(--background);
color: var(--foreground);
border-color: var(--border);
}
```
## Learn More
- [MCP App Studio](https://github.com/assistant-ui/assistant-ui/tree/main/packages/mcp-app-studio) — CLI and SDK documentation
- [MCP Specification](https://modelcontextprotocol.io/specification/) — Model Context Protocol
- [ChatGPT MCP Apps](https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt) — ChatGPT as an MCP host
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.
Filesystem
Node.js MCP Server for filesystem operations with dynamic access control.