ChatGPT Apps SDK: How Apps Inside ChatGPT Work

The ChatGPT Apps SDK now lives in OpenAI’s plugin documentation, but the idea is unchanged: an app is an MCP server, with optional UI that renders in the chat. The architecture, a minimal example, testing in developer mode, sign-in, review for the directory, and how an app differs from a connector and a custom GPT.

8 min read

The ChatGPT Apps SDK is how you build an app that runs inside ChatGPT, and an app is an MCP server. Your server defines tools the model can call and returns structured results; for tools where a picture helps, it can also return a small web page that ChatGPT renders in the conversation. The name has moved on: OpenAI’s old Apps SDK pages now redirect to its Plugins documentation, the app directory became the Plugin Directory, and a published app now ships inside a plugin that can also carry skills. The technology underneath is the same, and the UI part follows MCP Apps, an open standard, so the same component can run in other hosts that support it.

This is the builder’s view. For what apps and plugins can reach once they are installed, and what an admin controls, see ChatGPT connectors and apps. For every way OpenAI’s products speak MCP, from the API to Codex, see MCP with OpenAI.

The names, once

  • Apps SDK: the original name for the toolkit. The developers.openai.com/apps-sdk pages now redirect to the Plugins docs, although the names survive in OpenAI’s examples repository and its @openai/apps-sdk-ui component library.
  • App: a connection to a service, built on an MCP server. Connectors were renamed apps, and OpenAI’s admin documentation treats “app” and “MCP server” as the same thing.
  • Plugin: the package people install. OpenAI’s plugin architecture (opens in a new tab) page says a plugin can hold skills, an MCP server, or both, and that ChatGPT and Codex share one plugin directory.
  • Plugin Directory: where published plugins are listed. OpenAI’s help centre says the app directory moved to it in July 2026, and that existing app connections were unaffected.

The architecture

Three pieces, each optional except the first.

  • An MCP server at a stable HTTPS address, using the streamable HTTP transport, usually at /mcp. It lists tools, each with a name, description, input schema and, ideally, an output schema.
  • Tool results in three parts: structuredContent, data the model reads and can use in later calls; content, text that helps the model answer; and _meta, data for the UI that the model does not see.
  • UI resources: an HTML page registered as an MCP resource with a ui:// address and the MIME type text/html;profile=mcp-app. A tool points at it with _meta.ui.resourceUri, and ChatGPT renders it in an iframe beside the conversation.

The component talks to ChatGPT through the MCP Apps bridge, JSON-RPC messages over postMessage: it initialises with ui/initialize, receives a tool’s result through ui/notifications/tool-result, and can call your tools itself with tools/call. OpenAI’s guide to adding UI to an MCP server (opens in a new tab) says to build on that standard first, and to reach for ChatGPT’s own window.openai extensions, such as file uploads, modals, saved widget state and checkout, only when the standard does not cover what you need.

The same guide’s most useful rule: keep every tool useful without the UI. The model should be able to finish the job from the structured result alone, because not every client renders components, and many requests do not need one. A background lookup needs text; a comparison, a map or an editable list may earn a component.

A minimal example shape

OpenAI’s MCP server and UI quickstart (opens in a new tab) builds a small to-do app in Node with the official MCP TypeScript SDK, its MCP Apps helpers and zod. Stripped to its shape, one tool with a component looks like this:

TypeScript: one UI resource and one tool
const server = new McpServer({ name: "tasks", version: "0.1.0" });

server.registerResource("task-list", "ui://widget/tasks.html", {}, async () => ({
  contents: [{
    uri: "ui://widget/tasks.html",
    mimeType: "text/html;profile=mcp-app",
    text: taskListHtml, // your component: HTML, CSS and JS in one string
  }],
}));

server.registerTool("list_tasks", {
  title: "List tasks",
  description: "List open tasks, optionally filtered by lane.",
  inputSchema: { lane: z.string().optional() },
  outputSchema: { tasks: z.array(z.object({ id: z.string(), title: z.string() })) },
  annotations: { readOnlyHint: true },
  _meta: { ui: { resourceUri: "ui://widget/tasks.html" } },
}, async ({ lane }) => {
  const tasks = await loadTasks(lane);
  return {
    structuredContent: { tasks },
    content: [{ type: "text", text: `${tasks.length} open tasks.` }],
  };
});

Serve it over streamable HTTP at /mcp and you have an app. Leave out the resource and the _meta.ui line and you still have an app, just one without a component. OpenAI keeps fuller examples, in Node and Python, in its apps SDK examples repository.

Set the annotations honestly. readOnlyHint is for tools that cannot change anything, destructiveHint for ones that can do something hard to undo, and openWorldHint for ones that reach the public internet. OpenAI says they guide ChatGPT’s confirmation behaviour but do not replace authorisation or confirmation in your own server. What each MCP building block is for is covered in MCP tools, resources and prompts.

