@_davideast/stitch-mcp 0.1.0 → 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
@@ -72134,6 +72134,8 @@ var init_build2 = __esm(async () => {
72134
72134
  });
72135
72135
 
72136
72136
  // src/ui/copy-behaviors/clipboard.ts
72137
+ import { writeFile, unlink } from "fs/promises";
72138
+ import { spawn as spawn3 } from "child_process";
72137
72139
  async function copyText(text) {
72138
72140
  await clipboardy_default.write(text);
72139
72141
  }
@@ -72141,6 +72143,19 @@ async function copyJson(value) {
72141
72143
  const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
72142
72144
  await clipboardy_default.write(text);
72143
72145
  }
72146
+ function spawnAndWait(command, args) {
72147
+ return new Promise((resolve, reject) => {
72148
+ const proc = spawn3(command, args, { stdio: "ignore" });
72149
+ proc.on("close", (code) => {
72150
+ if (code === 0) {
72151
+ resolve();
72152
+ } else {
72153
+ reject(new Error(`Process exited with code ${code}`));
72154
+ }
72155
+ });
72156
+ proc.on("error", reject);
72157
+ });
72158
+ }
72144
72159
  async function downloadAndCopyImage(url2) {
72145
72160
  const response = await fetch(url2);
72146
72161
  if (!response.ok) {
@@ -72148,19 +72163,21 @@ async function downloadAndCopyImage(url2) {
72148
72163
  }
72149
72164
  const buffer = await response.arrayBuffer();
72150
72165
  const tempPath = `/tmp/stitch-clipboard-${Date.now()}.png`;
72151
- await Bun.write(tempPath, buffer);
72166
+ await writeFile(tempPath, Buffer.from(buffer));
72152
72167
  const platform3 = process.platform;
72153
- if (platform3 === "darwin") {
72154
- const proc = Bun.spawn(["osascript", "-e", `set the clipboard to (read (POSIX file "${tempPath}") as TIFF picture)`]);
72155
- await proc.exited;
72156
- } else if (platform3 === "linux") {
72157
- const proc = Bun.spawn(["xclip", "-selection", "clipboard", "-t", "image/png", "-i", tempPath]);
72158
- await proc.exited;
72159
- } else if (platform3 === "win32") {
72160
- const proc = Bun.spawn(["powershell", "-command", `Set-Clipboard -Path "${tempPath}"`]);
72161
- await proc.exited;
72162
- }
72163
- await Bun.file(tempPath).exists() && await Bun.$`rm ${tempPath}`.quiet();
72168
+ try {
72169
+ if (platform3 === "darwin") {
72170
+ await spawnAndWait("osascript", ["-e", `set the clipboard to (read (POSIX file "${tempPath}") as TIFF picture)`]);
72171
+ } else if (platform3 === "linux") {
72172
+ await spawnAndWait("xclip", ["-selection", "clipboard", "-t", "image/png", "-i", tempPath]);
72173
+ } else if (platform3 === "win32") {
72174
+ await spawnAndWait("powershell", ["-command", `Set-Clipboard -Path "${tempPath}"`]);
72175
+ }
72176
+ } finally {
72177
+ try {
72178
+ await unlink(tempPath);
72179
+ } catch {}
72180
+ }
72164
72181
  }
72165
72182
  async function downloadAndCopyText(url2) {
72166
72183
  const response = await fetch(url2);
@@ -72319,6 +72336,106 @@ var init_navigation_behaviors = __esm(() => {
72319
72336
  ];
72320
72337
  });
72321
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
+
72322
72439
  // node_modules/react/cjs/react-jsx-dev-runtime.development.js
72323
72440
  var require_react_jsx_dev_runtime_development = __commonJS((exports) => {
72324
72441
  var React10 = __toESM(require_react(), 1);
@@ -72543,6 +72660,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
72543
72660
  });
72544
72661
 
72545
72662
  // src/ui/JsonTree.tsx
72663
+ import { spawn as spawn5 } from "child_process";
72546
72664
  function getType(value) {
72547
72665
  if (value === null)
72548
72666
  return "null";
@@ -72648,6 +72766,32 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72648
72766
  }
72649
72767
  return;
72650
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
+ }
72651
72795
  if (input === "o") {
72652
72796
  const node = visibleNodes[selectedIndex];
72653
72797
  if (!node)
@@ -72682,7 +72826,7 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72682
72826
  if (projectId) {
72683
72827
  const url2 = `https://stitch.withgoogle.com/projects/${projectId}`;
72684
72828
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
72685
- Bun.spawn([openCmd, url2]);
72829
+ spawn5(openCmd, [url2], { stdio: "ignore", detached: true }).unref();
72686
72830
  if (feedbackTimeout.current)
72687
72831
  clearTimeout(feedbackTimeout.current);
72688
72832
  setFeedbackMessage(`\uD83D\uDD17 Opened project in browser`);
@@ -72837,7 +72981,7 @@ var import_react26, jsx_dev_runtime, JsonTree = ({ data, rootLabel, onNavigate,
72837
72981
  children: [
72838
72982
  "Selected Path: ",
72839
72983
  visibleNodes[selectedIndex]?.id || "none",
72840
- " | Press 'c' to copy, 'cc' for extended"
72984
+ " | 'c' copy, 'cc' extended, 's' preview"
72841
72985
  ]
72842
72986
  }, undefined, true, undefined, this),
72843
72987
  feedbackMessage && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -72853,6 +72997,7 @@ var init_JsonTree = __esm(async () => {
72853
72997
  await init_build2();
72854
72998
  init_copy_behaviors();
72855
72999
  init_navigation_behaviors();
73000
+ init_serve_behaviors();
72856
73001
  jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
72857
73002
  });
72858
73003
 
@@ -75538,10 +75683,10 @@ class McpConfigHandler {
75538
75683
  if (input.client === "claude-code" || input.client === "gemini-cli" || input.client === "codex") {
75539
75684
  return null;
75540
75685
  }
75541
- const env2 = {
75542
- STITCH_PROJECT_ID: input.projectId
75543
- };
75544
- if (input.apiKey) {
75686
+ const env2 = {};
75687
+ if (!input.apiKey) {
75688
+ env2.STITCH_PROJECT_ID = input.projectId;
75689
+ } else {
75545
75690
  env2.STITCH_API_KEY = input.apiKey;
75546
75691
  }
75547
75692
  if (input.client === "vscode") {
@@ -75701,24 +75846,52 @@ ${theme.green("Setup Gemini CLI:")}
75701
75846
  `;
75702
75847
  case "codex": {
75703
75848
  const isHttp = transport === "http";
75704
- const configBlock = isHttp ? [
75705
- "[mcp_servers.stitch]",
75706
- 'url = "https://stitch.googleapis.com/mcp"',
75707
- 'bearer_token_env_var = "STITCH_ACCESS_TOKEN"',
75708
- "",
75709
- "[mcp_servers.stitch.env_http_headers]",
75710
- 'X-Goog-User-Project = "GOOGLE_CLOUD_PROJECT"'
75711
- ].join(`
75712
- `) : [
75713
- "[mcp_servers.stitch]",
75714
- 'command = "npx"',
75715
- 'args = ["@_davideast/stitch-mcp", "proxy"]',
75716
- "",
75717
- "[mcp_servers.stitch.env]",
75718
- `STITCH_PROJECT_ID = "${projectId}"`
75719
- ].join(`
75849
+ let configBlock;
75850
+ if (isHttp) {
75851
+ if (apiKey) {
75852
+ configBlock = [
75853
+ "[mcp_servers.stitch]",
75854
+ 'url = "https://stitch.googleapis.com/mcp"',
75855
+ "",
75856
+ "[mcp_servers.stitch.env_http_headers]",
75857
+ `X-Goog-Api-Key = "${apiKey}"`
75858
+ ].join(`
75859
+ `);
75860
+ } else {
75861
+ configBlock = [
75862
+ "[mcp_servers.stitch]",
75863
+ 'url = "https://stitch.googleapis.com/mcp"',
75864
+ 'bearer_token_env_var = "STITCH_ACCESS_TOKEN"',
75865
+ "",
75866
+ "[mcp_servers.stitch.env_http_headers]",
75867
+ 'X-Goog-User-Project = "GOOGLE_CLOUD_PROJECT"'
75868
+ ].join(`
75869
+ `);
75870
+ }
75871
+ } else {
75872
+ if (apiKey) {
75873
+ configBlock = [
75874
+ "[mcp_servers.stitch]",
75875
+ 'command = "npx"',
75876
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
75877
+ "",
75878
+ "[mcp_servers.stitch.env]",
75879
+ `STITCH_API_KEY = "${apiKey}"`
75880
+ ].join(`
75720
75881
  `);
75721
- const note = isHttp ? `${theme.yellow("Note:")} Direct mode requires a valid access token in ${theme.blue("STITCH_ACCESS_TOKEN")} and a project id in ${theme.blue("GOOGLE_CLOUD_PROJECT")}.
75882
+ } else {
75883
+ configBlock = [
75884
+ "[mcp_servers.stitch]",
75885
+ 'command = "npx"',
75886
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
75887
+ "",
75888
+ "[mcp_servers.stitch.env]",
75889
+ `STITCH_PROJECT_ID = "${projectId}"`
75890
+ ].join(`
75891
+ `);
75892
+ }
75893
+ }
75894
+ const note = isHttp && !apiKey ? `${theme.yellow("Note:")} Direct mode requires a valid access token in ${theme.blue("STITCH_ACCESS_TOKEN")} and a project id in ${theme.blue("GOOGLE_CLOUD_PROJECT")}.
75722
75895
  ` : `${theme.yellow("Note:")} Proxy mode handles token refresh automatically.
75723
75896
  `;
75724
75897
  return `
@@ -77063,16 +77236,22 @@ ${theme.green("\uD83C\uDF89 Setup complete!")}
77063
77236
  return;
77064
77237
  }
77065
77238
  if (transport === "stdio") {
77239
+ const env3 = {
77240
+ PATH: process.env.PATH || ""
77241
+ };
77242
+ if (apiKey) {
77243
+ env3.STITCH_API_KEY = apiKey;
77244
+ } else {
77245
+ env3.STITCH_PROJECT_ID = projectId;
77246
+ }
77066
77247
  config.mcpServers.stitch = {
77067
77248
  command: "npx",
77068
77249
  args: ["@_davideast/stitch-mcp", "proxy"],
77069
- env: {
77070
- STITCH_PROJECT_ID: projectId,
77071
- PATH: process.env.PATH || ""
77072
- }
77250
+ env: env3
77073
77251
  };
77074
77252
  fs10.writeFileSync(extensionPath, JSON.stringify(config, null, 4));
77075
- spinner.succeed(`Stitch extension configured for STDIO: Project ID set to ${theme.blue(projectId)}`);
77253
+ const successMsg = apiKey ? "Stitch extension configured for STDIO with API Key" : `Stitch extension configured for STDIO: Project ID set to ${theme.blue(projectId)}`;
77254
+ spinner.succeed(successMsg);
77076
77255
  } else {
77077
77256
  const existingHeaders = config.mcpServers.stitch.headers || {};
77078
77257
  if (apiKey) {
package/dist/index.js CHANGED
@@ -18885,10 +18885,10 @@ class McpConfigHandler {
18885
18885
  if (input.client === "claude-code" || input.client === "gemini-cli" || input.client === "codex") {
18886
18886
  return null;
18887
18887
  }
18888
- const env2 = {
18889
- STITCH_PROJECT_ID: input.projectId
18890
- };
18891
- if (input.apiKey) {
18888
+ const env2 = {};
18889
+ if (!input.apiKey) {
18890
+ env2.STITCH_PROJECT_ID = input.projectId;
18891
+ } else {
18892
18892
  env2.STITCH_API_KEY = input.apiKey;
18893
18893
  }
18894
18894
  if (input.client === "vscode") {
@@ -19048,24 +19048,52 @@ ${theme.green("Setup Gemini CLI:")}
19048
19048
  `;
19049
19049
  case "codex": {
19050
19050
  const isHttp = transport === "http";
19051
- const configBlock = isHttp ? [
19052
- "[mcp_servers.stitch]",
19053
- 'url = "https://stitch.googleapis.com/mcp"',
19054
- 'bearer_token_env_var = "STITCH_ACCESS_TOKEN"',
19055
- "",
19056
- "[mcp_servers.stitch.env_http_headers]",
19057
- 'X-Goog-User-Project = "GOOGLE_CLOUD_PROJECT"'
19058
- ].join(`
19059
- `) : [
19060
- "[mcp_servers.stitch]",
19061
- 'command = "npx"',
19062
- 'args = ["@_davideast/stitch-mcp", "proxy"]',
19063
- "",
19064
- "[mcp_servers.stitch.env]",
19065
- `STITCH_PROJECT_ID = "${projectId}"`
19066
- ].join(`
19051
+ let configBlock;
19052
+ if (isHttp) {
19053
+ if (apiKey) {
19054
+ configBlock = [
19055
+ "[mcp_servers.stitch]",
19056
+ 'url = "https://stitch.googleapis.com/mcp"',
19057
+ "",
19058
+ "[mcp_servers.stitch.env_http_headers]",
19059
+ `X-Goog-Api-Key = "${apiKey}"`
19060
+ ].join(`
19061
+ `);
19062
+ } else {
19063
+ configBlock = [
19064
+ "[mcp_servers.stitch]",
19065
+ 'url = "https://stitch.googleapis.com/mcp"',
19066
+ 'bearer_token_env_var = "STITCH_ACCESS_TOKEN"',
19067
+ "",
19068
+ "[mcp_servers.stitch.env_http_headers]",
19069
+ 'X-Goog-User-Project = "GOOGLE_CLOUD_PROJECT"'
19070
+ ].join(`
19071
+ `);
19072
+ }
19073
+ } else {
19074
+ if (apiKey) {
19075
+ configBlock = [
19076
+ "[mcp_servers.stitch]",
19077
+ 'command = "npx"',
19078
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
19079
+ "",
19080
+ "[mcp_servers.stitch.env]",
19081
+ `STITCH_API_KEY = "${apiKey}"`
19082
+ ].join(`
19067
19083
  `);
19068
- const note = isHttp ? `${theme.yellow("Note:")} Direct mode requires a valid access token in ${theme.blue("STITCH_ACCESS_TOKEN")} and a project id in ${theme.blue("GOOGLE_CLOUD_PROJECT")}.
19084
+ } else {
19085
+ configBlock = [
19086
+ "[mcp_servers.stitch]",
19087
+ 'command = "npx"',
19088
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
19089
+ "",
19090
+ "[mcp_servers.stitch.env]",
19091
+ `STITCH_PROJECT_ID = "${projectId}"`
19092
+ ].join(`
19093
+ `);
19094
+ }
19095
+ }
19096
+ const note = isHttp && !apiKey ? `${theme.yellow("Note:")} Direct mode requires a valid access token in ${theme.blue("STITCH_ACCESS_TOKEN")} and a project id in ${theme.blue("GOOGLE_CLOUD_PROJECT")}.
19069
19097
  ` : `${theme.yellow("Note:")} Proxy mode handles token refresh automatically.
19070
19098
  `;
19071
19099
  return `
@@ -20412,16 +20440,22 @@ ${theme.green("\uD83C\uDF89 Setup complete!")}
20412
20440
  return;
20413
20441
  }
20414
20442
  if (transport === "stdio") {
20443
+ const env3 = {
20444
+ PATH: process.env.PATH || ""
20445
+ };
20446
+ if (apiKey) {
20447
+ env3.STITCH_API_KEY = apiKey;
20448
+ } else {
20449
+ env3.STITCH_PROJECT_ID = projectId;
20450
+ }
20415
20451
  config.mcpServers.stitch = {
20416
20452
  command: "npx",
20417
20453
  args: ["@_davideast/stitch-mcp", "proxy"],
20418
- env: {
20419
- STITCH_PROJECT_ID: projectId,
20420
- PATH: process.env.PATH || ""
20421
- }
20454
+ env: env3
20422
20455
  };
20423
20456
  fs10.writeFileSync(extensionPath, JSON.stringify(config, null, 4));
20424
- spinner.succeed(`Stitch extension configured for STDIO: Project ID set to ${theme.blue(projectId)}`);
20457
+ const successMsg = apiKey ? "Stitch extension configured for STDIO with API Key" : `Stitch extension configured for STDIO: Project ID set to ${theme.blue(projectId)}`;
20458
+ spinner.succeed(successMsg);
20425
20459
  } else {
20426
20460
  const existingHeaders = config.mcpServers.stitch.headers || {};
20427
20461
  if (apiKey) {
@@ -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.0",
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": {