RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
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.

Rork547OpenAI5Apps SDKChatGPTMCP4Distribution2

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",
    "Call this when the user mentions something they intend to do later. Do not use it to change an existing task — call update_task for that.",
    {
      title: z.string().describe("The user's own wording, copied verbatim. Do not summarize or rephrase."),
      due_date: z.string().optional().describe("Due date as YYYY-MM-DD. Omit it if the user never mentioned a date."),
    },
    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",
    "Fetch the user's current tasks. Always call this before updating or completing a task.",
    {},
    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.

Third, pass a description alongside the tool name — that's the second argument to server.tool() above. Leave it out and the only clue the model has is the identifier create_task. This is the single biggest lever on call accuracy, so it gets its own section next.

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 $15 for lifetime access
View Membership →

Related Articles

AI Models2026-08-07
Cutting MCP Tools Didn't Make Anything Lighter — 2,377 Bytes of Definitions vs 110,298 Bytes of Response
I wrote a minimal MCP server for a wallpaper catalog and measured the byte cost of tool definitions against the byte cost of responses. Here is which side actually matters, and the real reason to merge tools.
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.
📚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 →