@polymorphism-tech/morph-spec 4.20.0 → 4.21.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 +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/skills/level-0-meta/mcp-registry.json +21 -8
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/commands/mcp/mcp-setup.js +99 -2
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/installers/mcp-installer.js +140 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:** 4.
|
|
6
|
+
**Version:** 4.21.0
|
|
7
7
|
**Requires:** Node.js 18+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -364,4 +364,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
364
364
|
|
|
365
365
|
---
|
|
366
366
|
|
|
367
|
-
*morph-spec v4.
|
|
367
|
+
*morph-spec v4.21.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
|
@@ -164,20 +164,33 @@
|
|
|
164
164
|
"testCall": "Call list_projects to verify connectivity",
|
|
165
165
|
"testParams": {},
|
|
166
166
|
"successIndicator": "Returns a list of projects (possibly empty) without auth errors",
|
|
167
|
-
"failureHint": "Stitch MCP not responding.
|
|
167
|
+
"failureHint": "Stitch MCP not responding. Verify gcloud auth: run 'gcloud auth application-default print-access-token'. Check .claude/stitch-proxy.cjs exists and GCP project ID is correct."
|
|
168
168
|
},
|
|
169
169
|
"install": {
|
|
170
|
-
"autoInstall":
|
|
170
|
+
"autoInstall": false,
|
|
171
171
|
"transport": "stdio",
|
|
172
172
|
"config": {
|
|
173
|
-
"command": "
|
|
174
|
-
"args": ["/
|
|
173
|
+
"command": "node",
|
|
174
|
+
"args": [".claude/stitch-proxy.cjs"]
|
|
175
175
|
},
|
|
176
|
-
"credentials": [
|
|
177
|
-
|
|
176
|
+
"credentials": [
|
|
177
|
+
{
|
|
178
|
+
"name": "GCP Project ID",
|
|
179
|
+
"envVar": "STITCH_GCP_PROJECT",
|
|
180
|
+
"secret": false,
|
|
181
|
+
"helpUrl": "https://console.cloud.google.com/projectselector2/home/dashboard"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"prerequisites": [
|
|
185
|
+
{
|
|
186
|
+
"name": "gcloud CLI",
|
|
187
|
+
"checkCommand": "gcloud --version",
|
|
188
|
+
"installUrl": "https://cloud.google.com/sdk/docs/install"
|
|
189
|
+
}
|
|
190
|
+
],
|
|
178
191
|
"stackRelevance": ["nextjs", "nextjs-neon", "vite"],
|
|
179
|
-
"warnings": ["
|
|
180
|
-
"setupGuide": "
|
|
192
|
+
"warnings": ["Requires gcloud CLI with application-default credentials. Run 'gcloud auth application-default login' before setup.", "Add .claude/stitch-proxy.cjs to .gitignore (contains your GCP project ID)."],
|
|
193
|
+
"setupGuide": "Run: morph-spec mcp setup stitch (generates OAuth proxy script)"
|
|
181
194
|
},
|
|
182
195
|
"companionSkills": {
|
|
183
196
|
"repo": "google-labs-code/stitch-skills",
|
|
@@ -10,7 +10,7 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion,
|
|
|
10
10
|
mcp__playwright__browser_navigate, mcp__playwright__browser_resize,
|
|
11
11
|
mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot,
|
|
12
12
|
mcp__playwright__browser_evaluate, mcp__playwright__browser_console_messages
|
|
13
|
-
cliVersion: "4.
|
|
13
|
+
cliVersion: "4.21.0"
|
|
14
14
|
stacks:
|
|
15
15
|
- nextjs
|
|
16
16
|
---
|
|
@@ -3,7 +3,7 @@ name: morph-phase-codebase-analysis
|
|
|
3
3
|
description: MORPH-SPEC Design sub-phase that analyzes existing codebase and database schema, producing schema-analysis.md with real column names, types, relationships, and field mismatches. Use at the start of Design phase before generating contracts.cs to prevent incorrect field names or types.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
6
|
-
cliVersion: "4.
|
|
6
|
+
cliVersion: "4.21.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# MORPH Codebase Analysis - Design Sub-phase
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 2 (Design). Schema-first interactive design: reads
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
|
|
7
|
-
cliVersion: "4.
|
|
7
|
+
cliVersion: "4.21.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Design — Phase 2
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 3 (Plan). Generates a detailed implementation plan
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.
|
|
7
|
+
cliVersion: "4.21.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Plan — Phase 3
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 4 (Tasks). Breaks approved spec into bottom-up ord
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.
|
|
7
|
+
cliVersion: "4.21.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Tasks — Phase 4
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 2 (UI/UX). Creates design-system.md, mockups.md, c
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.
|
|
7
|
+
cliVersion: "4.21.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH UI/UX Design - Phase 2
|
|
@@ -4,7 +4,7 @@ description: Guided workflow for mid-implementation scope escalation — analyze
|
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "[feature-name]"
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.
|
|
7
|
+
cliVersion: "4.21.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Scope Escalation Workflow
|
package/package.json
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import inquirer from 'inquirer';
|
|
15
15
|
import ora from 'ora';
|
|
16
|
+
import { readFileSync, existsSync } from 'fs';
|
|
17
|
+
import { join } from 'path';
|
|
16
18
|
import {
|
|
17
19
|
orchestrateMcpSetup,
|
|
18
20
|
installAutoMcps,
|
|
@@ -21,15 +23,34 @@ import {
|
|
|
21
23
|
generateSetupInstructions,
|
|
22
24
|
formatMcpStatusTable,
|
|
23
25
|
loadMcpRegistry,
|
|
24
|
-
isRemoteMcp
|
|
26
|
+
isRemoteMcp,
|
|
27
|
+
generateStitchProxy,
|
|
28
|
+
patchConfigForPlatform
|
|
25
29
|
} from '../../lib/installers/mcp-installer.js';
|
|
26
30
|
import { detectClaudeConfig } from '../../lib/detectors/claude-config-detector.js';
|
|
27
31
|
import { detectProject } from '../../lib/detectors/index.js';
|
|
32
|
+
import { installMcpServers } from '../../utils/claude-settings-manager.js';
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
|
-
* Detect the current project's stack
|
|
35
|
+
* Detect the current project's stack.
|
|
36
|
+
* Prefers .morph/config/config.json (set during morph-spec init) over filesystem re-detection.
|
|
31
37
|
*/
|
|
32
38
|
async function detectStack(targetPath) {
|
|
39
|
+
// 1. Prefer the authoritative config (handles compound stacks like nextjs+dotnet)
|
|
40
|
+
try {
|
|
41
|
+
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
42
|
+
if (existsSync(configPath)) {
|
|
43
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
44
|
+
const configStack = config.project?.stack;
|
|
45
|
+
if (configStack && configStack !== 'unknown') {
|
|
46
|
+
return configStack;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Fall through to filesystem detection
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Fallback: detect from filesystem
|
|
33
54
|
try {
|
|
34
55
|
const results = await detectProject(targetPath, { conversation: false, generateStandards: false });
|
|
35
56
|
return results.structure?.stack || 'unknown';
|
|
@@ -186,6 +207,60 @@ async function setupSpecificMcp(targetPath, name, stack, existingMcps) {
|
|
|
186
207
|
|
|
187
208
|
const credentials = mcpEntry.install.credentials || [];
|
|
188
209
|
|
|
210
|
+
// Stitch MCP — custom OAuth proxy setup
|
|
211
|
+
if (name === 'stitch' && !mcpEntry.install.autoInstall) {
|
|
212
|
+
console.log(chalk.cyan(`\n Setting up Stitch MCP (OAuth proxy)\n`));
|
|
213
|
+
console.log(chalk.gray(` Usage: ${mcpEntry.usage}`));
|
|
214
|
+
console.log('');
|
|
215
|
+
|
|
216
|
+
const gcpCred = credentials.find(c => c.envVar === 'STITCH_GCP_PROJECT');
|
|
217
|
+
const { gcpProject } = await inquirer.prompt([{
|
|
218
|
+
type: 'input',
|
|
219
|
+
name: 'gcpProject',
|
|
220
|
+
message: gcpCred ? `${gcpCred.name}:` : 'GCP Project ID:',
|
|
221
|
+
}]);
|
|
222
|
+
|
|
223
|
+
if (!gcpProject || !gcpProject.trim()) {
|
|
224
|
+
console.log(chalk.red(' GCP Project ID is required.'));
|
|
225
|
+
console.log('');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const spinner = ora('Generating Stitch proxy...').start();
|
|
230
|
+
const { proxyPath } = generateStitchProxy(targetPath, gcpProject.trim());
|
|
231
|
+
spinner.succeed(`Proxy script generated: ${proxyPath}`);
|
|
232
|
+
|
|
233
|
+
const config = { ...mcpEntry.install.config };
|
|
234
|
+
const result = await installMcpServers(targetPath, { stitch: patchConfigForPlatform(config) });
|
|
235
|
+
if (result.added.length > 0) {
|
|
236
|
+
console.log(chalk.green(` ✓ Stitch MCP configured (using OAuth proxy)`));
|
|
237
|
+
} else {
|
|
238
|
+
console.log(chalk.yellow(` ⚠ Stitch MCP already configured`));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (mcpEntry.companionSkills?.installCommand) {
|
|
242
|
+
const { installSkills } = await inquirer.prompt([{
|
|
243
|
+
type: 'confirm',
|
|
244
|
+
name: 'installSkills',
|
|
245
|
+
message: 'Install Stitch companion skills (design, loop, etc.)?',
|
|
246
|
+
default: true
|
|
247
|
+
}]);
|
|
248
|
+
if (installSkills) {
|
|
249
|
+
const skillResults = installCompanionSkills({ stitch: mcpEntry });
|
|
250
|
+
for (const r of skillResults) {
|
|
251
|
+
if (r.success) {
|
|
252
|
+
console.log(chalk.green(` ✓ Companion skills installed: ${r.repo}`));
|
|
253
|
+
} else {
|
|
254
|
+
console.log(chalk.yellow(` ⚠ Failed to install companion skills: ${r.repo}`));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log('');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
189
264
|
// Auto-installable — just install
|
|
190
265
|
if (mcpEntry.install.autoInstall) {
|
|
191
266
|
const spinner = ora(`Installing ${name}...`).start();
|
|
@@ -349,6 +424,28 @@ async function interactiveWizard(targetPath, stack, existingMcps) {
|
|
|
349
424
|
}]);
|
|
350
425
|
|
|
351
426
|
if (setupNow) {
|
|
427
|
+
// Stitch — custom OAuth proxy flow
|
|
428
|
+
if (name === 'stitch' && !entry.install.autoInstall) {
|
|
429
|
+
const gcpCred = entry.install.credentials?.find(c => c.envVar === 'STITCH_GCP_PROJECT');
|
|
430
|
+
const { gcpProject } = await inquirer.prompt([{
|
|
431
|
+
type: 'input',
|
|
432
|
+
name: 'gcpProject',
|
|
433
|
+
message: gcpCred ? `${gcpCred.name}:` : 'GCP Project ID:',
|
|
434
|
+
}]);
|
|
435
|
+
if (gcpProject?.trim()) {
|
|
436
|
+
const { proxyPath } = generateStitchProxy(targetPath, gcpProject.trim());
|
|
437
|
+
console.log(chalk.green(` ✓ Proxy generated: ${proxyPath}`));
|
|
438
|
+
const config = { ...entry.install.config };
|
|
439
|
+
await installMcpServers(targetPath, { stitch: patchConfigForPlatform(config) });
|
|
440
|
+
console.log(chalk.green(` ✓ Stitch configured (OAuth proxy)`));
|
|
441
|
+
const skillResults = installCompanionSkills({ stitch: entry });
|
|
442
|
+
for (const r of skillResults) {
|
|
443
|
+
if (r.success) console.log(chalk.green(` ✓ Companion skills: ${r.repo}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
352
449
|
// Remote MCPs — launch OAuth via claude mcp add
|
|
353
450
|
if (isRemoteMcp(entry.install.config)) {
|
|
354
451
|
console.log(chalk.cyan(` Launching ${capitalizedName} OAuth...`));
|
|
@@ -30,7 +30,7 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
30
30
|
try {
|
|
31
31
|
// ── Package.json analysis ──────────────────────────────────────────────
|
|
32
32
|
// Collect deps from root and common monorepo subfolders
|
|
33
|
-
const pkgLocations = ['.', 'src/frontend', 'frontend', 'apps/web', 'packages/app'];
|
|
33
|
+
const pkgLocations = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
|
|
34
34
|
const allDeps = {};
|
|
35
35
|
let rootPkg = null;
|
|
36
36
|
let scripts = [];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { join } from 'path';
|
|
10
|
-
import { readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
11
|
import { execSync } from 'child_process';
|
|
12
12
|
import { installMcpServers } from '../../utils/claude-settings-manager.js';
|
|
13
13
|
|
|
@@ -173,6 +173,145 @@ export function installCompanionSkills(mcpsInstalled, exec = execSync) {
|
|
|
173
173
|
return results;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Generate the Stitch OAuth proxy template script.
|
|
178
|
+
* @param {string} gcpProject - GCP project ID for x-goog-user-project header
|
|
179
|
+
* @returns {string} CJS proxy script content
|
|
180
|
+
*/
|
|
181
|
+
export function getStitchProxyTemplate(gcpProject) {
|
|
182
|
+
return `/**
|
|
183
|
+
* Stitch MCP OAuth Proxy
|
|
184
|
+
* Generated by morph-spec mcp setup stitch
|
|
185
|
+
*
|
|
186
|
+
* Bridges stdio JSON-RPC to https://stitch.googleapis.com/mcp
|
|
187
|
+
* using gcloud application-default credentials (OAuth2 bearer token).
|
|
188
|
+
*/
|
|
189
|
+
const { execSync } = require('child_process');
|
|
190
|
+
const https = require('https');
|
|
191
|
+
|
|
192
|
+
const GCP_PROJECT = '${gcpProject}';
|
|
193
|
+
const STITCH_HOST = 'stitch.googleapis.com';
|
|
194
|
+
const STITCH_PATH = '/mcp';
|
|
195
|
+
const TOKEN_TTL_MS = 50 * 60 * 1000; // 50 minutes
|
|
196
|
+
|
|
197
|
+
let cachedToken = null;
|
|
198
|
+
let tokenExpiresAt = 0;
|
|
199
|
+
|
|
200
|
+
function getAccessToken() {
|
|
201
|
+
if (cachedToken && Date.now() < tokenExpiresAt) return cachedToken;
|
|
202
|
+
try {
|
|
203
|
+
cachedToken = execSync('gcloud auth application-default print-access-token', {
|
|
204
|
+
encoding: 'utf-8',
|
|
205
|
+
timeout: 10000,
|
|
206
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
207
|
+
}).trim();
|
|
208
|
+
tokenExpiresAt = Date.now() + TOKEN_TTL_MS;
|
|
209
|
+
return cachedToken;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
process.stderr.write('[stitch-proxy] Failed to get access token. Run: gcloud auth application-default login\\n');
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clearTokenCache() {
|
|
217
|
+
cachedToken = null;
|
|
218
|
+
tokenExpiresAt = 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function sendRequest(body) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
const token = getAccessToken();
|
|
224
|
+
const data = JSON.stringify(body);
|
|
225
|
+
const options = {
|
|
226
|
+
hostname: STITCH_HOST,
|
|
227
|
+
path: STITCH_PATH,
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: {
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
'Content-Length': Buffer.byteLength(data),
|
|
232
|
+
'Authorization': \`Bearer \${token}\`,
|
|
233
|
+
'x-goog-user-project': GCP_PROJECT,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const req = https.request(options, (res) => {
|
|
238
|
+
let responseData = '';
|
|
239
|
+
res.on('data', (chunk) => { responseData += chunk; });
|
|
240
|
+
res.on('end', () => {
|
|
241
|
+
if (res.statusCode === 401) {
|
|
242
|
+
// Token expired — retry once with fresh token
|
|
243
|
+
clearTokenCache();
|
|
244
|
+
const retryToken = getAccessToken();
|
|
245
|
+
options.headers['Authorization'] = \`Bearer \${retryToken}\`;
|
|
246
|
+
const retryData = JSON.stringify(body);
|
|
247
|
+
options.headers['Content-Length'] = Buffer.byteLength(retryData);
|
|
248
|
+
const retryReq = https.request(options, (retryRes) => {
|
|
249
|
+
let retryResponse = '';
|
|
250
|
+
retryRes.on('data', (chunk) => { retryResponse += chunk; });
|
|
251
|
+
retryRes.on('end', () => resolve(retryResponse));
|
|
252
|
+
});
|
|
253
|
+
retryReq.on('error', reject);
|
|
254
|
+
retryReq.write(retryData);
|
|
255
|
+
retryReq.end();
|
|
256
|
+
} else {
|
|
257
|
+
resolve(responseData);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
req.on('error', reject);
|
|
263
|
+
req.write(data);
|
|
264
|
+
req.end();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Buffer stdin and split on newlines (JSON-RPC messages)
|
|
269
|
+
let buffer = '';
|
|
270
|
+
process.stdin.setEncoding('utf-8');
|
|
271
|
+
process.stdin.on('data', async (chunk) => {
|
|
272
|
+
buffer += chunk;
|
|
273
|
+
const lines = buffer.split('\\n');
|
|
274
|
+
buffer = lines.pop(); // keep incomplete line in buffer
|
|
275
|
+
|
|
276
|
+
for (const line of lines) {
|
|
277
|
+
if (!line.trim()) continue;
|
|
278
|
+
try {
|
|
279
|
+
const request = JSON.parse(line);
|
|
280
|
+
const response = await sendRequest(request);
|
|
281
|
+
process.stdout.write(response + '\\n');
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const errorResponse = JSON.stringify({
|
|
284
|
+
jsonrpc: '2.0',
|
|
285
|
+
id: null,
|
|
286
|
+
error: { code: -32603, message: err.message || 'Proxy error' }
|
|
287
|
+
});
|
|
288
|
+
process.stdout.write(errorResponse + '\\n');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
process.stderr.write(\`[stitch-proxy] Ready — GCP project: \${GCP_PROJECT}\\n\`);
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Generate and write the Stitch OAuth proxy script to the target project.
|
|
299
|
+
* @param {string} targetPath - Project root directory
|
|
300
|
+
* @param {string} gcpProject - GCP project ID
|
|
301
|
+
* @returns {{ proxyPath: string }} Path to the generated proxy script
|
|
302
|
+
*/
|
|
303
|
+
export function generateStitchProxy(targetPath, gcpProject) {
|
|
304
|
+
const claudeDir = join(targetPath, '.claude');
|
|
305
|
+
if (!existsSync(claudeDir)) {
|
|
306
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const proxyPath = join(claudeDir, 'stitch-proxy.cjs');
|
|
310
|
+
writeFileSync(proxyPath, getStitchProxyTemplate(gcpProject), 'utf-8');
|
|
311
|
+
|
|
312
|
+
return { proxyPath };
|
|
313
|
+
}
|
|
314
|
+
|
|
176
315
|
/**
|
|
177
316
|
* Install a single MCP with provided credentials
|
|
178
317
|
* @param {string} targetPath - Project root directory
|