@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 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
+ });
@@ -0,0 +1,7 @@
1
+ export const applyDesignSystemTool = (client:any) => ({
2
+ name: "apply_design_system",
3
+ description: "apply design system",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("apply_design_system", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const createDesignSystemTool = (client:any) => ({
2
+ name: "create_design_system",
3
+ description: "create design system",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("create_design_system", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const createProjectTool = (client:any) => ({
2
+ name: "create_project",
3
+ description: "create project",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("create_project", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const editScreensTool = (client:any) => ({
2
+ name: "edit_screens",
3
+ description: "edit screens",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("edit_screens", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const generateScreenFromTextTool = (client:any) => ({
2
+ name: "generate_screen_from_text",
3
+ description: "generate screen from text",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("generate_screen_from_text", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const generateVariantsTool = (client:any) => ({
2
+ name: "generate_variants",
3
+ description: "generate variants",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("generate_variants", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const getProjectTool = (client:any) => ({
2
+ name: "get_project",
3
+ description: "get project",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("get_project", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const getScreenTool = (client:any) => ({
2
+ name: "get_screen",
3
+ description: "get screen",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("get_screen", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const listDesignSystemsTool = (client:any) => ({
2
+ name: "list_design_systems",
3
+ description: "list design systems",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("list_design_systems", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const listProjectsTool = (client:any) => ({
2
+ name: "list_projects",
3
+ description: "list projects",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("list_projects", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const listScreensTool = (client:any) => ({
2
+ name: "list_screens",
3
+ description: "list screens",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("list_screens", params);
6
+ },
7
+ });
@@ -0,0 +1,7 @@
1
+ export const updateDesignSystemTool = (client:any) => ({
2
+ name: "update_design_system",
3
+ description: "update design system",
4
+ async execute(_: string, params: any) {
5
+ return await client.request?.("update_design_system", params);
6
+ },
7
+ });
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
+ }