@_davideast/stitch-mcp 0.1.1 → 0.1.2

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
@@ -19,58 +19,14 @@ This single command will:
19
19
  6. Enable Stitch API
20
20
  7. Generate MCP configuration for your client
21
21
 
22
- **Example session:**
23
- ```
24
- Stitch MCP Setup
25
-
26
- Step 1: Select your MCP client
27
- ✔ Which MCP client are you using? Antigravity
28
-
29
- Step 2: Setting up Google Cloud CLI
30
- ✔ Google Cloud CLI ready (bundled): v552.0.0
31
-
32
- Step 3: Setup Authentication
33
- ✔ Check your current setup status? Yes
34
-
35
- Authenticate with Google Cloud
36
-
37
- CLOUDSDK_CONFIG="~/.stitch-mcp/config" gcloud auth login
38
-
39
- (copied to clipboard)
40
- ✔ Press Enter when complete Yes
41
- ✔ Logged in as you@gmail.com
42
-
43
- Authorize Application Default Credentials
44
-
45
- CLOUDSDK_CONFIG="~/.stitch-mcp/config" gcloud auth application-default login
46
-
47
- (copied to clipboard)
48
- ✔ Press Enter when complete Yes
49
- ✔ ADC configured
50
-
51
- Step 4: Select a Google Cloud project
52
- ✔ Select a project: My Project (my-project-id)
53
-
54
- Step 5: Configure IAM Permissions
55
- ✔ Required IAM role is already configured.
56
-
57
- Step 6: Generating MCP Configuration
58
- ✔ Configuration generated
59
-
60
- Setup Complete! ✔
61
- ```
62
-
63
- **How it works:** Commands are displayed and automatically copied to your clipboard. Run the command in your terminal, complete the OAuth flow in your browser, then press Enter to continue.
64
-
65
22
  **Example output:**
66
23
  ```json
67
24
  {
68
25
  "mcpServers": {
69
26
  "stitch": {
70
- "command": "npx",
71
- "args": ["@_davideast/stitch-mcp", "proxy"],
72
- "env": {
73
- "STITCH_PROJECT_ID": "your-project-id"
27
+ "serverUrl": "https://stitch.googleapis.com/mcp",
28
+ "headers": {
29
+ "X-Goog-Api-Key": "YOUR-API-KEY"
74
30
  }
75
31
  }
76
32
  }
@@ -79,46 +35,6 @@ Setup Complete! ✔
79
35
 
80
36
  Copy this config into your MCP client settings and you're ready to use the Stitch MCP server.
81
37
 
82
- ## Quick Start (Existing gcloud Users)
83
-
84
- If you already have `gcloud` configured, skip `init` and use the proxy directly.
85
-
86
- **Prerequisites:**
87
- ```bash
88
- # 1. Application Default Credentials
89
- gcloud auth application-default login
90
-
91
- # 2. Set project (if not already set)
92
- gcloud config set project <PROJECT_ID>
93
-
94
- # 3. Enable Stitch API (requires beta component)
95
- gcloud components install beta
96
- gcloud beta services mcp enable stitch.googleapis.com --project=<PROJECT_ID>
97
- ```
98
-
99
- **MCP Configuration:**
100
- ```json
101
- {
102
- "mcpServers": {
103
- "stitch": {
104
- "command": "npx",
105
- "args": ["@_davideast/stitch-mcp", "proxy"],
106
- "env": {
107
- "STITCH_USE_SYSTEM_GCLOUD": "1"
108
- }
109
- }
110
- }
111
- }
112
- ```
113
-
114
- **Environment Variables:**
115
- | Variable | Description |
116
- |----------|-------------|
117
- | `STITCH_USE_SYSTEM_GCLOUD` | Use system gcloud config instead of isolated config |
118
- | `STITCH_PROJECT_ID` | Override project ID |
119
- | `GOOGLE_CLOUD_PROJECT` | Alternative project ID variable |
120
- | `STITCH_HOST` | Custom Stitch API endpoint |
121
-
122
38
  ## Verify Your Setup
123
39
 
124
40
  ```bash
@@ -217,7 +133,7 @@ Revokes both user authentication and Application Default Credentials. Useful for
217
133
  - Clearing authentication for testing
218
134
  - Resetting state when troubleshooting
219
135
 
220
- #### `view` - View Stitch Resources
136
+ #### `view` - Interactive Resource Viewer
221
137
 
