关于 stitch-sdk-development
面向 `@google/stitch-sdk` 贡献者的开发技能,讲解 SDK 生成流程、架构边界和实现实践。
主要功能
- 01三阶段 SDK 生成流程
- 02MCP 工具到领域对象的绑定指引
- 03生成代码完整性检查
- 04副作用功能实现模式
- 05红绿黄测试流程
适用场景
- 01将 Stitch 新工具加入 SDK
- 02设计领域类和方法绑定
- 03实现文件系统或网络扩展
- 04测试并验证 SDK 变更
MCP 相关
介绍如何从 Stitch MCP 服务捕获工具模式,并映射为生成的 SDK 类;也说明 `StitchToolClient` 的传输机制和 `StitchProxy` MCP 代理服务。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ google-labs-code/ stitch-sdk --skill stitch-sdk-development安装后在 Claude Code 中输入/stitch-sdk-development确认已加载。
详细介绍
Stitch SDK 开发
此技能涵盖开发 @google/stitch-sdk 所需的专业知识——核心系统、模式和理念。它不会枚举每个方法(代码库才是事实来源),而是教你如何理解这个系统。
生成流水线
领域层完全由生成器生成。不包含手写的领域类。流水线分为 3 个阶段:
Stage 1: Capture Stage 2: Domain Design Stage 3: Generate
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐
│ capture-tools.ts │──────▶│ domain-map.json │───────▶│ generate-sdk.ts │
│ │ │ (the IR) │ │ │
│ Connects to MCP │ │ Classes, bindings│ │ Deterministic │
│ server, calls │ │ arg routing, │ │ codegen into │
│ tools/list │ │ cache, extraction│ │ packages/sdk/generated/ │
└──────────────────┘ └──────────────────┘ └──────────────────────────┘
│ │ │
▼ ▼ ▼
tools-manifest.json domain-map.json packages/sdk/generated/src/*.ts
(raw MCP tool schemas) (tool→class mapping) (Stitch, Project, Screen)
阶段 1(bun scripts/capture-tools.ts):连接到在线 Stitch MCP 服务,调用 tools/list,并写入 tools-manifest.json。这是现有工具的事实来源。
阶段 2(Agent/人工):读取清单并生成 domain-map.json——中间表示。判断工作就在此进行:每个工具映射到哪个类、哪些参数来自 self、param 或 computed、如何提取返回值,以及缓存哪些数据。
阶段 3(bun scripts/generate-sdk.ts):确定性代码生成。读取清单和 domain-map,在 packages/sdk/generated/src/ 中生成 TypeScript 类。不涉及大模型——纯模板展开。
完整性:stitch-sdk.lock 记录所有输入和输出的 SHA-256 哈希值。bun scripts/validate-generated.ts 用于验证一致性。在 CI 中运行,以防止发布过时的代码。
支持新工具
当 Stitch MCP 服务添加新工具时:
- 运行阶段 1,捕获更新后的清单
- 运行阶段 2:在
domain-map.json中为新工具添加绑定 - 运行阶段 3,重新生成 SDK 类
- 运行
validate-generated.ts,确认一致性 - 按需更新测试
Domain Map IR
domain-map.json 表示两件事:
类:存在哪些领域对象,以及如何构造它们。
{
"Screen": {
"constructorParams": ["projectId", "screenId"],
"parentField": "projectId",
"reference": { "keys": ["projectId", "screenId"] }
}
}
绑定:MCP 工具如何映射到类方法。
{
"tool": "generate_screen_from_text",
"class": "Project",
"method": "generate",
"args": {
"projectId": { "from": "self" },
"prompt": { "from": "param" },
"name": {
"from": "computed",
"template": "projects/{projectId}/screens/{screenId}"
}
},
"returns": {
"class": "Screen",
"projection": [
{ "prop": "outputComponents", "index": 0 },
{ "prop": "design" },
{ "prop": "screens", "index": 0 }
]
}
}
参数路由:self = 从 this 注入,param = 由调用方传入,computed = 在调用时根据模板构建,selfArray = 将 [this.field] 包装为数组。
响应投影:根据 outputSchema 验证的结构化 ProjectionStep[] 数组。单个项目使用 index,数组使用 each。空的 [] = 直接返回。
缓存:方法可以指定带有结构化 projection 的 cache,以便在调用 API 前检查 this.data:
{
"cache": {
"projection": [{ "prop": "htmlCode" }, { "prop": "downloadUrl" }],
"description": "Use cached download URL from generation response"
}
}
双模态
此 SDK 服务于两类需求各异的使用方:
Agent 模态 — StitchToolClient
适用于 AI Agent 和编排脚本。原始工具管道。Agent 接收工具架构、构建 JSON、发送 JSON,并获取 JSON 响应。无需领域知识。
快速入门(单例)
stitch 单例通过 Proxy 同时提供领域方法和工具方法。无需实例化——它会在首次访问时从环境变量中延迟创建 StitchToolClient。
import { stitch } from "@google/stitch-sdk";
// Discover available tools
const { tools } = await stitch.listTools();
// Call any tool by name with a JSON payload
const result = await stitch.callTool("generate_screen_from_text", {
projectId: "123",
prompt: "A login page",
});
// Clean up when done
await stitch.close();
单例从环境中读取 STITCH_API_KEY(或 STITCH_ACCESS_TOKEN + GOOGLE_CLOUD_PROJECT)。设置 STITCH_BASE_URL 可覆盖服务 URL。(STITCH_HOST 是已弃用的别名,将在 2.0 中移除。)
直接实例化
如需显式控制(多个客户端、自定义配置、测试),可直接实例化 StitchToolClient:
import { StitchToolClient } from "@google/stitch-sdk";
const client = new StitchToolClient({ apiKey: "my-key" });
const tools = await client.listTools();
const result = await client.callTool("create_project", { title: "My App" });
await client.close();
配置解析:构造函数参数 → 环境变量 → 默认值。身份验证需要 apiKey,或 accessToken + projectId(构造时通过 Zod 验证)。
连接:callTool 和 listTools 会在首次调用时自动连接。并发调用通过基于 Promise 的锁安全地共享连接。
AI SDK 适配器 — stitchTools()
适用于基于 Vercel AI SDK 构建的 Agent。将 MCP 工具架构转换为兼容 AI SDK 的工具定义,可与 generateText() 即插即用。
import { stitchTools } from "@google/stitch-sdk/ai";
import { generateText } from "ai";
import { google } from "@ai-sdk/google";
const result = await generateText({
model: google("gemini-2.0-flash"),
tools: stitchTools(), // all tools
// or: stitchTools({ include: ["create_project"] }) // filtered
prompt: "Create a project called My App",
});
stitchTools() 从 /ai 子路径导出,使 ai 依赖保持可选。它在内部使用相同的共享 StitchToolClient 单例。
/tools 子路径提供 O(1) 工具查找,并预先解析参数——静态、无需身份验证、不会发起网络调用:
import { toolMap } from "@google/stitch-sdk/tools";
const tool = toolMap.get("create_project");
tool.params; // ToolParam[] — flat, pre-parsed
tool.params.filter((p) => p.required); // required params only
tool.inputSchema; // raw ToolInputSchema still available
toolDefinitions 和 toolMap 均从 @google/stitch-sdk/tools 子路径导出(不放在根入口中,以保持精简)。
SDK 模态 — 生成的领域类
适用于需要编写精确程序化脚本的开发者。基于 callTool 生成的领域门面。参数带类型,返回领域对象,失败时抛出 StitchError。
const project = await stitch.createProject({ title: "My App" });
const generation = await project.generate("A login page");
const html = await generation.first.getHtml();
两种模态底层共用 StitchToolClient。领域类是在 callTool 之上的类型化层。
错误处理 — 在边界处抛出
所有生成的方法都使用 throw StitchError 处理错误。不采用 Result<T> 模式。
// Generated method pattern (inside each method):
try {
const raw = await this.client.callTool<any>("tool_name", args);
return /* extracted result */;
} catch (error) {
throw StitchError.fromUnknown(error);
}
StitchError.fromUnknown() 确保所有错误都规范化为 StitchError,并包含代码、消息和可恢复性提示。
基础设施(手写)
以下组件仍为手写,因为它们提供基础支撑:
StitchToolClient— MCP 传输、身份验证、工具调用StitchError— 带有代码、消息和恢复提示的类型化错误类StitchProxy— MCP 代理服务,用于将 Stitch 重新提供给其他 Agentsingleton.ts— 为stitch导出提供带环境变量配置的惰性代理
副作用边界
SDK 分为两个半球,由正式边界隔开:
- 生成半球 — 每个 MCP 工具调用、响应投影、参数路由和类构造函数。若发送 JSON 并接收 JSON,则必须生成。
- 手写半球 — 会影响现实世界的操作:文件系统 I/O、二进制流、非 MCP REST 端点。若涉及磁盘或使用私有 API,则必须手写。
该边界通过 domain-map.json 中任意带有 extensionPath 的类的 sideEffects 声明:
{
"Project": {
"extensionPath": "../../src/project-ext.js",
"sideEffects": [
{
"method": "upload",
"reason": "private_rest",
"specPath": "src/spec/upload.ts"
},
{
"method": "downloadAssets",
"reason": "filesystem_io",
"specPath": "src/spec/download.ts"
}
]
}
}
有效的 reason 值:filesystem_io、binary_data、private_rest、complex_orchestration。
生成器在第 3 阶段进行验证:
- 没有
sideEffect.method与生成的绑定方法名称冲突 - 每个
specPath都指向现有文件
添加新的副作用方法
遵循 Spec → Handler → Extension 模式:
-
创建 Spec(
src/spec/my-operation.ts):定义输入架构(Zod)、错误代码、Result 类型和接口。Handler 必须实现此接口,且不得抛出异常。 -
创建 Handler(
src/my-operation-handler.ts):实现 Spec 接口。所有失败都返回Result<T>,绝不使用throw。副作用逻辑(磁盘、网络、二进制数据)在此处实现。 -
添加到 Extension(
src/project-ext.ts):编写精简适配器(每个方法体少于 15 行),用于解析输入、调用 Handler,并在失败时将 Result 映射为throw StitchError。 -
在 domain-map.json 中声明:添加包含
method、reason和specPath的sideEffect条目。 -
重新生成:运行
bun scripts/generate-sdk.ts— 生成器会验证该声明。
规则
| 规则 | 理由 |
|---|---|
| Extension 方法不得覆盖生成的方法 | 防止静默遮蔽 |
| Extension 方法必须委托给 Handler | 防止内联业务逻辑 |
| Handler 必须实现 Spec 接口 | 类型化服务契约 |
| Handler 必须返回 Result,绝不抛出 | 保持错误接口一致 |
Extension 不得从 singleton.ts 导入 | 防止循环依赖 |
交通灯实现(红 → 绿 → 黄)
实现新功能或修复错误时,请遵循交通灯模式:
🔴 红色 — 编写失败测试
先编写测试。测试必须失败。这会在实现开始前定义契约。
# Unit tests for generated classes
npx vitest run test/unit/sdk.test.ts
# → FAIL (new method doesn't exist yet)
# E2E test for the public API
bun scripts/e2e-test.ts
# → FAIL (method doesn't exist yet)
🟢 绿色 — 实现
- 在
domain-map.json中添加绑定(阶段 2) - 运行
bun scripts/generate-sdk.ts(阶段 3) - 更新测试以验证行为正确
npx vitest run # All tests pass
bun scripts/e2e-test.ts # E2E passes
🟡 黄色 — 重构 / 优化 / 复查
以通过的测试作为安全保障:
- 为清晰起见进行重构(提取辅助函数、简化参数)
- 添加边界情况测试
- 运行
npx tsc验证类型安全性 - 运行
bun scripts/validate-generated.ts验证流水线完整性
熟悉代码库
直接阅读代码库,了解当前状态。关键入口包括:
- 公共接口:从
packages/sdk/src/index.ts开始——这里列出了所有公共导出 - 生成的类:
packages/sdk/generated/src/— Stitch、Project、Screen、DesignSystem - 流水线产物:
packages/sdk/generated/domain-map.json、packages/sdk/generated/tools-manifest.json - 基础设施:
packages/sdk/src/client.ts、packages/sdk/src/spec/errors.ts、packages/sdk/src/singleton.ts - 测试结构:单元测试位于
packages/sdk/test/unit/,实时测试位于packages/sdk/test/integration/ - 可用命令:阅读
package.json中的scripts字段
不要依赖缓存的文件或目录树描述。请阅读源代码。
导入约定
为兼容 ESM,请使用 .js 扩展名:
import { StitchError } from "../../src/spec/errors.js"; // ✓
import { StitchError } from "../../src/spec/errors"; // ✗