Lesson 2 of 3 · Building ChatGPT Apps

Anatomy of a ChatGPT App

reading12 min

Every ChatGPT App is made of two layers: the MCP Server (mandatory) and the Web Widget (optional). Understanding how these layers communicate -- and what each is responsible for -- is the foundation for everything else in this course.

The MCP Server Layer

Your MCP server is the brain of your app. It defines:

  • Tools: Functions the model can call. Each tool has a name, description, input schema, and handler.
  • Resources: Data the model can read. Think of these as read-only endpoints -- configuration files, documentation, knowledge bases.
  • Prompts: Pre-written templates the model can use. Useful for complex workflows with specific output formats.

For most ChatGPT Apps, you will primarily use tools. Resources and prompts are useful for advanced scenarios.

The Single Endpoint

Unlike traditional REST APIs with multiple routes, an MCP server exposes a single endpoint: /mcp. All communication flows through this endpoint using JSON-RPC 2.0:

POST /mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "search_products",
    "arguments": {
      "query": "wireless headphones",
      "maxPrice": 100
    }
  },
  "id": 1
}
JSON-RPC 2.0

JSON-RPC is a lightweight remote procedure call protocol. The key concepts: every request has a method and params. Every response has a result or error. The id field correlates requests with responses. It is simpler than REST (no URL routing, no HTTP method semantics) and well-suited for tool-style interactions.

The MCP Apps Bridge

ChatGPT does not connect to your MCP server using the standard MCP transport (stdio or SSE). Instead, it uses the MCP Apps Bridge -- a server-sent events (SSE) transport optimized for the ChatGPT environment.

The bridge handles:

  • Connection management (keep-alive, reconnection)
  • Authentication token injection
  • Rate limiting and abuse prevention
  • Message routing between the model and your server

You do not need to implement the bridge yourself. The @modelcontextprotocol/sdk handles it when you configure the transport as sse.

The Web Widget Layer

The widget is an HTML page rendered in a sandboxed iframe inside the ChatGPT interface. It communicates with ChatGPT through the window.openai API -- a JavaScript bridge injected into the iframe.

The widget can:

  • Call your MCP tools through window.openai.callTool()
  • Send follow-up messages to the conversation through window.openai.sendFollowUpMessage()
  • Upload files through window.openai.uploadFile()
  • Request different display sizes through window.openai.requestDisplayMode()
  • Share state with the model through window.openai.setWidgetState()
The Widget Cannot Call Your Server Directly

This is the most common mistake new developers make. The widget iframe is sandboxed. It cannot make fetch calls to your server. All communication goes through the window.openai bridge, which routes tool calls through ChatGPT to your MCP server. This is a security boundary, not a bug.

Component Types

ChatGPT can render tool responses as rich UI components without any widget code. These components are defined by the _meta field in your tool response:

ComponentUse CaseBest For
ListOrdered collections with titles and descriptionsSearch results, task lists, playlists
MapGeographic data with markers and routesLocation search, delivery tracking, travel
AlbumImage galleries with captionsProduct photos, portfolio, media
CarouselHorizontal scrolling cardsProduct browsing, recommendations
ShopProduct cards with prices and buy buttonsE-commerce, marketplace
ChartData visualizationAnalytics, dashboards, reports

Using components, you can build a rich, interactive app without writing any frontend code. The model calls your tools, your tools return structured data with component hints, and ChatGPT renders them beautifully.

Example: A Product Search Tool Response

JSON
{
  "content": [
    {
      "type": "text",
      "text": "Found 3 wireless headphones under $100"
    }
  ],
  "_meta": {
    "component": "shop",
    "items": [
      {
        "title": "SoundPro X1",
        "description": "Active noise cancellation, 30hr battery",
        "price": { "amount": 79.99, "currency": "USD" },
        "image_url": "https://example.com/x1.jpg",
        "action_url": "https://example.com/buy/x1"
      }
    ]
  }
}

ChatGPT renders this as a styled product card with image, price, and a buy button -- no HTML or CSS required from you.

When You Need a Widget vs When You Don't

This is the most important architectural decision you will make:

Do

Start without a widget. Use MCP tools + component types. Build a widget only when you need interaction that components cannot express: custom forms, drag-and-drop, real-time updates, complex visualizations, or multi-step workflows.

Don't

Build a widget from day one because it feels more like 'real development.' Many successful ChatGPT Apps use only MCP tools with component responses. The widget adds complexity -- both in code and in the review process for App Store submission.

beginner

Architecture Decision:

  1. Take the app idea from the previous lesson
  2. List every piece of data the model would need to see (tool response content)
  3. For each data type, decide: can a built-in component (List, Map, Shop, Chart, Album, Carousel) render it well enough?
  4. List any interactions that cannot be expressed through components alone
  5. Make your decision: tools-only or tools + widget? Write one paragraph justifying the choice.
Concept Card