222
138
  ```bash
223
139
  npx @_davideast/stitch-mcp view [options]
@@ -230,7 +146,41 @@ npx @_davideast/stitch-mcp view [options]
230
146
  - `--project <id>` - Project ID
231
147
  - `--screen <id>` - Screen ID
232
148
 
233
- Interactively view Stitch resources such as projects and screens. Displays the resource data in a JSON tree format.
149
+ Interactively browse Stitch resources in a navigable JSON tree. Supports drilling into nested objects and performing actions on selected nodes.
150
+
151
+ **Keyboard Shortcuts:**
152
+
153
+ | Key | Action |
154
+ |-----|--------|
155
+ | `↑` / `↓` | Navigate up/down |
156
+ | `Enter` | Expand/collapse or drill into nested object |
157
+ | `Backspace` | Go back one level |
158
+ | `c` | Copy selected value to clipboard |
159
+ | `cc` | Extended copy (downloads content for URLs) |
160
+ | `s` | **Preview HTML** - serves `htmlCode` in-memory and opens browser |
161
+ | `o` | Open project in Stitch web app |
162
+ | `q` | Quit viewer |
163
+
164
+ **HTML Preview Feature:**
165
+
166
+ When viewing a screen, select the `htmlCode` node and press `s` to:
167
+ 1. Download the HTML code from Stitch
168
+ 2. Start a local server (auto-closes after 5 minutes)
169
+ 3. Open your browser to preview the rendered HTML
170
+
171
+ ```
172
+ Selected Path: screen.htmlCode | 'c' copy, 'cc' extended, 's' preview
173
+ 🌐 Preview at http://127.0.0.1:54268 (auto-closes in 5 min)
174
+ ```
175
+
176
+ **Example Usage:**
177
+ ```bash
178
+ # Browse all projects
179
+ npx @_davideast/stitch-mcp view --projects
180
+
181
+ # View a specific screen
182
+ npx @_davideast/stitch-mcp view --project <project-id> --screen <screen-id>
183
+ ```
234
184
 
235
185
  #### `proxy` - MCP Proxy Server
236
186
 
@@ -249,6 +199,46 @@ This command is typically configured as the entry point in your MCP client setti
249
199
  - Error handling
250
200
  - Debug logging (when `--debug` is enabled to `/tmp/stitch-proxy-debug.log`)
251
201
 
202
+ ## OAuth setup with gcloud
203
+
204
+ If you already have `gcloud` configured, skip `init` and use the proxy directly.
205
+
206
+ **Prerequisites:**
207
+ ```bash
208
+ # 1. Application Default Credentials
209
+ gcloud auth application-default login
210
+
211
+ # 2. Set project (if not already set)
212
+ gcloud config set project <PROJECT_ID>
213
+
214
+ # 3. Enable Stitch API (requires beta component)
215
+ gcloud components install beta
216
+ gcloud beta services mcp enable stitch.googleapis.com --project=<PROJECT_ID>
217
+ ```
218
+
219
+ **MCP Configuration:**
220
+ ```json
221
+ {
222
+ "mcpServers": {
223
+ "stitch": {
224
+ "command": "npx",
225
+ "args": ["@_davideast/stitch-mcp", "proxy"],
226
+ "env": {
227
+ "STITCH_USE_SYSTEM_GCLOUD": "1"
228
+ }
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ **Environment Variables:**
235
+ | Variable | Description |
236
+ |----------|-------------|
237
+ | `STITCH_USE_SYSTEM_GCLOUD` | Use system gcloud config instead of isolated config |
238
+ | `STITCH_PROJECT_ID` | Override project ID |
239
+ | `GOOGLE_CLOUD_PROJECT` | Alternative project ID variable |
240
+ | `STITCH_HOST` | Custom Stitch API endpoint |
241
+
252
242
  ### How It Works
253
243
 
254
244
  #### Automatic gcloud Management
package/dist/cli.js CHANGED
@@ -72336,6 +72336,106 @@ var init_navigation_behaviors = __esm(() => {
72336
72336
  ];
72337
72337
  });
72338
72338
 
72339
+ // src/ui/serve-behaviors/server.ts
72340
+ import { createServer } from "node:http";
72341
+ import { spawn as spawn4 } from "node:child_process";
72342
+ async function serveHtmlInMemory(html, options) {
72343
+ const timeout = options?.timeout ?? 5 * 60 * 1000;
72344
+ const openBrowser = options?.openBrowser ?? true;
72345
+ return new Promise((resolve, reject) => {
72346
+ const server = createServer((req, res) => {
72347
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
72348
+ res.end(html);
72349
+ });
72350
+ server.listen(0, "127.0.0.1", () => {
72351
+ const address = server.address();
72352
+ if (!address || typeof address === "string") {
72353
+ reject(new Error("Failed to get server address"));
72354
+ return;
72355
+ }
72356
+ const url2 = `http://127.0.0.1:${address.port}`;
72357
+ const timer = setTimeout(() => server.close(), timeout);
72358
+ const stop = () => {
72359
+ clearTimeout(timer);
72360
+ server.close();
72361
+ };
72362
+ if (openBrowser) {
72363
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
72364
+ const args = process.platform === "win32" ? ["/c", "start", url2] : [url2];
72365
+ spawn4(cmd, args, { detached: true, stdio: "ignore" }).unref();
72366
+ }
72367
+ resolve({ url: url2, stop });
72368
+ });
72369
+ server.on("error", reject);
72370
+ });
72371
+ }
72372
+ var init_server = () => {};
72373
+
72374
+ // src/ui/serve-behaviors/handlers.ts
72375
+ var deps, htmlCodeServeHandler;
72376
+ var init_handlers2 = __esm(() => {
72377
+ init_server();
72378
+ deps = {
72379
+ serveHtmlInMemory
72380
+ };
72381
+ htmlCodeServeHandler = {
72382
+ async serve(ctx) {
72383
+ try {
72384
+ let url2;
72385
+ if (typeof ctx.value === "string") {
72386
+ url2 = ctx.value;
72387
+ } else if (typeof ctx.value === "object" && ctx.value?.downloadUrl) {
72388
+ url2 = ctx.value.downloadUrl;
72389
+ } else {
72390
+ return { success: false, message: "No download URL found" };
72391
+ }
72392
+ ctx.onProgress?.("\uD83D\uDCE5 Downloading HTML...");
72393
+ const response = await fetch(url2);
72394
+ if (!response.ok) {
72395
+ return { success: false, message: `Download failed: ${response.status} ${response.statusText}` };
72396
+ }
72397
+ const html = await response.text();
72398
+ ctx.onProgress?.("\uD83D\uDE80 Starting local server...");
72399
+ const { url: serveUrl } = await deps.serveHtmlInMemory(html);
72400
+ ctx.onProgress?.("\uD83C\uDF10 Opening browser...");
72401
+ return { success: true, message: `\uD83C\uDF10 Preview at ${serveUrl} (auto-closes in 5 min)`, url: serveUrl };
72402
+ } catch (error2) {
72403
+ return { success: false, message: `Serve failed: ${error2 instanceof Error ? error2.message : String(error2)}` };
72404
+ }
72405
+ }
72406
+ };
72407
+ });
72408
+
72409
+ // src/ui/serve-behaviors/registry.ts
72410
+ function registerServeHandler(matcher, handler2) {
72411
+ registrations2.push({ matcher, handler: handler2 });
72412
+ }
72413
+ function getServeHandler(path10) {
72414
+ for (let i2 = registrations2.length - 1;i2 >= 0; i2--) {
72415
+ const reg = registrations2[i2];
72416
+ if (reg && reg.matcher(path10))
72417
+ return reg.handler;
72418
+ }
72419
+ return null;
72420
+ }
72421
+ function endsWith2(suffix) {
72422
+ return (path10) => path10.endsWith(suffix);
72423
+ }
72424
+ var registrations2;
72425
+ var init_registry2 = __esm(() => {
72426
+ init_handlers2();
72427
+ registrations2 = [];
72428
+ registerServeHandler(endsWith2(".htmlCode"), htmlCodeServeHandler);
72429
+ registerServeHandler(endsWith2(".htmlCode.downloadUrl"), htmlCodeServeHandler);
72430
+ });
72431
+
72432
+ // src/ui/serve-behaviors/index.ts
72433
+ var init_serve_behaviors = __esm(() => {
72434
+ init_server();
72435
+ init_handlers2();
72436
+ init_registry2();
72437
+ });
72438
+
72339
72439
  // node_modules/react/cjs/react-jsx-dev-runtime.development.js
72340
72440
  var require_react_jsx_dev_runtime_development = __commonJS((exports) => {
72341
72441
  var React10 = __toESM(require_react(), 1);
@@ -72560,7 +72660,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
72560
72660
  });
72561
72661
 
72562
72662
  // src/ui/JsonTree.tsx
72563
- import { spawn as spawn4 } from "child_process";
72663
+ import { spawn as spawn5 } from "child_process";
72564
72664
  function getType(value) {
72565
72665
  if (value === null)
72566
72666
  return "null";
@@ -72666,6 +72766,32 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72666
72766
  }
72667
72767
  return;
72668
72768
  }
72769
+ if (input === "s") {
72770
+ const node = visibleNodes[selectedIndex];
72771
+ if (!node)
72772
+ return;
72773
+ const handler2 = getServeHandler(node.id);
72774
+ if (!handler2) {
72775
+ if (feedbackTimeout.current)
72776
+ clearTimeout(feedbackTimeout.current);
72777
+ setFeedbackMessage("⚠️ No preview available for this path");
72778
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 3000);
72779
+ return;
72780
+ }
72781
+ const onProgress = (message) => {
72782
+ if (feedbackTimeout.current)
72783
+ clearTimeout(feedbackTimeout.current);
72784
+ setFeedbackMessage(message);
72785
+ };
72786
+ const ctx = { key: node.key, value: node.value, path: node.id, onProgress };
72787
+ handler2.serve(ctx).then((result) => {
72788
+ if (feedbackTimeout.current)
72789
+ clearTimeout(feedbackTimeout.current);
72790
+ setFeedbackMessage(result.message);
72791
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 1e4);
72792
+ });
72793
+ return;
72794
+ }
72669
72795
  if (input === "o") {
72670
72796
  const node = visibleNodes[selectedIndex];
72671
72797
  if (!node)
@@ -72700,7 +72826,7 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72700
72826
  if (projectId) {
72701
72827
  const url2 = `https://stitch.withgoogle.com/projects/${projectId}`;
72702
72828
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
72703
- spawn4(openCmd, [url2], { stdio: "ignore", detached: true }).unref();
72829
+ spawn5(openCmd, [url2], { stdio: "ignore", detached: true }).unref();
72704
72830
  if (feedbackTimeout.current)
72705
72831
  clearTimeout(feedbackTimeout.current);
72706
72832
  setFeedbackMessage(`\uD83D\uDD17 Opened project in browser`);
@@ -72855,7 +72981,7 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72855
72981
  children: [
72856
72982
  "Selected Path: ",
72857
72983
  visibleNodes[selectedIndex]?.id || "none",
72858
- " | Press 'c' to copy, 'cc' for extended"
72984
+ " | 'c' copy, 'cc' extended, 's' preview"
72859
72985
  ]
72860
72986
  }, undefined, true, undefined, this),
