@kvasar/google-stitch 0.1.0
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 +162 -0
- package/openclaw.plugin.json +32 -0
- package/package.json +57 -0
- package/src/services/stitch-mcp-client.ts +15 -0
- package/src/tests/unit/stitch-mcp-client.test.ts +96 -0
- package/src/tools/apply_design_system.ts +7 -0
- package/src/tools/create_design_system.ts +7 -0
- package/src/tools/create_project.ts +7 -0
- package/src/tools/edit_screens.ts +7 -0
- package/src/tools/generate_screen_from_text.ts +7 -0
- package/src/tools/generate_variants.ts +7 -0
- package/src/tools/get_project.ts +7 -0
- package/src/tools/get_screen.ts +7 -0
- package/src/tools/list_design_systems.ts +7 -0
- package/src/tools/list_projects.ts +7 -0
- package/src/tools/list_screens.ts +7 -0
- package/src/tools/update_design_system.ts +7 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Google Stitch MCP — OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
Integrate Google Stitch MCP (Model Context Protocol) into OpenClaw to search resources, execute workflows, and monitor status.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
1. Clone or copy this plugin into your OpenClaw workspace:
|
|
8
|
+
```bash
|
|
9
|
+
cd ~/.openclaw/workspace
|
|
10
|
+
git clone <repo-url> openclaw-google-stitch-mcp
|
|
11
|
+
```
|
|
12
|
+
2. Install dependencies:
|
|
13
|
+
```bash
|
|
14
|
+
cd openclaw-google-stitch-mcp
|
|
15
|
+
npm install
|
|
16
|
+
```
|
|
17
|
+
3. Verify TypeScript and tests:
|
|
18
|
+
```bash
|
|
19
|
+
npm run check
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
Add the following configuration to your OpenClaw plugin settings:
|
|
25
|
+
|
|
26
|
+
| Key | Description |
|
|
27
|
+
|-----|-------------|
|
|
28
|
+
| `apiKey` | API key for authenticating with Stitch MCP (Bearer token) |
|
|
29
|
+
| `endpoint` | Stitch MCP endpoint URL (e.g., `https://stitch.googleapis.com/mcp`) |
|
|
30
|
+
|
|
31
|
+
Example configuration (in OpenClaw config file or UI):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"plugins": {
|
|
36
|
+
"entries": {
|
|
37
|
+
"google-stitch-mcp": {
|
|
38
|
+
"config": {
|
|
39
|
+
"apiKey": "YOUR_API_KEY",
|
|
40
|
+
"endpoint": "https://stitch.example.com/mcp"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A sample configuration file is provided in `config/.example.json`.
|
|
49
|
+
|
|
50
|
+
## Tools Reference
|
|
51
|
+
|
|
52
|
+
### `stitch_search`
|
|
53
|
+
|
|
54
|
+
Search Stitch MCP resources and workflows.
|
|
55
|
+
|
|
56
|
+
**Parameters:**
|
|
57
|
+
- `query` (string, required): Search query string.
|
|
58
|
+
- `filters` (object, optional): Optional filters (e.g., `{ "type": "flow" }`).
|
|
59
|
+
|
|
60
|
+
**Example:**
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"query": "data pipeline",
|
|
64
|
+
"filters": { "type": "flow" }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Response:** JSON with `resources` array and `total`.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `stitch_execute_flow`
|
|
73
|
+
|
|
74
|
+
Execute a Stitch flow/action.
|
|
75
|
+
|
|
76
|
+
**Parameters:**
|
|
77
|
+
- `flowId` (string, required): Identifier of the flow (from `stitch_search`).
|
|
78
|
+
- `inputs` (object, optional): Input parameters for the flow.
|
|
79
|
+
|
|
80
|
+
**Example:**
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"flowId": "flow_12345",
|
|
84
|
+
"inputs": { "source": "bigquery", "dataset": "analytics.events" }
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Response:** Initial execution result with `executionId` and `status`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `stitch_status`
|
|
93
|
+
|
|
94
|
+
Check execution status or service health.
|
|
95
|
+
|
|
96
|
+
**Parameters:**
|
|
97
|
+
- `executionId` (string, optional): Execution ID to poll. If omitted, returns service health.
|
|
98
|
+
|
|
99
|
+
**Example (check execution):**
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"executionId": "exec_67890"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Example (health):**
|
|
107
|
+
```json
|
|
108
|
+
{}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Response:** Status object with `healthy` (boolean) for health, or detailed execution status.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
The plugin surfaces errors with `isError: true` and a message. Common errors:
|
|
118
|
+
|
|
119
|
+
- **Configuration missing**: Ensure `apiKey` and `endpoint` are set.
|
|
120
|
+
- **Authentication/HTTP errors**: Check API key validity and endpoint reachability.
|
|
121
|
+
- **JSON-RPC errors**: Inspect `error.message` from Stitch; may indicate invalid method or parameters.
|
|
122
|
+
- **Network**: The client retries up to 3 times with exponential backoff; persistent failures are reported.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
- **TypeScript**: `npm run typecheck`
|
|
127
|
+
- **Tests**: `npm test` (Vitest)
|
|
128
|
+
- **Watch**: `npm run test:watch`
|
|
129
|
+
|
|
130
|
+
### Project Structure
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
openclaw-google-stitch-mcp/
|
|
134
|
+
├── openclaw.plugin.json
|
|
135
|
+
├── index.ts
|
|
136
|
+
├── services/
|
|
137
|
+
│ └── stitch-mcp-client.ts
|
|
138
|
+
├── tools/
|
|
139
|
+
│ ├── stitch_search.ts
|
|
140
|
+
│ ├── stitch_execute_flow.ts
|
|
141
|
+
│ └── stitch_status.ts
|
|
142
|
+
├── skills/
|
|
143
|
+
│ └── google-stitch/
|
|
144
|
+
│ └── SKILL.md
|
|
145
|
+
├── tests/unit/
|
|
146
|
+
│ └── stitch-mcp-client.test.ts
|
|
147
|
+
├── config/
|
|
148
|
+
│ └── .example.json
|
|
149
|
+
├── package.json
|
|
150
|
+
├── tsconfig.json
|
|
151
|
+
└── README.md
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Changelog
|
|
161
|
+
|
|
162
|
+
- **0.1.0**: Initial release with stitch_search, stitch_execute_flow, stitch_status. Client includes retry and session continuity.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-google-stitch",
|
|
3
|
+
"name": "Google Stitch MCP",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Integrates Google Stitch MCP services into OpenClaw",
|
|
6
|
+
"skills": ["skills"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"apiKey": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "API key for authenticating with Stitch MCP"
|
|
14
|
+
},
|
|
15
|
+
"endpoint": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Stitch MCP endpoint URL (e.g., https://stitch.googleapis.com/mcp)"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": ["apiKey", "endpoint"]
|
|
21
|
+
},
|
|
22
|
+
"uiHints": {
|
|
23
|
+
"apiKey": {
|
|
24
|
+
"label": "API Key",
|
|
25
|
+
"placeholder": "your_api_key"
|
|
26
|
+
},
|
|
27
|
+
"endpoint": {
|
|
28
|
+
"label": "Endpoint URL",
|
|
29
|
+
"placeholder": "https://stitch.example.com/mcp"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kvasar/google-stitch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw plugin for Google Stitch UI generation, screen design, variants, and design systems",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src/",
|
|
9
|
+
"openclaw.plugin.json",
|
|
10
|
+
"tsconfig.json",
|
|
11
|
+
"README.md",
|
|
12
|
+
"SKILL.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"check": "tsc --noEmit && vitest run",
|
|
19
|
+
"build": "tsc"
|
|
20
|
+
},
|
|
21
|
+
"openclaw": {
|
|
22
|
+
"extensions": ["./src/index.ts"],
|
|
23
|
+
"tools": [
|
|
24
|
+
"create_project",
|
|
25
|
+
"get_project",
|
|
26
|
+
"list_projects",
|
|
27
|
+
"list_screens",
|
|
28
|
+
"get_screen",
|
|
29
|
+
"generate_screen_from_text",
|
|
30
|
+
"edit_screens",
|
|
31
|
+
"generate_variants",
|
|
32
|
+
"create_design_system",
|
|
33
|
+
"update_design_system",
|
|
34
|
+
"list_design_systems",
|
|
35
|
+
"apply_design_system"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@sinclair/typebox": "^0.32.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.4.0",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"vitest": "^1.6.0"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"openclaw",
|
|
48
|
+
"plugin",
|
|
49
|
+
"google",
|
|
50
|
+
"stitch",
|
|
51
|
+
"ui-generation",
|
|
52
|
+
"design-system",
|
|
53
|
+
"wireframe",
|
|
54
|
+
"prototype"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT"
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class StitchMCPClient {
|
|
2
|
+
constructor(private config: { apiKey: string; endpoint: string }) {}
|
|
3
|
+
private async request(path: string, body?: unknown) {
|
|
4
|
+
const response = await fetch(`${this.config.endpoint}/${path}`, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: {
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
9
|
+
},
|
|
10
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) throw new Error(`Stitch API error: ${response.status}`);
|
|
13
|
+
return response.json();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { StitchMCPClient, SearchResult, ExecutionResult, StatusResult } from "../services/stitch-mcp-client.js";
|
|
3
|
+
|
|
4
|
+
describe("StitchMCPClient", () => {
|
|
5
|
+
let client: StitchMCPClient;
|
|
6
|
+
const apiKey = "test-api-key";
|
|
7
|
+
const endpoint = "https://stitch.example.com/mcp";
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
client = new StitchMCPClient({ apiKey, endpoint });
|
|
11
|
+
global.fetch = vi.fn();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should construct with config", () => {
|
|
19
|
+
expect(client).toBeInstanceOf(StitchMCPClient);
|
|
20
|
+
});
|
|
21
|
+
|
|
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
|
+
|
|
29
|
+
await client.search("query");
|
|
30
|
+
|
|
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
|
+
});
|
|
44
|
+
|
|
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 }),
|
|
50
|
+
});
|
|
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" } }),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await expect(client.search("q")).rejects.toThrow("Stitch MCP error -32600: Invalid Request");
|
|
63
|
+
});
|
|
64
|
+
|
|
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"),
|
|
70
|
+
});
|
|
71
|
+
|
|
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
|
+
});
|
|
84
|
+
|
|
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"));
|
|
92
|
+
|
|
93
|
+
await expect(client.search("q")).rejects.toThrow("network error");
|
|
94
|
+
expect(global.fetch).toHaveBeenCalledTimes(3);
|
|
95
|
+
});
|
|
96
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["index.ts", "src/**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|