# How do you build an app inside ChatGPT with the Apps SDK?

Date: 2025-10-19 · Author: Robert Scales · https://guidetoartificialintelligence.com/en/learn/build-app-inside-chatgpt-apps-sdk/

**Answer:** You build an MCP server that offers one or more tools, register a small HTML component for ChatGPT to display, and link each tool to that component. Then you expose the server over HTTPS and add it as a connector in ChatGPT's developer mode to test it in a real conversation.

Apps in ChatGPT, introduced on October 6, let a conversation show something more than text: a playlist you can play, a map, a board of tasks. Users call an app by name or ChatGPT suggests one, and the app's interface appears inside the chat. The Apps SDK, the open-source toolkit for building these apps, is in preview, and developers can already test their own apps in ChatGPT's developer mode.

An app has two halves. The **server** holds your logic and data, and offers **tools**, named actions the model can call, such as "show the book club picks". The **component** (OpenAI also calls it a widget) is a small web page that ChatGPT displays in a sandboxed frame inside the conversation. The tool's result fills the component with data.

## A worked example: a book club app

This minimal app has one tool that returns three book picks and one component that lists them. It uses the official TypeScript SDK for MCP and `zod`, a library for describing input shapes. The structure follows OpenAI's server-setup guide.

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "book-club", version: "1.0.0" });

// 1. The component: plain HTML that reads the data ChatGPT injects.
const WIDGET_HTML = `
<ul id="list"></ul>
<script type="module">
  const output = (window.openai && window.openai.toolOutput) || {};
  const list = document.getElementById("list");
  for (const book of output.books || []) {
    const item = document.createElement("li");
    item.textContent = book.title + " by " + book.author;
    list.appendChild(item);
  }
</script>`.trim();

server.registerResource("book-list", "ui://widget/book-list.html", {}, async () => ({
  contents: [{
    uri: "ui://widget/book-list.html",
    mimeType: "text/html+skybridge",
    text: WIDGET_HTML,
  }],
}));

// 2. The tool: linked to the component through _meta.
server.registerTool(
  "list-books",
  {
    title: "Show book club picks",
    description: "Lists the book club's next three reading picks.",
    inputSchema: { month: z.string().optional() },
    annotations: { readOnlyHint: true },
    _meta: {
      "openai/outputTemplate": "ui://widget/book-list.html",
      "openai/toolInvocation/invoking": "Fetching the picks",
      "openai/toolInvocation/invoked": "Here are the picks",
    },
  },
  async () => {
    const books = [
      { title: "Frankenstein", author: "Mary Shelley" },
      { title: "Pride and Prejudice", author: "Jane Austen" },
      { title: "The Time Machine", author: "H. G. Wells" },
    ];
    return {
      structuredContent: { books },
      content: [{ type: "text", text: "Three picks are on the list." }],
    };
  }
);
```

Three details carry the whole design:

- The resource's **`mimeType` of `text/html+skybridge`** tells ChatGPT this is a component to render, and its `ui://` address is the component's ID.
- **`openai/outputTemplate`** connects the tool to that component.
- The result's **`structuredContent`** is what the component receives as `window.openai.toolOutput`. The model can read it too, and `content` is extra text for the model. A third field, `_meta`, goes to the component only, which is useful for data the model does not need.

The `readOnlyHint` annotation tells ChatGPT the tool only reads data and changes nothing.

## Running and testing it

The server still needs a transport, the part that carries messages over the network. The protocol allows either Server-Sent Events or streaming HTTP, and OpenAI's examples repository includes a complete Node server you can copy that wiring from. Then:

1. Start the server and open **MCP Inspector**, a testing tool, at `http://localhost:<port>/mcp`. List the tools and call `list-books`; the Inspector checks the response and renders the component.
2. ChatGPT only connects over HTTPS, so during development run a tunnel such as `ngrok http <port>` to get a public address.
3. In ChatGPT, enable developer mode and add the tunnel URL, ending in `/mcp`, under Settings > Connectors.
4. In a chat, add the app from the "More" options and ask something like "What is my book club reading next?"

> **Try it:** Change the tool so it accepts a `genre` input and returns different picks for "mystery" and "science fiction". Ask ChatGPT for each and check which value it passes.

## Before anyone else uses it

OpenAI's rules for apps ask for a clear privacy policy, content suitable for all audiences, and collecting only the data the app needs. Components also need a strict content security policy, a list of the outside domains the page may load from or connect to, before broad distribution. If the app needs to know who the user is, the guides cover adding OAuth 2.1 sign-in. OpenAI says submissions and an app directory will open later this year.

## How does it relate to MCP?

The Model Context Protocol, or MCP, is an open standard for connecting AI applications to tools and data. The Apps SDK is built on it: your app's server is an MCP server. It lists its tools with JSON Schema descriptions, answers tool calls, and returns results, exactly as it would for any MCP client.

What the Apps SDK adds are extensions for interfaces: the `text/html+skybridge` component resources and the `openai/...` keys inside `_meta`, which tell ChatGPT what to display and what status messages to show. OpenAI's documentation points out that not every MCP client renders components, so a well-built tool should still return useful `content` and `structuredContent` on its own. That way the same server can serve ChatGPT with a rich interface and other MCP clients with plain results.

## Sources

- Set up your server (Apps SDK documentation): https://developers.openai.com/apps-sdk/build/mcp-server
- Apps SDK Examples Gallery, README: https://github.com/openai/openai-apps-sdk-examples/blob/cf88017f6375ad86ed409831b43dbc924642526a/README.md
- Introducing apps in ChatGPT and the new Apps SDK: https://openai.com/index/introducing-apps-in-chatgpt/
