viewgate-mcp 1.0.23 → 1.0.25
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 +106 -315
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,20 +48,20 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
48
48
|
tools: [
|
|
49
49
|
{
|
|
50
50
|
name: "get_annotations",
|
|
51
|
-
description: "Retrieves all feedback annotations. WORKFLOW: 1. Read these annotations, 2. Open the source files and apply SURGICAL fixes
|
|
51
|
+
description: "Retrieves all feedback annotations. Use 'status' to filter (e.g. 'pending,bug_fixing'). WORKFLOW: 1. Read these annotations, 2. Open the source files and apply SURGICAL fixes. 3. Call 'mark_annotation_ready' for EACH ticket. IMPORTANT: Uses 'id' for marking, but 'key' for readability.",
|
|
52
52
|
inputSchema: {
|
|
53
53
|
type: "object",
|
|
54
54
|
properties: {
|
|
55
55
|
limit: { type: "number", description: "Maximum number of annotations to retrieve (default: 3)", default: 3 },
|
|
56
|
-
status: { type: "string", description: "Comma-separated list
|
|
57
|
-
search: { type: "string", description: "Search term to filter
|
|
58
|
-
key: { type: "string", description: "Human
|
|
56
|
+
status: { type: "string", description: "Comma-separated list (e.g. 'pending,bug_fixing'). Use 'all' for any state.", default: "pending,bug_fixing" },
|
|
57
|
+
search: { type: "string", description: "Search term to filter by message or file." },
|
|
58
|
+
key: { type: "string", description: "Human key (e.g. VG-XXXX) to find a specific annotation." }
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
name: "mark_annotation_ready",
|
|
64
|
-
description: "CRITICAL: Call this tool AFTER applying code fixes
|
|
64
|
+
description: "CRITICAL: Call this tool AFTER applying code fixes. REQUIREMENT: Use the internal database ID (e.g. 675ba...), NOT the human key (VG-XXXX).",
|
|
65
65
|
inputSchema: {
|
|
66
66
|
type: "object",
|
|
67
67
|
properties: {
|
|
@@ -71,7 +71,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
71
71
|
type: "object",
|
|
72
72
|
properties: {
|
|
73
73
|
id: { type: "string", description: "The ID of the annotation to mark as ready" },
|
|
74
|
-
appliedChanges: { type: "string", description: "Descriptive summary of
|
|
74
|
+
appliedChanges: { type: "string", description: "Descriptive summary of changes in the preferredLanguage." }
|
|
75
75
|
},
|
|
76
76
|
required: ["id", "appliedChanges"]
|
|
77
77
|
}
|
|
@@ -82,7 +82,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
name: "planning",
|
|
85
|
-
description: "Planning tool for backlog tickets.
|
|
85
|
+
description: "Planning tool for backlog tickets. Fetch tickets or submit analysis.",
|
|
86
86
|
inputSchema: {
|
|
87
87
|
type: "object",
|
|
88
88
|
properties: {
|
|
@@ -91,27 +91,24 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
91
91
|
items: {
|
|
92
92
|
type: "object",
|
|
93
93
|
properties: {
|
|
94
|
-
id: { type: "string"
|
|
94
|
+
id: { type: "string" },
|
|
95
95
|
complejidad: { type: "number", minimum: 1, maximum: 3 },
|
|
96
96
|
incertidumbre: { type: "number", minimum: 1, maximum: 3 },
|
|
97
97
|
impacto: { type: "number", minimum: 1, maximum: 3 },
|
|
98
98
|
riesgo: { type: "number", minimum: 1, maximum: 3 },
|
|
99
99
|
tipo: { type: "string", enum: ["AI-friendly", "AI-assisted", "Human-critical"] },
|
|
100
|
-
aiAnalysis: { type: "string"
|
|
100
|
+
aiAnalysis: { type: "string" }
|
|
101
101
|
},
|
|
102
102
|
required: ["id", "complejidad", "incertidumbre", "impacto", "riesgo", "tipo", "aiAnalysis"]
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
|
-
force: {
|
|
106
|
-
type: "boolean",
|
|
107
|
-
description: "If true, overwrites existing planning data."
|
|
108
|
-
}
|
|
105
|
+
force: { type: "boolean" }
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
108
|
},
|
|
112
109
|
{
|
|
113
110
|
name: "sync_endpoints",
|
|
114
|
-
description: "Manually synchronizes backend endpoints.
|
|
111
|
+
description: "Manually synchronizes backend endpoints.",
|
|
115
112
|
inputSchema: {
|
|
116
113
|
type: "object",
|
|
117
114
|
properties: {
|
|
@@ -120,50 +117,38 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
120
117
|
items: {
|
|
121
118
|
type: "object",
|
|
122
119
|
properties: {
|
|
123
|
-
path: { type: "string"
|
|
124
|
-
method: { type: "string"
|
|
125
|
-
description: { type: "string"
|
|
126
|
-
hash: { type: "string"
|
|
127
|
-
contract: { type: "string"
|
|
120
|
+
path: { type: "string" },
|
|
121
|
+
method: { type: "string" },
|
|
122
|
+
description: { type: "string" },
|
|
123
|
+
hash: { type: "string" },
|
|
124
|
+
contract: { type: "string" }
|
|
128
125
|
},
|
|
129
126
|
required: ["path", "method", "description"]
|
|
130
127
|
}
|
|
131
128
|
},
|
|
132
|
-
clean: {
|
|
133
|
-
|
|
134
|
-
description: "If true, clears all existing endpoints before adding the new ones. Useful for the first batch of a fresh scan."
|
|
135
|
-
},
|
|
136
|
-
globalHash: {
|
|
137
|
-
type: "string",
|
|
138
|
-
description: "Optional SHA256 hash of the entire endpoint set. If provided and matches the backend, the entire sync is skipped for performance."
|
|
139
|
-
}
|
|
129
|
+
clean: { type: "boolean" },
|
|
130
|
+
globalHash: { type: "string" }
|
|
140
131
|
},
|
|
141
132
|
required: ["endpoints"]
|
|
142
133
|
}
|
|
143
134
|
},
|
|
144
135
|
{
|
|
145
136
|
name: "get_synced_endpoints",
|
|
146
|
-
description: "Retrieves the list of endpoints currently synchronized in the backend
|
|
147
|
-
inputSchema: {
|
|
148
|
-
type: "object",
|
|
149
|
-
properties: {},
|
|
150
|
-
},
|
|
137
|
+
description: "Retrieves the list of endpoints currently synchronized in the backend.",
|
|
138
|
+
inputSchema: { type: "object", properties: {} },
|
|
151
139
|
},
|
|
152
140
|
{
|
|
153
141
|
name: "get_optimizations",
|
|
154
|
-
description: "Retrieves reported JSON payload optimizations
|
|
155
|
-
inputSchema: {
|
|
156
|
-
type: "object",
|
|
157
|
-
properties: {},
|
|
158
|
-
},
|
|
142
|
+
description: "Retrieves reported JSON payload optimizations.",
|
|
143
|
+
inputSchema: { type: "object", properties: {} },
|
|
159
144
|
},
|
|
160
145
|
{
|
|
161
146
|
name: "mark_optimization_applied",
|
|
162
|
-
description: "Marks a payload optimization as applied
|
|
147
|
+
description: "Marks a payload optimization as applied.",
|
|
163
148
|
inputSchema: {
|
|
164
149
|
type: "object",
|
|
165
150
|
properties: {
|
|
166
|
-
id: { type: "string"
|
|
151
|
+
id: { type: "string" }
|
|
167
152
|
},
|
|
168
153
|
required: ["id"]
|
|
169
154
|
},
|
|
@@ -172,42 +157,39 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
172
157
|
};
|
|
173
158
|
});
|
|
174
159
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
160
|
+
const toolName = request.params.name;
|
|
161
|
+
console.error(`[MCP] Handling tool call: ${toolName}`);
|
|
162
|
+
try {
|
|
163
|
+
switch (toolName) {
|
|
164
|
+
case "get_annotations": {
|
|
178
165
|
const args = request.params.arguments;
|
|
179
166
|
const limit = args.limit || 3;
|
|
180
167
|
const statuses = args.status || 'pending,bug_fixing';
|
|
181
168
|
const search = args.search || '';
|
|
182
169
|
const key = args.key || '';
|
|
183
|
-
|
|
170
|
+
let fetchUrl = `${BACKEND_URL}/api/mcp/annotations?lock=true`;
|
|
184
171
|
if (statuses && statuses !== 'all')
|
|
185
|
-
|
|
172
|
+
fetchUrl += `&status=${statuses}`;
|
|
186
173
|
if (limit)
|
|
187
|
-
|
|
174
|
+
fetchUrl += `&limit=${limit}`;
|
|
188
175
|
if (search)
|
|
189
|
-
|
|
176
|
+
fetchUrl += `&search=${encodeURIComponent(search)}`;
|
|
190
177
|
if (key)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const response = await fetch(url.toString(), {
|
|
178
|
+
fetchUrl += `&key=${encodeURIComponent(key)}`;
|
|
179
|
+
const response = await fetch(fetchUrl, {
|
|
194
180
|
headers: {
|
|
195
181
|
'x-api-key': apiKey,
|
|
196
182
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
197
183
|
}
|
|
198
184
|
});
|
|
199
|
-
if (!response.ok)
|
|
185
|
+
if (!response.ok)
|
|
200
186
|
throw new Error(`Backend responded with ${response.status}`);
|
|
201
|
-
}
|
|
202
187
|
const rawData = (await response.json());
|
|
203
188
|
let rawAnnotations = Array.isArray(rawData) ? rawData : (rawData?.data || rawData?.annotations || []);
|
|
204
189
|
if (!Array.isArray(rawAnnotations)) {
|
|
205
|
-
return {
|
|
206
|
-
content: [{ type: "text", text: "Error: Invalid format from backend" }],
|
|
207
|
-
isError: true
|
|
208
|
-
};
|
|
190
|
+
return { content: [{ type: "text", text: "Error: Invalid format from backend" }], isError: true };
|
|
209
191
|
}
|
|
210
|
-
// Local filtering fallback for
|
|
192
|
+
// Local filtering fallback (for older backend versions)
|
|
211
193
|
if (search) {
|
|
212
194
|
const lowSearch = search.toLowerCase();
|
|
213
195
|
rawAnnotations = rawAnnotations.filter((ann) => (ann.message && ann.message.toLowerCase().includes(lowSearch)) ||
|
|
@@ -217,315 +199,135 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
217
199
|
if (key) {
|
|
218
200
|
rawAnnotations = rawAnnotations.filter((ann) => ann.key === key || ann._id === key);
|
|
219
201
|
}
|
|
220
|
-
const priorityMap = {
|
|
221
|
-
'urgente': 4,
|
|
222
|
-
'urgent': 4,
|
|
223
|
-
'alta': 3,
|
|
224
|
-
'high': 3,
|
|
225
|
-
'media': 2,
|
|
226
|
-
'medium': 2,
|
|
227
|
-
'baja': 1,
|
|
228
|
-
'low': 1
|
|
229
|
-
};
|
|
202
|
+
const priorityMap = { 'urgente': 4, 'urgent': 4, 'alta': 3, 'high': 3, 'media': 2, 'medium': 2, 'baja': 1, 'low': 1 };
|
|
230
203
|
const sortedAnnotations = rawAnnotations.sort((a, b) => {
|
|
231
|
-
const statusOrder = {
|
|
232
|
-
'pending': 2,
|
|
233
|
-
'bug_fixing': 1
|
|
234
|
-
};
|
|
204
|
+
const statusOrder = { 'pending': 2, 'bug_fixing': 1 };
|
|
235
205
|
const statusA = (a.status || 'pending').toLowerCase();
|
|
236
206
|
const statusB = (b.status || 'pending').toLowerCase();
|
|
237
207
|
if (statusOrder[statusA] !== statusOrder[statusB]) {
|
|
238
208
|
return (statusOrder[statusB] || 0) - (statusOrder[statusA] || 0);
|
|
239
209
|
}
|
|
240
|
-
|
|
241
|
-
const prioB = priorityMap[(b.priority || 'medium').toLowerCase()] || 0;
|
|
242
|
-
return prioB - prioA;
|
|
210
|
+
return (priorityMap[(b.priority || 'medium').toLowerCase()] || 0) - (priorityMap[(a.priority || 'medium').toLowerCase()] || 0);
|
|
243
211
|
});
|
|
244
212
|
const annotationsWithTips = sortedAnnotations.map((ann) => {
|
|
245
213
|
const file = ann.reference?.source?.split(':')[0];
|
|
246
214
|
const line = ann.reference?.source?.split(':')[1];
|
|
247
|
-
const source = ann.reference?.source;
|
|
248
215
|
const pendingCorrection = Array.isArray(ann.corrections)
|
|
249
216
|
? ann.corrections.find((c) => c.status === 'pending')
|
|
250
217
|
: (typeof ann.corrections === 'string' ? { text: ann.corrections } : null);
|
|
251
|
-
// Essential fields for the AI to perform a surgical fix
|
|
252
218
|
return {
|
|
253
219
|
id: ann._id,
|
|
254
220
|
key: ann.key,
|
|
255
221
|
priority: ann.priority,
|
|
256
222
|
status: ann.status,
|
|
257
223
|
message: ann.message,
|
|
258
|
-
corrections: ann.corrections,
|
|
259
|
-
source: source,
|
|
224
|
+
corrections: ann.corrections,
|
|
225
|
+
source: ann.reference?.source,
|
|
260
226
|
filePath: ann.filePath || file,
|
|
261
227
|
line: ann.line || (line ? parseInt(line) : undefined),
|
|
262
228
|
vgId: ann.reference?.vgId,
|
|
263
|
-
confidence: ann.reference?.confidence,
|
|
264
|
-
fingerprint: ann.reference?.fingerprint,
|
|
265
229
|
outerHtml: ann.reference?.outerHtml,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
${
|
|
276
|
-
|
|
277
|
-
"${pendingCorrection.text}"
|
|
278
|
-
${pendingCorrection.author ? `Requested by: ${pendingCorrection.author.name}` : ''}
|
|
279
|
-
PLEASE ENSURE THESE SPECIFIC ISSUES ARE ADDRESSED IN YOUR FIX.` : ''}
|
|
280
|
-
|
|
281
|
-
### 🔬 SURGICAL FIX PROTOCOL (Language: ${rawData.preferredLanguage || 'en'})
|
|
282
|
-
1. **PRIMARY**: Search for \`data-vg-id="${ann.reference?.vgId}"\`.
|
|
283
|
-
2. **SECONDARY**: Check \`${file}\` L${line} (Fiber Source).
|
|
284
|
-
3. **TERTIARY**: Match Tag(\`${ann.reference?.tag}\`) + Role(\`${ann.reference?.fingerprint?.role}\`) + Text("${ann.reference?.text?.slice(0, 30)}").
|
|
285
|
-
4. **VALIDATE**: Confirm parent context matches \`${ann.reference?.parentContext?.slice(0, 50).replace(/`/g, "'")}...\`.
|
|
286
|
-
|
|
287
|
-
IMPORTANT: All your analysis and implementation comments MUST BE IN ${rawData.preferredLanguage === 'es' ? 'SPANISH' : 'ENGLISH'}.
|
|
288
|
-
|
|
289
|
-
Confidence: [vgId: ${ann.reference?.confidence?.vgId || 0}, Fiber: ${ann.reference?.confidence?.fiber || 0}, Fingerprint: ${ann.reference?.confidence?.fingerprint || 0}]
|
|
290
|
-
Instruction: ${ann.message}${pendingCorrection ? `\nCorrection Needed: ${pendingCorrection.text}` : ''}`
|
|
230
|
+
_ia_fix_instruction: `### 🎯 DESIGN CONTEXT (Figma)
|
|
231
|
+
${ann.figmaReference ? `A design reference is available: ${ann.figmaReference}` : 'No explicit design linked.'}
|
|
232
|
+
|
|
233
|
+
${pendingCorrection ? `### ⚠️ PENDING CORRECTION\nUser feedback: "${pendingCorrection.text}"` : ''}
|
|
234
|
+
|
|
235
|
+
### 🔬 FIX PROTOCOL
|
|
236
|
+
1. **Target**: \`data-vg-id="${ann.reference?.vgId}"\` in \`${file}\`.
|
|
237
|
+
2. **Context**: \`${ann.reference?.parentContext?.slice(0, 50)}...\`
|
|
238
|
+
|
|
239
|
+
IMPORTANT: Respond in ${rawData.preferredLanguage === 'es' ? 'SPANISH' : 'ENGLISH'}.
|
|
240
|
+
Instruction: ${ann.message}`
|
|
291
241
|
};
|
|
292
242
|
});
|
|
293
243
|
return {
|
|
294
|
-
content: [
|
|
295
|
-
{
|
|
296
|
-
type: "text",
|
|
297
|
-
text: JSON.stringify({
|
|
298
|
-
preferredLanguage: rawData.preferredLanguage || 'en',
|
|
299
|
-
annotations: annotationsWithTips
|
|
300
|
-
}, null, 2),
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{
|
|
309
|
-
type: "text",
|
|
310
|
-
text: `Error fetching annotations: ${error.message}`,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
isError: true,
|
|
244
|
+
content: [{ type: "text", text: JSON.stringify({ preferredLanguage: rawData.preferredLanguage || 'en', annotations: annotationsWithTips }, null, 2) }],
|
|
314
245
|
};
|
|
315
246
|
}
|
|
316
|
-
|
|
317
|
-
case "mark_annotation_ready": {
|
|
318
|
-
try {
|
|
247
|
+
case "mark_annotation_ready": {
|
|
319
248
|
const args = request.params.arguments;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
body: JSON.stringify({ results: args.results })
|
|
331
|
-
});
|
|
332
|
-
if (!response.ok) {
|
|
333
|
-
throw new Error(`Backend responded with ${response.status}`);
|
|
334
|
-
}
|
|
335
|
-
const result = await response.json();
|
|
336
|
-
return {
|
|
337
|
-
content: [{ type: "text", text: `Successfully processed batch update: ${JSON.stringify(result, null, 2)}` }],
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
lastError = error;
|
|
342
|
-
console.error(`[MCP] mark_annotation_ready attempt ${attempt} failed:`, error.message);
|
|
343
|
-
if (attempt < 3)
|
|
344
|
-
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
throw lastError || new Error('Unknown error during batch update');
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
return {
|
|
351
|
-
content: [{ type: "text", text: `Error marking annotations ready: ${error.message}` }],
|
|
352
|
-
isError: true,
|
|
353
|
-
};
|
|
249
|
+
console.error(`[MCP] Marking ${args.results.length} annotations as ready.`);
|
|
250
|
+
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-ready`, {
|
|
251
|
+
method: 'PATCH',
|
|
252
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
253
|
+
body: JSON.stringify({ results: args.results })
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok)
|
|
256
|
+
throw new Error(`Backend responded with ${response.status}`);
|
|
257
|
+
const result = await response.json();
|
|
258
|
+
return { content: [{ type: "text", text: `Processed: ${JSON.stringify(result, null, 2)}` }] };
|
|
354
259
|
}
|
|
355
|
-
|
|
356
|
-
case "planning": {
|
|
357
|
-
try {
|
|
260
|
+
case "planning": {
|
|
358
261
|
const args = request.params.arguments;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const data = (await response.json());
|
|
371
|
-
return {
|
|
372
|
-
content: [{
|
|
373
|
-
type: "text",
|
|
374
|
-
text: JSON.stringify({
|
|
375
|
-
preferredLanguage: data.preferredLanguage || 'en',
|
|
376
|
-
backlog: data.data || []
|
|
377
|
-
}, null, 2)
|
|
378
|
-
}]
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
// Submit Results Mode
|
|
383
|
-
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-planning`, {
|
|
384
|
-
method: 'PATCH',
|
|
385
|
-
headers: {
|
|
386
|
-
'Content-Type': 'application/json',
|
|
387
|
-
'x-api-key': apiKey,
|
|
388
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
389
|
-
},
|
|
390
|
-
body: JSON.stringify({ results: args.results, force: args.force })
|
|
391
|
-
});
|
|
392
|
-
if (!response.ok) {
|
|
393
|
-
throw new Error(`Backend responded with ${response.status}`);
|
|
394
|
-
}
|
|
395
|
-
const result = await response.json();
|
|
396
|
-
return {
|
|
397
|
-
content: [{ type: "text", text: `Planning results processed: ${JSON.stringify(result, null, 2)}` }]
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
catch (error) {
|
|
402
|
-
return {
|
|
403
|
-
content: [{ type: "text", text: `Error in planning tool: ${error.message}` }],
|
|
404
|
-
isError: true
|
|
405
|
-
};
|
|
262
|
+
const url = args.results ? `${BACKEND_URL}/api/mcp/annotations/batch-planning` : `${BACKEND_URL}/api/mcp/backlog`;
|
|
263
|
+
const method = args.results ? 'PATCH' : 'GET';
|
|
264
|
+
const response = await fetch(url, {
|
|
265
|
+
method,
|
|
266
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
267
|
+
body: args.results ? JSON.stringify({ results: args.results, force: args.force }) : undefined
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok)
|
|
270
|
+
throw new Error(`Backend responded with ${response.status}`);
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
406
273
|
}
|
|
407
|
-
|
|
408
|
-
case "sync_endpoints": {
|
|
409
|
-
try {
|
|
274
|
+
case "sync_endpoints": {
|
|
410
275
|
const args = request.params.arguments;
|
|
411
|
-
if (!args.endpoints || !Array.isArray(args.endpoints)) {
|
|
412
|
-
throw new Error("Invalid endpoints format. Expected an array.");
|
|
413
|
-
}
|
|
414
276
|
const response = await fetch(`${BACKEND_URL}/api/mcp/sync-endpoints`, {
|
|
415
277
|
method: 'POST',
|
|
416
|
-
headers: {
|
|
417
|
-
|
|
418
|
-
'x-api-key': apiKey,
|
|
419
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
420
|
-
},
|
|
421
|
-
body: JSON.stringify({
|
|
422
|
-
endpoints: args.endpoints,
|
|
423
|
-
clean: args.clean,
|
|
424
|
-
globalHash: args.globalHash
|
|
425
|
-
})
|
|
278
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
279
|
+
body: JSON.stringify(args)
|
|
426
280
|
});
|
|
427
|
-
if (!response.ok)
|
|
281
|
+
if (!response.ok)
|
|
428
282
|
throw new Error(`Backend responded with ${response.status}`);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return {
|
|
432
|
-
content: [{ type: "text", text: `Endpoints synchronized successfully: ${result.count} routes processed. ${result.saved ? 'Changes saved.' : 'No changes detected.'}` }]
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
catch (error) {
|
|
436
|
-
return {
|
|
437
|
-
content: [{ type: "text", text: `Error synchronizing endpoints: ${error.message}` }],
|
|
438
|
-
isError: true
|
|
439
|
-
};
|
|
283
|
+
const result = await response.json();
|
|
284
|
+
return { content: [{ type: "text", text: `Sync results: ${JSON.stringify(result)}` }] };
|
|
440
285
|
}
|
|
441
|
-
|
|
442
|
-
case "get_synced_endpoints": {
|
|
443
|
-
try {
|
|
286
|
+
case "get_synced_endpoints": {
|
|
444
287
|
const response = await fetch(`${BACKEND_URL}/api/projects/endpoints`, {
|
|
445
|
-
headers: {
|
|
446
|
-
'x-api-key': apiKey,
|
|
447
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
448
|
-
}
|
|
288
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
449
289
|
});
|
|
450
|
-
if (!response.ok)
|
|
290
|
+
if (!response.ok)
|
|
451
291
|
throw new Error(`Backend responded with ${response.status}`);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
content: [{
|
|
456
|
-
type: "text",
|
|
457
|
-
text: JSON.stringify(data.endpoints || [], null, 2)
|
|
458
|
-
}]
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
return {
|
|
463
|
-
content: [{ type: "text", text: `Error fetching synced endpoints: ${error.message}` }],
|
|
464
|
-
isError: true
|
|
465
|
-
};
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
466
294
|
}
|
|
467
|
-
|
|
468
|
-
case "get_optimizations": {
|
|
469
|
-
try {
|
|
295
|
+
case "get_optimizations": {
|
|
470
296
|
const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations`, {
|
|
471
|
-
headers: {
|
|
472
|
-
'x-api-key': apiKey,
|
|
473
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
474
|
-
}
|
|
297
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
475
298
|
});
|
|
476
299
|
if (!response.ok)
|
|
477
300
|
throw new Error(`Backend responded with ${response.status}`);
|
|
478
301
|
const data = await response.json();
|
|
479
|
-
return {
|
|
480
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
catch (error) {
|
|
484
|
-
return {
|
|
485
|
-
content: [{ type: "text", text: `Error fetching optimizations: ${error.message}` }],
|
|
486
|
-
isError: true
|
|
487
|
-
};
|
|
302
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
488
303
|
}
|
|
489
|
-
|
|
490
|
-
case "mark_optimization_applied": {
|
|
491
|
-
try {
|
|
304
|
+
case "mark_optimization_applied": {
|
|
492
305
|
const args = request.params.arguments;
|
|
493
306
|
const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations/${args.id}/applied`, {
|
|
494
307
|
method: 'PATCH',
|
|
495
|
-
headers: {
|
|
496
|
-
'x-api-key': apiKey,
|
|
497
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
498
|
-
}
|
|
308
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
499
309
|
});
|
|
500
310
|
if (!response.ok)
|
|
501
311
|
throw new Error(`Backend responded with ${response.status}`);
|
|
502
|
-
|
|
503
|
-
return {
|
|
504
|
-
content: [{ type: "text", text: `Optimization marked as applied: ${JSON.stringify(result)}` }]
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
catch (error) {
|
|
508
|
-
return {
|
|
509
|
-
content: [{ type: "text", text: `Error marking optimization: ${error.message}` }],
|
|
510
|
-
isError: true
|
|
511
|
-
};
|
|
312
|
+
return { content: [{ type: "text", text: "Optimization marked as applied." }] };
|
|
512
313
|
}
|
|
314
|
+
default:
|
|
315
|
+
throw new Error("Unknown tool");
|
|
513
316
|
}
|
|
514
|
-
|
|
515
|
-
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.error(`[MCP Error] ${toolName}:`, error.message);
|
|
320
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
516
321
|
}
|
|
517
322
|
});
|
|
518
323
|
return server;
|
|
519
324
|
}
|
|
520
|
-
// --- TRANSPORT SELECTION ---
|
|
521
325
|
const useSSE = process.argv.includes("--sse") || process.env.MCP_TRANSPORT === "sse";
|
|
522
326
|
if (!useSSE) {
|
|
523
|
-
// STDIO Mode (NPM Package Default)
|
|
524
|
-
// In Stdio mode, we use the API key from environment variable
|
|
525
327
|
const apiKey = process.env.VIEWGATE_API_KEY || process.env.API_KEY || "";
|
|
526
328
|
const personalKey = process.env.VIEWGATE_PERSONAL_KEY || "";
|
|
527
329
|
if (!apiKey) {
|
|
528
|
-
console.error("Error: VIEWGATE_API_KEY environment variable is required
|
|
330
|
+
console.error("Error: VIEWGATE_API_KEY environment variable is required.");
|
|
529
331
|
process.exit(1);
|
|
530
332
|
}
|
|
531
333
|
const server = createMcpServer(apiKey, personalKey);
|
|
@@ -536,20 +338,10 @@ if (!useSSE) {
|
|
|
536
338
|
});
|
|
537
339
|
}
|
|
538
340
|
else {
|
|
539
|
-
// SSE Mode (Express/Remote)
|
|
540
341
|
const app = express();
|
|
541
|
-
|
|
542
|
-
app.use(
|
|
543
|
-
|
|
544
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
545
|
-
allowedHeaders: ['Content-Type', 'x-api-key', 'Authorization']
|
|
546
|
-
}));
|
|
547
|
-
// Apply JSON middleware only for non-MCP routes
|
|
548
|
-
app.use((req, res, next) => {
|
|
549
|
-
if (req.path === "/message")
|
|
550
|
-
return next();
|
|
551
|
-
express.json()(req, res, next);
|
|
552
|
-
});
|
|
342
|
+
app.use(cors());
|
|
343
|
+
app.use((req, res, next) => { if (req.path === "/message")
|
|
344
|
+
return next(); express.json()(req, res, next); });
|
|
553
345
|
app.get("/sse", async (req, res) => {
|
|
554
346
|
const apiKey = req.query.apiKey || req.headers['x-api-key'];
|
|
555
347
|
const personalKey = req.query.personalKey || req.headers['x-personal-key'];
|
|
@@ -573,6 +365,5 @@ else {
|
|
|
573
365
|
}
|
|
574
366
|
await session.transport.handlePostMessage(req, res);
|
|
575
367
|
});
|
|
576
|
-
app.listen(port, () => {
|
|
577
|
-
});
|
|
368
|
+
app.listen(port, () => { console.error(`[MCP SSE] Listening on port ${port}`); });
|
|
578
369
|
}
|