Claude
Skills
Sign in
Back

OpenAI Apps MCP

Included with Lifetime
$97 forever

Build ChatGPT apps with MCP servers on Cloudflare Workers. Extend ChatGPT with custom tools and interactive widgets (HTML/JS UI). Use when: developing ChatGPT extensions, implementing MCP servers, or troubleshooting CORS blocking (allow chatgpt.com), widget 404s (missing ui://widget/), wrong MIME type (text/html+skybridge), or ASSETS binding undefined.

Designscriptsassets

What this skill does


# Building OpenAI Apps with Stateless MCP Servers

**Status**: Production Ready
**Last Updated**: 2025-11-17
**Dependencies**: `cloudflare-worker-base`, `hono-routing` (optional, helpful for routing patterns)
**Latest Versions**: @modelcontextprotocol/[email protected], [email protected], [email protected]

---

## Overview

This skill provides production-tested patterns for building **OpenAI Apps** - applications that extend ChatGPT's functionality through the Model Context Protocol (MCP). Focus on **stateless MCP servers** using Cloudflare Workers, which covers 80% of OpenAI Apps use cases.

### What Are OpenAI Apps?

OpenAI Apps are extensions that integrate into the ChatGPT interface, allowing users to:
- Access third-party services directly in conversations
- Display interactive widgets (maps, carousels, lists, etc.)
- Execute tools that return structured UI components
- Enhance ChatGPT with domain-specific capabilities

### Architecture

```
ChatGPT User
    ↓
ChatGPT (discovers and invokes tools)
    ↓
MCP Server (your Cloudflare Worker)
    ├── Tool handlers (business logic)
    ├── Widget resources (HTML/JS UI)
    └── OpenAI metadata (output templates)
```

### Key Components

1. **MCP Server** - HTTP endpoint exposing tools via Model Context Protocol
2. **Tool Handlers** - Functions that process inputs and return results
3. **Widget Resources** - HTML pages that render in ChatGPT's iframe
4. **OpenAI Metadata** - Special annotations for widget routing and display

---

## Quick Start (10 Minutes)

### 1. Scaffold Project

```bash
npm create cloudflare@latest my-openai-app -- --type hello-world --ts --git --deploy false
cd my-openai-app

# Install dependencies
npm install @modelcontextprotocol/[email protected] [email protected] [email protected]
npm install -D @cloudflare/[email protected] [email protected]
```

**Why this matters:**
- `@modelcontextprotocol/sdk` is the official MCP protocol implementation
- `hono` provides lightweight routing perfect for API endpoints
- Vite + CloudFlare plugin enable building and serving widgets

### 2. Configure wrangler.jsonc

```jsonc
{
  "name": "my-openai-app",
  "main": "dist/index.js",
  "compatibility_date": "2025-10-08",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "dist/client",
    "binding": "ASSETS"
  },
  "observability": {
    "enabled": true
  }
}
```

**CRITICAL:**
- `nodejs_compat` flag is required for MCP SDK
- `assets.binding: "ASSETS"` must match TypeScript binding name
- `assets.directory` must match Vite build output

### 3. Create MCP Server

```typescript
// src/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';

type Bindings = {
  ASSETS: Fetcher;
};

const app = new Hono<{ Bindings: Bindings }>();

// CORS - must allow ChatGPT
app.use('/mcp/*', cors({
  origin: 'https://chatgpt.com',
  credentials: true,
  allowMethods: ['GET', 'POST', 'OPTIONS'],
  allowHeaders: ['Content-Type', 'Authorization']
}));

// Create MCP server
const mcpServer = new Server(
  { name: 'my-openai-app', version: '1.0.0' },
  { capabilities: { tools: {}, resources: {} } }
);

// Register a simple tool
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'hello_world',
    description: 'Use this when the user wants to see a hello world message',
    inputSchema: {
      type: 'object',
      properties: {
        name: { type: 'string', description: 'Name to greet' }
      },
      required: ['name']
    },
    annotations: {
      openai: {
        outputTemplate: 'ui://widget/hello.html'
      }
    }
  }]
}));

mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'hello_world') {
    const { name } = request.params.arguments as { name: string };
    return {
      content: [{ type: 'text', text: `Hello, ${name}!` }],
      _meta: { initialData: { name } }
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

// MCP endpoint
app.post('/mcp', async (c) => {
  const body = await c.req.json();
  const response = await mcpServer.handleRequest(body);
  return c.json(response);
});

// Serve widgets
app.get('/widgets/*', async (c) => c.env.ASSETS.fetch(c.req.raw));

export default app;
```

---

## The 5-Step Setup Process

### Step 1: Project Scaffolding

Use Cloudflare's official scaffolding:

```bash
npm create cloudflare@latest my-openai-app -- --type hello-world --ts --git --deploy false
```

**Key Points:**
- Creates Workers project with TypeScript
- Includes wrangler.jsonc
- Initializes git repository

### Step 2: Install Dependencies

```bash
npm install @modelcontextprotocol/[email protected] [email protected] [email protected]
npm install -D @cloudflare/[email protected] [email protected]
```

**What each package does:**
- `@modelcontextprotocol/sdk` - Official MCP protocol (Anthropic)
- `hono` - Fast, lightweight routing framework
- `zod` - Runtime type validation for tool inputs
- `@cloudflare/vite-plugin` - Build tool for Workers + static assets
- `vite` - Frontend build tool

### Step 3: Configure Build System

Create `vite.config.ts`:

```typescript
import { defineConfig } from 'vite';
import { cloudflareDevProxyVitePlugin as cloudflare } from '@cloudflare/vite-plugin';

export default defineConfig({
  plugins: [
    cloudflare({
      configPath: 'wrangler.jsonc',
      persist: { path: '.wrangler/state' }
    })
  ],
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: {
        worker: './src/index.ts'
      },
      output: {
        entryFileNames: (chunkInfo) => {
          if (chunkInfo.name === 'worker') return 'index.js';
          return 'client/[name]-[hash].js';
        }
      }
    }
  }
});
```

**Why this matters:**
- Builds both worker code and static assets
- Proper output structure for Workers + ASSETS binding
- Content hashing for cache busting

### Step 4: Create Widget HTML

```bash
mkdir -p src/widgets
```

Create `src/widgets/hello.html`:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello Widget</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      background: var(--background);
      color: var(--foreground);
    }
    .greeting {
      font-size: 24px;
      font-weight: 600;
    }
  </style>
