Add GPT Apps SDK widgets to any MCP server without modifying it.
If you have an existing MCP server (or are using a third-party one like the SQLite or Postgres MCP servers), and want to add rich UI widgets to tool responses in ChatGPT, this proxy lets you do that without touching the original server.
How it works:
ChatGPT (OpenAI)
│
▼
┌──────────────────────────────┐
│ MCP GPT Proxy (this app) │
│ - Forwards MCP requests │
│ - Injects widget metadata │
│ - Serves widget HTML │
└──────────────────────────────┘
│
▼
Your Existing MCP Server
git clone https://github.com/your-org/mcp-gpt-proxy.git
cd mcp-gpt-proxy
pnpm installcp .env.example .envEdit .env to point to your upstream MCP server:
MCP_SERVER_URL=http://localhost:3001/mcpEdit src/lib/config/proxy.config.ts to map tools to widgets:
export const config: ProxyConfig = {
mcpServerUrl: process.env.MCP_SERVER_URL || "http://localhost:3001/mcp",
widgets: [
{
toolName: "get_weather",
widgetPath: "/widgets/weather",
description: "Displays weather information",
prefersBorder: true,
},
{
toolName: "query_database",
widgetPath: "/widgets/data-table",
description: "Shows query results in a table",
},
],
};Create a new widget in src/app/widgets/[name]/page.tsx:
"use client";
import { useWidgetProps } from "@/app/hooks/use-widget-props";
import { useThemeSync } from "@/app/hooks/use-theme";
import { Badge } from "@openai/apps-sdk-ui/components/Badge";
import { Button } from "@openai/apps-sdk-ui/components/Button";
interface MyData {
title: string;
items: string[];
}
export default function MyWidget() {
useThemeSync(); // Sync with ChatGPT theme
const data = useWidgetProps<MyData>();
if (!data) return <div>Loading...</div>;
return (
<div className="p-4 rounded-xl border border-default bg-surface">
<h2 className="heading-lg">{data.title}</h2>
<ul className="mt-4 space-y-2">
{data.items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}pnpm devThe proxy runs on http://localhost:3000. Point ChatGPT to http://localhost:3000/mcp (or use ngrok for external access).
Get tool output data from the ChatGPT context:
const data = useWidgetProps<WeatherData>();Sync widget theme with ChatGPT (light/dark mode):
useThemeSync(); // Automatically applies theme to documentRead theme without auto-applying:
const theme = useTheme(); // "light" or "dark"| Option | Type | Description |
|---|---|---|
toolName |
string | MCP tool name to enhance |
widgetPath |
string | Path to widget (e.g., "/widgets/weather") |
description |
string? | Widget description for the model |
prefersBorder |
boolean? | Render with border (default: true) |
invokingText |
string? | Status text while tool runs |
invokedText |
string? | Status text after tool completes |
csp |
object? | Content Security Policy for external fetch |
This template includes @openai/apps-sdk-ui for building widgets. Available components:
Badge- Status indicatorsButton- Interactive buttonsIcon- Icon library (Calendar, Checkmark, etc.)- And more...
import { Badge } from "@openai/apps-sdk-ui/components/Badge";
import { Button } from "@openai/apps-sdk-ui/components/Button";
import { Calendar } from "@openai/apps-sdk-ui/components/Icon";vercelFROM node:20-alpine
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm build
CMD ["pnpm", "start"]ngrok http 3000
# Use the ngrok URL in ChatGPT: https://xxx.ngrok.app/mcpsrc/
├── app/
│ ├── mcp/
│ │ └── route.ts # Proxy endpoint
│ ├── widgets/
│ │ └── example/
│ │ └── page.tsx # Example widget
│ ├── hooks/
│ │ ├── use-widget-props.ts
│ │ └── use-theme.ts
│ ├── layout.tsx
│ └── page.tsx # Landing page
├── lib/
│ ├── config/
│ │ └── proxy.config.ts # Widget mappings
│ ├── proxy/
│ │ ├── inject-meta.ts # Metadata injection
│ │ └── widget-server.ts # Widget serving
│ └── types/
│ ├── mcp.ts # MCP protocol types
│ └── openai.ts # window.openai types
- axite-mcp-template - Full MCP server + widgets template (for building from scratch)
- OpenAI Apps SDK - Official documentation
- Apps SDK UI - Component library
MIT