ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β π shadcn/directory/brennenrocks/utilcn/chatgpt-app/frontend β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
The frontend block installs a widget entry point (index.jsx), shared hooks, and a Vite build script that generates the static assets ChatGPT loads inside the MCP widget iframe. Install the block and run the build script whenever you change the widget UI so the backend can serve the latest HTML, CSS, and JavaScript bundle.
src/components/add/index.jsx React Component that displays the tool output passed in through useWidgetProps.src/lib/build-chatgpt-widgets.ts Builds the React Components, hashes the outputs (ChatGPT Apps aggresively cache your components. You'll want new hashes for each build), and emits HTML wrappers in assets/.src/lib/chatgpt-types.ts and src/lib/use-openai-global.ts bridge the widget with globals exposed by ChatGPTβs web sandbox.use-display-mode, use-max-height, use-widget-props, and use-widget-state wrap the MCP globals so you can react to layout changes, persist state, and read structured tool output.The build script ships with TODO markersβupdate these before running it so the script points to your own workspace:
build-chatgpt-widgets.ts
import pkg from '../../../package.json' with { type: 'json' }; // <-- update to your package.json
const projectRoot = path.resolve(__dirname, '..', '..'); // <-- update to where you want your built `assets` directory to live
If your global CSS lives somewhere else (for example src/styles/globals.css), update the GLOBAL_CSS_LIST array accordingly.
targets array lists every widget entry directory that has an index.jsx or index.tsx. The default bundle only builds the add widget.(You should create a package.json script for this for ease of use)
# Go to folder with `build-chatgpt-widgets.ts`
bun build-chatgpt-widgets.ts
or
pnpm tsx registry/default/chatgpt-app/build-chatgpt-widgets.ts
index.jsx reads the tool output (a, b, and sum) and renders a card with fallbacks until the MCP runtime provides data:
import { createRoot } from 'react-dom/client';
import { Card } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { useWidgetProps } from '@/registry/default/chatgpt-app/use-widget-props';
function App() {
const { a, b, sum } = useWidgetProps({
a: null,
b: null,
sum: null,
});
const hasData = a !== null && b !== null && sum !== null;
return (
<div className="flex items-center justify-center w-full min-h-screen">
<Card className="w-full max-w-md p-6">
<div className="space-y-4">
<h2 className="text-xl font-bold">Sum Calculator</h2>
<div className="space-y-2">
<div className="flex justify-between">
<span>A:</span>
{hasData ? <span>{a}</span> : <Skeleton className="h-5 w-20" />}
</div>
<div className="flex justify-between">
<span>B:</span>
{hasData ? <span>{b}</span> : <Skeleton className="h-5 w-20" />}
</div>
<div className="flex justify-between font-bold">
<span>Sum:</span>
{hasData ? <span>{sum}</span> : <Skeleton className="h-5 w-20" />}
</div>
</div>
</div>
</Card>
</div>
);
}
createRoot(document.getElementById('add-root')).render(<App />);
You can extend the widget by registering additional hooks (for example useDisplayMode) or persisting UI state with useWidgetState.
β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