ChatGPT App
ChatGPT App developer guide
Build or extend Jobby.dev ChatGPT App widgets — the manifest, the OpenAI Apps SDK contract, CSP requirements.
Last updated
How to build, extend, or fork the Jobby.dev ChatGPT App. Aimed at contributors who want to add a widget or change widget behavior; non-developers should head back to the overview.
Repo layout
The App lives under apps/chatgpt-app/ in the Jobby.dev monorepo:
apps/chatgpt-app/
├── manifest.json # App manifest (widgets, scopes, MCP endpoint, CSP)
├── README.md # Setup + submission notes
└── widgets/
├── InterviewRoomWidget.tsx
├── UpgradeCheckoutWidget.tsx
└── QueueStatusWidget.tsxThe manifest
Declares everything OpenAI's directory and the Apps SDK need to know:
- Display name, description, logo.
- MCP endpoint URL (the hosted
https://jobby.dev/api/mcpin production). - OAuth scopes the App requests at install.
- Widget declarations: each entry binds a content-type to a widget component file and declares the iframe / network / script-src CSP allowlist that widget needs.
Widget contract
A widget is a React component that consumes two hooks from @openai/apps-sdk:
import { useToolResult, useToolInvoke, declareWidget } from "@openai/apps-sdk";
declareWidget({
id: "queue_status",
contentType: "application/json+jobbydev_queue_status",
});
export function QueueStatusWidget() {
const initialResult = useToolResult<QueueStatusToolResult>();
const invoke = useToolInvoke();
// ...
}useToolResult— returns the tool result that caused this widget to mount, fully typed if you pass a generic.useToolInvoke— lets the widget invoke other tools on the user's behalf (e.g. pollingjobbydev_queue_statusfor refreshes).declareWidget— registers the binding from content-type to component. Called at module-load time so the Apps SDK can dispatch.
CSP requirements
Each widget needs its iframe / network / script CSP allowlist declared in the manifest. Examples:
- InterviewRoom:
frame-srcincludes*.daily.co;connect-srcincludes Daily's WebRTC signaling endpoints. - UpgradeCheckout:
frame-srcincludesjs.stripe.com;connect-srcincludesapi.stripe.com. - QueueStatus: only needs
connect-srcjobby.devfor poll calls.
The OpenAI directory review checks these allowlists before approval. Tighter is better — request only what the widget actually needs.
Adding a new widget
- Define the tool result shape: which MCP tool causes this widget to mount, what fields the result carries.
- Add a content-type label: pick a unique
application/json+jobbydev_*string. - Update the corresponding tool handler on the Jobby.dev backend to set the content-type header on the response.
- Build the widget component in
apps/chatgpt-app/widgets/. - Register in the manifest.
- Update the widgets reference page: /docs/chatgpt-app/widgets.
Local development
Today: scaffold-only — @openai/apps-sdkisn't installed in the monorepo yet (the README has the install dance). Once installed, the Apps SDK has a local-dev playground that mounts widgets against synthetic tool results without round-tripping through ChatGPT.
Submission
The OpenAI directory submission process is documented at docs/agent-directory-submissions.md in this repo. Pre-submission requirements:
- Manifest validated against OpenAI's schema.
- CSP review passes (frame-src / connect-src tight).
- OAuth flow tested end-to-end against Jobby.dev staging.
- Each widget mounts without errors against a synthetic tool result.