A ChatGPT app can look finished while its release evidence is still missing. The tool responds, a demo prompt works, and the result seems ready. Then the uncomfortable questions arrive: Does the input schema collect more than the job requires? Is the action really read-only? What happens on a retry? Which test proves that an unrelated prompt does not trigger the app?
The answer is not another feature. It is a smaller contract and a record that another person can inspect.
This guide follows one fictional app, Slot Finder, from a tool definition to a review packet. Its only job is to return public appointment slots for a known venue, service, and date. It does not identify a customer, hold a slot, or create a booking. The example is a design exercise, not a report of a HUMAI deployment or an OpenAI review result. The source review for this draft took place on July 18, 2026. Check each cited page again before publication.
Start with a promise small enough to test
An Apps SDK app needs an MCP server. A web component is optional. That distinction in the official Apps SDK quickstart is useful because it stops UI work from hiding a weak tool contract.
Slot Finder begins as one tool named find_open_slots. The name says what it does. The description says that it reads public availability for one venue, one service, and one calendar date. It does not claim to recommend the best time or complete a reservation.
The proposed inputs are deliberately narrow:
venue_id, selected from the service's own venue directoryservice_id, selected from that venue's published servicesdate, expressed as a calendar date rather than free-form prose
The result contains a list of available intervals with a slot identifier, start time, end time, and time zone. It does not return a raw provider response, request ID, server log, customer profile, or chat transcript. An unknown venue or service produces a specific validation error. A valid request with no availability produces an empty list with a plain explanation.
Because this version only reads, its contract can state readOnlyHint: true and destructiveHint: false. Do not infer openWorldHint from those two values. Slot Finder consults a public external schedule, and OpenAI's current guidance describes that boundary in two different ways: the app guidelines broadly include tools that interact with external systems, while the submission FAQ explains the hint in terms of writing to or changing publicly visible internet state. Record the chosen value and justification against the live documentation and portal review, then compare Scan Tools with the deployed contract. Any mismatch or unresolved classification is a release blocker. The handler should also be safe to call again with the same inputs.
| Gate | Evidence to retain | Reason to stop the release |
|---|---|---|
| Tool contract | Versioned names, descriptions, schemas, annotations, error cases, and retry behavior | A field or side effect exists only in implementation notes |
| UI boundary | A written decision to ship tool-only, plus the condition that would justify a component | The component adds decoration but no clearer task control |
| Authentication | A data map showing that this release reads public inventory and performs no customer action | Any handler can reach private data or change a booking |
| Deployment | Production endpoint, TLS check, release revision, redacted error sample, metrics view, and rollback owner | The endpoint depends on a development tunnel or secrets stored in source control |
| Testing | Handler results, Inspector capture, developer-mode prompt log, and web and mobile checks | Only the happy-path demo was recorded |
| Submission | Scanned tool metadata, policies, test cases, listing copy, release notes, and reviewer access where required | The portal snapshot differs from the deployed contract |
Gate one: make the tool contract a test target
The model uses tool metadata to decide whether a tool fits a request. That makes the name, description, schemas, and annotations part of the product surface. A vague description cannot be repaired by a careful handler because the selection decision happens before the handler runs.
For Slot Finder, the acceptance record should answer five questions without opening the source code:
- Which user request should select this tool?
- Which requests should not select it?
- What fields are required, and what format does each field accept?
- What result shape is returned for success, no availability, and invalid input?
- Can the same call be repeated without changing state?
The negative boundary matters as much as the happy path. “Show open times at venue 17 next Friday” is in scope once the date is resolved. “Book the 10:30 slot for me” is not. The app should explain that it can show availability but cannot reserve it. It must not quietly translate a booking request into a read call and leave the user thinking a reservation exists.
OpenAI's app guidelines for tools call for specific names, descriptions that match behavior, minimal inputs, correct action labels, and visible side effects. The same review catches promotional metadata such as “best slot finder” and broad context fields added just in case. Neither helps the task.
Gate two: let a component earn its place
A short list of three appointment times can be read in chat. Slot Finder does not need a component for its first release. Mark that choice as tool-only in the acceptance record and test the actual text and structured result on both web and mobile.
A component becomes reasonable when it changes the work, not merely the appearance. Comparing many dates in a calendar, filtering a long list, or selecting a slot for a later confirmed action could justify one. If the team adds that interface, the component becomes another release surface with its own output handling, accessibility, responsive layout, error states, and Content Security Policy.
The current MCP server guide documents the boundary: the server defines tools and data, an optional widget renders inside an iframe, and the model decides when to call a tool. It also says new UIs should use the MCP Apps bridge by default. A tool-only release can leave that entire component path out. Submission screenshots should also be omitted when the app has no UI, as OpenAI's submission instructions specify.
This decision prevents a common form of false progress. A polished calendar does not prove that the underlying availability is correct, that time zones are handled, or that an empty result is honest.
Gate three: put authentication on the data boundary
Slot Finder reads a public schedule and does not act on an account, so anonymous access is a defensible first boundary. The data map should still name the upstream source, cache duration, returned fields, and retention policy. Anonymous does not mean unvalidated or unmonitored.
The boundary changes the moment the app exposes private availability, a customer's existing bookings, or a reservation action. That work should become a separate tool with a separate contract. It should not be smuggled into find_open_slots behind an optional customer identifier.
For customer-specific data or write actions, OpenAI's Apps SDK authentication guide expects an OAuth 2.1 flow that follows the MCP authorization specification. The resource server must verify the token on every request, including its signature, issuer, audience, expiry, and scopes. ChatGPT transporting a token does not transfer that authorization duty to ChatGPT.
A future booking tool would also need accurate write annotations, clear confirmation before an irreversible outcome, narrow scopes, and documented retry behavior. If a second call could reserve twice, that risk must be eliminated or made explicit before release.
Tokens, passwords, API keys, and raw authorization responses do not belong in tool results, component properties, screenshots, or application logs. The security and privacy guide also advises teams to validate all inputs on the server, plan for prompt injection, redact personal information in logs, and avoid storing raw prompts unless necessary.
Gate four: demand operational proof from the deployment
A tunnel is useful while developing. It is not production evidence. OpenAI's deployment guide calls for a stable HTTPS endpoint, dependable TLS, responsive streaming on /mcp, useful HTTP error statuses, logs, and metrics.
The release record for Slot Finder should identify the deployed revision and the person who can roll it back. It should include a successful endpoint check, one redacted example of a handled error, and links to operational views for latency, request volume, CPU, memory, and repeated failures. Secrets should be injected through the hosting platform or a secret manager, never committed with the app.
Logging needs a boundary too. An opaque call identifier, duration, result category, and error class can help diagnose an incident. The full user prompt, access token, returned customer record, or internal debug payload usually creates more exposure than value. If a field is retained, the data map should say why and for how long.
The final deployment check is simple to state: could an on-call engineer distinguish an invalid input from an upstream outage without reading private user content? If not, the telemetry is either too weak or too invasive.
Gate five: test correctness, selection, and the real client
The official integration testing guide separates tool correctness, component UX, and discovery precision. Slot Finder has no component, so the component line in the test record should say “not applicable for this release.” It should not be silently marked as passed.
Tool-handler tests cover representative inputs before ChatGPT enters the picture. For this app, the fixture set should include a normal day, no availability, an unknown venue, an unsupported service, an invalid date, a time-zone boundary, and an upstream timeout. The assertions should compare the declared result shape and user-facing error, not just the HTTP status.
Next, MCP Inspector should list the deployed tool and call it with the same fixtures. Retain the captured tool definition, raw request, raw response, and any rendering result. That artifact proves which metadata and payload were actually exposed at the time of the check.
Developer-mode testing answers a different question: does ChatGPT select the tool when it should? Use a fixed prompt sheet with three groups:
- Direct prompts, such as asking for slots at a named venue on a date
- Indirect prompts, such as asking whether there is time for an appointment after lunch
- Negative prompts, such as asking to book, cancel, or reveal another person's appointment
Record the selected tool, supplied arguments, returned result, clarification, and any confirmation shown. Repeat the release set in ChatGPT on the web and on mobile. A server test cannot reveal a clipped result, a confusing consent step, or a client-specific failure.
Gate six: build the review draft from deployed facts
OpenAI's current process submits and publishes apps as plugins. The plugin submission guide is the source of truth for the portal flow. Submission starts review; it does not publish the plugin.
Before opening the draft, the publisher needs the appropriate Apps Management permission and a verified individual or business identity. The listing needs public website, support, privacy, and terms URLs that match that identity. If authentication is required, reviewer credentials must work without MFA, email confirmation, SMS confirmation, or private-network access.
For an MCP-backed app, the portal scans the production server. That scan imports tool names, descriptions, schemas, security schemes, annotations, UI metadata, and server instructions. A written justification does not override the scanned values. If the portal shows the wrong annotation, fix the server, deploy it, and scan again.
The current form also asks for exactly five positive and three negative test cases with reproducible expected behavior. Slot Finder's cases should use the retained fixtures rather than a live schedule that may change during review. The negative cases should show refusal, clarification, or a safe fallback for booking, private-data, and unrelated requests.
A component-free app still needs an explicit security posture. Follow the portal's current Content Security Policy validation and represent a no-browser-fetch design as narrowly as the portal allows. Do not add wildcard domains just to make a check disappear. If the portal and written documentation differ, treat the live validation as a release blocker and resolve it before submission.
Assemble a release packet someone else can audit
The packet is the end of the build, not a folder created afterward. For Slot Finder, it should contain:
- the versioned tool contract, including schemas, annotations, errors, and retry behavior;
- the data map and the written decision to remain anonymous and read-only;
- handler fixtures and automated test output;
- the MCP Inspector tool listing and call captures;
- the developer-mode prompt log for direct, indirect, and negative discovery;
- web and mobile results, with the component check marked not applicable;
- the production endpoint, deployed revision, operational views, and rollback owner;
- the scanned metadata export and any annotation justifications;
- five positive and three negative review cases tied to stable fixtures;
- the privacy policy, terms, website, support route, listing copy, and release notes.
A release candidate is ready when each line points to a retained artifact and the artifact matches the deployed server. A missing capture is not proof that a check probably passed. It is a check that still needs to be run.