@kvasar/google-stitch 0.1.29 → 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 +42 -36
- package/openclaw.plugin.json +4 -3
- package/package.json +9 -15
- package/skills/SKILL.md +1 -1
- package/src/tests/unit/stitch-mcp-client.test.ts +58 -77
- package/src/tools/get_screen.ts +1 -1
- package/src/types/openclaw-plugin-sdk.d.ts +3 -0
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
# Google Stitch MCP — OpenClaw Plugin
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
OpenClaw config path:
|
|
16
10
|
|
|
17
11
|
```json
|
|
18
12
|
{
|
|
19
13
|
"plugins": {
|
|
20
14
|
"entries": {
|
|
21
|
-
"google-stitch
|
|
15
|
+
"openclaw-google-stitch": {
|
|
22
16
|
"config": {
|
|
23
17
|
"apiKey": "YOUR_API_KEY",
|
|
24
|
-
"endpoint": "https://stitch.
|
|
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
|
-
|
|
26
|
+
Required keys:
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
##
|
|
39
|
+
## Exposed Tools
|
|
56
40
|
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-google-stitch",
|
|
3
|
-
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"name": "Google Stitch MCP",
|
|
4
|
+
"version": "0.1.31",
|
|
5
5
|
"description": "Integrates Google Stitch MCP services into OpenClaw",
|
|
6
|
-
|
|
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.
|
|
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",
|
|
@@ -23,20 +23,14 @@
|
|
|
23
23
|
"extensions": [
|
|
24
24
|
"./index.ts"
|
|
25
25
|
],
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"stitch_generate_variants",
|
|
35
|
-
"stitch_create_design_system",
|
|
36
|
-
"stitch_update_design_system",
|
|
37
|
-
"stitch_list_design_systems",
|
|
38
|
-
"stitch_apply_design_system"
|
|
39
|
-
]
|
|
26
|
+
"compat": {
|
|
27
|
+
"pluginApi": ">=2026.3.24-beta.2",
|
|
28
|
+
"minGatewayVersion": "2026.3.24-beta.2"
|
|
29
|
+
},
|
|
30
|
+
"build": {
|
|
31
|
+
"openclawVersion": "2026.3.24-beta.2",
|
|
32
|
+
"pluginSdkVersion": "2026.3.24-beta.2"
|
|
33
|
+
}
|
|
40
34
|
},
|
|
41
35
|
"dependencies": {
|
|
42
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
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 `
|
|
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 {
|
|
2
|
-
import { StitchMCPClient, SearchResult, ExecutionResult, StatusResult } from "../services/stitch-mcp-client.js";
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
+
describe("StitchMCPClient", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.resetModules();
|
|
23
|
+
connectMock.mockReset();
|
|
24
|
+
callToolMock.mockReset();
|
|
25
|
+
connectMock.mockResolvedValue(undefined);
|
|
20
26
|
});
|
|
21
27
|
|
|
22
|
-
it("
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
35
|
+
callToolMock.mockResolvedValueOnce({ content: [] });
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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("
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
});
|
package/src/tools/get_screen.ts
CHANGED
|
@@ -42,7 +42,7 @@ type ContentItem = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export const getScreenTool = (client: StitchMCPClient) => ({
|
|
45
|
-
name: "
|
|
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) {
|