chatgpt-app-builder

DEPRECATED: Use mcp-app-builder instead. Build ChatGPT apps with interactive widgets and zero-config React development. This skill is deprecated; migrate to mcp-app-builder for continued support and updates Enables collaborative UI between human users and LLM through shared interactive widgets Provides server handlers, React widget scaffolding, state management, and built-in hooks for tool calls and follow-up messages Covers display modes (inline, fullscreen, picture-in-picture), theming, CSP configuration, and advanced widget patterns

INSTALLATION
npx skills add https://github.com/mcp-use/mcp-use --skill chatgpt-app-builder
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

IMPORTANT: How to Use This Skill

This file provides a NAVIGATION GUIDE ONLY. Before implementing any MCP server features, you MUST:

  • Read this overview to understand which reference files are relevant
  • ALWAYS read the specific reference file(s) for the features you're implementing
  • Apply the detailed patterns from those files to your implementation

Do NOT rely solely on the quick reference examples in this file - they are minimal examples only. The reference files contain critical best practices, security considerations, and advanced patterns.

MCP Server Best Practices

Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and widgets using mcp-use.

⚠️ FIRST: New Project or Existing Project?

Before doing anything else, determine whether you are inside an existing mcp-use project.

Detection: Check the workspace for a package.json that lists "mcp-use" as a dependency, OR any .ts file that imports from "mcp-use/server".

├─ mcp-use project FOUND → Do NOT scaffold. You are already in a project.

│  └─ Skip to "Quick Navigation" below to add features.

│

├─ NO mcp-use project (empty dir, unrelated project, or greenfield)

│  └─ Scaffold first with npx create-mcp-use-app, then add features.

│     See "Scaffolding a New Project" below.

│

└─ Inside an UNRELATED project (e.g. Next.js app) and user wants an MCP server

   └─ Ask the user where to create it, then scaffold in that directory.

      Do NOT scaffold inside an existing unrelated project root.

**NEVER manually create MCPServer boilerplate, package.json, or project structure by hand.** The CLI sets up TypeScript config, dev scripts, inspector integration, hot reload, and widget compilation that are difficult to replicate manually.

Scaffolding a New Project

npx create-mcp-use-app my-server

cd my-server

npm run dev

For full scaffolding details and CLI flags, see quickstart.md.

Quick Navigation

Choose your path based on what you're building:

🚀 Foundations

When: ALWAYS read these first when starting MCP work in a new conversation. Reference later for architecture/concept clarification.

  • concepts.md - MCP primitives (Tool, Resource, Prompt, Widget) and when to use each
  • architecture.md - Server structure (Hono-based), middleware system, server.use() vs server.app
  • deployment.md - Deploying to Manufact Cloud, self-hosting, Docker, managing deployments

Load these before diving into tools/resources/widgets sections.

🔐 Adding Authentication?

When: Protecting your server with OAuth (Auth0, Better Auth, Clerk, WorkOS, Supabase, Keycloak, or any other provider)

-

overview.md

  • When: First time adding auth, understanding ctx.auth, or choosing a provider / integration mode
  • Covers: Remote auth vs OAuth proxy, oauth config, ctx.auth shape, provider comparison, common mistakes

-

auth0.md

  • When: Using Auth0 — DCR (Early Access) or a standard Regular Web App via oauthProxy
  • Covers: Setup for both modes, extraAuthorizeParams.audience, permissions via rfc9068_profile_authz

-

better-auth.md

  • When: Using Better Auth with the @better-auth/oauth-provider plugin (self-hosted OAuth 2.1)
  • Covers: oauthBetterAuthProvider, auth URL / metadata routes, login and consent flows

-

clerk.md

  • When: Using Clerk (DCR-based OAuth)
  • Covers: oauthClerkProvider, enabling DCR, Frontend API URL, organization context

-

workos.md

  • When: Using WorkOS AuthKit (DCR only)
  • Covers: Setup, env vars, roles/permissions, multi-tenant org filtering, WorkOS API calls

-

supabase.md

  • When: Using Supabase's OAuth 2.1 server
  • Covers: Setup, publishable keys, ES256 vs HS256, hosting the consent UI, RLS-aware SDK calls

-

keycloak.md

  • When: Using Keycloak via native DCR
  • Covers: DCR trusted hosts + web origins, audience enforcement, realm vs resource roles, userinfo

-

custom.md

  • When: Any other provider — DCR-capable via oauthCustomProvider, or pre-registered (Google, GitHub, Okta, Azure AD) via oauthProxy
  • Covers: oauthCustomProvider, oauthProxy + jwksVerifier, provider examples, opaque-token verification

🔧 Building Server Backend (No UI)?

When: Implementing MCP features (actions, data, templates). Read the specific file for the primitive you're building.

-

tools.md

  • When: Creating backend actions the AI can call (send-email, fetch-data, create-user)
  • Covers: Tool definition, schemas, annotations, context, error handling

-

resources.md

  • When: Exposing read-only data clients can fetch (config, user profiles, documentation)
  • Covers: Static resources, dynamic resources, parameterized resource templates, URI completion

-

