# Authentication
Source: https://docs.fastapps.org/auth/index
OAuth 2.1 authentication for FastApps widgets and tools with built-in JWT verification.
## Overview
Comming soon. \
You would be able to use this feature starting from Nov 10.
### Using Authentication in Widgets
Access user information in your widgets:
```python theme={null}
from fastapps import BaseWidget, auth_required, UserContext
@auth_required(scopes=["user"])
class ProtectedWidget(BaseWidget):
identifier = "protected"
title = "Protected Widget"
input_schema = ProtectedInput
async def execute(self, input_data, context, user: UserContext):
# Access authenticated user
return {
"user_id": user.subject,
"email": user.claims.get('email'),
"scopes": user.scopes
}
```
## Next Steps
Ready to add authentication to your widgets?
Configure OAuth at the server level
Protect specific widgets
Set up Auth0 or other providers
See real-world implementations
***
**Need help?** Check our [GitHub repository](https://github.com/DooiLabs/FastApps) or reach out to the community.
# All posts
Source: https://docs.fastapps.org/blog/index
Explorations, updates, and deep dives into MCP tooling and FastApps development.
Dive deep into the technical architecture of the OpenAI Apps SDK, exploring MCP servers, widgets, and the window\.openai bridge that powers ChatGPT apps.
# Inside the ChatGPT Apps SDK: How It Actually Works
Source: https://docs.fastapps.org/blog/inside-the-chatgpt-apps-sdk-how-it-actually-works
Dive deep into the technical architecture of the OpenAI Apps SDK, exploring MCP servers, widgets, and the window.openai bridge that powers ChatGPT apps.
Written by [**Zach Park**](https://www.linkedin.com/in/zachhere/), Co-founder of Dooilabs
🕒 8 min read • 🔗 navigator.clipboard.writeText('https://docs.fastapps.org/blog/inside-the-chatgpt-apps-sdk-how-it-actually-works')} style={{cursor: 'pointer', textDecoration: 'underline'}}>Copy URL • Oct 23, 2025
The **OpenAI Apps SDK** introduces a way for developers to build rich, interactive experiences that live directly inside **ChatGPT**. These apps extend what users can do within a conversation, displaying information and tools, without ever breaking the natural flow of chat. Instead of sending users to an external website or separate interface, Apps in ChatGPT appear as integrated, lightweight components that maintain the platform's clarity and trusted conversational tone.
Every ChatGPT app is basically a **web component** that runs in a **sandboxed iframe** inside the conversation. This makes each app a "mini web app" hosted by ChatGPT itself. Developers can create interactive frontends that communicate with ChatGPT through the `window.openai` bridge. This bridge allows the app's UI to exchange data with the surrounding conversation and its corresponding **MCP server**, enabling the app to stay in sync with model responses and external data.
Now that we know what ChatGPT apps are, let's look under the hood to see how they actually work.
## Understanding MCP and Widgets in the OpenAI Apps SDK
Essentially, every Apps SDK app consists of two main parts: the **MCP server**, which hosts the tools, and the **web app views(widgets)**, which render interactive content for users.
The **Model Context Protocol(MCP)** forms the backbone of how the **OpenAI Apps SDK** connects models to external tools, data, and user interfaces. It defines an open standard for communication between language model clients and external systems, ensuring that the model, server, and UI remain perfectly synchronized. Through MCP, developers can expose custom tools that the model can invoke during a conversation, returning structured results enriched with metadata, such as inline HTML, to create dynamic, interactive widgets directly within the ChatGPT interface.
Widgets are the visual layer that bring your app to life inside ChatGPT. They are interactive web components, usually compiled React views, that are fetched from the MCP server and rendered directly within the chat interface. When a model calls a tool, the server can return not only structured data but also metadata that references a widget resource. The Apps SDK then loads this resource, typically inside an iFrame, to display content such as tables, forms, charts, or previews all inline with the model's responses.
Because MCP is transport-agnostic, it supports both Server-Sent Events and streaming HTTP. This flexibility allows the Apps SDK to deliver seamless, real-time app experiences that blend conversational logic with interactive UI components.
## High-level workflow
Under the hood, every ChatGPT app built with the OpenAI Apps SDK operates through a coordinated sequence of interactions between the model, the MCP server, and the widget interface. To illustrate, imagine a Spotify app where a user asks ChatGPT to "show my recently played songs on Spotify"
### Step 1: Model Triggers a Tool Invocation
The process begins when ChatGPT's model interprets the user's request and determines that it requires the Spotify app. The model issues a `call_tool` request to the app's MCP server, specifying the tool to execute(e.g., `getRecentTracks`) and any necessary parameters such as user authorization tokens or filtering options.
### Step 2: MCP Server Executes the Request and Returns Widget Metadata
The MCP server authenticates the user, queries Spotify's API for the relevant data, and constructs a structured response. Along with this data, it attaches metadata that references a widget resource: an identifier pointing to the UI component that should be rendered in ChatGPT.
### Step 3: Widget Resource Delivers the Compiled Interface Component
The widget resource hosts a precompiled web component, typically a React-based element, that defines the visual layout and interaction logic for the app. For example, it may include an interactive list of tracks with album artwork, song titles, and playback buttons. This resource is isolated so that ChatGPT can safely retrieve and render it.
### Step 4: ChatGPT Loads and Renders the Widget in a Sandboxed iFrame
Finally, ChatGPT fetches the widget and runs it inside a sandboxed iframe embedded within the conversation. Through the `window.openai` bridge, the widget communicates securely with ChatGPT and the MCP server, enabling real-time updates, data exchange, and user actions(like adding a song to a playlist or refreshing the feed) without leaving the chat.
Through these four stages, the Apps SDK coordinates model reasoning, tool execution, and UI rendering into one seamless pipeline.
With the widget now running inside ChatGPT, the next piece to understand is how it actually communicates.
## Understanding `window.openai`
In the Apps SDK, `window.openai` is the bridge that connects your app's frontend, the React widget running inside an iframe, to ChatGPT itself. It gives your component awareness of the ChatGPT environment, like the current theme, language, and display mode, while also providing live access to the app's data layer: inputs from the model, outputs from your server, and any persisted widget state. Through this shared surface, your component can stay synchronized with the conversation, react to layout or theme changes, and feel naturally embedded in ChatGPT rather than running as a detached web page.
Beyond being a passive data source, [`window.openai`](https://developers.openai.com/apps-sdk/build/custom-ux) also lets your widget act. It can invoke your MCP tools directly with `callTool`, send follow-up chat messages, open external links, or request a layout change(for example, expanding to fullscreen). The result is a true two-way bridge between the model, the user interface, and your backend, so every click, update, or message becomes part of a living conversation instead of a disconnected app session.
With this bridge in place, we can now look at how users actually encounter and interact with these apps inside ChatGPT.
## How Users Interact with Apps
### 1. Discovering Apps
[User interaction](https://developers.openai.com/apps-sdk/concepts/user-interaction) in the OpenAI Apps SDK starts with how ChatGPT recognizes or suggests the right app for the user's intent. Discovery happens in a few ways: through named mentions(e.g., "Spotify, show my workout playlist"), in-conversation discovery where the model analyzes chat history, tool metadata, brand mentions, and linking state, and external browsing via the app directory.
Inside the conversation, users can also launch apps directly from the "+" button, a high-intent entry point that ranks available apps based on current context. These pathways ensure apps appear naturally when they're relevant, keeping discovery effortless and contextual.
### 2. Using Apps in ChatGPT
When an app is selected, ChatGPT validates inputs, shows confirmation if needed, and renders the app inline within the chat. The interface inherits ChatGPT's theme and layout, maintaining consistency with the surrounding conversation.
The SDK encourages clear, action-oriented descriptions and concise metadata so the model can identify, confirm, and render tools smoothly.
### 3. Context and Continuity
Once linked, an app remains active in the model's context. ChatGPT considers previous results, user preferences, and conversation history to guide follow-up actions. Structured responses with stable identifiers allow users to refine or summarize earlier outputs, creating a sense of continuity without requiring persistent app state. The result is an experience that feels conversational yet functionally cohesive.
## Build ChatGPT Apps in Minutes
The OpenAI Apps SDK provides all the building blocks you need to create powerful, conversational applications inside ChatGPT, but you don't have to start from scratch.
**[FastApps](https://github.com/DooiLabs/FastApps)** streamlines the entire workflow so you can go from idea to running widget in under five minutes.
FastApps wraps the MCP server and Apps SDK widget scaffolding into a single, developer-friendly toolkit. It automatically sets up your project structure, connects your tools to ChatGPT, and handles the build pipeline for your React-based widgets, all with one command.
Here's how simple it is to begin:
```bash theme={null}
pip install fastapps
fastapps init my-app
```
This creates a ready-to-run project with:
* **Server** – Preconfigured MCP server with tool auto-discovery
* **Widgets** – React components wired to `window.openai` out of the box
* **Build & Dev** – One-command build pipeline with live reload and ngrok tunnel
Once your app is initialized, just edit:
* `server/tools/my_widget_tool.py` – Define your logic and structured output
* `widgets/my-widget/index.jsx` – Design your UI and bind to tool data
Then build and run:
```bash theme={null}
npm run build
fastapps dev
```
You'll get a public endpoint instantly shareable with ChatGPT or inspectable via MCPJam.
No manual config and no boilerplate.
## Final Thoughts
In many ways, this feels like a glimpse of the agentic internet taking shape where the Model Context Protocol serves as the connective tissue, the Apps SDK as its distribution platform, and ChatGPT users as the emerging marketplace. It's not just a new developer surface, but it's the early architecture of how intelligent agents, apps, and people will coexist in one continuous conversational ecosystem.
For more context on the broader vision of ChatGPT apps and their potential impact, check out our [exploration of what Apps in ChatGPT represent](https://docs.fastapps.org/blog/what-are-apps-in-chatgpt-and-why-they-are-the-future-of-software) and why they're shaping the future of software.
# What Are Apps in ChatGPT and Why They're the Future of Software
Source: https://docs.fastapps.org/blog/what-are-apps-in-chatgpt-and-why-they-are-the-future-of-software
Learn what Apps in ChatGPT are and how OpenAI's Apps SDK powers a new generation of conversational, AI-driven software.
Written by [**Zach Park**](https://www.linkedin.com/in/zachhere/), Co-founder of Dooilabs
🕒 3 min read • 🔗 navigator.clipboard.writeText('https://docs.fastapps.org/blog/what-are-apps-in-chatgpt-and-why-they-are-the-future-of-software')} style={{cursor: 'pointer', textDecoration: 'underline'}}>Copy URL • Oct 22, 2025
## What Are Apps In ChatGPT and OpenAI Apps SDK
[Apps in ChatGPT](https://openai.com/index/introducing-apps-in-chatgpt/) are lightweight web experiences that live directly inside the ChatGPT interface. Instead of switching to an external site or native program, users can interact with mini web apps like Spotify, Canva, or Zillow right within the chat. Each app runs as a sandboxed web component inside an iframe, powered by the Apps SDK. This SDK lets developers build with familiar web technologies such as HTML, CSS, and JavaScript (or frameworks like React and Vue), while ChatGPT handles rendering, data exchange, and context. From the user's perspective, it feels like a native part of the conversation: interactive cards and panels that respond to natural language.
Under the hood, the Apps SDK builds on the [Model Context Protocol(MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) - the same protocol that connects GPT to external tools and APIs. The developer's MCP server defines tools for logic and resources for UI templates, and ChatGPT orchestrates the rest: invoking the right tool, hydrating the web component with structured output, and embedding the interface in the chat. OpenAI also provides [design and accessibility guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines) to ensure every app looks and behaves consistently within ChatGPT. The result is a unified conversational ecosystem where developers can create small, powerful experiences that feel native to dialogue rather than detached from it.
## From the App Store to the Chat Window
When Apple launched the App Store in 2008, it changed the way we thought about software: instead of heavy installations, we had lightweight apps that followed us everywhere. Today we stand on the brink of another inflection point. These apps still run on servers and devices, but the experience now sits beyond the GUI layer, in the intelligence that understands and responds to what we say.
ChatGPT apps arrive at a moment when computing is ripe for reinvention. For decades, software depended on tapping and swiping; now, natural language is becoming the primary interface. In practice, ChatGPT apps still rely on a back‑end server and a front‑end component that runs in an iframe inside ChatGPT. But from the user's perspective, the interaction flows through conversation rather than a discrete user interface. OpenAI has released the Apps SDK in preview and plans to open app submissions later this year. The promise is that, just as the App Store democratized mobile software, the ChatGPT ecosystem could democratize software that lives in dialogue.
## How ChatGPT Apps Flip the Software Model
Traditional apps integrate AI as a feature: voice search or predictive text sits inside an app that is otherwise built on a standard operating system. ChatGPT apps invert that relationship. Developers define tools and UI components, but the intelligence layer, the GPT mode, becomes the primary runtime. Each app is anchored by an MCP server that exposes tools the model can call, enforces authentication, and packages structured data with an HTML template for the client to render. Instead of simply returning text, the app can trigger actions, display interactive widgets, and update its state via window\.openai calls. In this sense, the AI isn't just augmenting an app; it orchestrates the workflow itself.
## Designing for Conversation, Not Screens
Because ChatGPT understands language, images and even voice inputs, apps can span modalities. A single component can display a card with search results, accept follow‑up questions, call an external API via the MCP tool and return a refreshed dataset. Combined with ChatGPT's built‑in memory, this creates experiences that feel continuous: the app remembers context across turns and adapts its output accordingly.
Each GPT App still has its own interface, built through the Apps SDK's component system, but it lives inside the conversation instead of outside it. That makes it lightweight, contextual, and instantly actionable. Today, users still need to connect or explicitly trigger a GPT App, but the direction is unmistakable. Over time, apps will feel less like separate tools and more like natural extensions of the dialogue itself. The interface becomes conversational, living inside the dialogue rather than outside it.
## Why Developers Should Care
One of the most compelling aspects of ChatGPT apps is the distribution potential. ChatGPT counts hundreds of millions of weekly users globally, giving developers a large audience from day one. While getting into the ecosystem does require building an MCP server, hosting it on a secure HTTPS endpoint and registering a connector via ChatGPT's developer mode, the friction is lower than traditional app stores. A single integration can instantly reach a global user base already inside ChatGPT.
## Inside the ChatGPT App Ecosystem
What makes the ecosystem truly novel is how apps cooperate. Each app defines one or more tools, functions the model can call, plus a corresponding UI component. ChatGPT orchestrates when to call which tool in MCP and merges the results into the conversation. Developers don't need to worry about drawing windows or implementing navigation; they focus on the task their tool performs and the data it returns. The best apps are conversational, time‑bound and visually succinct; they extend ChatGPT rather than replicate existing web workflows. Think booking a flight, ordering food or summarizing the morning's calendar - tasks that can be completed in a few turns and presented in a clear card. This agentic model turns ChatGPT into more than a host; it becomes the mediator that connects intent to action.
## Monetization
Every platform needs an economic model, and ChatGPT apps are no exception. OpenAI already offers subscription plans for ChatGPT, and the company has signaled that monetization policies for apps will be announced when the submission process opens. Although details are still forthcoming, it is easy to see the potential: usage‑based billing, revenue sharing and premium features could create a marketplace akin to the App Store, but oriented around actions rather than downloads. For developers, the allure is clear: a single integration could reach millions of users and generate revenue without the overhead of mobile app development.
## Why OpenAI Is Betting Big
OpenAI's ambitions go far beyond apps. Its collaboration with Jony Ive on a new hardware device hints at a world where users don't "open" apps. They simply talk to them. In that vision, the Apps SDK becomes the application layer, GPT functions as the operating system, and the device itself is just an input/output surface.
The release of Atlas, OpenAI's new AI-powered browser, makes that direction even clearer. Atlas isn't just a browser; it's a conversational interface for the web. Instead of switching between tabs or typing queries into search boxes, users can ask GPT directly about what's on the page they're viewing. This blurs the boundary between browsing, searching, and reasoning - turning the browser into an intelligent workspace.
That's why OpenAI is investing now in MCP servers, design guidelines, and developer policies: the apps and tools built today will soon live not only inside ChatGPT, but across hardware and browsers where GPT becomes the universal interaction layer.
## The Next Software Era
We're moving from a world where software lives on devices to one where it lives inside intelligence. ChatGPT apps still depend on servers, SDKs, and deployment pipelines, but the user experience is now rooted in conversation.
Atlas accelerates that shift. By embedding GPT directly into the browser, it turns the act of browsing into an intelligent dialogue, letting users reason about information, take action, or connect data sources without ever leaving the page. It's the same philosophy that drives ChatGPT apps: context-aware, multimodal, and fluidly conversational.
For developers, this opens a platform with global reach, layered on top of one of the most capable AI models ever built. For users, it promises smarter, more integrated workflows that unfold naturally in dialogue. Just as the App Store defined the smartphone era, GPT-powered apps, and now Atlas, are poised to define the intelligence era.
## What's Still Hard And What's Coming Next
As exciting as this ecosystem is, building ChatGPT Apps today still isn't easy.
Developers have to wire up MCP servers, handle authentication, define tools, and manually decide how each app is triggered in a conversation. There's no simple framework for routing intent, managing context, or connecting multiple GPT Apps smoothly, which makes rapid experimentation difficult even for experienced teams.
That's the gap we're closing.
We built [FastApps](https://docs.fastapps.org/), an open-source, zero-boilerplate framework for creating ChatGPT apps powered by OpenAI's Apps SDK and FastMCP. It lets developers focus on ideas, not infrastructure and get from concept to working prototype in minutes.
Ready to start building? With FastApps, you can create your first ChatGPT app in under five minutes. Simply run `pip install fastapps && fastapps init my-app` and you'll have a fully functional MCP server with React widgets ready to deploy.
# Deploy FastApps Server
Source: https://docs.fastapps.org/deployment/index
Deploy your first FastApps server using FastApps Cloud commands.
## Quick Start
Deploying your FastApps server to production is simple:
```bash theme={null}
fastapps cloud deploy
```
That's it! The CLI will guide you through the entire process.
***
## What Happens During Deployment
When you run `fastapps cloud deploy`, here's what happens automatically:
### 1. **Project Validation**
The CLI validates your project structure to ensure all required files exist:
* `package.json` - Node.js dependencies
* `requirements.txt` - Python dependencies
* `server/` directory with `main.py`
* `widgets/` directory with your widget code
### 2. **Widget Build**
If your `assets/` directory doesn't exist or is outdated, you'll be prompted:
```
⚠️ Assets directory not found
Press Enter to confirm (default: yes)
Build widgets now? (Y/n):
```
Just press **Enter** to build automatically. The CLI runs `npm run build` to compile your React widgets into optimized HTML bundles.
### 3. **Authentication Check**
If you haven't logged in yet, you'll be prompted to authenticate:
```bash theme={null}
fastapps cloud login
```
This opens your browser for secure OAuth authentication with FastApps Team.
### 4. **Project Selection**
If this is your first deployment from this directory, you'll see:
```
⚠️ No project linked to this directory
1. Create new project
2. Link to existing project
Choose [1-2]:
```
**Option 1: Create New Project**
You'll be asked to provide a project slug (URL-friendly identifier):
```
Project slug requirements:
• Lowercase letters, numbers, and hyphens only
• 3-63 characters long
• Must start and end with letter or number
Project slug [my-app-a1b2]:
```
The CLI auto-suggests a slug based on your directory name. Just press Enter to accept, or type your own.
**Option 2: Link to Existing Project**
Select from your existing FastApps Cloud projects to deploy updates.
This will automatically happen on subsequent deployments from the same directory.
### 5. **Deployment Summary**
Before deploying, you'll see a summary:
```
Deployment Summary
Project my-app-a1b2
Project ID 6fd19c55-319a-45ce-ab05-d9b39f5569b3
Widgets 1
Server https://cloud-api.dooi.app
Press Enter to confirm (default: yes)
Deploy to FastApps Cloud? (Y/n):
```
Press **Enter** to continue.
### 6. **Package & Deploy**
The CLI packages your app and deploys it securely:
```
Packaging deployment artifacts...
✓ Package created (0.09 MB)
Deploying to FastApps Cloud...
⠋ Deploying safely...
```
Your code is:
* Compressed into a secure tarball
* Uploaded to FastApps Cloud via encrypted connection
* Deployed to Vercel's serverless infrastructure
* Assigned a custom `*.dooi.app` subdomain
### 7. **Success!**
Once deployed, you'll see your live URL:
```
🚀 Deployment Complete
Your app is live at:
https://my-app-a1b2.dooi.app
Deployment ID: dep_abc123
Project: my-app-a1b2 (6fd19c55-319a-45ce-ab05-d9b39f5569b3)
Test endpoints:
• https://my-app-a1b2.dooi.app/
```
***
## Quick Deployment Flags
Skip confirmations and build steps with optional flags:
```bash theme={null}
# Skip all confirmation prompts (auto-confirm with 'yes')
fastapps cloud deploy --yes
# Skip widget build step (use existing assets/)
fastapps cloud deploy --no-build
# Deploy to specific project (override linked project)
fastapps cloud deploy --project-id my-app
# Combine flags
fastapps cloud deploy --yes --no-build
```
***
## Understanding Project Slugs
A **project slug** is a URL-friendly identifier for your project:
✅ **Valid slugs:**
* `my-app`
* `todo-app-2024`
* `user-dashboard`
* `api-v2`
❌ **Invalid slugs:**
* `My App` (no spaces)
* `my_app` (no underscores)
* `my-app-` (can't end with hyphen)
* `-my-app` (can't start with hyphen)
* `ab` (too short, min 3 characters)
The CLI automatically converts your input to a valid slug:
* Converts to lowercase
* Replaces spaces/underscores with hyphens
* Removes invalid characters
***
## Deployment Lifecycle
### First Deployment
1. Create project (gets assigned a unique ID)
2. Directory is automatically linked to project
3. App is deployed and assigned `*.dooi.app` subdomain
### Subsequent Deployments
1. CLI detects linked project
2. Deploys update to same project
3. Domain remains unchanged
4. Zero-downtime deployment
### Cleanup
After deployment completes (success or failure), the CLI automatically:
* Deletes temporary build artifacts
* Cleans up the packaged tarball
* Even if you cancel (Ctrl+C), cleanup happens. no worries!
***
## What's Next?
Now that your app is deployed, dive deeper into building powerful FastApps :
Add more functionality to your FastApps server
Learn how to create rich, interactive UIs with full React support
Secure your FastApps with user OAuth 2.0 providers
# FastApps Quick Start
Source: https://docs.fastapps.org/quickstart/index
Spin up your first FastApps project and run a widget in under five minutes.
## Quick Start
Get started with FastApps in just 3 steps:
```bash theme={null}
# 0. Install using uv
uv tool install fastapps
uv tool install --upgrade fastapps # Update to the latest version
# 1. Create project
fastapps init my-app
# 2. Run
cd my-app
fastapps dev
```
That's it! Your example widget is now running at a public URL. The public URL is temporary, and is issued with [cloudflared](https://github.com/cloudflare/cloudflared) behind the scenes. You can view your widget at `https://.trycloudflare.com/mcp` and add it to ChatGPT under "Settings > Connectors".
***
## Project Structure
When you run `fastapps init my-app`, this structure is generated:
```
my-app/
├── server/
│ ├── __init__.py # Empty file
│ ├── main.py # Auto-discovery server (pre-configured)
│ └── tools/
│ ├── __init__.py # Empty file
│ └── my_widget_tool.py # ← YOUR CODE: Widget backend
│
├── widgets/
│ └── my-widget/
│ └── index.jsx # ← YOUR CODE: Widget frontend
│
├── requirements.txt
└── package.json
```
***
## Creating More Widgets
You can create additional widgets anytime:
```bash theme={null}
fastapps create another-widget
```
***
## Edit Your Widget Code
You only need to edit these 2 files:
### `server/tools/my_widget_tool.py` - Backend Logic
```python theme={null}
from fastapps import BaseWidget, Field, ConfigDict
from pydantic import BaseModel
from typing import Dict, Any
class MyWidgetInput(BaseModel):
model_config = ConfigDict(populate_by_name=True)
name: str = Field(default="World")
class MyWidgetTool(BaseWidget):
identifier = "my-widget"
title = "My Widget"
input_schema = MyWidgetInput
invoking = "Processing..."
invoked = "Done!"
widget_csp = {
"connect_domains": [], # APIs you'll call
"resource_domains": [] # Images/fonts you'll use
}
async def execute(self, input_data: MyWidgetInput) -> Dict[str, Any]:
# Your logic here
return {
"name": input_data.name,
"message": f"Hello, {input_data.name}!"
}
```
### `widgets/my-widget/index.jsx` - Frontend UI
```jsx theme={null}
import React from 'react';
import { useWidgetProps } from 'fastapps';
export default function MyWidget() {
const props = useWidgetProps();
return (
{props.message}
Welcome, {props.name}!
);
}
```
**That's it! These are the only files you need to write.**
***
## Test Your App
With your app running (`fastapps dev`) and public URL is ready, \
you have two options to test your widget.
**Option A: Test on MCPJam Inspector**
Add your public URL + /mcp to ChatGPT : \
Example URL : `https://.trycloudflare.com/mcp`
```bash theme={null}
npx @mcpjam/inspector@latest
```
Test your app with:
* Tools tab: Deterministically call tools and view your UI
* LLM playground: See your Apps SDK UI in a chat environment
**Option B: Test on ChatGPT**
Add your public URL + /mcp to ChatGPT's "Settings > Connectors" : \
Example URL : `https://.trycloudflare.com/mcp`
## ✅ Ready for Next Steps
* Follow the Tutorial for a guided build
* Explore Widgets and Tools to customize logic and UI
* Connect APIs or state management when you need more power
# Tool Basics
Source: https://docs.fastapps.org/server/basics/index
Learn the fundamentals of creating mcp server tools with FastApps.
## What is a Tool?
A tool is a Python class that:
* Lives in `server/tools/_tool.py`
* Extends `BaseWidget`
* Defines inputs with Pydantic
* Implements widget logic in `execute()`
## Core Concepts
### BaseWidget
`BaseWidget` is the **abstract base class** that all FastApps widgets must inherit from. It handles all the MCP (Model Context Protocol) wiring and widget lifecycle management automatically.
#### Required Class Attributes
| Attribute | Type | Description | Example |
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `identifier` | `str` | Unique widget identifier. Must match the widget folder name in `widgets/`. Used as the resource URI identifier | `"greeting"` for `widgets/greeting/` |
| `title` | `str` | Human-readable tool name displayed in ChatGPT interface. Shown when the model considers calling this tool | `"Show Greeting Widget"` |
| `input_schema` | `Type[BaseModel]` | Pydantic model defining the tool's input parameters. ChatGPT uses this JSON schema to understand when and how to call your tool | `GreetingInput` |
| `invoking` | `str` | Short, localized status message shown to users **while** the tool is being executed. Maps to `openai/toolInvocation/invoking` | `"Preparing your greeting…"` |
| `invoked` | `str` | Short, localized status message shown to users **after** the tool completes. Maps to `openai/toolInvocation/invoked` | `"Greeting ready!"` |
#### Optional Class Attributes
| Attribute | Type | Description | Example |
| ------------------- | ------ | --------------------------------------------------------------------------- | ------------------------------------------ |
| `description` | `str` | Optional tool description. Helps the model understand when to use this tool | `"Display a personalized greeting widget"` |
| `widget_accessible` | `bool` | Whether the widget can initiate tool calls from its React component | `True` for interactive widgets |
### Basic Tool Structure
```python theme={null}
from fastapps import BaseWidget
from pydantic import BaseModel
class GreetingInput(BaseModel):
name: str
message: str
class GreetingWidget(BaseWidget):
identifier = "greeting"
title = "Show Greeting Widget"
input_schema = GreetingInput
invoking = "Preparing your greeting…"
invoked = "Greeting ready!"
def execute(self, inputs: GreetingInput, ctx):
return {
"name": inputs.name,
"message": inputs.message,
"timestamp": datetime.now().isoformat()
}
```
## Common Patterns
### Simple Data Display
```python theme={null}
class WeatherWidget(BaseWidget):
identifier = "weather"
title = "Show Weather Forecast"
input_schema = WeatherInput
invoking = "Fetching weather data…"
invoked = "Weather forecast ready!"
def execute(self, inputs: WeatherInput, ctx):
# Your business logic here
forecast = get_weather_forecast(inputs.city)
return {
"city": inputs.city,
"temperature": forecast.temperature,
"description": forecast.description,
"humidity": forecast.humidity
}
```
### User Input Collection
```python theme={null}
class SurveyInput(BaseModel):
questions: List[str]
class SurveyWidget(BaseWidget):
identifier = "survey"
title = "Create Survey Widget"
input_schema = SurveyInput
invoking = "Setting up your survey…"
invoked = "Survey ready for responses!"
def execute(self, inputs: SurveyInput, ctx):
return {
"questions": inputs.questions,
"survey_id": generate_survey_id(),
"created_at": datetime.now().isoformat()
}
```
### Conditional Logic
```python theme={null}
class ConditionalWidget(BaseWidget):
identifier = "conditional"
title = "Show Conditional Content"
input_schema = ConditionalInput
invoking = "Processing your request…"
invoked = "Content ready!"
def execute(self, inputs: ConditionalInput, ctx):
if inputs.user_type == "admin":
return {
"content": "Admin dashboard",
"permissions": ["read", "write", "delete"],
"admin_panel": True
}
else:
return {
"content": "User dashboard",
"permissions": ["read"],
"admin_panel": False
}
```
## Input Validation
Use Pydantic models to define and validate inputs:
```python theme={null}
from pydantic import BaseModel, Field, validator
from typing import List, Optional
class ProductSearchInput(BaseModel):
query: str = Field(..., min_length=1, max_length=100)
category: Optional[str] = None
price_range: Optional[tuple] = Field(None, description="Min and max price")
limit: int = Field(default=10, ge=1, le=100)
@validator('query')
def validate_query(cls, v):
if len(v.strip()) == 0:
raise ValueError('Query cannot be empty')
return v.strip()
@validator('price_range')
def validate_price_range(cls, v):
if v and v[0] > v[1]:
raise ValueError('Min price must be less than max price')
return v
```
## Error Handling
Handle errors gracefully and provide meaningful feedback:
```python theme={null}
class RobustWidget(BaseWidget):
identifier = "robust"
title = "Robust Widget Example"
input_schema = RobustInput
invoking = "Processing…"
invoked = "Done!"
def execute(self, inputs: RobustInput, ctx):
try:
# Your business logic
result = risky_operation(inputs.data)
return {"status": "success", "data": result}
except ValidationError as e:
ctx.logger.warning(f"Validation error: {e}")
return {
"status": "error",
"message": "Invalid input data",
"details": str(e)
}
except Exception as e:
ctx.logger.exception(f"Unexpected error: {e}")
return {
"status": "error",
"message": "Something went wrong",
"fallback_data": get_fallback_data()
}
```
## Next Steps
Integrate external MCP servers using Metorial
Connect to external APIs
# Set up your server
Source: https://docs.fastapps.org/server/index
Build your MCP server and expose widgets as tools to ChatGPT.
## How does ChatGPT apps work?
Your MCP server is the foundation of every ChatGPT App. It exposes tools that the model can call, enforces authentication, and packages the structured data plus component that the ChatGPT client renders inline. This guide walks through the core building blocks with examples in FastApps.
## Tool Architecture
```mermaid theme={null}
graph TD
A[ChatGPT User] -->|Natural language| B[ChatGPT Model]
B -->|Tool call| E[FastApps Server]
E -->|Structured data| F[Widget UI]
```
## BaseWidget Class
Every FastApps tool must inherit from `BaseWidget`:
```python theme={null}
class MyWidget(BaseWidget):
# Required attributes
identifier = "my_widget" # Unique identifier
title = "My Widget Title" # Display name
input_schema = MyInputModel # Pydantic model
invoking = "Setting up widget…" # Progress message
invoked = "Widget ready!" # Completion message
# Optional attributes
description = "Widget description" # Help text
widget_accessible = True # Allow component calls
def execute(self, inputs, ctx):
# Your business logic here
return {"data": "processed"}
```
## Next Steps
Ready to start building?
Learn the fundamentals
Connect to external services
Complex business logic
Or jump straight to the [**Quick Start Guide**](/quickstart/index) for a complete walkthrough.
# MCP Integration
Source: https://docs.fastapps.org/server/mcp-integration/index
Integrate other MCP servers as APIs using Metorial
## Overview
FastApps allows you to integrate external MCP servers as APIs in your widgets using [Metorial](https://metorial.com/). Metorial is an open-source integration platform that provides access to 600+ MCP servers with enterprise-grade observability and scaling.
With FastApps + Metorial integration, you can:
* Access hundreds of verified MCP servers (Slack, Gmail, Google Calendar, etc.)
* Use external MCP servers directly as APIs in your tools
* Get instant deployment with built-in scaling
* Monitor and debug with detailed logging
***
## Quick Setup
### 1. Generate Integration File
Run the FastApps CLI command to generate the Metorial integration:
```bash theme={null}
fastapps use metorial
```
This creates a `metorial_mcp.py` file under the `/api` folder in your project.
### 2. Configure Environment Variables
Add your Metorial API key, OpenAI API key, and deployment ID to your environment:
```bash theme={null}
# .env
METORIAL_API_KEY=your_metorial_api_key
OPENAI_API_KEY=your_openai_api_key
METORIAL_DEPLOYMENT_ID=your_deployment_id
```
Get your Metorial API key and deployment ID at [https://metorial.com/](https://metorial.com/)
### 3. Use in Your Widgets
Import and use the Metorial integration in your FastApps tools:
```python theme={null}
from server.api.metorial_mcp import call_metorial
# Use in your widget
result = await call_metorial("Search Hackernews for latest AI discussions")
```
***
## Generated File Structure
When you run `fastapps use metorial`, a `metorial_mcp.py` file is created in the `/server/api` folder:
**`/server/api/metorial_mcp.py`**
```python theme={null}
import os
import asyncio
from metorial import Metorial
from openai import AsyncOpenAI
async def call_metorial(
message: str,
deployment_id: str = None,
model: str = "gpt-4o",
max_steps: int = 25
):
# Get credentials from environment
metorial_api_key = os.getenv('METORIAL_API_KEY')
openai_api_key = os.getenv('OPENAI_API_KEY')
deployment_id = deployment_id or os.getenv('METORIAL_DEPLOYMENT_ID')
if not all([metorial_api_key, openai_api_key, deployment_id]):
raise ValueError("Missing environment variables: METORIAL_API_KEY, OPENAI_API_KEY, METORIAL_DEPLOYMENT_ID")
# Initialize clients
metorial = Metorial(api_key=metorial_api_key)
openai = AsyncOpenAI(api_key=openai_api_key)
# Run query
response = await metorial.run(
message=message,
server_deployments=[deployment_id],
client=openai,
model=model,
max_steps=max_steps
)
return response.text
```
### What This File Does
The `metorial_mcp.py` file provides a simple API wrapper to:
1. **Load Credentials** - Automatically loads API keys and deployment ID from environment variables
2. **Simple Interface** - Provides a clean `call_metorial()` function with minimal parameters
3. **Handle Authentication** - Validates required environment variables
4. **Return Results** - Processes and returns text responses from MCP servers
***
## Using MCP Servers in Widgets
### Basic Example
Here's how to integrate external MCP servers in your FastApps widget:
```python theme={null}
from fastapps import BaseWidget
from pydantic import BaseModel, Field
from server.api.metorial_mcp import call_metorial
class NewsSearchInput(BaseModel):
query: str = Field(..., description="Search query for news")
class NewsSearchWidget(BaseWidget):
identifier = "news-search"
title = "Search News"
input_schema = NewsSearchInput
invoking = "Searching..."
invoked = "Search complete!"
async def execute(self, input_data: NewsSearchInput, ctx):
# Simple usage - uses default deployment ID from environment
result = await call_metorial(f"Search for: {input_data.query}")
return {
"query": input_data.query,
"results": result
}
```
### Custom Deployment
Use a specific deployment ID or customize the model:
```python theme={null}
from fastapps import BaseWidget
from pydantic import BaseModel, Field
from server.api.metorial_mcp import call_metorial
class CustomSearchInput(BaseModel):
query: str = Field(..., description="Search query")
use_mini: bool = Field(default=False, description="Use mini model")
class CustomSearchWidget(BaseWidget):
identifier = "custom-search"
title = "Custom Search"
input_schema = CustomSearchInput
async def execute(self, input_data: CustomSearchInput, ctx):
# Customize deployment and model
result = await call_metorial(
message=f"Find latest: {input_data.query}",
deployment_id="custom_deployment_id", # Optional: override default
model="gpt-4o-mini" if input_data.use_mini else "gpt-4o",
max_steps=10
)
return {
"query": input_data.query,
"results": result
}
```
## Learn More
For complete documentation and advanced features, visit:
**Metorial Documentation**: [https://metorial.com/](https://metorial.com/)
***
## Next Steps
Back to Tool Basics
External API Integration
Advanced Tool Patterns
# How ChatGPT apps work
Source: https://docs.fastapps.org/what-is-fastapps/index
Understand how ChatGPT apps work, and how to build one with FastApps
## How ChatGPT apps work
At its core, a ChatGPT app is an MCP server that exposes multiple tools. Each tools return structured contents including a widget.
Specifically, each tools include
* Metadata : Description of the tool (so that the model could call the right tool at the right time), input schema, the message to be displayed before/after the tool is executed, etc.
* Widgets : This is the component that is being displayed on the screen.
## Building ChatGPT apps
With FastApps, you could simply use the prebuilt library to resgister tools and widgets.
**Tools** (`server/tools/hello_tool.py`)
```python theme={null}
from fastapps import BaseWidget, ConfigDict
from pydantic import BaseModel
from typing import Dict, Any
class MyWidgetInput(BaseModel):
model_config = ConfigDict(populate_by_name=True)
class MyWidgetTool(BaseWidget):
identifier = "hello"
title = "My Widget"
input_schema = MyWidgetInput
invoking = "Loading widget..."
invoked = "Widget ready!"
widget_csp = {
"connect_domains": [],
"resource_domains": []
}
async def execute(self, input_data: MyWidgetInput, context=None, user=None) -> Dict[str, Any]:
return {
"message": "Welcome to FastApps"
}
```
**Widgets** (`widgets/hello/index.jsx`)
```jsx theme={null}
import React from 'react';
export default function HelloWidget() {
return (
Hello world!
);
}
```
## What FastApps provide
FastApps provide everything you need to build sophisticated ChatGPT apps:
**Widget Registration**: Use the `BaseWidget` class to simply register it as an MCP tool. We handle the rest - no complex configuration or manual registration needed.
**CLI commands**: Just type `fastapps init` to set up the whole project. Use `fastapps create mywidget` to make an widget. Everything from component creation to MCP tool registeration will be set up automatically.
**Simple auth**: Authentication and other advanced features are all handled with our simple decorators. Just add `@auth_required` and you're done.
## Next Steps
Get up and running with FastApps in minutes
# Widget Basics
Source: https://docs.fastapps.org/widgets/basics/index
Learn the fundamentals of creating widgets with FastApps
## What is a Widget?
A widget is a React component that:
* Lives in `widgets//index.jsx`
* Receives props from your MCP tool
* Renders in the ChatGPT interface
* Can be interactive and stateful
## Basic Widget Structure
```jsx theme={null}
import React from "react";
import { useWidgetProps } from "fastapps";
export default function MyWidget() {
// 1. Get data from Python backend
const props = useWidgetProps();
// 2. Render UI based on props
return (
);
}
```
## Next Steps
Learn about React Hooks
Explore Advanced Patterns
# Building Widgets
Source: https://docs.fastapps.org/widgets/index
Create interactive React components that is rendered inside ChatGPT.
## Quick Start
The fastest way to create a widget:
```bash theme={null}
fastapps create mywidget
```
This creates:
* `server/tools/mywidget_tool.py` - Python backend logic
* `widgets/mywidget/index.jsx` - React frontend component
## Widget Architecture
```mermaid theme={null}
graph TD
A[MCP Tool] -->|Returns toolOutput & metadata| B[window.openai]
B -->|Exposes globals and data| C[React Component]
C -->|Reads via useWidgetProps and useOpenAiGlobal| B
C -->|Manages user state| D[useWidgetState]
D -->|Calls setWidgetState| E[window.openai]
E -->|Persists state| F[ChatGPT Host Context]
```
## Core Concepts
### 1. **Data Flow**
| **MCP Tool → window\.openai** | The MCP tool returns toolOutput and metadata, which are stored inside window\.openai. |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ |
| **window\.openai → React Component** | The component reads data (like tool input/output, theme, layout) from window\.openai using hooks such as useWidgetProps or useOpenAiGlobal. |
| **React Component → useWidgetState** | User interactions (clicks, filters, toggles) are handled in React and synchronized with the host through useWidgetState. |
| **useWidgetState → window\.openai.setWidgetState** | When state changes, it’s sent to the host via window\.openai.setWidgetState() for persistence. |
| **window\.openai → ChatGPT Host Context** | ChatGPT stores the widget state persistently, so it’s available across sessions and visible to the model for reasoning. |
### 2. **File Structure**
```
widgets/
mywidget/
index.jsx # React component
server/tools/
mywidget_tool.py # Python backend
```
### 3. **React Hooks**
* `useWidgetProps()` - Access tool output data
* `useWidgetState()` - Manage persistent state
* `useOpenAiGlobal()` - Access ChatGPT environment
* `useDisplayMode()` / `useMaxHeight()` - Layout convenience hooks
## Next Steps
Ready to start building?
Learn the fundamentals
Master the hook system
Complex interactions
# React Hooks
Source: https://docs.fastapps.org/widgets/react-hooks/index
Complete guide to FastApps React hooks for managing states.
## Core Hooks
### useWidgetProps()
Access data returned from your MCP tool's `execute()` method.
```tsx theme={null}
import { useWidgetProps } from 'fastapps';
interface MyWidgetProps {
message: string;
count: number;
items: string[];
}
export default function MyWidget() {
const props = useWidgetProps();
return (
{props.message}
Count: {props.count}
{props.items.map((item) => (
{item}
))}
);
}
```
**How it works:**
* Maps to `window.openai.toolOutput`
* Data comes from your MCP tool's `return` statement
* Updates automatically on new tool calls
### useWidgetState()
Manage persistent state that survives across ChatGPT sessions.
```tsx theme={null}
import { useWidgetState } from 'fastapps';
export default function Counter() {
const [state, setState] = useWidgetState({ count: 0 });
const increment = () => {
setState({ count: state.count + 1 });
};
return (
Count: {state?.count || 0}
);
}
```
**How it works:**
* Maps to `window.openai.widgetState` and `setWidgetState()`
* State persists in ChatGPT's conversation context
* Survives page refreshes and widget re-renders
* Accepts initial state as default value
**Advanced usage:**
```tsx theme={null}
// With TypeScript
interface CounterState {
count: number;
lastUpdated: string;
}
const [state, setState] = useWidgetState({
count: 0,
lastUpdated: new Date().toISOString()
});
// Update state
setState({
count: state.count + 1,
lastUpdated: new Date().toISOString()
});
```
### useOpenAiGlobal()
Access ChatGPT environment information like theme, layout, and locale. This is the base hook for accessing any global property.
```tsx theme={null}
import { useOpenAiGlobal } from 'fastapps';
export default function ThemedWidget() {
const theme = useOpenAiGlobal('theme');
const displayMode = useOpenAiGlobal('displayMode');
const locale = useOpenAiGlobal('locale');
const maxHeight = useOpenAiGlobal('maxHeight');
return (
Current theme: {theme}
Display mode: {displayMode}
User locale: {locale}
);
}
```
## Convenience Hooks
### useDisplayMode()
Convenience hook for accessing the current display mode. Equivalent to `useOpenAiGlobal('displayMode')`.
```tsx theme={null}
import { useDisplayMode } from 'fastapps';
export default function ResponsiveWidget() {
const displayMode = useDisplayMode();
return (
{displayMode === 'fullscreen' ? (
Full Screen Layout
More space to show detailed content
) : displayMode === 'pip' ? (
Picture-in-Picture view
) : (
Inline compact view
)}
);
}
```
**Display modes:**
* `inline` - Default mode, widget appears inline with the conversation
* `pip` - Picture-in-picture mode (mobile may coerce to fullscreen)
* `fullscreen` - Full screen takeover
### useMaxHeight()
Convenience hook for accessing the maximum height constraint. Equivalent to `useOpenAiGlobal('maxHeight')`.
```tsx theme={null}
import { useMaxHeight } from 'fastapps';
export default function ScrollableWidget() {
const maxHeight = useMaxHeight();
return (
Long Content
This content will scroll if it exceeds the max height...
{/* More content */}
);
}
```
**Best practices:**
* Always respect the `maxHeight` constraint
* Use `overflow: auto` to enable scrolling
* Consider the user's viewport size when designing layouts
## Available Globals
Use `useOpenAiGlobal(key)` to access:
| Key | Type | Description | Example |
| ------------- | ----------------------------------- | ----------------------------------------------------- | ------------------------------------- |
| `theme` | `'light' \| 'dark'` | ChatGPT's current theme | `'dark'` |
| `displayMode` | `'inline' \| 'pip' \| 'fullscreen'` | Current display mode | `'inline'` |
| `locale` | `string` | User's preferred locale (IETF BCP 47) | `'en-US'`, `'fr-FR'` |
| `maxHeight` | `number` | Maximum height constraint in pixels | `600` |
| `safeArea` | `SafeArea` | Safe area insets for mobile layouts | `{ insets: { top: 20, ... }}` |
| `userAgent` | `UserAgent` | Device and capability information | `{ device: { type: 'mobile' }, ... }` |
| `toolInput` | `object` | Input parameters passed to your tool | `{ city: 'NYC' }` |
| `toolOutput` | `object` | Current tool output (same as `useWidgetProps()`) | `{ message: 'Hello' }` |
| `widgetState` | `object` | Current persistent state (same as `useWidgetState()`) | `{ count: 5 }` |
## TypeScript Support
All hooks include full TypeScript type definitions:
```tsx theme={null}
import type {
OpenAiGlobals,
Theme,
DisplayMode,
UserAgent,
SafeArea
} from 'fastapps';
// Strongly typed props
interface MyProps {
message: string;
count: number;
}
const props = useWidgetProps();
// props.message is string ✓
// props.count is number ✓
// Strongly typed state
interface MyState {
items: string[];
}
const [state, setState] = useWidgetState({ items: [] });
// state.items is string[] ✓
// All globals are typed
const theme: Theme | null = useOpenAiGlobal('theme');
const mode: DisplayMode | null = useOpenAiGlobal('displayMode');
// Convenience hooks are also typed
const displayMode: DisplayMode | null = useDisplayMode();
const maxHeight: number | null = useMaxHeight();
```
## Creating Custom Convenience Hooks
You can easily create your own convenience hooks for frequently accessed globals:
```tsx theme={null}
import { useOpenAiGlobal } from 'fastapps';
// Custom hook for theme
export function useTheme() {
return useOpenAiGlobal('theme');
}
// Custom hook for tool input
export function useToolInput() {
return useOpenAiGlobal('toolInput') as T | null;
}
// Custom hook for locale
export function useLocale() {
return useOpenAiGlobal('locale');
}
```
## Next Steps
Back to Widget Basics
Explore Advanced Patterns
# Templates
Source: https://docs.fastapps.org/widgets/templates/index
Create widgets quickly using pre-built templates
## Create widget from a template
FastApps provides several pre-built templates to help you get started quickly. Instead of creating a widget from scratch, you can use a template that includes common patterns and best practices.
## Available Templates
### List Template
Create a vertical list widget with items:
```bash theme={null}
uv run fastapps create my-list --template list
```
This template creates a widget that displays items in a vertical list format, perfect for displaying collections of data like tasks, products, or any list-based content.
### Carousel Template
Create a horizontal scrolling cards widget:
```bash theme={null}
uv run fastapps create my-carousel --template carousel
```
This template creates a widget with horizontal scrolling cards, ideal for showcasing multiple items in a compact, interactive format.
### Albums Template
Create a photo gallery viewer:
```bash theme={null}
uv run fastapps create my-albums --template albums
```
This template creates a photo gallery widget that allows users to browse through images in an organized, visually appealing way.
## What Gets Created
When you use a template, FastApps generates:
* `widgets//index.jsx` - Pre-configured React component based on the template
* `server/tools/_tool.py` - Python backend tool with example data structure
## Customizing Templates
After creating a widget from a template, you can:
1. Modify the React component in `widgets//index.jsx` to match your design needs
2. Update the Python tool in `server/tools/_tool.py` to connect to your data source
3. Add additional features and interactions as needed
## Next Steps
Learn the fundamentals of widgets
Master the hook system