Testing in developer mode

  1. Run the server locally and exercise every tool with MCP Inspector (npx @modelcontextprotocol/inspector@latest), including empty results, missing IDs and auth errors. How to use MCP Inspector walks through it.
  2. Give ChatGPT a way in: a public HTTPS tunnel such as ngrok, or OpenAI’s Secure MCP Tunnel for a server you do not want exposed.
  3. In ChatGPT, turn on developer mode under Settings, Security and login. Availability can depend on your account and workspace policy.
  4. Open Plugins, choose the plus button, enter the URL including /mcp, name it and create it. Check the tools ChatGPT discovered.
  5. Start a new chat, add the app from the plus menu, and try direct requests, indirect ones, follow-ups that reuse IDs, writes that need confirmation, and requests that should not call a tool at all.
  6. After changing tool names, descriptions, schemas or UI, redeploy and choose Refresh on the app’s page, then start a new chat and rerun the affected tests.

Authentication

An app that only reads public data can run anonymously. Anything that touches a user’s own data or acts for them should sign them in, and OpenAI’s authentication guide (opens in a new tab) expects OAuth 2.1 as the MCP authorization specification describes it: your MCP server is the resource server, your identity provider is the authorisation server, and ChatGPT is the client.

  • Publish protected resource metadata at /.well-known/oauth-protected-resource, naming your authorisation servers and scopes.
  • Let ChatGPT identify itself through a client ID metadata document, dynamic client registration or a client you register in advance, with PKCE.
  • Declare each tool’s policy with securitySchemes: noauth for anonymous calls, oauth2 with the scopes it needs, or both for “works signed out, better signed in”.
  • Verify the token, its scopes and its audience on every call. The declarations tell ChatGPT when to show its sign-in screen; they do not protect anything.

The same flow, independent of ChatGPT, is in how to add authentication to an MCP server.

Submission and review for the directory

Developer mode is for you and your testers. To appear in the Plugin Directory, OpenAI’s guide to submitting plugins (opens in a new tab) describes a review through its plugin submission portal on the OpenAI Platform.

  • Access: the submitter needs the organisation permission the Platform labels Apps Management, set to Write.
  • Identity: every public submission uses a verified individual or business identity, matching the listing’s name, website, support contact, privacy policy and terms.
  • The server: a stable public HTTPS endpoint, domain verification, accurate tool metadata, and readOnlyHint, openWorldHint and destructiveHint on every tool.
  • Tests: at least five positive and three negative test cases, with demo credentials a reviewer can use without MFA, SMS or email confirmation.
  • Review, then publish: approval does not publish the plugin. You choose when, and it then appears in the directory for both ChatGPT and Codex.
  • After launch: OpenAI periodically re-reads your tools. New and changed tool definitions go live after automated checks pass; a change to the listing or skills needs a new version and review.

App vs connector vs custom GPT

  • A connector is the older name for an app. What people used to call a connector, such as a data source for search and deep research, is now an app that exposes read-only tools. OpenAI’s build guide says an app becomes eligible as a company knowledge source by implementing standard search and fetch tools.
  • An app is your MCP server: live data and actions, optionally with UI, callable from any chat where it is enabled, and from Codex once published.
  • A custom GPT is a configured assistant: instructions, knowledge files and custom actions that call outside REST APIs, which users open by name. OpenAI is retiring them. Its guide to moving custom GPTs to plugins (opens in a new tab) says instructions become a skill and knowledge becomes reference files, but custom actions do not transfer and need rebuilding as an app’s actions or a custom MCP server. OpenAI’s help centre gives 11 December 2026 as the standard retirement date.

So if you are choosing today, the answer to “app or custom GPT?” is an app, packaged in a plugin, with a skill for the instructions a GPT used to carry. How a GPT compares with a project is in Claude Projects vs custom GPTs.

Using an existing MCP server instead

You do not need to build an app to use a service that already runs a remote MCP server. fenbs, a small task board, is one: it lives at https://fenbs.ai/api/mcp with OAuth sign-in, so it can be added in developer mode where your plan allows it. ChatGPT then holds your role on the board narrowed by the scopes you tick, and every task it adds, moves or comments on is recorded in History under its own name. If you are building an app of your own, a board is also a practical place to track the tool list, the review test cases and what changed in each version.

Related

Connecting ChatGPT to a board: ChatGPT and fenbs. Building the server itself: how to build an MCP server. The risks to design against: MCP security risks.

Questions people ask.

Is the ChatGPT Apps SDK still available?

Yes, under a new name. OpenAI’s Apps SDK pages now redirect to its Plugins documentation, and apps are published inside plugins in the Plugin Directory shared by ChatGPT and Codex. Apps are still MCP servers with optional UI, built the same way.

Do I need a UI to build a ChatGPT app?

No. An app can be an MCP server that returns structured results and text. Add a UI component only when people need to inspect, compare, edit or confirm information, and keep each tool useful without it.

How do I test a ChatGPT app before it is published?

Turn on developer mode in ChatGPT under Settings, Security and login, then create an app from Plugins with your server’s public HTTPS address ending in /mcp. Refresh it after every change to your tools and start a new chat to test.

What is the difference between a ChatGPT app and a custom GPT?

An app is an MCP server that adds tools and optional UI to any chat. A custom GPT is a configured assistant with instructions, files and actions that you open by name. OpenAI is retiring custom GPTs in favour of plugins, and custom actions have to be rebuilt as an app or MCP server.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.