prompts.md

  • When: Creating reusable message templates for AI interactions (code-review, summarize)
  • Covers: Prompt definition, parameterization, argument completion, prompt best practices

-

response-helpers.md

  • When: Formatting responses from tools/resources (text, JSON, markdown, images, errors)
  • Covers: text(), object(), markdown(), image(), error(), mix()

-

proxy.md

  • When: Composing multiple MCP servers into one unified aggregator server
  • Covers: server.proxy(), config API, explicit sessions, sampling routing

-

architecture.md

  • When: Adding cross-cutting logic (logging, auth checks, rate limiting, tool filtering) that spans multiple tools/resources
  • Covers: server.use('mcp:...') middleware, MiddlewareContext (method, params, auth, state), pattern matching, HTTP vs MCP middleware

🎨 Building Visual Widgets (Interactive UI)?

When: Creating React-based visual interfaces for browsing, comparing, or selecting data

-

basics.md

  • When: Creating your first widget or adding UI to an existing tool
  • Covers: Widget setup, useWidget() hook, isPending checks, props handling

-

state.md

  • When: Managing UI state (selections, filters, tabs) within widgets
  • Covers: useState, setState, state persistence, when to use tool vs widget state

-

interactivity.md

  • When: Adding buttons, forms, or calling tools from within widgets
  • Covers: useCallTool(), form handling, action buttons, optimistic updates

-

ui-guidelines.md

  • When: Styling widgets to support themes, responsive layouts, or accessibility
  • Covers: useWidgetTheme(), light/dark mode, autoSize, layout patterns, CSS best practices

-

advanced.md

  • When: Building complex widgets with async data, error boundaries, or performance optimizations
  • Covers: Loading states, error handling, memoization, code splitting

-

model-context.md

  • When: Keeping the AI model aware of what the user is currently seeing (active tab, hovered item, selected product) without requiring tool calls
  • Covers: <ModelContext> component, modelContext.set/remove imperative API, nesting, tree serialization, lifecycle rules

-

files.md

  • When: Uploading or downloading files from within a widget (ChatGPT Apps SDK only)
  • Covers: useFiles() hook, isSupported guard, model visibility (modelVisible), storing fileId, temporary download URLs

📚 Need Complete Examples?

When: You want to see full implementations of common use cases

  • End-to-end examples: weather app, todo list, recipe browser
  • Shows: Server code + widget code + best practices in context

🔁 Testing from the Terminal (Agent Feedback Loops)

When: You want to verify a tool or widget without the inspector UI — the canonical flow for AI agents iterating on MCP servers.

-

**mcp-use client** — drives MCP servers from the terminal. Auto-runs OAuth on 401, persists saved servers under a short name, and one-shot subcommands exit cleanly so they're safe to spawn from harnesses.

npx mcp-use client connect dev http://localhost:3000/mcp

npx mcp-use client dev tools list

npx mcp-use client dev tools call get-weather city=Tokyo --screenshot

Every per-server command takes the saved name as its first positional arg (mcp-use client <name> <scope> <action>) — there is no "active session". Args use key=value (with key:='<json>' for nested values) or a single JSON object. When a tool renders a widget, pass --screenshot to also save a PNG (./<view>-<timestamp>.png by default, or override with --screenshot-output <path>).

-

**mcp-use client screenshot** — headless render of a widget tool to a PNG. Use this when you want to visually verify a widget change without opening the inspector, especially in loops where you call a tool, screenshot, eyeball the output, and edit. Two forms:

# Saved-server form — reuses the auth from `mcp-use client connect`

npx mcp-use client dev screenshot --tool get-weather city=Tokyo \

  --width 800 --height 600 --theme light \

  --output ./weather.png

# Ad-hoc form — connect inline (use -H for headers on authenticated servers)

npx mcp-use client screenshot --mcp http://localhost:3000/mcp \

  --tool get-weather city=Tokyo

Add --device-scale-factor 2 for Retina output, or --cdp-url <ws> plus --inspector <publicly-reachable-url> to drive a remote Chromium (e.g. Notte) from a sandbox without a local Chrome install.

Both commands are documented in full at docs/typescript/client/cli.

Decision Tree

What do you need?

├─ New project from scratch

│  └─> quickstart.md (scaffolding + setup)

│

├─ OAuth / user authentication

│  └─> authentication/overview.md → provider-specific guide

│

├─ Simple backend action (no UI)

│  └─> Use Tool: server/tools.md

│

├─ Read-only data for clients

│  └─> Use Resource: server/resources.md

│

├─ Reusable prompt template

│  └─> Use Prompt: server/prompts.md

│

├─ Cross-cutting logic (logging, auth checks, rate limiting, tool filtering)

│  └─> Use Middleware: architecture.md#mcp-middleware

│

├─ Visual/interactive UI

│  └─> Use Widget: widgets/basics.md

│

├─ Keep model aware of what user is seeing in widget

│  └─> widgets/model-context.md

├─ Upload/download files in a widget

│  └─> widgets/files.md (ChatGPT Apps SDK only)

│

├─ Verify a tool or widget from the terminal (agent feedback loop)

│  └─> See "Testing from the Terminal" above — `mcp-use client` for tool runs,

