viewgate-mcp 1.0.42 → 1.0.44
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/dist/index.js +96 -289
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,8 +23,6 @@ import dotenv from "dotenv";
|
|
|
23
23
|
import path from "path";
|
|
24
24
|
import os from "os";
|
|
25
25
|
import { fileURLToPath } from "url";
|
|
26
|
-
import fs from "fs";
|
|
27
|
-
import fsp from "fs/promises";
|
|
28
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
27
|
const __dirname = path.dirname(__filename);
|
|
30
28
|
dotenv.config({ path: path.join(__dirname, "..", ".env") });
|
|
@@ -64,7 +62,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
64
62
|
},
|
|
65
63
|
{
|
|
66
64
|
name: "generate_ui_components",
|
|
67
|
-
description: "
|
|
65
|
+
description: "Fetch pending UI component specs and return clear instructions for the LLM to implement a real, functional component. Does not write files, upload previews, or mark components as generated.",
|
|
68
66
|
inputSchema: {
|
|
69
67
|
type: "object",
|
|
70
68
|
properties: {
|
|
@@ -73,6 +71,19 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
73
71
|
},
|
|
74
72
|
},
|
|
75
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: "mark_ui_component_generated",
|
|
76
|
+
description: "Mark a UI component as generated by submitting its code and props to the backend. This enables the dashboard iframe preview (/preview/:id).",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
id: { type: "string", description: "Internal UI component id." },
|
|
81
|
+
code: { type: "string", description: "Generated component code used for iframe preview." },
|
|
82
|
+
props: { type: "object", description: "Props object used for iframe preview." }
|
|
83
|
+
},
|
|
84
|
+
required: ["id", "code"]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
76
87
|
{
|
|
77
88
|
name: "get_annotations",
|
|
78
89
|
description: "Fetch feedback. Keys ('VG-XXXX') or IDs. Workflow: 1. Fetch, 2. Fix, 3. Mark Ready.",
|
|
@@ -88,6 +99,19 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
88
99
|
},
|
|
89
100
|
},
|
|
90
101
|
},
|
|
102
|
+
{
|
|
103
|
+
name: "get_ui_improvements",
|
|
104
|
+
description: "Fetch pending UI/UX improvement tickets. STRICTLY LIMITED TO VISUAL/CSS CHANGES. Do not add functional logic or modify backend integrations.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
limit: { type: "number", description: "Max results.", default: 5 },
|
|
109
|
+
search: { type: "string", description: "Search message/file." },
|
|
110
|
+
key: { type: "string", description: "VG-XXXX key." },
|
|
111
|
+
ids: { type: "string", description: "Internal IDs." }
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
91
115
|
{
|
|
92
116
|
name: "mark_annotation_ready",
|
|
93
117
|
description: "Mark as ready/applied. Use internal IDs. IMPORTANT: appliedChanges must be in the project's preferredLanguage (e.g. SPANISH).",
|
|
@@ -189,247 +213,6 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
189
213
|
],
|
|
190
214
|
};
|
|
191
215
|
});
|
|
192
|
-
const toPascalCase = (input) => {
|
|
193
|
-
return (input || '')
|
|
194
|
-
.replace(/[^a-zA-Z0-9 ]+/g, ' ')
|
|
195
|
-
.split(' ')
|
|
196
|
-
.map((w) => w.trim())
|
|
197
|
-
.filter(Boolean)
|
|
198
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
199
|
-
.join('');
|
|
200
|
-
};
|
|
201
|
-
const normalizePropTokens = (raw) => {
|
|
202
|
-
const line = (raw || '').trim();
|
|
203
|
-
if (!line)
|
|
204
|
-
return [];
|
|
205
|
-
// Split "onClick / onChange / onFocus / onBlur" style
|
|
206
|
-
const parts = line.split('/').map((s) => s.trim()).filter(Boolean);
|
|
207
|
-
return parts.flatMap((p) => {
|
|
208
|
-
// Take part before spaces or parentheses for "variant (primary...)" patterns
|
|
209
|
-
const base = p.split('(')[0].trim();
|
|
210
|
-
const token = base.split(' ')[0].trim();
|
|
211
|
-
return token ? [token] : [];
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
const buildPropModel = (commonProps, requiredProps) => {
|
|
215
|
-
const out = {
|
|
216
|
-
hasChildren: false,
|
|
217
|
-
hasAs: false,
|
|
218
|
-
hasDataIndex: false,
|
|
219
|
-
hasAriaIndex: false,
|
|
220
|
-
common: [],
|
|
221
|
-
required: [],
|
|
222
|
-
};
|
|
223
|
-
const add = (arr, target) => {
|
|
224
|
-
for (const raw of arr) {
|
|
225
|
-
for (const token of normalizePropTokens(raw)) {
|
|
226
|
-
if (token === 'children')
|
|
227
|
-
out.hasChildren = true;
|
|
228
|
-
if (token === 'as')
|
|
229
|
-
out.hasAs = true;
|
|
230
|
-
if (token.startsWith('data-'))
|
|
231
|
-
out.hasDataIndex = true;
|
|
232
|
-
if (token.startsWith('aria-'))
|
|
233
|
-
out.hasAriaIndex = true;
|
|
234
|
-
if (token === 'ref')
|
|
235
|
-
continue; // handled via forwardRef if needed later
|
|
236
|
-
if (token === 'data-*' || token === 'aria-*')
|
|
237
|
-
continue;
|
|
238
|
-
// skip wildcard tokens, handled by index signatures
|
|
239
|
-
if (token.endsWith('*'))
|
|
240
|
-
continue;
|
|
241
|
-
target.push(token);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
add(commonProps || [], out.common);
|
|
246
|
-
add(requiredProps || [], out.required);
|
|
247
|
-
const uniq = (xs) => [...new Set(xs)].filter(Boolean);
|
|
248
|
-
out.common = uniq(out.common);
|
|
249
|
-
out.required = uniq(out.required);
|
|
250
|
-
return out;
|
|
251
|
-
};
|
|
252
|
-
const readJsonIfExists = async (p) => {
|
|
253
|
-
try {
|
|
254
|
-
const txt = await fsp.readFile(p, 'utf8');
|
|
255
|
-
return JSON.parse(txt);
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
const looksLikeFrontendPackageJson = (pkg) => {
|
|
262
|
-
const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
|
|
263
|
-
const hasReact = typeof deps.react === 'string';
|
|
264
|
-
const hasViteOrNext = typeof deps.vite === 'string' || typeof deps.next === 'string';
|
|
265
|
-
return hasReact && hasViteOrNext;
|
|
266
|
-
};
|
|
267
|
-
const listSubdirs = async (dir) => {
|
|
268
|
-
try {
|
|
269
|
-
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
270
|
-
return entries.filter((e) => e.isDirectory()).map((e) => path.join(dir, e.name));
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
return [];
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
const findFrontendRoot = async () => {
|
|
277
|
-
// In many MCP clients, INIT_CWD points to the directory where `npx viewgate-mcp` was launched.
|
|
278
|
-
const baseDir = process.env.VIEWGATE_WORKSPACE || process.env.INIT_CWD || process.cwd();
|
|
279
|
-
// Scan baseDir and one level deep for candidate frontends.
|
|
280
|
-
const candidates = [baseDir, ...(await listSubdirs(baseDir))];
|
|
281
|
-
const hits = [];
|
|
282
|
-
for (const c of candidates) {
|
|
283
|
-
const pkg = await readJsonIfExists(path.join(c, 'package.json'));
|
|
284
|
-
if (pkg && looksLikeFrontendPackageJson(pkg))
|
|
285
|
-
hits.push(c);
|
|
286
|
-
}
|
|
287
|
-
if (hits.length === 0)
|
|
288
|
-
return null;
|
|
289
|
-
if (hits.length === 1)
|
|
290
|
-
return hits[0];
|
|
291
|
-
// Prefer common names when multiple projects exist.
|
|
292
|
-
const preferred = hits.find((h) => {
|
|
293
|
-
const name = path.basename(h).toLowerCase();
|
|
294
|
-
return name === 'dashboard' || name === 'view-gate-dashboard' || name.includes('dashboard');
|
|
295
|
-
});
|
|
296
|
-
return preferred || hits[0];
|
|
297
|
-
};
|
|
298
|
-
const resolveComponentsDir = async () => {
|
|
299
|
-
const frontendRoot = await findFrontendRoot();
|
|
300
|
-
const cwd = process.cwd();
|
|
301
|
-
// Order matters: prefer src/components for SPA projects like Vite.
|
|
302
|
-
const rootsToTry = [
|
|
303
|
-
...(frontendRoot ? [frontendRoot] : []),
|
|
304
|
-
cwd,
|
|
305
|
-
];
|
|
306
|
-
for (const root of rootsToTry) {
|
|
307
|
-
const c1 = path.join(root, 'src', 'components');
|
|
308
|
-
if (fs.existsSync(c1))
|
|
309
|
-
return c1;
|
|
310
|
-
const c2 = path.join(root, 'components');
|
|
311
|
-
if (fs.existsSync(c2))
|
|
312
|
-
return c2;
|
|
313
|
-
}
|
|
314
|
-
// Fallback: create in src/components under detected frontendRoot if present.
|
|
315
|
-
if (frontendRoot)
|
|
316
|
-
return path.join(frontendRoot, 'src', 'components');
|
|
317
|
-
return path.join(cwd, 'components');
|
|
318
|
-
};
|
|
319
|
-
const writeComponentFile = async (componentType, model) => {
|
|
320
|
-
const componentsDir = await resolveComponentsDir();
|
|
321
|
-
await fsp.mkdir(componentsDir, { recursive: true });
|
|
322
|
-
const baseName = toPascalCase(componentType) || 'Component';
|
|
323
|
-
const projectRoot = path.dirname(componentsDir);
|
|
324
|
-
const hasTs = fs.existsSync(path.join(projectRoot, 'tsconfig.json'));
|
|
325
|
-
const ext = hasTs ? 'tsx' : 'jsx';
|
|
326
|
-
const fileBase = `${baseName}.${ext}`;
|
|
327
|
-
let filePath = path.join(componentsDir, fileBase);
|
|
328
|
-
if (fs.existsSync(filePath)) {
|
|
329
|
-
// never overwrite existing
|
|
330
|
-
let i = 2;
|
|
331
|
-
while (fs.existsSync(path.join(componentsDir, `${baseName}.${i}.${ext}`)))
|
|
332
|
-
i++;
|
|
333
|
-
filePath = path.join(componentsDir, `${baseName}.${i}.${ext}`);
|
|
334
|
-
}
|
|
335
|
-
const isTs = ext === 'tsx';
|
|
336
|
-
const commonOptional = model.common.map((p) => `${p}${isTs ? '?: any' : ''}`);
|
|
337
|
-
const required = model.required.map((p) => `${p}${isTs ? ': any' : ''}`);
|
|
338
|
-
const ariaIndex = model.hasAriaIndex ? (isTs ? `\n [key: \`aria-\${string}\`]: any;` : '') : '';
|
|
339
|
-
const dataIndex = model.hasDataIndex ? (isTs ? `\n [key: \`data-\${string}\`]: any;` : '') : '';
|
|
340
|
-
const propsBlock = isTs
|
|
341
|
-
? `export type ${baseName}Props = {\n${[
|
|
342
|
-
model.hasAs ? ` as?: React.ElementType;` : null,
|
|
343
|
-
model.hasChildren ? ` children?: React.ReactNode;` : null,
|
|
344
|
-
...required.map((l) => ` ${l};`),
|
|
345
|
-
...commonOptional.map((l) => ` ${l};`),
|
|
346
|
-
].filter(Boolean).join('\n')}${ariaIndex}${dataIndex}\n};`
|
|
347
|
-
: '';
|
|
348
|
-
const destructure = [
|
|
349
|
-
model.hasAs ? 'as: Comp = "div"' : null,
|
|
350
|
-
model.hasChildren ? 'children' : null,
|
|
351
|
-
...model.required,
|
|
352
|
-
...model.common,
|
|
353
|
-
'...rest'
|
|
354
|
-
].filter(Boolean).join(', ');
|
|
355
|
-
const jsxTag = model.hasAs ? '<Comp' : '<div';
|
|
356
|
-
const jsxClose = model.hasAs ? '</Comp>' : '</div>';
|
|
357
|
-
const content = isTs
|
|
358
|
-
? `import React from 'react';\n\n${propsBlock}\n\nexport function ${baseName}({ ${destructure} }: ${baseName}Props) {\n return (\n ${jsxTag} {...rest}>\n {children}\n ${jsxClose}\n );\n}\n`
|
|
359
|
-
: `import React from 'react';\n\nexport function ${baseName}(props) {\n const { ${destructure} } = props || {};\n return (\n ${jsxTag} {...rest}>\n {children}\n ${jsxClose}\n );\n}\n`;
|
|
360
|
-
await fsp.writeFile(filePath, content, 'utf8');
|
|
361
|
-
return { filePath, fileName: path.basename(filePath) };
|
|
362
|
-
};
|
|
363
|
-
const makePreviewSvg = (title, props) => {
|
|
364
|
-
const safeTitle = (title || '').replace(/[<>]/g, '');
|
|
365
|
-
const list = (props || []).slice(0, 14).map((p) => p.replace(/[<>]/g, '')).join(' • ');
|
|
366
|
-
const text = `${safeTitle}${list ? ' — ' + list : ''}`;
|
|
367
|
-
return `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="420">\n <defs>\n <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">\n <stop offset="0" stop-color="#0f172a"/>\n <stop offset="1" stop-color="#111827"/>\n </linearGradient>\n </defs>\n <rect width="1200" height="420" rx="32" fill="url(#g)"/>\n <rect x="40" y="40" width="1120" height="340" rx="28" fill="#0b1220" stroke="rgba(255,255,255,0.08)"/>\n <text x="80" y="150" font-family="Inter,ui-sans-serif,system-ui" font-size="46" font-weight="800" fill="#e2e8f0">${safeTitle}</text>\n <text x="80" y="220" font-family="ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace" font-size="22" font-weight="700" fill="#94a3b8">Preview (fast)</text>\n <text x="80" y="280" font-family="Inter,ui-sans-serif,system-ui" font-size="18" font-weight="600" fill="#64748b">${text}</text>\n</svg>`;
|
|
368
|
-
};
|
|
369
|
-
const buildDefaultProps = (propNames) => {
|
|
370
|
-
const defaults = {};
|
|
371
|
-
for (const p of propNames || []) {
|
|
372
|
-
if (!p)
|
|
373
|
-
continue;
|
|
374
|
-
const name = String(p);
|
|
375
|
-
if (name === 'disabled' || name === 'loading' || name === 'fullWidth') {
|
|
376
|
-
defaults[name] = false;
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
if (name === 'variant') {
|
|
380
|
-
defaults[name] = 'primary';
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (name === 'size') {
|
|
384
|
-
defaults[name] = 'md';
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
if (name === 'type') {
|
|
388
|
-
defaults[name] = 'button';
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
if (name === 'href') {
|
|
392
|
-
defaults[name] = '#';
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (name.toLowerCase().startsWith('on')) {
|
|
396
|
-
defaults[name] = null;
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
if (name === 'iconLeft' || name === 'iconRight') {
|
|
400
|
-
defaults[name] = null;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
// Fallback
|
|
404
|
-
defaults[name] = '';
|
|
405
|
-
}
|
|
406
|
-
return defaults;
|
|
407
|
-
};
|
|
408
|
-
const buildComponentCode = (componentType, requiredProps) => {
|
|
409
|
-
const safeName = toPascalCase(componentType) || 'Component';
|
|
410
|
-
const props = (requiredProps || []).filter(Boolean);
|
|
411
|
-
const destructure = props.length > 0 ? `{ ${props.join(', ')} }` : 'props';
|
|
412
|
-
// IMPORTANT: This code is executed inside a browser ESM module where React is already imported.
|
|
413
|
-
// Avoid JSX to keep it runnable without a build step.
|
|
414
|
-
const lines = [];
|
|
415
|
-
lines.push(`const ${safeName} = (${destructure}) => {`);
|
|
416
|
-
lines.push(` return React.createElement(`);
|
|
417
|
-
lines.push(` "div",`);
|
|
418
|
-
lines.push(` { style: { fontFamily: 'ui-sans-serif, system-ui', padding: 12 } },`);
|
|
419
|
-
lines.push(` React.createElement(`);
|
|
420
|
-
lines.push(` "div",`);
|
|
421
|
-
lines.push(` { style: { fontSize: 14, fontWeight: 700, marginBottom: 8 } },`);
|
|
422
|
-
lines.push(` ${JSON.stringify(safeName)}
|
|
423
|
-
),`);
|
|
424
|
-
lines.push(` React.createElement(`);
|
|
425
|
-
lines.push(` "pre",`);
|
|
426
|
-
lines.push(` { style: { fontSize: 12, opacity: 0.8, whiteSpace: 'pre-wrap' } },`);
|
|
427
|
-
lines.push(` JSON.stringify({ ${props.join(', ')} }, null, 2)
|
|
428
|
-
)`);
|
|
429
|
-
lines.push(` );`);
|
|
430
|
-
lines.push(`};`);
|
|
431
|
-
return { exportName: safeName, code: lines.join('\n') };
|
|
432
|
-
};
|
|
433
216
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
434
217
|
const toolName = request.params.name;
|
|
435
218
|
console.error(`[MCP] Handling tool call: ${toolName}`);
|
|
@@ -458,7 +241,6 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
458
241
|
case "generate_ui_components": {
|
|
459
242
|
const args = request.params.arguments;
|
|
460
243
|
const limit = Math.min(args.limit || 1, 10);
|
|
461
|
-
const targetComponentsDir = await resolveComponentsDir();
|
|
462
244
|
const fetchUrl = new URL(`${BACKEND_URL}/api/mcp/components`);
|
|
463
245
|
fetchUrl.searchParams.append("limit", limit.toString());
|
|
464
246
|
fetchUrl.searchParams.append("status", "pending");
|
|
@@ -482,51 +264,29 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
482
264
|
const componentType = item.componentType;
|
|
483
265
|
const requiredProps = (item.requiredProps || []);
|
|
484
266
|
const commonProps = (item.commonProps || []);
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const errorBody = await uploadResp.text();
|
|
502
|
-
throw new Error(`Preview upload failed (${uploadResp.status}): ${errorBody}`);
|
|
503
|
-
}
|
|
504
|
-
const uploaded = (await uploadResp.json());
|
|
505
|
-
const previewUrl = uploaded?.url || null;
|
|
506
|
-
const markResp = await fetch(`${BACKEND_URL}/api/mcp/components/${item._id}/generated`, {
|
|
507
|
-
method: 'PATCH',
|
|
508
|
-
headers: {
|
|
509
|
-
'Content-Type': 'application/json',
|
|
510
|
-
'x-api-key': apiKey,
|
|
511
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
512
|
-
},
|
|
513
|
-
body: JSON.stringify({ previewImage: previewUrl, code: built.code, props: defaults })
|
|
514
|
-
});
|
|
515
|
-
if (!markResp.ok) {
|
|
516
|
-
const errorBody = await markResp.text();
|
|
517
|
-
throw new Error(`Mark generated failed (${markResp.status}): ${errorBody}`);
|
|
518
|
-
}
|
|
519
|
-
const marked = (await markResp.json());
|
|
267
|
+
const llmInstruction = {
|
|
268
|
+
componentType,
|
|
269
|
+
requiredProps,
|
|
270
|
+
commonProps,
|
|
271
|
+
figmaUrl: item.figmaUrl,
|
|
272
|
+
htmlContent: item.htmlContent,
|
|
273
|
+
cssContent: item.cssContent,
|
|
274
|
+
sourceType: item.sourceType,
|
|
275
|
+
constraints: {
|
|
276
|
+
mustBeFunctional: true,
|
|
277
|
+
mustSupportRequiredProps: true,
|
|
278
|
+
mustSupportCommonProps: true,
|
|
279
|
+
avoidBreakingChanges: true,
|
|
280
|
+
useProvidedHtmlAndCssIfAvailable: true
|
|
281
|
+
}
|
|
282
|
+
};
|
|
520
283
|
results.push({
|
|
521
284
|
componentType,
|
|
522
285
|
figmaUrl: item.figmaUrl,
|
|
523
286
|
requiredProps,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
},
|
|
528
|
-
previewImage: previewUrl,
|
|
529
|
-
db: marked?.data?._id || item._id
|
|
287
|
+
commonProps,
|
|
288
|
+
db: item._id,
|
|
289
|
+
llmInstruction
|
|
530
290
|
});
|
|
531
291
|
}
|
|
532
292
|
return {
|
|
@@ -534,14 +294,34 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
534
294
|
type: "text",
|
|
535
295
|
text: JSON.stringify({
|
|
536
296
|
ok: true,
|
|
537
|
-
generated:
|
|
538
|
-
instruction:
|
|
539
|
-
targetComponentsDir,
|
|
297
|
+
generated: 0,
|
|
298
|
+
instruction: "IMPORTANTE: Este tool NO debe generar código ni escribir archivos. Solo entrega al LLM la especificación (componentType + props) para que el LLM implemente un componente real y funcional en el repo.",
|
|
540
299
|
results
|
|
541
300
|
}, null, 2)
|
|
542
301
|
}]
|
|
543
302
|
};
|
|
544
303
|
}
|
|
304
|
+
case "mark_ui_component_generated": {
|
|
305
|
+
const args = request.params.arguments;
|
|
306
|
+
if (!args?.id || !args?.code) {
|
|
307
|
+
throw new Error("id and code are required");
|
|
308
|
+
}
|
|
309
|
+
const response = await fetch(`${BACKEND_URL}/api/mcp/components/${args.id}/generated`, {
|
|
310
|
+
method: 'PATCH',
|
|
311
|
+
headers: {
|
|
312
|
+
'Content-Type': 'application/json',
|
|
313
|
+
'x-api-key': apiKey,
|
|
314
|
+
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
315
|
+
},
|
|
316
|
+
body: JSON.stringify({ code: args.code, props: args.props })
|
|
317
|
+
});
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
const errorBody = await response.text();
|
|
320
|
+
throw new Error(`Backend responded with ${response.status}: ${errorBody}`);
|
|
321
|
+
}
|
|
322
|
+
const data = await response.json();
|
|
323
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
324
|
+
}
|
|
545
325
|
case "get_annotations": {
|
|
546
326
|
const args = request.params.arguments;
|
|
547
327
|
const limit = args.limit || 3;
|
|
@@ -714,6 +494,33 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
714
494
|
: (data.preferredLanguage === 'en' ? "\n*** [INSTRUCTION: Provide all comments and analysis in English.] ***\n\n\n\n" : "");
|
|
715
495
|
return { content: [{ type: "text", text: langHint + JSON.stringify(data, null, 2) }] };
|
|
716
496
|
}
|
|
497
|
+
case "get_ui_improvements": {
|
|
498
|
+
const args = request.params.arguments;
|
|
499
|
+
const fetchUrl = new URL(`${BACKEND_URL}/api/mcp/ui-improvements`);
|
|
500
|
+
if (args.limit)
|
|
501
|
+
fetchUrl.searchParams.append("limit", args.limit.toString());
|
|
502
|
+
if (args.search)
|
|
503
|
+
fetchUrl.searchParams.append("search", args.search);
|
|
504
|
+
if (args.key)
|
|
505
|
+
fetchUrl.searchParams.append("key", args.key);
|
|
506
|
+
if (args.ids)
|
|
507
|
+
fetchUrl.searchParams.append("ids", args.ids);
|
|
508
|
+
const response = await fetch(fetchUrl, {
|
|
509
|
+
headers: {
|
|
510
|
+
'x-api-key': apiKey,
|
|
511
|
+
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
if (!response.ok) {
|
|
515
|
+
const errorBody = await response.text();
|
|
516
|
+
throw new Error(`Backend responded with ${response.status}: ${errorBody}`);
|
|
517
|
+
}
|
|
518
|
+
const data = (await response.json());
|
|
519
|
+
const langHint = data.preferredLanguage === 'es'
|
|
520
|
+
? "\n*** [INSTRUCTION: Project is in SPANISH. Provide all CSS/Visual changes in SPANISH comments if requested.] ***\n\n\n\n"
|
|
521
|
+
: "";
|
|
522
|
+
return { content: [{ type: "text", text: langHint + JSON.stringify(data, null, 2) }] };
|
|
523
|
+
}
|
|
717
524
|
case "sync_endpoints": {
|
|
718
525
|
const args = request.params.arguments;
|
|
719
526
|
const response = await fetch(`${BACKEND_URL}/api/mcp/sync-endpoints`, {
|