</head>
<body>
  <div class="greeting" id="greeting">Loading...</div>

  <script>
    // Access initial data from tool handler
    if (window.openai && window.openai.getInitialData) {
      const data = window.openai.getInitialData();
      document.getElementById('greeting').textContent = `Hello, ${data.name}! 👋`;
    }
  </script>
</body>
</html>
```

**What to avoid:**
- Don't use third-party CDN scripts (CSP may block)
- Don't use custom fonts (use system fonts)
- Don't make external API calls without CORS

### Step 5: Deploy and Test

```bash
# Build
npm run build

# Deploy to Cloudflare
npx wrangler deploy

# Test with MCP Inspector
npx @modelcontextprotocol/inspector https://my-openai-app.workers.dev/mcp
```

---

## Critical Rules

### Always Do

✅ Set CORS to allow `https://chatgpt.com`
✅ Use resource URI pattern `ui://widget/` for widgets
✅ Set MIME type to `text/html+skybridge` for HTML resources
✅ Include `_meta.initialData` in tool responses for widget initialization
✅ Use action-oriented tool descriptions ("Use this when...")
✅ Validate tool inputs with Zod schemas
✅ Test with MCP Inspector before deploying to ChatGPT

### Never Do

❌ Use custom MIME types (must be `text/html+skybridge`)
❌ Forget CORS configuration (ChatGPT won't connect)
❌ Use resource URIs without `ui://widget/` prefix
❌ Bundle widgets in worker code (use ASSETS binding)
❌ Skip input validation (tools receive untrusted 
Files: 10
Size: 40.7 KB
Complexity: 82/100
Category: Design

Related in Design