+ );
+}
+
+const root = createRoot(document.getElementById("root")!);
+root.render();
+```
+
+:::note
+This is a simplified version of the UI. For the complete implementation with player slots, better styling, and game state management, check out the [full example on GitHub](https://github.com/cloudflare/agents/tree/main/openai-sdk/chess-app/src/app.tsx).
+:::
+
+## 7. Build and deploy
+
+1. Build your React UI:
+
+```sh
+npm run build
+```
+
+This compiles your React app into a single HTML file in the `dist` directory.
+
+2. Deploy to Cloudflare:
+
+```sh
+npx wrangler deploy
+```
+
+After deployment, you will see your app URL:
+
+```
+https://my-chess-app.YOUR_SUBDOMAIN.workers.dev
+```
+
+## 8. Connect to ChatGPT
+
+Now connect your deployed app to ChatGPT:
+
+1. Open [ChatGPT](https://chat.openai.com/).
+2. Go to **Settings** > **Apps & Connectors** > **Create**
+3. Give your app a **name**, and optionally a **description** and **icon**.
+4. Enter your MCP endpoint: `https://my-chess-app.YOUR_SUBDOMAIN.workers.dev/mcp`.
+5. Select **"No authentication"**.
+6. Select **"Create"**.
+
+## 9. Play chess in ChatGPT
+
+Try it out:
+
+1. In your ChatGPT conversation, type: "Let's play chess".
+2. ChatGPT will call the `playChess` tool and render your interactive chess widget.
+3. Select **"Start a new game"** to create a game.
+4. Share the game ID with a friend who can join via their own ChatGPT conversation.
+5. Make moves by dragging pieces on the board.
+6. Select **"Ask for help"** to get strategic advice from ChatGPT
+
+
+:::note
+You might need to manually select the connector in the prompt box the first time you use it. Select **"+"** > **"More"** > **[App name]**.
+:::
+
+## Key concepts
+
+### MCP Server
+
+The Model Context Protocol (MCP) server defines tools and resources that ChatGPT can access:
+
+```ts
+const server = new McpServer({ name: "Chess", version: "v1.0.0" });
+
+// Register a UI resource that ChatGPT can render
+server.registerResource(
+ "chess",
+ "ui://widget/index.html",
+ {},
+ async (_uri, extra) => {
+ return {
+ contents: [
+ {
+ uri: "ui://widget/index.html",
+ mimeType: "text/html+skybridge",
+ text: await getWidgetHtml(extra.requestInfo?.headers.host as string)
+ }
+ ]
+ };
+ }
+);
+
+// Register a tool that ChatGPT can call to render the UI
+server.registerTool(
+ "playChess",
+ {
+ title: "Renders a chess game menu, ready to start or join a game.",
+ annotations: { readOnlyHint: true },
+ _meta: {
+ "openai/outputTemplate": "ui://widget/index.html",
+ "openai/toolInvocation/invoking": "Opening chess widget",
+ "openai/toolInvocation/invoked": "Chess widget opened"
+ }
+ },
+ async (_, _extra) => {
+ return {
+ content: [{ type: "text", text: "Successfully rendered chess game menu" }]
+ };
+ }
+);
+```
+
+### Game Engine with Agents
+
+The `ChessGame` class extends `Agent` to create a stateful game engine:
+
+```tsx
+export class ChessGame extends Agent {
+ initialState: State = {
+ board: new Chess().fen(),
+ players: {},
+ status: "waiting"
+ };
+
+ game = new Chess();
+
+ constructor(
+ ctx: DurableObjectState,
+ public env: Env
+ ) {
+ super(ctx, env);
+ this.game.load(this.state.board);
+ }
+```
+
+Each game gets its own Agent instance, enabling:
+- **Isolated state** per game
+- **Real-time synchronization** across players
+- **Persistent storage** that survives worker restarts
+
+### Callable methods
+
+Use the `@callable()` decorator to expose methods that clients can invoke:
+
+```ts
+@callable()
+join(params: { playerId: string; preferred?: Color | "any" }) {
+ const { playerId, preferred = "any" } = params;
+ const { connection } = getCurrentAgent();
+ if (!connection) throw new Error("Not connected");
+
+ connection.setState({ playerId });
+ const s = this.state;
+
+ // Already seated? Return seat
+ const already = this.colorOf(playerId);
+ if (already) {
+ return { ok: true, role: already as Color, state: s };
+ }
+
+ // Choose a seat
+ const free: Color[] = (["w", "b"] as const).filter((c) => !s.players[c]);
+ if (free.length === 0) {
+ return { ok: true, role: "spectator" as const, state: s };
+ }
+
+ let seat: Color = free[0];
+ if (preferred === "w" && free.includes("w")) seat = "w";
+ if (preferred === "b" && free.includes("b")) seat = "b";
+
+ s.players[seat] = playerId;
+ s.status = s.players.w && s.players.b ? "active" : "waiting";
+ this.setState(s);
+ return { ok: true, role: seat, state: s };
+}
+```
+
+### React integration
+
+The `useAgent` hook connects your React app to the Durable Object:
+
+```tsx
+const { stub } = useAgent({
+ host,
+ name: gameId ?? "__lobby__",
+ agent: "chess",
+ onStateUpdate: (s) => {
+ gameRef.current.load(s.board);
+ setFen(s.board);
+ setServerState(s);
+ }
+});
+```
+
+Call methods on the agent:
+
+```tsx
+const res = await stub.join({ playerId, preferred: "any" });
+await stub.move({ from: "e2", to: "e4" });
+```
+
+### Bidirectional communication
+
+Your app can send messages to ChatGPT:
+
+```ts
+const handleHelpClick = () => {
+ window.openai?.sendFollowUpMessage?.({
+ prompt: `Help me with my chess game. I am playing as ${myColor} and the board is: ${fen}. Please only offer written advice as there are no tools for you to use.`
+ });
+};
+```
+
+This creates a new message in the ChatGPT conversation with context about the current game state.
+
+## Next steps
+
+Now that you have a working ChatGPT App, you can:
+
+- Add more tools: Expose additional capabilities and UIs through MCP tools and resources.
+- Enhance the UI: Build more sophisticated interfaces with React.
+
+## Related resources
+
+- [Agents documentation](/agents/api-reference/agents-api)
+- [Durable Objects documentation](/durable-objects/)
+- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
+- [OpenAI Apps SDK Reference](https://developers.openai.com/apps-sdk/)
\ No newline at end of file