│      `mcp-use client <server> screenshot --tool <tool>` for headless widget PNGs

│

└─ Deploy to production

   └─> deployment.md (cloud deploy, self-hosting, Docker)

Core Principles

  • Tools for actions - Backend operations with input/output
  • Resources for data - Read-only data clients can fetch
  • Prompts for templates - Reusable message templates
  • Widgets for UI - Visual interfaces when helpful
  • Mock data first - Prototype quickly, connect APIs later

❌ Common Mistakes

Avoid these anti-patterns found in production MCP servers:

Tool Definition

  • ❌ Returning raw objects instead of using response helpers
  • ✅ Use text(), object(), widget(), error() helpers
  • ❌ Skipping Zod schema .describe() on every field
  • ✅ Add descriptions to all schema fields for better AI understanding
  • ❌ No input validation or sanitization
  • ✅ Validate inputs with Zod, sanitize user-provided data
  • ❌ Throwing errors instead of returning error() helper
  • ✅ Use error("message") for graceful error responses

Widget Development

  • ❌ Accessing props without checking isPending
  • ✅ Always check if (isPending) return <Loading/>
  • ❌ Widget handles server state (filters, selections)
  • ✅ Widgets manage their own UI state with useState
  • ❌ Missing McpUseProvider wrapper or autoSize
  • ✅ Wrap root component: <McpUseProvider autoSize>
  • ❌ Inline styles without theme awareness
  • ✅ Use useWidgetTheme() for light/dark mode support

Security &#x26; Production

  • ❌ Hardcoded API keys or secrets in code
  • ✅ Use process.env.API_KEY, document in .env.example
  • ❌ No error handling in tool handlers
  • ✅ Wrap in try/catch, return error() on failure
  • ❌ Expensive operations without caching
  • ✅ Cache API calls, computations with TTL
  • ❌ Missing CORS configuration
  • ✅ Configure CORS for production deployments

🔒 Golden Rules

Opinionated architectural guidelines:

1. One Tool = One Capability

Split broad actions into focused tools:

  • manage-users (too vague)
  • create-user, delete-user, list-users

2. Return Complete Data Upfront

Tool calls are expensive. Avoid lazy-loading:

  • list-products + get-product-details (2 calls)
  • list-products returns full data including details

3. Widgets Own Their State

UI state lives in the widget, not in separate tools:

  • select-item tool, set-filter tool
  • ✅ Widget manages with useState or setState

4. exposeAsTool Defaults to false

Widgets are registered as resources only by default. Use a custom tool (recommended) or set exposeAsTool: true to expose a widget to the model:

// ✅ ALL 4 STEPS REQUIRED for proper type inference:

// Step 1: Define schema separately

const propsSchema = z.object({

  title: z.string(),

  items: z.array(z.string())

});

// Step 2: Reference schema variable in metadata

export const widgetMetadata: WidgetMetadata = {

  description: "...",

  props: propsSchema,  // ← NOT inline z.object()

  exposeAsTool: false

};

// Step 3: Infer Props type from schema variable

type Props = z.infer<typeof propsSchema>;

// Step 4: Use typed Props with useWidget

export default function MyWidget() {

  const { props, isPending } = useWidget<Props>();  // ← Add <Props>

  // ...

}

⚠️ Common mistake: Only doing steps 1-2 but skipping 3-4 (loses type safety)

5. Validate at Boundaries Only

  • Trust internal code and framework guarantees
  • Validate user input, external API responses
  • Don't add error handling for scenarios that can't happen

6. Prefer Widgets for Browsing/Comparing

When in doubt, add a widget. Visual UI improves:

  • Browsing multiple items
  • Comparing data side-by-side
  • Interactive selection workflows

Quick Reference

Minimal Server

import { MCPServer, text } from "mcp-use/server";

import { z } from "zod";

const server = new MCPServer({

  name: "my-server",

  title: "My Server",

  version: "1.0.0"

});

server.tool(

  {

    name: "greet",

    description: "Greet a user",

    schema: z.object({ name: z.string().describe("User's name") })

  },

  async ({ name }) => text("Hello " + name + "!"),

);

server.listen();

Response Helpers

Helper

Use When

Example

text()

Simple string response

text("Success!")

object()

Structured data

object({ status: "ok" })

markdown()

Formatted text

markdown("# Title\nContent")

widget()

Visual UI

widget({ props: {...}, output: text(...) })

mix()

Multiple contents

mix(text("Hi"), image(url))

error()

Error responses

error("Failed to fetch data")

resource()

Embed resource refs

resource("docs://guide", "text/markdown")

Server methods:

  • server.tool() - Define executable tool
  • server.resource() - Define static/dynamic resource
  • server.resourceTemplate() - Define parameterized resource
  • server.prompt() - Define prompt template
  • server.proxy() - Compose/Proxy multiple MCP servers
  • server.uiResource() - Define widget resource
  • server.listen() - Start server
  • server.use('mcp:tools/call', fn) - MCP middleware (tools, resources, prompts, list ops)
  • server.use('mcp:*', fn) - Catch-all MCP middleware
  • server.use(fn) - HTTP middleware (Hono)
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card