stitch-sdk-development
$
npx mdskill add google-labs-code/stitch-sdk/stitch-sdk-developmentBuild deterministic Stitch SDK code via generated pipelines.
- Develops core systems without handwritten domain classes.
- Integrates MCP servers for tool capture and generation.
- Executes three-stage workflow from capture to code output.
- Delivers deterministic packages into the generated directory.
SKILL.md
.github/skills/stitch-sdk-developmentView on GitHub ↗
---
name: stitch-sdk-development
description: Develop the Stitch SDK. Covers the generation pipeline, dual modality (agent vs SDK), error handling, and Traffic Light (Red-Green-Yellow) implementation workflow. Use when adding features, fixing bugs, or understanding the architecture.
---
# Stitch SDK Development
This skill encodes the expertise needed to develop `@google/stitch-sdk` — the core systems, patterns, and philosophies. It does not enumerate every method (the codebase is the source of truth for that). It teaches you how to think about the system.
---
## The Generation Pipeline
The domain layer is **fully generated**. No handwritten domain classes. The pipeline has 3 stages:
```
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)
```
**Stage 1** (`bun scripts/capture-tools.ts`): Connects to the live Stitch MCP server, calls `tools/list`, writes `tools-manifest.json`. Source of truth for what tools exist.
**Stage 2** (agent/human): Reads the manifest and produces `domain-map.json` — the intermediate representation. This is where judgment lives: which tool maps to which class, what args come from `self` vs `param` vs `computed`, how to extract the return value, and what data to cache.
**Stage 3** (`bun scripts/generate-sdk.ts`): Deterministic codegen. Reads manifest + domain-map, emits TypeScript classes in `packages/sdk/generated/src/`. No LLM involved — pure template expansion.
**Integrity**: `stitch-sdk.lock` records SHA-256 hashes of all inputs and outputs. `bun scripts/validate-generated.ts` verifies consistency. Run in CI to prevent publishing stale code.
### Supporting a New Tool
When the Stitch MCP server adds a new tool:
1. Run Stage 1 to capture the updated manifest
2. Run Stage 2: add a binding in `domain-map.json` for the new tool
3. Run Stage 3 to regenerate the SDK classes
4. Run `validate-generated.ts` to confirm consistency
5. Update tests as needed
### The Domain Map IR
`domain-map.json` expresses two things:
**Classes**: What domain objects exist and how they're constructed.
```json
{
"Screen": {
"constructorParams": ["projectId", "screenId"],
"fieldMapping": {
"projectId": { "from": "projectId" },
"screenId": {
"from": "id",
"fallback": { "field": "name", "splitOn": "/screens/" }
}
},
"parentField": "projectId",
"idField": "screenId"
}
}
```
**Bindings**: How MCP tools map to class methods.
```json
{
"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 }
]
}
}
```
**Arg routing**: `self` = injected from `this`, `param` = passed by the caller, `computed` = built from a template at call time, `selfArray` = `[this.field]` wrapped as array.
**Response projections**: Structured `ProjectionStep[]` arrays validated against `outputSchema`. Use `index` for single items, `each` for arrays. Empty `[]` = direct return.
**Cache**: Methods can specify a `cache` with a structured `projection` to check `this.data` before calling the API:
```json
{
"cache": {
"projection": [{ "prop": "htmlCode" }, { "prop": "downloadUrl" }],
"description": "Use cached download URL from generation response"
}
}
```
---
## Dual Modality
The SDK serves two distinct consumers with different needs:
### Agent Modality — `StitchToolClient`
For AI agents and orchestration scripts. Raw tool pipe. The agent receives tool schemas, constructs JSON, sends it, gets JSON back. No domain knowledge required.
#### Quick Start (Singleton)
The `stitch` singleton exposes both domain methods and tool methods via a `Proxy`. No instantiation needed — it lazily creates a `StitchToolClient` from env vars on first access.
```typescript
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();
```
The singleton reads `STITCH_API_KEY` (or `STITCH_ACCESS_TOKEN` + `GOOGLE_CLOUD_PROJECT`) from the environment. Set `STITCH_HOST` to override the server URL.
#### Direct Instantiation
For explicit control (multiple clients, custom config, testing), instantiate `StitchToolClient` directly:
```typescript
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();
```
**Config resolution**: Constructor params → env vars → defaults. Auth requires either `apiKey` or `accessToken` + `projectId` (validated via Zod at construction time).
**Connection**: `callTool` and `listTools` auto-connect on first call. Concurrent calls safely share the connection via a promise-based lock.
#### AI SDK Adapter — `stitchTools()`
For agents built on the [Vercel AI SDK](https://sdk.vercel.ai/). Transforms MCP tool schemas into AI SDK-compatible tool definitions, enabling plug-and-play with `generateText()`.
```typescript
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()` is exported from the `/ai` subpath to keep the `ai` dependency optional. It uses the same shared `StitchToolClient` singleton internally.
`stitch.toolMap` provides O(1) tool lookup with pre-parsed params — static, auth-free, no network call:
```typescript
const tool = stitch.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
```
The raw `toolDefinitions` array and standalone `toolMap` are also exported from the main entry point.
### SDK Modality — Generated Domain Classes
For humans writing precise, programmatic scripts. Generated domain facade over `callTool`. Typed parameters, domain objects returned, `StitchError` thrown on failure.
```typescript
const project = await stitch.createProject("My App");
const screen = await project.generate("A login page");
const html = await screen.getHtml();
```
Both modalities share `StitchToolClient` underneath. The domain classes are a typed layer over `callTool`.
### Error Handling — Throws at the Boundary
All generated methods use `throw StitchError` for error handling. No `Result<T>` pattern.
```typescript
// 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()` ensures all errors are normalized to `StitchError` with a code, message, and recoverability hint.
### Infrastructure (Handwritten)
These components remain handwritten as they provide foundational plumbing:
- `StitchToolClient` — MCP transport, auth, tool invocation
- `StitchError` — typed error class with codes, messages, recovery hints
- `StitchProxy` — MCP proxy server for re-exposing Stitch to other agents
- `singleton.ts` — lazy proxy for `stitch` export with env var config
### The Side-Effect Membrane
The SDK has two hemispheres separated by a formal boundary:
- **Generated Hemisphere** — Every MCP tool call, response projection, arg routing, and class constructor. If it sends JSON and gets JSON back, it MUST be generated.
- **Handwritten Hemisphere** — Operations that touch the real world: filesystem I/O, binary streams, non-MCP REST endpoints. If it touches disk or uses a private API, it MUST be handwritten.
The membrane is declared in `domain-map.json` via `sideEffects` on any class with an `extensionPath`:
```json
{
"Project": {
"extensionPath": "../../src/project-ext.js",
"sideEffects": [
{
"method": "uploadImage",
"reason": "private_rest",
"specPath": "src/spec/upload.ts"
},
{
"method": "downloadAssets",
"reason": "filesystem_io",
"specPath": "src/spec/download.ts"
}
]
}
}
```
Valid `reason` values: `filesystem_io`, `binary_data`, `private_rest`, `complex_orchestration`.
The generator validates at Stage 3:
1. No `sideEffect.method` collides with a generated binding method name
2. Each `specPath` points to an existing file
#### Adding a New Side-Effect Method
Follow the Spec → Handler → Extension pattern:
1. **Create the Spec** (`src/spec/my-operation.ts`): Define input schema (Zod), error codes, Result type, and interface. The Handler must implement this interface and never throw.
2. **Create the Handler** (`src/my-operation-handler.ts`): Implement the Spec interface. All failures return `Result<T>`, never `throw`. This is where side-effect logic lives (disk, network, binary).
3. **Add to the Extension** (`src/project-ext.ts`): A thin adapter (< 15 lines per method body) that parses input, calls the Handler, and maps the Result to `throw StitchError` on failure.
4. **Declare in domain-map.json**: Add a `sideEffect` entry with `method`, `reason`, and `specPath`.
5. **Regenerate**: Run `bun scripts/generate-sdk.ts` — the generator validates the declaration.
#### Rules
| Rule | Rationale |
| ----------------------------------------------------- | ------------------------------ |
| Extension methods must NOT override generated methods | Prevents silent shadowing |
| Extension methods must delegate to a Handler | Prevents inline business logic |
| Handlers must implement a Spec interface | Typed service contract |
| Handlers must return Result, never throw | Consistent error surface |
| Extensions must NOT import from `singleton.ts` | Prevents circular dependencies |
---
## Traffic Light Implementation (Red → Green → Yellow)
When implementing a new feature or fixing a bug, follow the Traffic Light pattern:
### 🔴 Red — Write Breaking Tests
Write the test first. It must fail. This defines the contract before any implementation exists.
```bash
# 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)
```
### 🟢 Green — Implement
1. Add a binding to `domain-map.json` (Stage 2)
2. Run `bun scripts/generate-sdk.ts` (Stage 3)
3. Update tests to verify correct behavior
```bash
npx vitest run # All tests pass
bun scripts/e2e-test.ts # E2E passes
```
### 🟡 Yellow — Refactor / Refine / Revisit
With passing tests as your safety net:
- Refactor for clarity (extract helpers, simplify args)
- Add edge case tests
- Run `npx tsc` to verify type safety
- Run `bun scripts/validate-generated.ts` to verify pipeline integrity
---
## Orienting in the Codebase
Discover the current state by reading the codebase directly. The key entry points:
- **Public surface**: Start at `packages/sdk/src/index.ts` — every public export is listed here
- **Generated classes**: `packages/sdk/generated/src/` — Stitch, Project, Screen, DesignSystem
- **Pipeline artifacts**: `packages/sdk/generated/domain-map.json`, `packages/sdk/generated/tools-manifest.json`
- **Infrastructure**: `packages/sdk/src/client.ts`, `packages/sdk/src/spec/errors.ts`, `packages/sdk/src/singleton.ts`
- **Test structure**: `packages/sdk/test/unit/` for unit tests, `packages/sdk/test/integration/` for live tests
- **Available commands**: Read the `scripts` field in `package.json`
Do not rely on cached descriptions of files or directory trees. Read the source.
## Import Convention
Use `.js` extensions for ESM compatibility:
```typescript
import { StitchError } from "../../src/spec/errors.js"; // ✓
import { StitchError } from "../../src/spec/errors"; // ✗
```
More from google-labs-code/stitch-sdk
- github-codebase-briefing>-
- stitch-sdk-bug-bashFind bugs in the Stitch SDK using a real API key. Covers standard functional edges and tricky situations.
- stitch-sdk-domain-designDesign the domain model for the Stitch SDK. Use when mapping MCP tools to domain classes and bindings in domain-map.json. This is Stage 2 of the generation pipeline.
- stitch-sdk-pipelineRun the full Stitch SDK generation pipeline. Use when a new tool is added, or the SDK needs to be regenerated end-to-end.
- stitch-sdk-readmeGenerate or update the README for the Stitch SDK. Use the Bookstore Test structure and source the current API from the codebase. Use when the README needs to be written or updated.
- stitch-sdk-usageUse the Stitch SDK to generate, edit, and iterate on UI screens from text prompts, manage projects, and retrieve screen HTML/images. Use when the user wants to consume the SDK in their application.