RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/AI Models
AI Models/2026-04-27Advanced

Rork × OpenAI Apps SDK: Ship a ChatGPT-Embedded App From Your Rork Codebase

A complete implementation guide for distributing your Rork app's business logic as a ChatGPT-embedded mini app. Covers MCP server design, UI Resources, shared OAuth, Universal Link conversation handoffs, and pricing parity from a real production lens.

Rork515OpenAI5Apps SDKChatGPTMCP3Distribution2

Premium Article

A new distribution channel quietly opened in late 2025: apps that live inside ChatGPT. For years the App Store and Google Play were the two front doors to mobile, and every other user had to be reached through SEO or paid ads. With the OpenAI Apps SDK, you can now drop your product straight into the conversation surface that hundreds of millions of people use daily. The harder questions are the implementation ones: can you reuse the Rork codebase you've already shipped, do auth and billing become a duplicated nightmare, and where exactly does production tend to break? Public docs are accurate but thin on these realities.

This guide walks through the design choices I make when adding an Apps SDK distribution layer on top of an existing Rork app, with code you can paste into a Cloudflare Workers project. The focus is on the parts that hurt later if you skip them — keeping a single user identity across ChatGPT and the mobile app, handing a conversation off mid-flow into the mobile UI, and keeping pricing coherent even though the payment rails are different. I'll also share the specific failures I ran into during my own integration, because the details that don't make it into official docs are usually where you lose a weekend.

Before we start, a quick framing point. Apps SDK is not a replacement for your mobile app. It's a complementary surface that's particularly strong at one thing: low-friction, conversational entry into your product's core actions. The mobile app remains where users do extended editing, deep configuration, and viewing rich media. Designing for that division of labor up front saves you from building two competing products in one codebase.

Designing a Shared Foundation Between Your ChatGPT App and Your Rork App

The first decision is whether to put the Apps SDK server in the same repository as your Rork backend or in a separate project. I prefer one repo with a /mcp route mounted alongside the existing tRPC handlers, because keeping users, billing state, and analytics in one place pays back in operational simplicity. Splitting them feels cleaner on day one, but a few months in you'll be writing sync jobs to keep two databases consistent, and that's wasted complexity.

Concretely, I structure the codebase so that domain logic (creating tasks, searching, updating) lives in a core package, and two thin adapters consume it: one for the mobile app, one for ChatGPT.

packages/
  core/                 # pure domain logic
  mobile-api/           # REST/tRPC endpoints called by the Rork app
  apps-sdk/             # MCP server called by ChatGPT
  shared/               # auth, billing, user-id types shared by both

The big benefit of this layout is that release cycles diverge cleanly. The Apps SDK side ships when you push to Cloudflare; the mobile side ships when Apple finishes reviewing your build. If those two were tangled in one runtime, your ChatGPT iteration speed would drop to App Store cadence — which is the wrong direction. Conversely, a domain logic change made for the mobile app immediately becomes available to the ChatGPT integration, so you don't have to re-test the same business rule twice.

There's one piece of advice I'd give myself if I were starting over: define the domain types in core first, before you write any tool definitions or screens. The schemas you choose here become the boundary contract for both adapters, and changing them once both adapters are live is painful. A Task type with an id, title, dueDate, priority, and status enum is enough to start; resist the urge to add fields you might "eventually" need.

Standing Up the MCP Server on Cloudflare Workers in 30 Minutes

The Apps SDK speaks the Model Context Protocol (MCP). ChatGPT acts as the MCP client and calls tools you expose. A minimum Cloudflare Workers setup is shorter than you'd expect.

// apps-sdk/src/server.ts
import { Hono } from "hono";
import { McpServer } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { createTask, listTasks } from "@core/tasks";
 
type Env = { DB: D1Database; OPENAI_APPS_SHARED_SECRET: string };
 
const app = new Hono<{ Bindings: Env }>();
 
