@kvasar/google-stitch 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,27 +1,21 @@
1
1
  # Google Stitch MCP — OpenClaw Plugin
2
2
 
3
- Integrate Google Stitch MCP (Model Context Protocol) into OpenClaw to search resources, execute workflows, and monitor status.
4
-
3
+ OpenClaw plugin that exposes Google Stitch MCP tools for project management, screen generation, screen editing, design variants, and design systems.
5
4
 
6
5
  ## Configuration
7
6
 
8
- Add the following configuration to your OpenClaw plugin settings:
9
-
10
- | Key | Description |
11
- |-----|-------------|
12
- | `apiKey` | API key for authenticating with Stitch MCP (Bearer token) |
13
- | `endpoint` | Stitch MCP endpoint URL (e.g., `https://stitch.googleapis.com/mcp`) |
7
+ The plugin will not load unless both required config values are present.
14
8
 
15
- Example configuration (in OpenClaw config file or UI):
9
+ OpenClaw config path:
16
10
 
17
11
  ```json
18
12
  {
19
13
  "plugins": {
20
14
  "entries": {
21
- "google-stitch-mcp": {
15
+ "openclaw-google-stitch": {
22
16
  "config": {
23
17
  "apiKey": "YOUR_API_KEY",
24
- "endpoint": "https://stitch.example.com/mcp"
18
+ "endpoint": "https://stitch.googleapis.com/mcp"
25
19
  }
26
20
  }
27
21
  }
@@ -29,35 +23,47 @@ Example configuration (in OpenClaw config file or UI):
29
23
  }
30
24
  ```
31
25
 
32
- ### Project Structure
26
+ Required keys:
33
27
 
34
- ```
35
- openclaw-google-stitch-mcp/
36
- ├── openclaw.plugin.json
37
- ├── index.ts
38
- ├── services/
39
- │ └── stitch-mcp-client.ts
40
- ├── tools/
41
- │ ├── stitch_search.ts
42
- │ ├── stitch_execute_flow.ts
43
- │ └── stitch_status.ts
44
- ├── skills/
45
- │ └── SKILL.md
46
- ├── tests/unit/
47
- │ └── stitch-mcp-client.test.ts
48
- ├── config/
49
- │ └── .example.json
50
- ├── package.json
51
- ├── tsconfig.json
52
- └── README.md
28
+ | Key | Description |
29
+ | --- | --- |
30
+ | `apiKey` | Google Stitch API key sent as `X-Goog-Api-Key` |
31
+ | `endpoint` | Stitch MCP endpoint URL |
32
+
33
+ If either value is missing, plugin registration fails with:
34
+
35
+ ```text
36
+ Google Stitch plugin requires plugins.entries.openclaw-google-stitch.config.apiKey and .endpoint
53
37
  ```
54
38
 
55
- ## License
39
+ ## Exposed Tools
56
40
 
57
- MIT
41
+ - `stitch_create_project`
42
+ - `stitch_get_project`
43
+ - `stitch_list_projects`
44
+ - `stitch_list_screens`
45
+ - `stitch_get_screen`
46
+ - `stitch_generate_screen_from_text`
47
+ - `stitch_edit_screens`
48
+ - `stitch_generate_variants`
49
+ - `stitch_create_design_system`
50
+ - `stitch_update_design_system`
51
+ - `stitch_list_design_systems`
52
+ - `stitch_apply_design_system`
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ npm run typecheck
58
+ npm test
59
+ npm run build
60
+ ```
58
61
 
59
- ---
62
+ Notes:
60
63
 
61
- ## Changelog
64
+ - This package expects the OpenClaw runtime to provide `openclaw/plugin-sdk/plugin-entry` at execution time.
65
+ - The local TypeScript build includes a minimal declaration shim so the package can be typechecked in isolation in this repo.
62
66
 
63
- - **0.1.0**: Initial release with stitch_search, stitch_execute_flow, stitch_status. Client includes retry and session continuity.
67
+ ## License
68
+
69
+ MIT
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "id": "openclaw-google-stitch",
3
- "name": "Google Stitch MCP",
4
- "version": "0.1.30",
3
+ "name": "Google Stitch MCP",
4
+ "version": "0.1.31",
5
5
  "description": "Integrates Google Stitch MCP services into OpenClaw",
