Val Town深度实战:当「让代码动起来」成为AI编程的新范式——从Deno Runtime到MCP驱动的Serverless JavaScript全栈指南(2026)
引言:代码是惰性的,这是个问题
"Code is inert. How do you make it ert?"
— Paul Ford, What is Code
2015年,Paul Ford在Bloomberg上发表那篇著名的长文《What is Code》时,提出了一个深刻的问题:代码天生是惰性的——它躺在磁盘里,不运行,不响应,不与任何人交互。作为程序员,我们花了大量时间去"部署"代码,让它从静态文件变成活的服务。这个过程涉及服务器、容器、域名、DNS、CI/CD流水线……每一步都是摩擦力,都是障碍。
2026年的今天,这个问题的严重性被放大了无数倍。
当AI开始大规模生成代码,代码的生产速度已经远远超过人类部署代码的速度。据Paul Kinlan的估算,GitHub上约5%的公开提交已经由语言模型贡献——而且这只是未提交、未署名的LLM代码的冰山一角。更可怕的是,AI生成的代码比人类写的代码更容易成为"惰性代码":人类程序员至少知道代码在本地怎么跑,而AI生成的代码往往在"生成-复制-粘贴-放弃"的生命周期里迅速死去。
Val Town(val.town)正是为解决这个矛盾而生的平台。它的核心主张简单而有力:Val Town让代码动起来(makes code ert)。本文将深入剖析这个平台的技术架构、MCP集成、AI编程工作流,以及它如何重新定义"部署"这个概念。
一、Val Town是什么:重新定义Serverless JavaScript
1.1 产品定位
Val Town是一个运行在浏览器中的JavaScript代码编辑器和Serverless运行时。用户在这个平台上写代码、保存代码、运行代码——三件事是同一个动作。没有任何"部署"步骤,代码保存后100毫秒内就在云端运行了。
这不是一个玩具。Val Town支持:
- HTTP处理函数:写出
export default就能接收HTTP请求 - 定时任务(Cron Jobs):像写普通函数一样定义定时任务
- 数据库:内置SQLite和Blob存储
- 电子邮件处理:接收和发送邮件
- WebSocket:双向实时通信
- AI Agent集成:通过MCP协议与Claude Code等AI编程工具深度集成
从技术栈看,Val Town基于Deno运行时,支持现代JavaScript/TypeScript标准。这意味着:
// val.town 上的一个HTTP处理函数——保存即上线
export default async function(req: Request): Promise<Response> {
const { name } = await req.json();
return Response.json({
message: `Hello, ${name}! The code is ert.`,
timestamp: Date.now(),
runtime: "Deno"
});
}
保存这个函数后,立即可以通过https://yourname.val.town/访问。
1.2 技术架构解析
Val Town的架构设计非常精妙,它的每一层都服务于同一个目标:消除代码运行的摩擦。
运行时层:Deno
Val Town选择Deno而非Node.js作为底层运行时,是经过深思熟虑的决定。Deno的几个特性与Val Town的需求完美契合:
- 原生TypeScript支持:无需构建步骤,TypeScript代码直接运行
- 安全沙箱:默认不允许文件、网络、环境的任意访问,适合多租户Serverless场景
- 标准库完整:Deno标准库的质量和覆盖度相当高,减少了对npm生态的依赖
- 现代ES模块系统:Deno原生支持ES模块,URL导入和版本管理更清晰
// Deno原生支持ES模块,URL直接导入
import { Hono } from "https://deno.land/x/hono@v4.3.0/mod.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const app = new Hono();
app.get("/api/users/:id", async (c) => {
const id = c.req.param("id");
return c.json({ id, platform: "val.town", runtime: "deno" });
});
export default app.fetch;
执行模型:保存即运行
传统Serverless平台(Cloudflare Workers、Vercel Functions等)的执行模型是:写代码 → 构建 → 部署 → 等待生效。这个流程引入了不可忽视的延迟和不确定性。
Val Town的执行模型是:
- 用户在浏览器编辑器中编辑代码
- 每次保存(Ctrl/Cmd+S),代码通过WebSocket同步到服务器
- 服务器在100毫秒内完成代码的验证和加载
- 下一个HTTP请求即命中新代码
这个"即时生效"的特性,使得Val Town的开发体验非常接近本地开发,但又完全在云端运行。对于需要快速迭代的AI Agent工作流来说,这个特性简直是量身定做——AI可以在循环中不断修改代码,每次修改立即可见。
存储层:SQLite + Blob Storage
Val Town为每个val提供了持久化存储能力:
- SQLite:每个val有独立的SQLite数据库,可以存储结构化数据
- Blob Storage:大文件存储,适合AI Agent缓存、生成的图片等二进制数据
// 访问当前val的SQLite数据库
import {.Sqlite } from "https://esm.town/vrt/sqlite.ts";
export default async function(req: Request): Promise<Response> {
const db = await Sqlite.open("myapp");
// 初始化表
await db.execute(`
CREATE TABLE IF NOT EXISTS visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER,
user_agent TEXT
)
`);
// 记录访问
await db.execute({
sql: "INSERT INTO visits (timestamp, user_agent) VALUES (?, ?)",
args: [Date.now(), req.headers.get("user-agent") ?? "unknown"]
});
// 查询统计
const result = await db.select<{count: number}[]>(
"SELECT COUNT(*) as count FROM visits"
);
return Response.json({ visits: result[0].count });
}
1.3 与传统平台的对比
| 特性 | Val Town | Cloudflare Workers | Vercel Functions | Replit |
|---|---|---|---|---|
| 部署延迟 | <100ms | 数秒 | 数秒 | 秒级 |
| 原生TypeScript | ✅ | ✅ | ✅ | ✅ |
| 内置数据库 | SQLite + Blob | D1 (SQLite) | Postgres | Postgres |
| MCP集成 | ✅✅ | ❌ | ❌ | ❌ |
| 协作编辑 | ✅ (即将) | ❌ | ❌ | ✅ |
| 免费额度 | 足够个人用 | 100k req/天 | 100k req/月 | 0.5GB RAM |
| 定价策略 | 按量计费 | 按量计费 | 按量计费 | 订阅制 |
二、MCP协议:Val Town与AI Agent的深度集成
2.1 什么是MCP
MCP(Model Context Protocol)是Anthropic在2024年末推出的开放协议,用于让AI编程助手(如Claude Code)与外部工具和数据源进行标准化交互。MCP的核心价值是:让AI能够真正操作系统,而不只是生成代码文本。
打个比方:如果说传统AI编程助手是"用嘴指挥别人写代码",那么MCP加持的AI助手就是"直接用手写代码"——它不仅能生成代码,还能调用工具执行代码、读写文件、运行测试、管理数据库。
2.2 Val Town MCP Server的架构
Val Town在2025-2026年期间投入大量工程资源开发了功能完整的MCP Server,这个Server提供了40+个工具,涵盖了Val Town平台的全部核心能力:
MCP Server 工具集
├── 代码操作
│ ├── val_town_readCode — 读取指定val的源代码
│ ├── val_town_writeCode — 写入/更新指定val的代码
│ ├── val_town_runCode — 立即运行val并获取输出
│ └── val_town_remix — 复制一个val作为新项目起点
│
├── 存储操作
│ ├── val_town_sqliteRead — 查询SQLite数据库
│ ├── val_town_sqliteWrite — 执行SQL写入
│ └── val_town_blobRead/Write — Blob文件读写
│
├── 运行时控制
│ ├── val_town_cronCreate — 创建定时任务
│ ├── val_town_cronDelete — 删除定时任务
│ └── val_town_logs — 获取运行日志
│
└── 外部交互
├── val_town_httpGet/Post — 发起HTTP请求
└── val_town_visitUrl — 访问并解析URL内容
这个工具集的广度和深度令人印象深刻。基本上,在Val Town Web UI上能做的所有事情,都可以通过MCP协议远程完成。
2.3 Claude Code + Val Town:AI编程的完美闭环
在Val Town的2026年5月更新中,最重要的变化之一是将Claude Code推到了产品核心位置。现在,每个val的主页上都提供了一个可复制的命令,用于快速在Claude Code中连接Val Town MCP:
# 在Claude Code中执行这条命令,即可在对话中操作Val Town
npx @valTown/mcp-cli connect --token YOUR_TOKEN
连接建立后,Claude Code就可以:
场景一:AI帮你构建后端API
用户描述需求:"帮我做一个URL缩短服务,存储在SQLite里,支持自定义短码"——
用户 → Claude Code: 帮我做一个URL缩短服务
Claude Code → Val Town MCP:
1. 调用 val_town_writeCode 创建 urlShortener.ts
2. 调用 val_town_sqliteWrite 初始化数据库表
3. 调用 val_town_runCode 测试基本功能
4. 调用 val_town_httpPost 测试HTTP端点
Claude Code → 用户: "已创建 https://you.val.town/urlShortener"
场景二:AI直接修改运行中的代码
// 原始代码
export default async function(req: Request): Promise<Response> {
const name = await req.json();
return Response.json({ greeting: `Hello ${name}` });
}
// AI通过MCP修改后的代码(直接在生产环境)
export default async function(req: Request): Promise<Response> {
const { name, language = "en" } = await req.json();
const greetings: Record<string, string> = {
en: `Hello ${name}`,
zh: `你好,${name}`,
ja: `こんにちは、${name}さん`,
fr: `Bonjour ${name}`
};
return Response.json({
greeting: greetings[language] ?? greetings.en,
languages: Object.keys(greetings)
});
}
这个修改在保存后立即生效,无需任何构建、部署流程。AI可以像使用本地编辑器一样使用Val Town,唯一的区别是代码在云端运行。
2.4 Townie:Val Town原生的AI助手
除了MCP集成,Val Town还内置了一个名为Townie的AI助手。Townie直接集成在Val Town的编辑器界面中,用户可以在编写代码时随时召唤AI协助。
2026年5月的更新中,Townie迎来了重大升级:
- 默认模型升级到Claude Opus 4.6,支持Opus 4.7 + xhigh effort level
- 新增"Allow all"模式(aka YOLO模式):Townie在明确授权后可执行所有操作,无需逐次确认
- 按量计费:推理费用按成本价收取,无任何溢价
- 模型选择器:支持Opus 4.7、Sonnet 4.6、Haiku 4.5,满足不同场景的速度-成本权衡
// Townie模式下,AI可以直接修改代码——这是Val Town特有的能力
// 在编辑器中右键 → "Ask Townie" → "帮我把这个API改成支持分页"
// Townie会直接修改代码并保存
三、核心能力深度拆解
3.1 HTTP处理函数:Serverless端点的极简写法
Val Town的HTTP处理函数是平台最核心的能力。与传统框架的路由系统不同,Val Town鼓励一个val = 一个HTTP端点的极简设计哲学。
// 一个完整的RESTful API — 所有路由在同一个文件
import { Hono } from "https://deno.land/x/hono@v4.3.0/mod.ts";
import { Sqlite } from "https://esm.town/vrt/sqlite.ts";
const app = new Hono();
const db = await Sqlite.open("blog");
// 初始化数据库
await db.execute(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at INTEGER DEFAULT (unixepoch())
)
`);
// GET /posts — 列出所有文章
app.get("/posts", async (c) => {
const posts = await db.select<{id: number; title: string; created_at: number}[]>(
"SELECT id, title, created_at FROM posts ORDER BY created_at DESC"
);
return c.json({ posts });
});
// GET /posts/:id — 获取单篇文章
app.get("/posts/:id", async (c) => {
const id = c.req.param("id");
const post = await db.select<{id: number; title: string; content: string}[]>(
"SELECT * FROM posts WHERE id = ?", [id]
);
if (post.length === 0) {
return c.json({ error: "Post not found" }, 404);
}
return c.json({ post: post[0] });
});
// POST /posts — 创建文章
app.post("/posts", async (c) => {
const { title, content } = await c.req.json();
if (!title || !content) {
return c.json({ error: "title and content required" }, 400);
}
const result = await db.execute({
sql: "INSERT INTO posts (title, content) VALUES (?, ?)",
args: [title, content]
});
return c.json({ id: result.lastInsertId, title, content }, 201);
});
export default app.fetch;
这种写法的优雅之处在于:没有任何服务器配置,没有package.json,没有Dockerfile,没有CI/CD。你写的代码,就是运行中的API。
3.2 定时任务(Cron Jobs):优雅的后台处理
Val Town的Cron Job与HTTP处理函数共用同一套代码模型。要创建一个每小时执行的任务,只需:
// val.town 会自动识别这个函数为定时任务
// 通过 @cron 装饰器指定执行频率
export async function hourlyReport() {
const db = await Sqlite.open("analytics");
// 统计过去一小时的访问数据
const oneHourAgo = Date.now() - 3600 * 1000;
const stats = await db.select<{count: number}[]>(
"SELECT COUNT(*) as count FROM page_views WHERE timestamp > ?",
[oneHourAgo]
);
// 发送邮件报告(Val Town内置邮件功能)
await fetch("https://api.val.town/v1/sendEmail", {
method: "POST",
headers: { "Authorization": `Bearer ${Deno.env.get("SMTP_PASS")}` },
body: JSON.stringify({
to: "dev@example.com",
subject: `Hourly Report: ${stats[0].count} views`,
text: `过去1小时共 ${stats[0].count} 次页面访问`
})
});
console.log(`[${new Date().toISOString()}] Report sent: ${stats[0].count} views`);
}
在Val Town UI中,可以直观地设置Cron表达式(支持标准5段式Cron和自然语言描述):
hourlyReport — "Every hour" — ✅ Active
dailyCleanup — "Every day 3am" — ✅ Active
weeklyDigest — "Every Monday" — ⏸ Paused
3.3 Blob存储:AI Agent的数据后盾
Val Town的Scoped Blob Storage是2026年6月新增的功能。每个val现在都有自己独立的Blob存储空间,这意味着AI Agent可以在运行过程中存储和读取任意数据。
// 使用Blob存储AI生成的图片
import { BlobStorage } from "https://esm.town/vrt/blob.ts";
export async function generateAndStoreImage(prompt: string): Promise<string> {
// 调用AI图像生成API
const imageResponse = await fetch("https://api.stability.ai/v1/generation", {
method: "POST",
headers: {
"Authorization": `Bearer ${Deno.env.get("STABILITY_API_KEY")}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ prompt, width: 1024, height: 1024 })
});
const imageBuffer = await imageResponse.arrayBuffer();
// 存储到当前val的Blob空间
const blob = new BlobStorage();
const filename = `generated_${Date.now()}.png`;
await blob.set(filename, new Uint8Array(imageBuffer), "image/png");
// 获取访问URL
const url = await blob.url(filename);
return url;
}
3.4 邮件处理:Serverless邮件服务
Val Town还支持接收和处理邮件,这使得构建自动化邮件服务变得异常简单:
// 处理收到的邮件
export async function handleEmail(email: {
from: string;
subject: string;
text: string;
html: string;
attachments: { filename: string; content: Uint8Array }[]
}): Promise<{ action: string }> {
// 解析邮件内容,提取命令
const command = email.text.trim().toLowerCase();
if (command === "status") {
// 回复状态
const db = await Sqlite.open("monitor");
const stats = await db.select<{metric: string; value: number}[]>(
"SELECT metric, value FROM system_stats ORDER BY timestamp DESC LIMIT 5"
);
return {
action: "reply",
body: `System Status:\n${stats.map(s => `• ${s.metric}: ${s.value}`).join("\n")}`
};
}
if (command.startsWith("deploy ")) {
// 触发部署流程
const service = command.substring(7);
return {
action: "trigger_deploy",
service
};
}
return { action: "no_action" };
}
四、生产级项目实战:构建一个AI驱动的个人助手
4.1 项目需求
让我们用一个完整项目来展示Val Town的生产级使用方式:构建一个AI个人助手,它能够:
- 接收邮件指令
- 在数据库中管理任务列表
- 每天定时汇总任务状态并发送邮件报告
- AI Agent可以通过MCP随时查询和修改任务
4.2 项目结构
my-ai-assistant/
├── main.ts — HTTP入口,接收Web/API请求
├── email_handler.ts — 邮件处理逻辑
├── task_manager.ts — 任务CRUD操作
├── daily_report.ts — 每日报告生成和发送
└── db_schema.ts — 数据库初始化
4.3 数据库设计
// db_schema.ts
import { Sqlite } from "https://esm.town/vrt/sqlite.ts";
export async function initDatabase() {
const db = await Sqlite.open("ai_assistant");
await db.execute(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'done', 'cancelled')),
priority INTEGER DEFAULT 3 CHECK(priority BETWEEN 1 AND 5),
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch()),
completed_at INTEGER
)
`);
await db.execute(`
CREATE TABLE IF NOT EXISTS task_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER REFERENCES tasks(id),
action TEXT NOT NULL,
old_value TEXT,
new_value TEXT,
timestamp INTEGER DEFAULT (unixepoch())
)
`);
return db;
}
export type Task = {
id: number;
title: string;
description: string | null;
status: "pending" | "in_progress" | "done" | "cancelled";
priority: number;
created_at: number;
updated_at: number;
completed_at: number | null;
};
4.4 任务管理核心逻辑
// task_manager.ts
import { Sqlite } from "https://esm.town/vrt/sqlite.ts";
import { initDatabase, type Task } from "./db_schema.ts";
export async function createTask(title: string, description?: string, priority = 3): Promise<Task> {
const db = await initDatabase();
const result = await db.execute({
sql: "INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)",
args: [title, description ?? null, priority]
});
const task = await db.select<Task[]>(
"SELECT * FROM tasks WHERE id = ?", [result.lastInsertId]
);
await logHistory(db, result.lastInsertId, "created", null, { title, priority });
return task[0];
}
export async function updateTaskStatus(
taskId: number,
newStatus: Task["status"]
): Promise<Task | null> {
const db = await initDatabase();
const current = await db.select<Task[]>("SELECT * FROM tasks WHERE id = ?", [taskId]);
if (current.length === 0) return null;
const completedAt = newStatus === "done" ? Date.now() : null;
await db.execute({
sql: "UPDATE tasks SET status = ?, updated_at = unixepoch(), completed_at = ? WHERE id = ?",
args: [newStatus, completedAt, taskId]
});
await logHistory(db, taskId, "status_changed", current[0].status, newStatus);
const updated = await db.select<Task[]>("SELECT * FROM tasks WHERE id = ?", [taskId]);
return updated[0];
}
export async function listTasks(filters?: {
status?: Task["status"];
priority?: number;
limit?: number;
}): Promise<Task[]> {
const db = await initDatabase();
let sql = "SELECT * FROM tasks WHERE 1=1";
const args: (string | number)[] = [];
if (filters?.status) {
sql += " AND status = ?";
args.push(filters.status);
}
if (filters?.priority) {
sql += " AND priority = ?";
args.push(filters.priority);
}
sql += " ORDER BY priority ASC, created_at DESC";
if (filters?.limit) {
sql += ` LIMIT ${filters.limit}`;
}
return db.select<Task[]>(sql, args);
}
export async function generateDailyReport(): Promise<string> {
const db = await initDatabase();
const stats = await db.select<{
total: number;
pending: number;
in_progress: number;
done: number;
high_priority_pending: number;
}[]>(`
SELECT
COUNT(*) as total,
SUM(status = 'pending') as pending,
SUM(status = 'in_progress') as in_progress,
SUM(status = 'done') as done,
SUM(status = 'pending' AND priority <= 2) as high_priority_pending
FROM tasks
`);
const highPriorityTasks = await db.select<Task[]>(`
SELECT * FROM tasks
WHERE status = 'pending' AND priority <= 2
ORDER BY priority ASC
LIMIT 10
`);
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);
const todayTasks = await db.select<{count: number}[]>(`
SELECT COUNT(*) as count FROM tasks WHERE created_at >= ?
`, [Math.floor(todayStart.getTime() / 1000)]);
return `📊 AI Assistant 日报 — ${new Date().toLocaleDateString("zh-CN")}
📈 今日概况:
• 新建任务: ${todayTasks[0].count}
• 高优先级待办: ${stats[0].high_priority_pending}
• 进行中: ${stats[0].in_progress}
• 已完成: ${stats[0].done}
• 合计: ${stats[0].total}
🔥 高优先级待办:
${highPriorityTasks.map(t => ` [P${t.priority}] ${t.title}`).join("\n") || " 无"}`;
}
async function logHistory(
db: Awaited<ReturnType<typeof initDatabase>>,
taskId: number,
action: string,
oldValue: string | null,
newValue: unknown
) {
await db.execute({
sql: "INSERT INTO task_history (task_id, action, old_value, new_value) VALUES (?, ?, ?, ?)",
args: [taskId, action, oldValue, JSON.stringify(newValue)]
});
}
4.5 HTTP入口整合
// main.ts
import { Hono } from "https://deno.land/x/hono@v4.3.0/mod.ts";
import { createTask, updateTaskStatus, listTasks, generateDailyReport } from "./task_manager.ts";
const app = new Hono();
// POST /tasks — 创建任务
app.post("/tasks", async (c) => {
const { title, description, priority } = await c.req.json();
if (!title) return c.json({ error: "title is required" }, 400);
const task = await createTask(title, description, priority);
return c.json(task, 201);
});
// PATCH /tasks/:id — 更新状态
app.patch("/tasks/:id", async (c) => {
const id = parseInt(c.req.param("id"));
const { status } = await c.req.json();
if (!["pending", "in_progress", "done", "cancelled"].includes(status)) {
return c.json({ error: "Invalid status" }, 400);
}
const task = await updateTaskStatus(id, status);
if (!task) return c.json({ error: "Task not found" }, 404);
return c.json(task);
});
// GET /tasks — 列出任务
app.get("/tasks", async (c) => {
const status = c.req.query("status") as Task["status"] | undefined;
const priority = c.req.query("priority") ? parseInt(c.req.query("priority")!) : undefined;
const limit = c.req.query("limit") ? parseInt(c.req.query("limit")!) : undefined;
const tasks = await listTasks({ status, priority, limit });
return c.json({ tasks });
});
// GET /report — 生成日报
app.get("/report", async (c) => {
const report = await generateDailyReport();
return c.json({ report });
});
export default app.fetch;
五、AI Agent集成实战:用Claude Code驱动Val Town
5.1 工作流程
这是Val Town最激动人心的使用场景:AI Agent通过MCP协议完整控制Val Town上的应用。以下是典型的工作流:
用户 → Claude Code: "帮我创建一个图片水印服务,支持设置文字水印和透明度"
↓
Claude Code(思考):
1. 设计API接口(接收图片 + 水印文字 + 透明度)
2. 编写图像处理代码(使用Canvas/DENO)
3. 创建SQLite表存储使用记录
4. 部署到Val Town
↓
Claude Code → MCP工具链:
val_town_writeCode("watermark.ts", imageProcessingCode)
val_town_sqliteWrite(initTable)
val_town_runCode("watermark.ts")
val_town_httpPost("https://you.val.town/watermark", testData)
↓
Claude Code → 用户: "服务已上线: https://you.val.town/watermark"
5.2 完整AI Agent脚本示例
以下是一个Claude Code脚本,展示如何通过MCP批量操作Val Town vals:
/**
* Claude Code MCP Script: 批量构建AI工具集
* 在Claude Code中运行此脚本,自动在Val Town上部署多个AI工具
*/
// MCP工具引用(Claude Code会自动解析这些函数调用)
// const { val_town_writeCode, val_town_runCode, val_town_sqliteWrite } = mcpTools;
// 工具1: 智能摘要服务
const summarizerCode = `
import { Hono } from "https://deno.land/x/hono@v4.3.0/mod.ts";
import { Sqlite } from "https://esm.town/vrt/sqlite.ts";
const app = new Hono();
const db = await Sqlite.open("summarizer");
await db.execute(\`
CREATE TABLE IF NOT EXISTS summaries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
original_text TEXT,
summary TEXT,
model TEXT,
created_at INTEGER DEFAULT (unixepoch())
)
\`);
app.post("/", async (c) => {
const { text, model = "claude-3-5-sonnet" } = await c.req.json();
if (!text) return c.json({ error: "text required" }, 400);
// 调用AI API进行摘要
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": Deno.env.get("ANTHROPIC_API_KEY")!,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
body: JSON.stringify({
model,
max_tokens: 1024,
messages: [{
role: "user",
content: \`请用中文简洁地总结以下内容(100字以内):\n\n\${text}\`
}]
})
});
const result = await response.json();
const summary = result.content?.[0]?.text ?? "摘要生成失败";
await db.execute({
sql: "INSERT INTO summaries (original_text, summary, model) VALUES (?, ?, ?)",
args: [text.slice(0, 500), summary, model]
});
return c.json({ summary, id: db.lastInsertId });
});
app.get("/history", async (c) => {
const limit = parseInt(c.req.query("limit") ?? "10");
const history = await db.select("SELECT * FROM summaries ORDER BY created_at DESC LIMIT ?", [limit]);
return c.json({ history });
});
export default app.fetch;
`;
// 工具2: URL监控服务
const urlMonitorCode = `
import { Hono } from "https://deno.land/x/hono@v4.3.0/mod.ts";
import { Sqlite } from "https://esm.town/vrt/sqlite.ts";
const app = new Hono();
const db = await Sqlite.open("url_monitor");
await db.execute(\`
CREATE TABLE IF NOT EXISTS monitors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
interval_minutes INTEGER DEFAULT 60,
last_check INTEGER,
last_status INTEGER,
last_response_time_ms INTEGER,
is_alive BOOLEAN DEFAULT 1
)
\`);
// 添加监控目标
app.post("/monitors", async (c) => {
const { url, interval_minutes = 60 } = await c.req.json();
const result = await db.execute({
sql: "INSERT INTO monitors (url, interval_minutes) VALUES (?, ?)",
args: [url, interval_minutes]
});
return c.json({ id: result.lastInsertId, url, interval_minutes });
});
// 执行健康检查
app.post("/monitors/:id/check", async (c) => {
const id = parseInt(c.req.param("id"));
const monitors = await db.select<{url: string}[]>("SELECT url FROM monitors WHERE id = ?", [id]);
if (monitors.length === 0) return c.json({ error: "Not found" }, 404);
const start = Date.now();
try {
const response = await fetch(monitors[0].url, {
method: "HEAD",
signal: AbortSignal.timeout(10000)
});
const responseTime = Date.now() - start;
const isAlive = response.ok;
await db.execute({
sql: \`UPDATE monitors SET last_check = unixepoch(),
last_status = ?, last_response_time_ms = ?, is_alive = ? WHERE id = ?\`,
args: [response.status, responseTime, isAlive ? 1 : 0, id]
});
return c.json({ is_alive: isAlive, status: response.status, response_time_ms: responseTime });
} catch (e) {
await db.execute({
sql: "UPDATE monitors SET last_check = unixepoch(), is_alive = 0 WHERE id = ?",
args: [id]
});
return c.json({ is_alive: false, error: String(e) });
}
});
// 获取所有监控状态
app.get("/monitors", async (c) => {
const monitors = await db.select("SELECT * FROM monitors ORDER BY id");
return c.json({ monitors });
});
export default app.fetch;
`;
console.log("🚀 开始批量部署AI工具集...");
// await val_town_writeCode("aiSummarizer", summarizerCode);
// await val_town_writeCode("urlMonitor", urlMonitorCode);
// await val_town_sqliteWrite(initDatabases);
// await val_town_runCode("aiSummarizer");
// await val_town_runCode("urlMonitor");
console.log("✅ 所有工具已部署:");
console.log(" 摘要服务: https://yourname.val.town/aiSummarizer");
console.log(" URL监控: https://yourname.val.town/urlMonitor");
六、适用场景与局限性分析
6.1 Val Town最擅长的场景
AI Agent的专属后端:当AI需要存储数据、执行定时任务、发送通知时,Val Town是最便捷的后端选择。相比手动创建云服务,MCP集成让AI能自主完成这一切。
快速原型和小工具:不需要完整的项目管理,不需要Git仓库,几个val就能搭建一个完整的服务。非常适合hackathon和个人工具。
个人自动化:定时爬取数据、聚合RSS、处理邮件、自动发帖——这些"脚本级"需求Val Town比任何框架都更轻便。
团队共享工具:Val Town的vals可以设置为公开或私有,团队成员可以复用彼此的工具,形成一个"工具市场"。
6.2 Val Town的局限性
冷启动延迟:虽然运行延迟低,但Val Town作为Serverless平台,函数在闲置后再次调用时会有冷启动延迟。对于延迟敏感的实时应用,需要注意。
计算资源限制:Val Town没有公开具体的资源限制文档,但在高并发场景下可能出现资源争抢。生产级高流量应用仍建议使用传统云服务。
供应商锁定:运行在Val Town专有平台上,迁移到其他平台需要重写代码。相比使用标准框架(Express/Fastify)和标准云服务(Cloudflare/AWS),有一定锁定风险。
调试工具链:虽然日志功能在持续改进,但与本地开发相比,生产环境调试仍然不便。
七、与其他AI编程平台的横评
2026年的AI编程平台战场已经相当拥挤,Val Town需要与多个强劲对手竞争:
Val Town vs. Replit Agent
Replit是最早做AI编程的平台之一,优势在于完整的开发环境(IDE + 运行时 + 部署)。但Replit的Agent更偏向"全栈替代",而Val Town的MCP更偏向"工具扩展"。如果你需要AI在你的基础设施上操作,Val Town更合适。
Val Town vs. Cursor
Cursor是目前最火的AI IDE,但它是本地编辑器+云端AI的组合。Val Town的优势在于不需要本地环境——纯浏览器即可工作,而且部署和运行是无缝的。
Val Town vs. Bolt (Linear)
Bolt.diy是另一个纯浏览器AI编程平台,定位与Val Town接近。Val Town的优势在于MCP工具链的完整性和Deno运行时的成熟度;Bolt的优势在于更激进的AI全栈生成能力。
Val Town vs. Cloudflare Workers + Workers AI
这是"Serverless + AI"的组合方案。Val Town的优势是开发体验更流畅,MCP集成更原生;Cloudflare Workers的优势是全球CDN边缘部署,性能更强。
八、性能与安全:生产使用的关键考量
8.1 冷启动性能
Val Town的冷启动时间通常在200-500ms之间,相比传统容器Serverless(Cloudflare Workers在50ms左右)略慢,但比Lambda(经常1s+)快很多。对于非延迟敏感的API,这个差距感知不明显。
8.2 安全模型
Val Town的安全模型基于Deno的权限系统。每个val都运行在独立的沙箱中:
// Deno的权限模型 — Val Town在底层使用相同的安全策略
// 默认情况下,val中的代码无法:
// - 访问文件系统(除非通过Val Town提供的API)
// - 发起任意网络请求(只能访问显式授权的URL)
// - 读取环境变量(除非通过Deno.env.get()并已配置)
// 用户需要通过Val Town UI授权敏感权限
8.3 成本分析
Val Town采用按量计费模式:
- 推理费用:与AI模型的官方定价一致(无溢价)
- 运行费用:根据执行时间计费
- 存储费用:SQLite和Blob存储按容量计费
- 传出流量:按GB计费
对于个人用户和小型项目,免费额度通常足够使用。
九、未来展望:Val Town的演进方向
从Val Town官方博客2026年的更新来看,平台正在向以下几个方向演进:
9.1 协作编辑
Val Town正在开发多人实时协作编辑功能,类似于Figma或飞书文档的协作体验。这意味着Val Town不仅是一个代码平台,还可能成为团队共建AI Agent的协作空间。
9.2 更强的MCP工具
随着MCP协议的普及,Val Town正在扩展MCP工具集,包括:
- 更细粒度的版本控制(diff查看、单行回滚)
- 更好的调试工具(性能分析、内存使用可视化)
- 更丰富的AI集成(支持更多模型、更复杂的Agent工作流)
9.3 Teams与企业功能
2026年Val Town开始推出团队功能,支持:
- 团队vals(私有/公开/内部共享)
- 权限管理(谁可以修改、谁只能查看)
- 团队用量统计和计费
结语:让代码动起来
Paul Ford问的那个问题——"Code is inert. How do you make it ert?"——Val Town给出了一个相当有说服力的答案。
Val Town的核心价值不是它用了什么新技术(Web编辑器、Deno、MCP都不是新概念),而是它将这些技术以正确的方式组合在一起,创造了一种零摩擦的开发体验。代码保存即运行,AI能直接操作系统,协作可以像在线文档一样自然——这些特性单独看都不稀奇,但组合在一起,就形成了一个真正有差异化的产品。
2026年,当AI开始大规模参与代码生成时,"让代码动起来"的成本变得前所未有的重要。GitHub上5%的提交由AI贡献,但那5%中又有多少进入了生产环境?Val Town正是为了解决这个问题而生的:不仅让AI生成代码,更让AI生成的那些代码能够真正运行起来、真正服务于用户。
从这个角度看,Val Town代表的不仅是一个技术产品,更是一种关于"代码应该如何存在"的哲学立场:代码不应是躺在磁盘里的静态文本,而应是随时可以响应世界召唤的活的存在。
对于正在构建AI Agent工作流的开发者来说,Val Town是一个值得认真研究的平台。它可能不是所有场景的最佳选择,但在"MCP驱动的AI后端"这个赛道上,它目前是最成熟、最完整的解决方案之一。
让代码动起来,让AI帮你动起来。
参考资源:
- Val Town官方文档: https://docs.val.town
- Val Town博客: https://blog.val.town
- Val Town MCP文档: https://docs.val.town/guides/prompting/claude-code/
- Deno运行时文档: https://deno.land/manual
- MCP协议规范: https://modelcontextprotocol.io
标签:Val Town | Deno | Serverless | MCP | AI编程 | JavaScript | TypeScript | Claude Code | 2026技术