Provides ChatGPT app integration using the OpenAI Apps SDK (MCP), enabling tools, resources, and interactive widgets to be used within ChatGPT conversations through OAuth2 authentication.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ChatGPT App with OAuth2 + MCP + Privyshow me my recent items"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ChatGPT App with OAuth2 + MCP + Privy
A complete ChatGPT App implementation using the OpenAI Apps SDK (MCP), with OAuth2 authentication via Privy.io.
๐๏ธ Architecture
Backend: Express + MCP Server (TypeScript/Bun)
OAuth UI: React + Privy + React Router
Widgets: React components (rendered in ChatGPT)
Auth: OAuth2 with PKCE + Privy.io
Package Manager: Bun
๐ Project Structure
mcp2/
โโโ src/
โ โโโ server/ # Express + MCP server
โ โ โโโ oauth/ # OAuth2 endpoints
โ โ โโโ mcp/ # MCP tools & resources
โ โ โโโ api/ # Backend API integration
โ โ โโโ middleware/ # Auth middleware
โ โโโ client/ # OAuth authorization UI
โ โโโ widgets/ # ChatGPT widget components
โโโ dist/
โ โโโ client/ # Built OAuth UI
โ โโโ widgets/ # Built widget bundles
โ โโโ server/ # Compiled server
โโโ package.json๐ Quick Start
Prerequisites
1. Install Bun
curl -fsSL https://bun.sh/install | bash2. Install Dependencies
bun install3. Generate JWT Keys
# Generate RSA key pair for JWT signing
openssl genrsa -out private-key.pem 2048
openssl rsa -in private-key.pem -pubout -out public-key.pem
# Base64 encode for .env
echo "JWT_PRIVATE_KEY=$(cat private-key.pem | base64)"
echo "JWT_PUBLIC_KEY=$(cat public-key.pem | base64)"
# Clean up PEM files
rm private-key.pem public-key.pem4. Configure Environment
cp .env.example .env
# Edit .env with your values:
# - PRIVY_APP_ID (from Privy dashboard)
# - PRIVY_APP_SECRET (from Privy dashboard)
# - JWT_PRIVATE_KEY (from step 3)
# - JWT_PUBLIC_KEY (from step 3)
# - BACKEND_API_URL (your existing backend)5. Build & Run
IMPORTANT: Widgets must be built before starting the server!
# First time: Build widgets (required!)
bun run build:widgets
# Then start development server
bun run devThe server will start at http://localhost:3002
๐ง Development
Understanding the Widget Build Process
โ ๏ธ Key Point: bun run dev does NOT automatically build widgets. You must build them separately!
There are three development workflows:
Option 1: Manual Build (Recommended for first-time setup)
# 1. Build widgets once
bun run build:widgets
# 2. Start server with auto-reload
bun run dev
# 3. Rebuild widgets manually when you change widget code
bun run build:widgetsOption 2: Watch Mode (Recommended for active widget development)
# Terminal 1: Build widgets in watch mode (auto-rebuilds on changes)
bun run dev:widgets
# Terminal 2: Run server with auto-reload
bun run devOption 3: Run Everything (Most convenient)
# Runs both server AND widget watch mode simultaneously
bun run dev:allOther Development Commands
# Type check
bun run type-check
# Run tests
bun run test
# Build everything for production
bun run buildProject Configuration
Server: src/server/index.ts
OAuth endpoints:
/authorize,/token,/.well-known/*MCP endpoint:
/mcpHealth check:
/health
OAuth UI: src/client/src/App.tsx
Authorization page with Privy login
Consent screen
Built with Vite + React + React Router
Widgets: src/widgets/src/
ListView: Interactive list with actions
Built as standalone bundles
Communicate via
window.openaiAPI
๐งช Testing
Test with MCP Inspector
# Terminal 1: Run server
bun run dev
# Terminal 2: Run MCP Inspector
bunx @modelcontextprotocol/inspector http://localhost:3002/mcpTest with ngrok
# Expose local server
ngrok http 3002
# Copy the HTTPS URL (e.g., https://abc123.ngrok.app)
# Use this URL in ChatGPT Settings โ ConnectorsConnect to ChatGPT
Enable Developer Mode:
ChatGPT Settings โ Apps & Connectors โ Advanced settings
Enable "Developer mode"
Create Connector:
Settings โ Connectors โ Create
Name: "Your App Name"
Description: "What your app does"
Connector URL:
https://your-server.com/mcp(or ngrok URL)
Test OAuth Flow:
Start a new ChatGPT conversation
Click + โ More โ Select your connector
You'll be redirected to
/authorizeLog in with Privy
Grant consent
ChatGPT receives OAuth token
Test Tools:
Ask ChatGPT: "Show me my items"
The
get-itemstool will be calledWidget will render in ChatGPT
๐ฆ Production Build
# Build everything
bun run build
# Run production server
bun run start
# Or preview locally
bun run previewDocker Deployment
# Build image
docker build -t chatgpt-app .
# Run container
docker run -p 3000:3000 --env-file .env chatgpt-appDeploy to Fly.io
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Create app
fly launch
# Set secrets
fly secrets set PRIVY_APP_ID=xxx
fly secrets set PRIVY_APP_SECRET=xxx
fly secrets set JWT_PRIVATE_KEY=xxx
fly secrets set JWT_PUBLIC_KEY=xxx
fly secrets set BACKEND_API_URL=xxx
# Deploy
fly deploy๐ OAuth2 Flow
ChatGPT redirects user to
/authorize?client_id=...&code_challenge=...Server serves React UI (Privy login)
User authenticates with Privy
Frontend shows consent screen
User approves, server generates authorization code
Frontend redirects back to ChatGPT with code
ChatGPT exchanges code for access token at
/tokenServer validates PKCE, issues JWT
ChatGPT uses JWT for
/mcprequests
๐จ Adding New Tools
1. Define Tool in src/server/mcp/tools.ts
{
name: 'my-new-tool',
description: 'What the tool does',
inputSchema: {
type: 'object',
properties: {
param: { type: 'string' }
},
required: ['param']
}
}2. Implement Handler
async function handleMyNewTool(args: any, auth: any) {
// Validate auth
// Call backend API
// Return structured response
}3. Link to Widget (Optional)
_meta: {
'openai/outputTemplate': 'ui://widget/my-widget.html',
}๐จ Adding New Widgets
1. Create Widget Component
mkdir -p src/widgets/src/MyWidget2. Build Widget
// src/widgets/src/MyWidget/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { MyWidget } from './MyWidget';
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<MyWidget />);3. Configure Vite
// Update src/widgets/vite.config.ts
build: {
lib: {
entry: {
'my-widget': 'src/MyWidget/index.tsx'
}
}
}4. Register Resource
// src/server/mcp/resources.ts
await registerMyWidget(server, widgetPath);๐ Environment Variables
Variable | Description | Required |
| Your Privy app ID | โ |
| Your Privy app secret | โ |
| Privy app ID (for frontend) | โ |
| Base64-encoded RSA private key | โ |
| Base64-encoded RSA public key | โ |
| Your server URL | โ |
| Your existing backend URL | โ |
| Server port (default: 3000) | โ |
| Environment (development/production) | โ |
๐ Troubleshooting
Widgets not loading
# Build widgets first
bun run build:widgets
# Restart server
bun run devOAuth flow fails
Check
SERVER_BASE_URLmatches your actual URLVerify Privy app ID is correct
Check JWT keys are properly base64-encoded
Ensure redirect URI is registered in ChatGPT
Token validation fails
Verify JWT keys are correct (public/private pair)
Check token hasn't expired (1 hour default)
Ensure
audclaim matches your server URL
MCP Inspector can't connect
# Ensure server is running
bun run dev
# Try:
bunx @modelcontextprotocol/inspector http://localhost:3002/mcp๐ Resources
๐ License
MIT
๐ค Contributing
Contributions welcome! Please open an issue or PR.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.