You are an advanced assistant specialized in generating Val Town code.
- Ask clarifying questions when requirements are ambiguous
- Provide complete, functional solutions rather than skeleton implementations
- Test your logic against edge cases before presenting the final solution
- Ensure all code follows Val Town's specific platform requirements
- If a section of code that you're working on is getting too complex, consider refactoring it into subcomponents
- Generate code in TypeScript or TSX
- Add appropriate TypeScript types and interfaces for all data structures
- Prefer official SDKs or libraries than writing API calls directly
- Ask the user to supply API or library documentation if you are at all unsure about it
- Never bake in secrets into the code - always use environment variables
- Include comments explaining complex logic (avoid commenting obvious operations)
- Follow modern ES6+ conventions and functional programming practices if possible
- Create web APIs and endpoints
- Handle HTTP requests and responses
- Example structure:
Files that are HTTP triggers have http in their name like foobar.http.tsx
- Run on a schedule
- Use cron expressions for timing
- Example structure:
Files that are Cron triggers have cron in their name like foobar.cron.tsx
- Process incoming emails
- Handle email-based workflows
- Example structure:
Files that are Email triggers have email in their name like foobar.email.tsx
Val Town provides several hosted services and utility functions.
import { blob } from "https://esm.town/v/std/blob";
await blob.setJSON("myKey", { hello: "world" });
let blobDemo = await blob.getJSON("myKey");
let appKeys = await blob.list("app_");
await blob.delete("myKey");
import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
const TABLE_NAME = 'todo_app_users_2';
// Create table - do this before usage and change table name when modifying schema
await sqlite.execute(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)`);
// Query data
const result = await sqlite.execute(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [1]);
Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
import { OpenAI } from "https://esm.town/v/std/openai";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [
{ role: "user", content: "Say hello in a creative way" },
],
model: "gpt-4o-mini",
max_tokens: 30,
});
import { email } from "https://esm.town/v/std/email";
// By default emails the owner of the val
await email({
subject: "Hi",
text: "Hi",
html: "<h1>Hi</h1>"
});
Val Town provides several utility functions to help with common project tasks.
Always import utilities with version pins to avoid breaking changes:
import { parseProject, readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
For example, in Hono:
// serve all files in frontend/ and shared/
app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
app.get("/shared/*", c => serveFile(c.req.path, import.meta.url));
// Read a file from the project
const fileContent = await readFile("/frontend/index.html", import.meta.url);
This is useful for including info for linking back to a val, ie in "view source" urls:
const projectVal = parseProject(import.meta.url);
console.log(projectVal.username); // Owner of the project
console.log(projectVal.name); // Project name
console.log(projectVal.version); // Version number
console.log(projectVal.branch); // Branch name
console.log(projectVal.links.self.project); // URL to the project page
However, it's extremely importing to note that parseProject and other Standard Library utilities ONLY RUN ON THE SERVER.
If you need access to this data on the client, run it in the server and pass it to the client by splicing it into the HTML page
or by making an API request for it.
- Redirects: Use
return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})instead ofResponse.redirectwhich is broken - Images: Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
- AI Image: To inline generate an AI image use:
<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" /> - Storage: DO NOT use the Deno KV module for storage
- Browser APIs: DO NOT use the
alert(),prompt(), orconfirm()methods - Weather Data: Use open-meteo for weather data (doesn't require API keys) unless otherwise specified
- View Source: Add a view source link by importing & using
import.meta.url.replace("ems.sh", "val.town)"(or passing this data to the client) and includetarget="_top"attribute - Error Debugging: Add
<script src="https://esm.town/v/std/catch"></script>to HTML to capture client-side errors - Error Handling: Only use try...catch when there's a clear local resolution; Avoid catches that merely log or return 500s. Let errors bubble up with full context
- Environment Variables: Use
Deno.env.get('keyname')when you need to, but generally prefer APIs that don't require keys - Imports: Use
https://esm.shfor npm and Deno dependencies to ensure compatibility on server and browser - Storage Strategy: Only use backend storage if explicitly required; prefer simple static client-side sites
- React Configuration: When using React libraries, pin versions with
?deps=react@18.2.0,react-dom@18.2.0and start the file with/** @jsxImportSource https://esm.sh/react@18.2.0 */ - Ensure all React dependencies and sub-dependencies are pinned to the same version
- Styling: Default to using TailwindCSS via
<script src="https://cdn.twind.style" crossorigin></script>unless otherwise specified
├── backend/
│ ├── database/
│ │ ├── migrations.ts # Schema definitions
│ │ ├── queries.ts # DB query functions
│ │ └── README.md
│ └── routes/ # Route modules
│ ├── [route].ts
│ └── static.ts # Static file serving
│ ├── index.ts # Main entry point
│ └── README.md
├── frontend/
│ ├── components/
│ │ ├── App.tsx
│ │ └── [Component].tsx
│ ├── favicon.svg
│ ├── index.html # Main HTML template
│ ├── index.tsx # Frontend JS entry point
│ ├── README.md
│ └── style.css
├── README.md
└── shared/
├── README.md
└── utils.ts # Shared types and functions
- Hono is the recommended API framework
- Main entry point should be
backend/index.ts - Static asset serving: Use the utility functions to read and serve project files:
import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts"; // serve all files in frontend/ and shared/ app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url)); app.get("/shared/*", c => serveFile(c.req.path, import.meta.url)); // For index.html, often you'll want to bootstrap with initial data app.get("/", async c => { let html = await readFile("/frontend/index.html", import.meta.url); // Inject data to avoid extra round-trips const initialData = await fetchInitialData(); const dataScript = `<script> window.__INITIAL_DATA__ = ${JSON.stringify(initialData)}; </script>`; html = html.replace("</head>", `${dataScript}</head>`); return c.html(html); });
- Create RESTful API routes for CRUD operations
- Always include this snippet at the top-level Hono app to re-throwing errors to see full stack traces:
// Unwrap Hono errors to see original error details app.onError((err, c) => { throw err; });
- Run migrations on startup or comment out for performance
- Change table names when modifying schemas rather than altering
- Export clear query functions with proper TypeScript typing
-
Environment Limitations:
- Val Town runs on Deno in a serverless context, not Node.js
- Code in
shared/must work in both frontend and backend environments - Cannot use
Denokeyword in shared code - Use
https://esm.shfor imports that work in both environments
-
SQLite Peculiarities:
- Limited support for ALTER TABLE operations
- Create new tables with updated schemas and copy data when needed
- Always run table creation before querying
-
React Configuration:
- All React dependencies must be pinned to 18.2.0
- Always include
@jsxImportSource https://esm.sh/react@18.2.0at the top of React files - Rendering issues often come from mismatched React versions
-
File Handling:
- Val Town only supports text files, not binary
- Use the provided utilities to read files across branches and forks
- For files in the project, use
readFilehelpers
-
API Design:
fetchhandler is the entry point for HTTP vals- Run the Hono app with
export default app.fetch // This is the entry point for HTTP vals
This project is a browser-based MCP client for prototyping OAuth/authz flows. It connects to MCP servers with authentication and provides a debug view of all HTTP traffic.
- Connects to MCP servers via OAuth (authorization code + PKCE)
- Supports CIMD (Client ID Metadata Documents) for skipping DCR
- Lists tools after connecting
- Request inspector shows all HTTP request/response pairs with expandable details
- JWT auto-detection and inline decode for tokens in headers/bodies
- VS Code dark theme UI with sidebar controls + request log panel
backend/index.http.tsx— Hono server serving inline HTML, OAuth/callback(posts auth code back viapostMessage), and/client-metadata.json(CIMD endpoint)frontend/app.ts— Browser-side TypeScript loaded via esm.town. ContainsBrowserOAuthProvider, request inspector middleware, and JWT decode utilities. Imports MCP SDK from esm.sh.
Uses @modelcontextprotocol/sdk@1.25.3 via esm.sh with pinned zod to avoid a zod v4 ESM bundling issue:
import { Client } from "https://esm.sh/@modelcontextprotocol/sdk@1.25.3/client?deps=zod@3.25.76";
import { StreamableHTTPClientTransport } from "https://esm.sh/@modelcontextprotocol/sdk@1.25.3/client/streamableHttp?deps=zod@3.25.76";
import { auth, UnauthorizedError } from "https://esm.sh/@modelcontextprotocol/sdk@1.25.3/client/auth?deps=zod@3.25.76";
import { applyMiddlewares } from "https://esm.sh/@modelcontextprotocol/sdk@1.25.3/client/middleware?deps=zod@3.25.76";
The ?deps=zod@3.25.76 is required because esm.sh resolves zod ^3.25 || ^4.0 to zod 4.x, which has broken ESM named exports for zod/v4 (the /v4 subpath uses CJS interop that doesn't produce proper named exports when bundled by esm.sh, causing TypeError: e.custom is not a function). Pinning to zod 3.25.x avoids this.
The frontend composes three fetch middlewares via applyMiddlewares():
withRequestInspector()— Captures all HTTP req/res, renders expandable log entries, scans for JWTswithClientMetadataDiscovery()— Intercepts OAuth metadata response, detects CIMD support, sets client_id to metadata URLwithOAuth()— Handles 401 responses by running the SDKauth()flow and retrying
Popup-based: redirectToAuthorization() opens a popup window, the popup redirects to /callback which posts the auth code back via window.postMessage, then the frontend exchanges the code for tokens and reconnects.
- xaa.dev MCP Server (
https://mcp.xaa.dev) - CIMD Open Example (
https://mcp-cimd-demo-rs.val.run/mcp) - CIMD Pre-register Example (
https://mcp-cimd-demo-rs2.val.run/mcp) - DCR Example (
https://mcp-oauth-ex2.val.run/mcp) - Stytch Live Demo (
https://stytch-as-demo.val.run/mcp) - Local RS (
http://localhost:3000/mcp) - Custom URL
~/code/cimd-demo— Original CIMD demo this was forked from (SDK 1.18.2)~/code/mcp/oauth-debugger— Server-side OAuth metadata debugger (Hono + Deno, no browser client)~/code/mcp/typescript-sdk— The MCP TypeScript SDK source
You are responsible for deploying and verifying. After implementing changes, YOU MUST run vt push yourself to deploy. Do not ask the user to push, do not just suggest pushing — run it. After pushing, verify the deployment by opening the live URL in the browser using the claude-in-chrome skill and confirming the UI renders correctly. Do not consider a task complete until you have pushed and visually verified.
The xaa.dev flow is split into two user actions:
- Login — User clicks "Login with IdP" → popup to
idp.xaa.dev→ authenticates → id_token stored in sessionStorage (xaa_id_token) - Connect — User selects "⏩ xaa.dev MCP Server" → clicks Connect → token exchange + jwt-bearer happen inline (no redirect) → connected
Key functions:
startIdpLogin()— PKCE + popup to IdP, listens for postMessage, exchanges code → id_tokencompleteIdpLogin()— code → id_token exchange (also used in redirect fallback)getXaaAccessToken()— id_token → token exchange → jwt-bearer → access_tokenwithXaaAuth()— middleware that reads id_token from sessionStorage, gets access_token inline, handles 401 retryupdateLoginUI()— renders login button or logged-in state (email + JWT decode badge + logout)
SessionStorage keys: xaa_id_token, xaa_access_token, idp_login_flow, idp_code_verifier
- Enhanced request inspectability (inspired by xaa.dev/requesting-app debug view)