app.post("/mcp", async (c) => {
  // Verify OpenAI's signature on every request
  const sig = c.req.header("x-openai-apps-signature");
  if (!verifySignature(sig, await c.req.text(), c.env.OPENAI_APPS_SHARED_SECRET)) {
    return c.json({ error: "invalid_signature" }, 401);
  }
 
  const server = new McpServer({ name: "task-app", version: "1.0.0" });
 
  server.tool(
    "create_task",
    {
      title: z.string().describe("Task title"),
      due_date: z.string().optional().describe("YYYY-MM-DD"),
    },
    async ({ title, due_date }, ctx) => {
      const userId = await resolveUserId(ctx, c.env.DB);
      const task = await createTask(c.env.DB, { userId, title, dueDate: due_date });
      return {
        content: [
          { type: "resource", resource: { uri: `ui://task-card/${task.id}` } },
        ],
        structuredContent: { taskId: task.id, title: task.title },
      };
    }
  );
 
  server.tool("list_tasks", {}, async (_, ctx) => {
    const userId = await resolveUserId(ctx, c.env.DB);
    const tasks = await listTasks(c.env.DB, userId);
    return {
      content: [
        { type: "resource", resource: { uri: `ui://task-list?count=${tasks.length}` } },
      ],
      structuredContent: { tasks },
    };
  });
 
  const transport = new StreamableHTTPServerTransport({});
  await server.connect(transport);
  return transport.handle(c.req.raw);
});
 
export default app;

Two things matter most here. First, ship signature verification on day one. OpenAI sends an HMAC in x-openai-apps-signature; store the shared secret in Cloudflare Secrets and verify every request. If you defer this, your MCP endpoint becomes a public RPC for anyone who finds the URL — and someone will, because the manifest you submit lists it.

Second, return both structuredContent and content from every tool. The model uses the structured payload for reasoning later in the conversation; the content array is what users see. I dropped structuredContent early on and watched ChatGPT lose track of which task the user had just created — every follow-up question forced a redundant re-fetch. Returning both from the start avoids that whole class of bug.

A third subtler point: the descriptions you put in the z.string().describe(...) calls are read by the model when deciding whether to call your tool. If your description is "Task title" the model often invokes the tool with vague placeholder text. If it's "The exact phrase the user said they want to remember, copied verbatim from the conversation," the tool gets meaningful arguments. Tool descriptions are prompt engineering disguised as type definitions — treat them seriously.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
If you've been wanting to ship a ChatGPT-embedded experience but didn't know where to begin, you can now start today by reusing your Rork codebase end-to-end
You'll walk away with working code for the MCP server, UI Resources, shared OAuth, and Universal Link handoff — the same patterns I run in production to keep one user identity across ChatGPT and the mobile app
You'll open a third distribution channel beyond App Store and Google Play, exposing your product directly to the hundreds of millions of people who live inside ChatGPT every day
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

AI Models2026-04-02
Rork × MCP (Model Context Protocol) Complete Implementation Guide — Building Next-Gen Mobile Apps with AI Tool Integration
A complete guide to integrating MCP (Model Context Protocol) into your Rork app. Covers architecture design, streaming, authentication, and production operations with working code examples.
AI Models2026-07-17
The Three-Minute Video That Kept Failing — Moving Rork's Gemini Uploads Off the Worker
How I rerouted video uploads to the Gemini Files API from a Cloudflare Workers relay to a direct device-to-Google path — why the 128MB isolate limit breaks the relay, how to hand out a resumable upload URL without leaking your API key, and how resolution and frame rate actually decide the bill.
AI Models2026-07-05
When Your Finance App's AI Keeps Dumping Everything Into 'Other' — Field Notes on Catching Silent Classification Drift
An AI expense classifier in a Rork finance app can be accurate at launch and quietly decay over months until the monthly advice goes wrong. Here is how I instrumented confidence and category distribution to get ahead of the drift, with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →