Public
Like
1
ChatAppSDKStarter
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.
Viewing readonly version of main branch: v91View latest version
Full-stack message board with ChatGPT widget integration built for Val Town.
- Hono web framework
- MCP Lite server (Model Context Protocol)
- Drizzle ORM with SQLite
- Deno runtime
- React 19
- TanStack Router (code-first routing)
- TanStack Query (server state management)
- Tailwind CSS
- TypeScript
- React 19 with runtime transpilation
- TanStack Router for widget navigation
- OpenAI App SDK integration
- No build step required
- Message board with persistent storage
- ChatGPT integration via MCP server
- Interactive widgets in ChatGPT conversations
- Client-side routing
- Optimistic updates
- Server-side data injection
- Type-safe database operations
├── backend/ # Hono server running on Val Town
│ ├── database/ # Drizzle schema, migrations, and queries
│ ├── mcp/ # MCP server with ChatGPT tools and widgets
│ └── index.ts # Main Hono application
├── frontend/ # Message board React app
│ ├── components/ # React components
│ ├── lib/ # Utilities and hooks
│ └── router.tsx # TanStack Router configuration
├── widget/ # ChatGPT widget (runtime transpilation)
│ ├── components/ # Widget React components
│ ├── routes.tsx # Widget router configuration
│ └── index.tsx # Widget entry point
└── shared/ # Code shared between all parts
GET /- Serves the React application with initial dataGET /api/messages- Fetch all messages (JSON)POST /api/messages- Create a new messageGET /public/**- Static assets (CSS, JS, etc.)
POST /mcp- MCP server endpoint for ChatGPTGET /widget-assets/**- Widget assets (auto-transpiled TSX → JS)
- Read-only filesystem after deployment
- ESM imports via esm.sh for npm packages
- Code-first routing (no file-based routing)
- Runtime transpilation for TypeScript/TSX (no build step)
const homeRoute = new Route({
getParentRoute: () => rootRoute,
path: "/",
component: () => <App {...window.__INITIAL_DATA__} />
});
export function usePostMessage() {
return useMutation({
mutationFn: (content: string) => postMessage(content),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["messages"] });
},
});
}
export const messages = sqliteTable("messages", {
id: integer("id").primaryKey({ autoIncrement: true }),
content: text("content").notNull(),
timestamp: text("timestamp").notNull().default(new Date().toISOString()),
});
-
Deploy to Val Town:
vt push -
Get your Val URL:
https://yourname-projectname.val.run -
Add MCP server to ChatGPT:
- Go to ChatGPT settings
- Add MCP server:
https://yourname-projectname.val.run/mcp
-
Use in ChatGPT:
User: "List all messages" ChatGPT: [Calls list_messages tool] [Interactive widget appears showing messages]
list_messages- Shows interactive message list widgetadd_message- Adds a message and shows updated listgreet- Personalized greeting widget
The widget uses runtime transpilation - no build step required:
- MCP server returns HTML widget resource
- ChatGPT iframe loads widget HTML
- Browser requests TSX files from
/widget-assets/* - Val Town's
serveFiletranspiles TSX → JS automatically - React app hydrates with tool output
- TanStack Router navigates based on output
kind
See widget/README.md for detailed documentation.
# Pull from Val Town vt pull # Watch for changes and auto-deploy vt watch # Edit files locally - changes deploy in ~100ms
Alternatively, edit directly on val.town with instant deployment.