stitch-sdk-development

작성자: google-labs-code

Stitch SDK를 개발합니다. 생성 파이프라인, 이중 모드(에이전트 대 SDK), 오류 처리, 트래픽 라이트(빨간색-초록색-노란색) 구현을 다룹니다…

npx skills add https://github.com/google-labs-code/stitch-sdk --skill stitch-sdk-development

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.

{
  "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.

{
  "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:

{
  "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.

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:

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. Transforms MCP tool schemas into AI SDK-compatible tool definitions, enabling plug-and-play with 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() 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:

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.

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.

// 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:

{
  "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

RuleRationale
Extension methods must NOT override generated methodsPrevents silent shadowing
Extension methods must delegate to a HandlerPrevents inline business logic
Handlers must implement a Spec interfaceTyped service contract
Handlers must return Result, never throwConsistent error surface
Extensions must NOT import from singleton.tsPrevents 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.

# 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
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:

import { StitchError } from "../../src/spec/errors.js"; // ✓
import { StitchError } from "../../src/spec/errors"; // ✗

google-labs-code의 다른 스킬

remotion
google-labs-code
Stitch 앱 디자인에서 Remotion을 사용하여 부드러운 전환과 텍스트 오버레이가 포함된 전문 워크스루 비디오를 제작합니다. Stitch 프로젝트에서 화면을 가져와 확대 효과, 페이드 전환, 상황별 텍스트 오버레이와 함께 Remotion 비디오 구성으로 오케스트레이션합니다. ScreenSlide 및 WalkthroughComposition 컴포넌트를 포함한 모듈식 컴포넌트 아키텍처와 대화형 핫스팟 및 음성 해설 통합과 같은 고급 기능을 지원합니다. 화면 매니페스트를 생성하고 다운로드합니다...
official
ink
google-labs-code
Ink 터미널 렌더러로, JSON 사양을 대화형 터미널 UI로 변환합니다. @json-render/ink로 작업하거나 터미널 UI를 구축할 때 사용하세요.
official
stitch-sdk-pipeline
google-labs-code
전체 Stitch SDK 생성 파이프라인을 실행합니다. 새 도구가 추가되거나 SDK를 처음부터 끝까지 다시 생성해야 할 때 사용하세요.
official
stitch-sdk-readme
google-labs-code
Stitch SDK의 README를 생성하거나 업데이트합니다. Bookstore Test 구조를 사용하고 코드베이스에서 현재 API를 참조합니다. README가 필요할 때 사용합니다.
official
typed-service-contracts
google-labs-code
견고하고 타입 세이프한 TypeScript 서비스를 "Spec and Handler" 패턴으로 구축하기 위한 아키텍처 표준입니다. CLI, 라이브러리, 또는 복잡한 시스템을 구축할 때 사용합니다.
official
agent-dx-cli-scale
google-labs-code
A scoring scale for evaluating how well a CLI is designed for AI agents, based on the "Rewrite Your CLI for AI Agents" principles.
official
ink
google-labs-code
Ink terminal renderer for json-render that turns JSON specs into interactive terminal UIs. Use when working with @json-render/ink, building terminal UIs from…
official
stitch-sdk-bug-bash
google-labs-code
Find bugs in the Stitch SDK using a real API key. Covers standard functional edges and tricky situations.
official