RORK LABJP
PRICE — Rork Max spans $200 to $1,800 per month, with the upper tiers aimed at heavier builders and teamsFREE — The free tier lands at roughly five prompts per week, enough to try it but not to build on continuouslySHIP — App Store publishing is automated through builds, certificates, and submission, so you can ship an iOS app without a Mac or XcodeSIM — A browser-streamed simulator lets you watch your app run in a real Apple environment from your own browserNATIVE — It reaches HealthKit, ARKit and LiDAR, NFC, Dynamic Island, and Metal 3D — territory React Native cannot touchFUNDING — Rork raised a $15M seed led by Left Lane Capital, announced April 9, 2026, and acquired app builder PaperlinePRICE — Rork Max spans $200 to $1,800 per month, with the upper tiers aimed at heavier builders and teamsFREE — The free tier lands at roughly five prompts per week, enough to try it but not to build on continuouslySHIP — App Store publishing is automated through builds, certificates, and submission, so you can ship an iOS app without a Mac or XcodeSIM — A browser-streamed simulator lets you watch your app run in a real Apple environment from your own browserNATIVE — It reaches HealthKit, ARKit and LiDAR, NFC, Dynamic Island, and Metal 3D — territory React Native cannot touchFUNDING — Rork raised a $15M seed led by Left Lane Capital, announced April 9, 2026, and acquired app builder Paperline
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.

Rork532OpenAI5Apps 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",
    {
      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-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 →