6
- "skills": ["skills"],
6
+ "skills": ["skills"],
7
+
7
8
  "configSchema": {
8
9
  "type": "object",
9
10
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kvasar/google-stitch",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "OpenClaw plugin for Google Stitch UI generation, screen design, variants, and design systems",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/skills/SKILL.md CHANGED
@@ -107,7 +107,7 @@ Important:
107
107
  - Screen generation usually takes a few minutes to complete
108
108
  - Do **not retry automatically** if a connection error or timeout occurs
109
109
  - A connection error does **not necessarily mean the generation failed**
110
- - After waiting a few minutes, use `get_screen` or `list_screens` to verify whether the screen was successfully created
110
+ - After waiting a few minutes, use `stitch_get_screen` or `list_screens` to verify whether the screen was successfully created
111
111
  - Avoid duplicate retries to prevent generating the same screen multiple times
112
112
 
113
113
  ## Supported device types:
@@ -1,96 +1,77 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { StitchMCPClient, SearchResult, ExecutionResult, StatusResult } from "../services/stitch-mcp-client.js";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
2
 
4
- describe("StitchMCPClient", () => {
5
- let client: StitchMCPClient;
6
- const apiKey = "test-api-key";
7
- const endpoint = "https://stitch.example.com/mcp";
3
+ const connectMock = vi.fn();
4
+ const callToolMock = vi.fn();
8
5
 
9
- beforeEach(() => {
10
- client = new StitchMCPClient({ apiKey, endpoint });
11
- global.fetch = vi.fn();
12
- });
6
+ vi.mock("@modelcontextprotocol/sdk/client/index.js", () => ({
7
+ Client: vi.fn().mockImplementation(() => ({
8
+ connect: connectMock,
9
+ callTool: callToolMock,
10
+ })),
11
+ }));
13
12
 
14
- afterEach(() => {
15
- vi.restoreAllMocks();
16
- });
13
+ vi.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
14
+ StreamableHTTPClientTransport: vi.fn().mockImplementation((url, options) => ({
15
+ url,
16
+ options,
17
+ })),
18
+ }));
17
19
 
