Skip to main content 홈 크리에이터 diegosouzapw awesome-omni-skill chatgpt-app-builder
chatgpt-app-builder Build ChatGPT apps with interactive widgets using mcp-use and OpenAI Apps SDK. Use when creating ChatGPT apps, building MCP servers with widgets, defining React widgets, working with Apps SDK, or when user mentions ChatGPT widgets, mcp-use widgets, or Apps SDK development.
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill chatgpt-app-builder명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... name chatgpt-app-builder description Build ChatGPT apps with interactive widgets using mcp-use and OpenAI Apps SDK. Use when creating ChatGPT apps, building MCP servers with widgets, defining React widgets, working with Apps SDK, or when user mentions ChatGPT widgets, mcp-use widgets, or Apps SDK development.
ChatGPT App Builder
Build production-ready ChatGPT apps with interactive widgets using mcp-use. Zero-config widget development with automatic registration and built-in React hooks.
Quick Start
npx create-mcp-use-app my-chatgpt-app --template mcp-apps
cd my-chatgpt-app
npm install && npm run dev
Project structure:
my-chatgpt-app/
├── resources/ # React widgets (auto-registered!)
│ ├── weather-display.tsx # Example widget
│ └── product-card.tsx # Another widget
├── public/ # Static assets
│ └── images/
├── index.ts # MCP server entry
├── package.json
└── tsconfig.json
Why mcp-use for ChatGPT Apps?
Traditional OpenAI Apps SDK requires significant manual setup:
Separate project structure (server/ and web/ folders)
Manual esbuild/webpack configuration
Custom useWidgetState hook implementation
Manual React mounting code
Manual CSP configuration
Manual widget registration
mcp-use simplifies everything:
Single command setup
Drop widgets in resources/ folder - auto-registered
Built-in useWidget() hook with state, props, tool calls
Automatic bundling with hot reload
Automatic CSP configuration
Built-in Inspector for testing
Dual-protocol support (works with ChatGPT AND MCP Apps clients)
MCP Apps vs ChatGPT Apps SDK
Protocol Use Case Compatibility Status MCP Apps (type: "mcpApps")Maximum compatibility ChatGPT + MCP Apps clients Recommended ChatGPT Apps SDK (type: "appsSdk")ChatGPT-only features ChatGPT only Supported
Why MCP Apps? It's the official standard (SEP-1865) for interactive widgets:
Universal : Works with ChatGPT, Claude Desktop, Goose, and all MCP Apps clients
Future-proof : Based on open specification
Zero config : With type: "mcpApps", mcp-use generates metadata for BOTH protocols automatically
Creating Widgets
Simple Widget (Single File) Create resources/weather-display.tsx:
import { McpUseProvider , useWidget, type WidgetMetadata } from "mcp-use/react" ;
import { z } from "zod" ;
export const widgetMetadata : WidgetMetadata = {
description : "Display current weather for a city" ,
props : z.object ({
city : z.string ().describe ("City name" ),
temperature : z.number ().describe ("Temperature in Celsius" ),
conditions : z.string ().describe ("Weather conditions" ),
humidity : z.number ().describe ("Humidity percentage" ),
}),
};
const WeatherDisplay : React .FC = () => {
const { props, isPending } = useWidget ();
if (isPending) {
return (
<McpUseProvider autoSize >
<div className ="animate-pulse p-4" > Loading weather...</div >
</McpUseProvider >
);
}
return (
<McpUseProvider autoSize >
<div className ="weather-card p-4 rounded-lg shadow" >
<h2 className ="text-2xl font-bold" > {props.city}</h2 >
<div className ="temp text-4xl" > {props.temperature}°C</div >
<p className ="conditions" > {props.conditions}</p >
<p className ="humidity" > Humidity: {props.humidity}%</p >
</div >
</McpUseProvider >
);
};
export default WeatherDisplay ;
Registered as MCP tool weather-display
Registered as MCP resource ui://widget/weather-display.html
Bundled for Apps SDK compatibility
Complex Widget (Folder Structure) For widgets with multiple components:
resources/
└── product-search/
├── widget.tsx # Entry point (required name)
├── components/
│ ├── ProductCard.tsx
│ └── FilterBar.tsx
├── hooks/
│ └── useFilter.ts
└── types.ts
Entry point must be named widget.tsx and export widgetMetadata + default component.
Widget Metadata export const widgetMetadata : WidgetMetadata = {
description : "Display weather information" ,
props : z.object ({
city : z.string ().describe ("City name" ),
temperature : z.number (),
}),
exposeAsTool : true ,
metadata : {
csp : {
connectDomains : ["https://api.weather.com" ],
resourceDomains : ["https://cdn.weather.com" ],
},
prefersBorder : true ,
autoResize : true ,
widgetDescription : "Interactive weather display" ,
},
};
description: Used for tool and resource descriptions
props: Zod schema defines widget input parameters
exposeAsTool: Set to false if only using widget via custom tools
metadata: Unified configuration for both protocols (recommended)
Content Security Policy (CSP) Control external resources your widget can access:
export const widgetMetadata : WidgetMetadata = {
description : "Weather widget" ,
props : z.object ({ city : z.string () }),
metadata : {
csp : {
connectDomains : ["https://api.weather.com" , "https://backup.weather.com" ],
resourceDomains : ["https://cdn.weather.com" ],
frameDomains : ["https://embed.weather.com" ],
scriptDirectives : ["'unsafe-inline'" ],
},
},
};
Specify exact domains: https://api.weather.com
Avoid wildcards in production
Never use 'unsafe-eval' unless necessary
useWidget Hook const {
props,
isPending,
state,
setState,
theme,
callTool,
displayMode,
requestDisplayMode,
output,
} = useWidget<MyPropsType , MyOutputType >();
Props and Loading States Critical: Widgets render BEFORE tool execution completes. Always handle isPending:
const { props, isPending } = useWidget<WeatherProps >();
if (isPending) {
return <div > Loading...</div > ;
}
return (
<div >
{isPending ? <LoadingSpinner /> : <div > {props.city}</div > }
</div >
);
return (
<div >
<h1 > {props.city ?? "Loading..."}</h1 >
</div >
);
Widget State Persist data across widget interactions:
const { state, setState } = useWidget ();
const addFavorite = async (city : string ) => {
await setState ({
favorites : [...(state?.favorites || []), city],
});
};
await setState ((prev ) => ({
...prev,
count : (prev?.count || 0 ) + 1 ,
}));
Calling MCP Tools Widgets can call other tools:
const { callTool } = useWidget ();
const refreshData = async ( ) => {
try {
const result = await callTool ("get-weather" , { city : "Tokyo" });
console .log ("Result:" , result.content );
} catch (error) {
console .error ("Tool call failed:" , error);
}
};
Display Mode Control const { displayMode, requestDisplayMode } = useWidget ();
const goFullscreen = async ( ) => {
await requestDisplayMode ("fullscreen" );
};
console .log (displayMode);
Custom Tools with Widgets Create tools that return widgets:
import { MCPServer , widget, text } from "mcp-use/server" ;
import { z } from "zod" ;
const server = new MCPServer ({
name : "weather-app" ,
version : "1.0.0" ,
baseUrl : process.env .MCP_URL || "http://localhost:3000" ,
});
server.tool (
{
name : "get-weather" ,
description : "Get current weather for a city" ,
schema : z.object ({
city : z.string ().describe ("City name" ),
}),
widget : {
name : "weather-display" ,
invoking : "Fetching weather..." ,
invoked : "Weather data loaded" ,
},
},
async ({ city }) => {
const data = await fetchWeatherAPI (city);
return widget ({
props : {
city,
temperature : data.temp ,
conditions : data.conditions ,
humidity : data.humidity ,
},
output : text (`Weather in ${city} : ${data.temp} °C` ),
message : `Current weather for ${city} ` ,
});
}
);
server.listen ();
baseUrl in server config enables proper asset loading
widget: { name, invoking, invoked } on tool definition
widget({ props, output }) helper returns runtime data
props passed to widget, output shown to model
Static Assets Use public/ folder for images, fonts:
my-app/
├── resources/
├── public/
│ ├── images/
│ │ ├── logo.svg
│ │ └── banner.png
│ └── fonts/
└── index.ts
import { Image } from "mcp-use/react" ;
function MyWidget ( ) {
return (
<div >
{/* Paths relative to public/ folder */}
<Image src ="/images/logo.svg" alt ="Logo" />
<img src ={window.__getFile?.( "images /banner.png ")} alt ="Banner" />
</div >
);
}
Components
McpUseProvider Unified provider combining all common setup:
import { McpUseProvider } from "mcp-use/react" ;
function MyWidget ( ) {
return (
<McpUseProvider
autoSize // Auto-resize widget
viewControls // Add debug /fullscreen buttons
debug // Show debug info
>
<div > Widget content</div >
</McpUseProvider >
);
}
Image Component Handles both data URLs and public paths:
import { Image } from "mcp-use/react" ;
<Image src ="/images/photo.jpg" alt ="Photo" />
<Image src ="data:image/png;base64,..." alt ="Data URL" />
ErrorBoundary import { ErrorBoundary } from "mcp-use/react" ;
<ErrorBoundary
fallback ={ <div > Something went wrong</div > }
onError={(error) => console.error(error)}
>
<MyComponent />
</ErrorBoundary >
Testing
Using the Inspector
Start development: npm run dev
Open http://localhost:3000/inspector
Click Tools tab → Find your widget → Enter parameters → Execute
Debug with browser console, RPC logs, state inspection
Testing in ChatGPT
Enable Developer Mode: Settings → Connectors → Advanced → Developer mode
Add your server: Connectors tab → Add remote MCP server URL
Test: Select Developer Mode from Plus menu → Choose connector → Use tools
Be explicit: "Use the weather-app connector's get-weather tool..."
Disallow alternatives: "Do not use built-in tools, only use my connector"
Specify input: "Call get-weather with { city: 'Tokyo' }"
Dual-protocol note: With type: "mcpApps", widgets work in both ChatGPT and MCP Apps clients without code changes.
Best Practices
Schema Design
const schema = z.object ({
city : z.string ().describe ("City name (e.g., Tokyo, Paris)" ),
temperature : z.number ().min (-50 ).max (60 ).describe ("Temp in Celsius" ),
});
const schema = z.object ({
city : z.string (),
temp : z.number (),
});
Theme Support const { theme } = useWidget ();
const bgColor = theme === "dark" ? "bg-gray-900" : "bg-white" ;
const textColor = theme === "dark" ? "text-white" : "text-gray-900" ;
Loading States Always check isPending first:
const { props, isPending } = useWidget<MyProps >();
if (isPending) return <LoadingSpinner /> ;
return <div > {props.field}</div > ;
Widget Focus Keep widgets focused on one thing:
export const widgetMetadata : WidgetMetadata = {
description : "Display weather for a city" ,
props : z.object ({ city : z.string () }),
};
export const widgetMetadata : WidgetMetadata = {
description : "Weather, forecast, map, news, and more" ,
props : z.object ({ }),
};
Error Handling const { callTool } = useWidget ();
const fetchData = async ( ) => {
try {
const result = await callTool ("fetch-data" , { id : "123" });
if (result.isError ) {
console .error ("Tool returned error" );
}
} catch (error) {
console .error ("Tool call failed:" , error);
}
};
Configuration
Production Setup const server = new MCPServer ({
name : "my-app" ,
version : "1.0.0" ,
baseUrl : process.env .MCP_URL || "https://myserver.com" ,
});
Environment Variables MCP_URL=https://myserver.com
MCP_SERVER_URL=https://myserver.com/api
CSP_URLS=https://cdn.example.com,https://api.example.com
Deployment npx mcp-use login
npm run deploy
Troubleshooting
Widget Not Appearing
Ensure .tsx extension
Export widgetMetadata object
Export default React component
Check server logs for errors
Verify widget name matches file/folder name
Props Not Received
Check isPending first (props empty while pending)
Use useWidget() hook (not React props)
Verify widgetMetadata.props is valid Zod schema
Check tool parameters match schema
CSP Errors
Set baseUrl in server config
Add domains to CSP via metadata.csp
Use HTTPS for all resources
Check browser console for CSP violations
Protocol Compatibility
Use type: "mcpApps" for dual-protocol support
Set baseUrl correctly in server config
Use metadata (camelCase) not appsSdkMetadata for dual-protocol
Test in Inspector which supports both protocols
Quick Reference
npx create-mcp-use-app my-app --template mcp-apps - Bootstrap
npm run dev - Development with hot reload
npm run build - Build for production
npm start - Run production server
npm run deploy - Deploy to mcp-use Cloud
resources/widget-name.tsx - Single file widget
resources/widget-name/widget.tsx - Folder-based widget entry
public/ - Static assets
description - Widget description (required)
props - Zod schema for input (required)
exposeAsTool - Auto-register as tool (default: true)
metadata - Unified config (dual-protocol)
metadata.csp - Content Security Policy
props - Widget input parameters
isPending - Loading state flag
state, setState - Persistent state
callTool - Call other tools
theme - Current theme (light/dark)
displayMode, requestDisplayMode - Display control
References
Learn More