72861
72987
  feedbackMessage && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -72871,6 +72997,7 @@ var init_JsonTree = __esm(async () => {
72871
72997
  await init_build2();
72872
72998
  init_copy_behaviors();
72873
72999
  init_navigation_behaviors();
73000
+ init_serve_behaviors();
72874
73001
  jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
72875
73002
  });
72876
73003
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Serve handlers - isolated behavior implementations.
3
+ */
4
+ import type { ServeHandler } from './types.js';
5
+ import { serveHtmlInMemory } from './server.js';
6
+ export declare const deps: {
7
+ serveHtmlInMemory: typeof serveHtmlInMemory;
8
+ };
9
+ export declare const htmlCodeServeHandler: ServeHandler;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Serve behaviors for JSON viewer.
3
+ */
4
+ export type { ServeHandler, ServeContext, ServeResult, PathMatcher } from './types.js';
5
+ export { serveHtmlInMemory } from './server.js';
6
+ export { htmlCodeServeHandler } from './handlers.js';
7
+ export { registerServeHandler, getServeHandler, endsWith, contains } from './registry.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Registry for path-based serve handler selection.
3
+ */
4
+ import type { ServeHandler, PathMatcher } from './types.js';
5
+ import { htmlCodeServeHandler } from './handlers.js';
6
+ export declare function registerServeHandler(matcher: PathMatcher, handler: ServeHandler): void;
7
+ export declare function getServeHandler(path: string): ServeHandler | null;
8
+ export declare function endsWith(suffix: string): PathMatcher;
9
+ export declare function contains(segment: string): PathMatcher;
10
+ export { htmlCodeServeHandler };
@@ -0,0 +1,8 @@
1
+ export interface ServeInstance {
2
+ url: string;
3
+ stop: () => void;
4
+ }
5
+ export declare function serveHtmlInMemory(html: string, options?: {
6
+ timeout?: number;
7
+ openBrowser?: boolean;
8
+ }): Promise<ServeInstance>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Serve behavior types for the JSON viewer.
3
+ */
4
+ export interface ServeContext {
5
+ key: string;
6
+ value: any;
7
+ path: string;
8
+ onProgress?: (message: string) => void;
9
+ }
10
+ export interface ServeResult {
11
+ success: boolean;
12
+ message: string;
13
+ url?: string;
14
+ }
15
+ export interface ServeHandler {
16
+ serve(ctx: ServeContext): Promise<ServeResult>;
17
+ }
18
+ export type PathMatcher = (path: string) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_davideast/stitch-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Stitch MCP CLI helper. Automates Google Cloud authentication. Generates MCP config for your client. Sets up a proxy server.",
5
5
  "type": "module",
6
6
  "publishConfig": {