18
- it("should construct with config", () => {
19
- expect(client).toBeInstanceOf(StitchMCPClient);
20
+ describe("StitchMCPClient", () => {
21
+ beforeEach(() => {
22
+ vi.resetModules();
23
+ connectMock.mockReset();
24
+ callToolMock.mockReset();
25
+ connectMock.mockResolvedValue(undefined);
20
26
  });
21
27
 
22
- it("should send JSON-RPC request with auth header", async () => {
23
- const mockResponse = {
24
- ok: true,
25
- json: vi.fn().mockResolvedValue({ jsonrpc: "2.0", id: 1, result: { total: 1, resources: [] } }),
26
- };
27
- (global.fetch as any).mockResolvedValue(mockResponse);
28
+ it("connects once and lists projects through the MCP tool name", async () => {
29
+ const { StitchMCPClient } = await import("../../services/stitch-mcp-client.js");
30
+ const client = new StitchMCPClient({
31
+ apiKey: "test-api-key",
32
+ endpoint: "https://stitch.example.com/mcp",
33
+ });
28
34
 
29
- await client.search("query");
35
+ callToolMock.mockResolvedValueOnce({ content: [] });
30
36
 
31
- expect(global.fetch).toHaveBeenCalledWith(
32
- endpoint,
33
- expect.objectContaining({
34
- method: "POST",
35
- headers: expect.objectContaining({
36
- "Authorization": `Bearer ${apiKey}`,
37
- "Content-Type": "application/json",
38
- "X-Session-ID": client.sessionId,
39
- }),
40
- body: expect.stringContaining('"method":"stitch.search"'),
41
- })
42
- );
43
- });
37
+ await client.listProjects("view=owned");
38
+ await client.listProjects("view=shared");
44
39
 
45
- it("should return result on success", async () => {
46
- const result: SearchResult = { total: 1, resources: [{ id: "r1", name: "Flow1", type: "flow" }] };
47
- (global.fetch as any).mockResolvedValue({
48
- ok: true,
49
- json: vi.fn().mockResolvedValue({ jsonrpc: "2.0", id: 1, result }),
40
+ expect(connectMock).toHaveBeenCalledTimes(1);
41
+ expect(callToolMock).toHaveBeenNthCalledWith(1, {
42
+ name: "list_projects",
43
+ arguments: { filter: "view=owned" },
50
44
  });
51
-
52
- const res = await client.search("flow");
53
- expect(res).toEqual(result);
54
- });
55
-
56
- it("should throw on JSON-RPC error", async () => {
57
- (global.fetch as any).mockResolvedValue({
58
- ok: true,
59
- json: vi.fn().mockResolvedValue({ jsonrpc: "2.0", id: 1, error: { code: -32600, message: "Invalid Request" } }),
45
+ expect(callToolMock).toHaveBeenNthCalledWith(2, {
46
+ name: "list_projects",
47
+ arguments: { filter: "view=shared" },
60
48
  });
61
-
62
- await expect(client.search("q")).rejects.toThrow("Stitch MCP error -32600: Invalid Request");
63
49
  });
64
50
 
65
- it("should throw on HTTP error", async () => {
66
- (global.fetch as any).mockResolvedValue({
67
- ok: false,
68
- status: 401,
69
- text: vi.fn().mockResolvedValue("Unauthorized"),
51
+ it("passes generate screen parameters to the current MCP tool name", async () => {
52
+ const { StitchMCPClient } = await import("../../services/stitch-mcp-client.js");
53
+ const client = new StitchMCPClient({
54
+ apiKey: "test-api-key",
55
+ endpoint: "https://stitch.example.com/mcp",
70
56
  });
71
57
 
72
- await expect(client.search("q")).rejects.toThrow("Stitch MCP HTTP 401: Unauthorized");
73
- });
74
-
75
- it("should retry on network failure up to 3 times", async () => {
76
- // Fail first two fetches, succeed on third
77
- (global.fetch as any)
78
- .mockRejectedValueOnce(new Error("network error"))
79
- .mockRejectedValueOnce(new Error("network error"))
80
- .mockResolvedValueOnce({
81
- ok: true,
82
- json: vi.fn().mockResolvedValue({ jsonrpc: "2.0", id: 1, result: { total: 0, resources: [] } }),
83
- });
58
+ callToolMock.mockResolvedValueOnce({ screen: { id: "screen-1" } });
84
59
 
85
- const res = await client.search("q");
86
- expect(res).toEqual({ total: 0, resources: [] });
87
- expect(global.fetch).toHaveBeenCalledTimes(3);
88
- });
89
-
90
- it("should throw after max retries exceeded", async () => {
91
- (global.fetch as any).mockRejectedValue(new Error("network error"));
60
+ await client.generateScreen({
61
+ projectId: "project-123",
62
+ prompt: "Create a pricing page",
63
+ deviceType: "DESKTOP",
64
+ modelId: "GEMINI_3_1_PRO",
65
+ });
92
66
 
93
- await expect(client.search("q")).rejects.toThrow("network error");
94
- expect(global.fetch).toHaveBeenCalledTimes(3);
67
+ expect(callToolMock).toHaveBeenCalledWith({
68
+ name: "generate_screen_from_text",
69
+ arguments: {
70
+ projectId: "project-123",
71
+ prompt: "Create a pricing page",
72
+ deviceType: "DESKTOP",
73
+ modelId: "GEMINI_3_1_PRO",
74
+ },
75
+ });
95
76
  });
96
77
  });
@@ -42,7 +42,7 @@ type ContentItem = {
42
42
  };
43
43
 
44
44
  export const getScreenTool = (client: StitchMCPClient) => ({
45
- name: "get_screen",
45
+ name: "stitch_get_screen",
46
46
  description: "Retrieves and visually renders a specific screen within a Stitch project.",
47
47
  parameters: GetScreenSchema,
48
48
  async execute(_: string, params: GetScreenParams) {
@@ -0,0 +1,3 @@
1
+ declare module "openclaw/plugin-sdk/plugin-entry" {
2
+ export function definePluginEntry<T>(definition: T): T;
3
+ }
package/tsconfig.json CHANGED
@@ -12,6 +12,6 @@
12
12
  "declarationMap": true,
13
13
  "sourceMap": true
14
14
  },
15
- "include": ["index.ts", "src/**/*.ts"],
15
+ "include": ["index.ts", "src/**/*.ts", "src/**/*.d.ts"],
16
16
  "exclude": ["node_modules", "dist"]
17
17
  }