• Townie
    AI
  • Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
damixnr

damixnr

OpenCloset

Remix of laurynas/ChatAppSDKStarter
Public
Like
OpenCloset
Home
Code
10
backend
5
frontend
2
shared
4
.vtignore
OPENCLOSET.md
README.md
TESTING.md
TROUBLESHOOTING.md
WIDGET_REGISTRATION.md
deno.json
Branches
1
Pull requests
Remixes
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
README.md

Val Town ChatGPT App SDK Starter

A starter template for building ChatGPT apps with interactive widgets using MCP (Model Context Protocol) on Val Town.

Tech Stack

  • Backend: Hono + MCP Lite + Drizzle ORM + SQLite + Cloudinary
  • Widget: React 19 + TanStack Router + OpenAI App SDK

Quick Start

Environment Variables

Before using the OpenCloset functionality, add these environment variables in Val Town:

  • CLOUDINARY_CLOUD_NAME - Your Cloudinary cloud name
  • CLOUDINARY_API_KEY - Your Cloudinary API key
  • CLOUDINARY_API_SECRET - Your Cloudinary API secret
  • CLOUDINARY_UPLOAD_PRESET - (Optional) Unsigned upload preset for easier setup

Setup

  1. Visit your deployment URL to get the MCP endpoint

  2. Add to ChatGPT:

    • Open ChatGPT settings β†’ Apps & Connectors -> Create (you will need developer mode enabled)
    • Add MCP server with your endpoint URL
  3. 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"

Available Tools

Message Board

  • list_messages - Shows all messages in an interactive widget
  • add_message - Adds a new message and shows updated list
  • get_message - Shows details for a specific message

Counter Widget

  • show_counter - Display an interactive counter with increment/decrement buttons
  • increment_counter - Increment the counter by the step amount
  • decrement_counter - Decrement the counter by the step amount
  • reset_counter - Reset the counter to zero

Todo Widget

  • show_todos - Display an interactive todo list with progress tracking

Weather Widget

  • show_weather - Display current weather information for a location

Wardrobe Widgets

  • show_item_card - Display a single wardrobe item card
  • show_multi_item_card - Display multiple wardrobe items in a grid
  • show_outfit_card - Display a complete outfit with top and bottom pieces

OpenCloset Tools (Real Functionality)

  • opencloset.capture_items - Upload and categorize clothing photos to your closet
  • opencloset.suggest_outfit - Get outfit suggestions from your saved items
  • opencloset.list_items - View all items in your closet (with optional category filter)

Widget Components

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

How It Works

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.

Message Scoping

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.

Where do I go from here?

  1. Add tool in backend/mcp/server.ts
  2. Define output schema in shared/types.ts
  3. Create widget component in frontend/widgets/components/
  4. Add route in frontend/widgets/routes.tsx
  5. Update NavigationSync.tsx to handle new kind

OpenCloset Documentation

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
FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
Β© 2025 Val Town, Inc.