A starter template for building ChatGPT apps with interactive widgets using MCP (Model Context Protocol) on Val Town.
- Backend: Hono + MCP Lite + Drizzle ORM + SQLite + Cloudinary
- Widget: React 19 + TanStack Router + OpenAI App SDK
Before using the OpenCloset functionality, add these environment variables in Val Town:
CLOUDINARY_CLOUD_NAME- Your Cloudinary cloud nameCLOUDINARY_API_KEY- Your Cloudinary API keyCLOUDINARY_API_SECRET- Your Cloudinary API secretCLOUDINARY_UPLOAD_PRESET- (Optional) Unsigned upload preset for easier setup
-
Visit your deployment URL to get the MCP endpoint
-
Add to ChatGPT:
- Open ChatGPT settings β Apps & Connectors -> Create (you will need developer mode enabled)
- Add MCP server with your endpoint URL
-
Test in ChatGPT:
- "List all messages"
- "Add a message: Hello!"
- "Show me message #1"
- "Show me a counter widget"
- "Show me a todo list"
- "Show me the weather"
- "Show me an item card"
- "Show me multiple wardrobe items"
- "Show me an outfit"
- Upload clothing photos and say "Add these to my closet as tops"
- "Suggest an outfit from my closet"
- "Show me all my closet items"
list_messages- Shows all messages in an interactive widgetadd_message- Adds a new message and shows updated listget_message- Shows details for a specific message
show_counter- Display an interactive counter with increment/decrement buttonsincrement_counter- Increment the counter by the step amountdecrement_counter- Decrement the counter by the step amountreset_counter- Reset the counter to zero
show_todos- Display an interactive todo list with progress tracking
show_weather- Display current weather information for a location
show_item_card- Display a single wardrobe item cardshow_multi_item_card- Display multiple wardrobe items in a gridshow_outfit_card- Display a complete outfit with top and bottom pieces
opencloset.capture_items- Upload and categorize clothing photos to your closetopencloset.suggest_outfit- Get outfit suggestions from your saved itemsopencloset.list_items- View all items in your closet (with optional category filter)
The project includes several React widget components:
- CounterWidget (
/counter) - Interactive counter with +/- buttons and reset - TodoWidget (
/todo) - Todo list with completion tracking and progress bar - WeatherWidget (
/weather) - Weather display with temperature, conditions, and stats - DemoWidget (
/demo) - Showcase page displaying all three widgets together - ItemCard - Individual wardrobe item display component
- MultiItemCard - Grid display for multiple wardrobe items
- OutfitCard - Complete outfit display with top and bottom pieces
Tools return discriminated unions that drive widget navigation:
{ kind: "message_list", messages: [...] } // β /list route
{ kind: "message_detail", id: 1, ... } // β /detail/:id route
{ kind: "counter", count: 5, step: 1 } // β /counter route
{ kind: "todo_list", todos: [...] } // β /todo route
{ kind: "weather", location: "NYC", ... } // β /weather route
{ kind: "item_card", item: {...} } // β Individual ItemCard component
{ kind: "multi_item_card", items: [...] } // β Individual MultiItemCard component
{ kind: "outfit_card", top: {...}, bottom: {...} } // β Individual OutfitCard component
The widget automatically navigates based on the kind field.
Messages are automatically scoped using the openai/subject field that ChatGPT includes in request metadata. This provides authless data isolation - each subject gets its own message board.
The scoping happens in tool handlers:
const subject = ctx.request.params._meta?.["openai/subject"];
const messages = await getMessages(subject);
The exact semantics of openai/subject are determined by ChatGPT.
- Add tool in
backend/mcp/server.ts - Define output schema in
shared/types.ts - Create widget component in
frontend/widgets/components/ - Add route in
frontend/widgets/routes.tsx - Update
NavigationSync.tsxto handle newkind
For detailed information about the OpenCloset digital wardrobe functionality, see:
- OPENCLOSET.md - Complete implementation guide
- TESTING.md - Testing instructions for all widgets
- TROUBLESHOOTING.md - Fix common Cloudinary setup issues