viewgate-mcp 1.0.24 → 1.0.26
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 +109 -308
- package/dist/test_fix.js +59 -0
- package/dist/test_sse.js +23 -0
- 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: Tickets already resolved by the AI in the current sprint are automatically filtered out to prevent redundant work.",
|
|
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. This also registers the tickets as resolved by the AI for the current sprint to avoid re-fetching them. 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,61 +117,56 @@ 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
|
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "get_ai_resolved_tickets",
|
|
158
|
+
description: "Retrieves the list of ticket IDs that have already been resolved by the AI in the current sprint.",
|
|
159
|
+
inputSchema: { type: "object", properties: {} },
|
|
170
160
|
}
|
|
171
161
|
],
|
|
172
162
|
};
|
|
173
163
|
});
|
|
174
164
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
165
|
+
const toolName = request.params.name;
|
|
166
|
+
console.error(`[MCP] Handling tool call: ${toolName}`);
|
|
167
|
+
try {
|
|
168
|
+
switch (toolName) {
|
|
169
|
+
case "get_annotations": {
|
|
178
170
|
const args = request.params.arguments;
|
|
179
171
|
const limit = args.limit || 3;
|
|
180
172
|
const statuses = args.status || 'pending,bug_fixing';
|
|
@@ -189,25 +181,20 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
189
181
|
fetchUrl += `&search=${encodeURIComponent(search)}`;
|
|
190
182
|
if (key)
|
|
191
183
|
fetchUrl += `&key=${encodeURIComponent(key)}`;
|
|
192
|
-
console.error(`[MCP] Fetching annotations from: ${fetchUrl}`);
|
|
193
184
|
const response = await fetch(fetchUrl, {
|
|
194
185
|
headers: {
|
|
195
186
|
'x-api-key': apiKey,
|
|
196
187
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
197
188
|
}
|
|
198
189
|
});
|
|
199
|
-
if (!response.ok)
|
|
190
|
+
if (!response.ok)
|
|
200
191
|
throw new Error(`Backend responded with ${response.status}`);
|
|
201
|
-
}
|
|
202
192
|
const rawData = (await response.json());
|
|
203
193
|
let rawAnnotations = Array.isArray(rawData) ? rawData : (rawData?.data || rawData?.annotations || []);
|
|
204
194
|
if (!Array.isArray(rawAnnotations)) {
|
|
205
|
-
return {
|
|
206
|
-
content: [{ type: "text", text: "Error: Invalid format from backend" }],
|
|
207
|
-
isError: true
|
|
208
|
-
};
|
|
195
|
+
return { content: [{ type: "text", text: "Error: Invalid format from backend" }], isError: true };
|
|
209
196
|
}
|
|
210
|
-
// Local filtering fallback for
|
|
197
|
+
// Local filtering fallback (for older backend versions)
|
|
211
198
|
if (search) {
|
|
212
199
|
const lowSearch = search.toLowerCase();
|
|
213
200
|
rawAnnotations = rawAnnotations.filter((ann) => (ann.message && ann.message.toLowerCase().includes(lowSearch)) ||
|
|
@@ -217,317 +204,142 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
217
204
|
if (key) {
|
|
218
205
|
rawAnnotations = rawAnnotations.filter((ann) => ann.key === key || ann._id === key);
|
|
219
206
|
}
|
|
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
|
-
};
|
|
207
|
+
const priorityMap = { 'urgente': 4, 'urgent': 4, 'alta': 3, 'high': 3, 'media': 2, 'medium': 2, 'baja': 1, 'low': 1 };
|
|
230
208
|
const sortedAnnotations = rawAnnotations.sort((a, b) => {
|
|
231
|
-
const statusOrder = {
|
|
232
|
-
'pending': 2,
|
|
233
|
-
'bug_fixing': 1
|
|
234
|
-
};
|
|
209
|
+
const statusOrder = { 'pending': 2, 'bug_fixing': 1 };
|
|
235
210
|
const statusA = (a.status || 'pending').toLowerCase();
|
|
236
211
|
const statusB = (b.status || 'pending').toLowerCase();
|
|
237
212
|
if (statusOrder[statusA] !== statusOrder[statusB]) {
|
|
238
213
|
return (statusOrder[statusB] || 0) - (statusOrder[statusA] || 0);
|
|
239
214
|
}
|
|
240
|
-
|
|
241
|
-
const prioB = priorityMap[(b.priority || 'medium').toLowerCase()] || 0;
|
|
242
|
-
return prioB - prioA;
|
|
215
|
+
return (priorityMap[(b.priority || 'medium').toLowerCase()] || 0) - (priorityMap[(a.priority || 'medium').toLowerCase()] || 0);
|
|
243
216
|
});
|
|
244
217
|
const annotationsWithTips = sortedAnnotations.map((ann) => {
|
|
245
218
|
const file = ann.reference?.source?.split(':')[0];
|
|
246
219
|
const line = ann.reference?.source?.split(':')[1];
|
|
247
|
-
const source = ann.reference?.source;
|
|
248
220
|
const pendingCorrection = Array.isArray(ann.corrections)
|
|
249
221
|
? ann.corrections.find((c) => c.status === 'pending')
|
|
250
222
|
: (typeof ann.corrections === 'string' ? { text: ann.corrections } : null);
|
|
251
|
-
// Essential fields for the AI to perform a surgical fix
|
|
252
223
|
return {
|
|
253
224
|
id: ann._id,
|
|
254
225
|
key: ann.key,
|
|
255
226
|
priority: ann.priority,
|
|
256
227
|
status: ann.status,
|
|
257
228
|
message: ann.message,
|
|
258
|
-
corrections: ann.corrections,
|
|
259
|
-
source: source,
|
|
229
|
+
corrections: ann.corrections,
|
|
230
|
+
source: ann.reference?.source,
|
|
260
231
|
filePath: ann.filePath || file,
|
|
261
232
|
line: ann.line || (line ? parseInt(line) : undefined),
|
|
262
233
|
vgId: ann.reference?.vgId,
|
|
263
|
-
confidence: ann.reference?.confidence,
|
|
264
|
-
fingerprint: ann.reference?.fingerprint,
|
|
265
234
|
outerHtml: ann.reference?.outerHtml,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
selector: ann.reference?.selector,
|
|
269
|
-
tag: ann.reference?.tag,
|
|
270
|
-
figmaReference: ann.figmaReference,
|
|
271
|
-
backendEndpoints: ann.backendEndpoints || [ann.backendEndpoint].filter(Boolean),
|
|
272
|
-
_ia_fix_instruction: `### 🎨 DESIGN CONTEXT (Figma)
|
|
273
|
-
${ann.figmaReference ? `A design reference is available: ${ann.figmaReference}. Use the Figma link to understand the UX requirements.` : 'No explicit figma design linked.'}
|
|
274
|
-
|
|
275
|
-
${pendingCorrection ? `### ⚠️ PENDING CORRECTION (FEEDBACK)
|
|
276
|
-
The user has requested the following corrections:
|
|
277
|
-
"${pendingCorrection.text}"
|
|
278
|
-
${pendingCorrection.author ? `Requested by: ${pendingCorrection.author.name}` : ''}
|
|
279
|
-
PLEASE ENSURE THESE SPECIFIC ISSUES ARE ADDRESSED IN YOUR FIX.` : ''}
|
|
235
|
+
_ia_fix_instruction: `### 🎯 DESIGN CONTEXT (Figma)
|
|
236
|
+
${ann.figmaReference ? `A design reference is available: ${ann.figmaReference}` : 'No explicit design linked.'}
|
|
280
237
|
|
|
281
|
-
|
|
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, "'")}...\`.
|
|
238
|
+
${pendingCorrection ? `### ⚠️ PENDING CORRECTION\nUser feedback: "${pendingCorrection.text}"` : ''}
|
|
286
239
|
|
|
287
|
-
|
|
240
|
+
### 🔬 FIX PROTOCOL
|
|
241
|
+
1. **Target**: \`data-vg-id="${ann.reference?.vgId}"\` in \`${file}\`.
|
|
242
|
+
2. **Context**: \`${ann.reference?.parentContext?.slice(0, 50)}...\`
|
|
288
243
|
|
|
289
|
-
|
|
290
|
-
Instruction: ${ann.message}
|
|
244
|
+
IMPORTANT: Respond in ${rawData.preferredLanguage === 'es' ? 'SPANISH' : 'ENGLISH'}.
|
|
245
|
+
Instruction: ${ann.message}`
|
|
291
246
|
};
|
|
292
247
|
});
|
|
293
248
|
return {
|
|
294
|
-
content: [
|
|
295
|
-
{
|
|
296
|
-
type: "text",
|
|
297
|
-
text: JSON.stringify({
|
|
298
|
-
preferredLanguage: rawData.preferredLanguage || 'en',
|
|
299
|
-
annotations: annotationsWithTips
|
|
300
|
-
}, null, 2),
|
|
301
|
-
},
|
|
302
|
-
],
|
|
249
|
+
content: [{ type: "text", text: JSON.stringify({ preferredLanguage: rawData.preferredLanguage || 'en', annotations: annotationsWithTips }, null, 2) }],
|
|
303
250
|
};
|
|
304
251
|
}
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{
|
|
309
|
-
type: "text",
|
|
310
|
-
text: `Error fetching annotations: ${error.message}`,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
isError: true,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
case "mark_annotation_ready": {
|
|
318
|
-
try {
|
|
252
|
+
case "mark_annotation_ready": {
|
|
319
253
|
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
|
-
};
|
|
254
|
+
console.error(`[MCP] Marking ${args.results.length} annotations as ready.`);
|
|
255
|
+
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-ready`, {
|
|
256
|
+
method: 'PATCH',
|
|
257
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
258
|
+
body: JSON.stringify({ results: args.results })
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok)
|
|
261
|
+
throw new Error(`Backend responded with ${response.status}`);
|
|
262
|
+
const result = await response.json();
|
|
263
|
+
return { content: [{ type: "text", text: `Processed: ${JSON.stringify(result, null, 2)}` }] };
|
|
354
264
|
}
|
|
355
|
-
|
|
356
|
-
case "planning": {
|
|
357
|
-
try {
|
|
265
|
+
case "planning": {
|
|
358
266
|
const args = request.params.arguments;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const data = (await response.json());
|
|
371
|
-
const backlogNodes = Array.isArray(data) ? data : (data.data || data.backlog || []);
|
|
372
|
-
return {
|
|
373
|
-
content: [{
|
|
374
|
-
type: "text",
|
|
375
|
-
text: JSON.stringify({
|
|
376
|
-
preferredLanguage: data.preferredLanguage || 'en',
|
|
377
|
-
backlog: backlogNodes
|
|
378
|
-
}, null, 2)
|
|
379
|
-
}]
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
// Submit Results Mode
|
|
384
|
-
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-planning`, {
|
|
385
|
-
method: 'PATCH',
|
|
386
|
-
headers: {
|
|
387
|
-
'Content-Type': 'application/json',
|
|
388
|
-
'x-api-key': apiKey,
|
|
389
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
390
|
-
},
|
|
391
|
-
body: JSON.stringify({ results: args.results, force: args.force })
|
|
392
|
-
});
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
throw new Error(`Backend responded with ${response.status}`);
|
|
395
|
-
}
|
|
396
|
-
const result = await response.json();
|
|
397
|
-
return {
|
|
398
|
-
content: [{ type: "text", text: `Planning results processed: ${JSON.stringify(result, null, 2)}` }]
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
return {
|
|
404
|
-
content: [{ type: "text", text: `Error in planning tool: ${error.message}` }],
|
|
405
|
-
isError: true
|
|
406
|
-
};
|
|
267
|
+
const url = args.results ? `${BACKEND_URL}/api/mcp/annotations/batch-planning` : `${BACKEND_URL}/api/mcp/backlog`;
|
|
268
|
+
const method = args.results ? 'PATCH' : 'GET';
|
|
269
|
+
const response = await fetch(url, {
|
|
270
|
+
method,
|
|
271
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
272
|
+
body: args.results ? JSON.stringify({ results: args.results, force: args.force }) : undefined
|
|
273
|
+
});
|
|
274
|
+
if (!response.ok)
|
|
275
|
+
throw new Error(`Backend responded with ${response.status}`);
|
|
276
|
+
const data = await response.json();
|
|
277
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
407
278
|
}
|
|
408
|
-
|
|
409
|
-
case "sync_endpoints": {
|
|
410
|
-
try {
|
|
279
|
+
case "sync_endpoints": {
|
|
411
280
|
const args = request.params.arguments;
|
|
412
|
-
if (!args.endpoints || !Array.isArray(args.endpoints)) {
|
|
413
|
-
throw new Error("Invalid endpoints format. Expected an array.");
|
|
414
|
-
}
|
|
415
281
|
const response = await fetch(`${BACKEND_URL}/api/mcp/sync-endpoints`, {
|
|
416
282
|
method: 'POST',
|
|
417
|
-
headers: {
|
|
418
|
-
|
|
419
|
-
'x-api-key': apiKey,
|
|
420
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
421
|
-
},
|
|
422
|
-
body: JSON.stringify({
|
|
423
|
-
endpoints: args.endpoints,
|
|
424
|
-
clean: args.clean,
|
|
425
|
-
globalHash: args.globalHash
|
|
426
|
-
})
|
|
283
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
284
|
+
body: JSON.stringify(args)
|
|
427
285
|
});
|
|
428
|
-
if (!response.ok)
|
|
286
|
+
if (!response.ok)
|
|
429
287
|
throw new Error(`Backend responded with ${response.status}`);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return {
|
|
433
|
-
content: [{ type: "text", text: `Endpoints synchronized successfully: ${result.count} routes processed. ${result.saved ? 'Changes saved.' : 'No changes detected.'}` }]
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
return {
|
|
438
|
-
content: [{ type: "text", text: `Error synchronizing endpoints: ${error.message}` }],
|
|
439
|
-
isError: true
|
|
440
|
-
};
|
|
288
|
+
const result = await response.json();
|
|
289
|
+
return { content: [{ type: "text", text: `Sync results: ${JSON.stringify(result)}` }] };
|
|
441
290
|
}
|
|
442
|
-
|
|
443
|
-
case "get_synced_endpoints": {
|
|
444
|
-
try {
|
|
291
|
+
case "get_synced_endpoints": {
|
|
445
292
|
const response = await fetch(`${BACKEND_URL}/api/projects/endpoints`, {
|
|
446
|
-
headers: {
|
|
447
|
-
'x-api-key': apiKey,
|
|
448
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
449
|
-
}
|
|
293
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
450
294
|
});
|
|
451
|
-
if (!response.ok)
|
|
295
|
+
if (!response.ok)
|
|
452
296
|
throw new Error(`Backend responded with ${response.status}`);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const endpointsList = Array.isArray(data) ? data : (data.endpoints || data.routes || data.data || []);
|
|
456
|
-
return {
|
|
457
|
-
content: [{
|
|
458
|
-
type: "text",
|
|
459
|
-
text: JSON.stringify(endpointsList, null, 2)
|
|
460
|
-
}]
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
catch (error) {
|
|
464
|
-
return {
|
|
465
|
-
content: [{ type: "text", text: `Error fetching synced endpoints: ${error.message}` }],
|
|
466
|
-
isError: true
|
|
467
|
-
};
|
|
297
|
+
const data = await response.json();
|
|
298
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
468
299
|
}
|
|
469
|
-
|
|
470
|
-
case "get_optimizations": {
|
|
471
|
-
try {
|
|
300
|
+
case "get_optimizations": {
|
|
472
301
|
const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations`, {
|
|
473
|
-
headers: {
|
|
474
|
-
'x-api-key': apiKey,
|
|
475
|
-
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
476
|
-
}
|
|
302
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
477
303
|
});
|
|
478
304
|
if (!response.ok)
|
|
479
305
|
throw new Error(`Backend responded with ${response.status}`);
|
|
480
306
|
const data = await response.json();
|
|
481
|
-
return {
|
|
482
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
483
|
-
};
|
|
307
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
484
308
|
}
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
content: [{ type: "text", text: `Error fetching optimizations: ${error.message}` }],
|
|
488
|
-
isError: true
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
case "mark_optimization_applied": {
|
|
493
|
-
try {
|
|
309
|
+
case "mark_optimization_applied": {
|
|
494
310
|
const args = request.params.arguments;
|
|
495
311
|
const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations/${args.id}/applied`, {
|
|
496
312
|
method: 'PATCH',
|
|
497
|
-
headers: {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
313
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
314
|
+
});
|
|
315
|
+
return { content: [{ type: "text", text: "Optimization marked as applied." }] };
|
|
316
|
+
}
|
|
317
|
+
case "get_ai_resolved_tickets": {
|
|
318
|
+
const response = await fetch(`${BACKEND_URL}/api/mcp/resolved-tickets`, {
|
|
319
|
+
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
501
320
|
});
|
|
502
321
|
if (!response.ok)
|
|
503
322
|
throw new Error(`Backend responded with ${response.status}`);
|
|
504
|
-
const
|
|
505
|
-
return {
|
|
506
|
-
content: [{ type: "text", text: `Optimization marked as applied: ${JSON.stringify(result)}` }]
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
catch (error) {
|
|
510
|
-
return {
|
|
511
|
-
content: [{ type: "text", text: `Error marking optimization: ${error.message}` }],
|
|
512
|
-
isError: true
|
|
513
|
-
};
|
|
323
|
+
const data = await response.json();
|
|
324
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
514
325
|
}
|
|
326
|
+
default:
|
|
327
|
+
throw new Error("Unknown tool");
|
|
515
328
|
}
|
|
516
|
-
|
|
517
|
-
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
console.error(`[MCP Error] ${toolName}:`, error.message);
|
|
332
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
518
333
|
}
|
|
519
334
|
});
|
|
520
335
|
return server;
|
|
521
336
|
}
|
|
522
|
-
// --- TRANSPORT SELECTION ---
|
|
523
337
|
const useSSE = process.argv.includes("--sse") || process.env.MCP_TRANSPORT === "sse";
|
|
524
338
|
if (!useSSE) {
|
|
525
|
-
// STDIO Mode (NPM Package Default)
|
|
526
|
-
// In Stdio mode, we use the API key from environment variable
|
|
527
339
|
const apiKey = process.env.VIEWGATE_API_KEY || process.env.API_KEY || "";
|
|
528
340
|
const personalKey = process.env.VIEWGATE_PERSONAL_KEY || "";
|
|
529
341
|
if (!apiKey) {
|
|
530
|
-
console.error("Error: VIEWGATE_API_KEY environment variable is required
|
|
342
|
+
console.error("Error: VIEWGATE_API_KEY environment variable is required.");
|
|
531
343
|
process.exit(1);
|
|
532
344
|
}
|
|
533
345
|
const server = createMcpServer(apiKey, personalKey);
|
|
@@ -538,20 +350,10 @@ if (!useSSE) {
|
|
|
538
350
|
});
|
|
539
351
|
}
|
|
540
352
|
else {
|
|
541
|
-
// SSE Mode (Express/Remote)
|
|
542
353
|
const app = express();
|
|
543
|
-
|
|
544
|
-
app.use(
|
|
545
|
-
|
|
546
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
547
|
-
allowedHeaders: ['Content-Type', 'x-api-key', 'Authorization']
|
|
548
|
-
}));
|
|
549
|
-
// Apply JSON middleware only for non-MCP routes
|
|
550
|
-
app.use((req, res, next) => {
|
|
551
|
-
if (req.path === "/message")
|
|
552
|
-
return next();
|
|
553
|
-
express.json()(req, res, next);
|
|
554
|
-
});
|
|
354
|
+
app.use(cors());
|
|
355
|
+
app.use((req, res, next) => { if (req.path === "/message")
|
|
356
|
+
return next(); express.json()(req, res, next); });
|
|
555
357
|
app.get("/sse", async (req, res) => {
|
|
556
358
|
const apiKey = req.query.apiKey || req.headers['x-api-key'];
|
|
557
359
|
const personalKey = req.query.personalKey || req.headers['x-personal-key'];
|
|
@@ -575,6 +377,5 @@ else {
|
|
|
575
377
|
}
|
|
576
378
|
await session.transport.handlePostMessage(req, res);
|
|
577
379
|
});
|
|
578
|
-
app.listen(port, () => {
|
|
579
|
-
});
|
|
380
|
+
app.listen(port, () => { console.error(`[MCP SSE] Listening on port ${port}`); });
|
|
580
381
|
}
|
package/dist/test_fix.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
async function test() {
|
|
3
|
+
console.log("1. Connecting to http://localhost:3333/sse...");
|
|
4
|
+
const response = await fetch("http://localhost:3333/sse?apiKey=2af5628c9442f10b93cfe6970897d4de26578d958ba43a4e3bcb684fe0f92668");
|
|
5
|
+
if (!response.ok) {
|
|
6
|
+
console.error("Failed to connect to SSE:", response.status, await response.text());
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
console.log("SSE connected. Reading stream for sessionId...");
|
|
10
|
+
const body = response.body;
|
|
11
|
+
let sessionId = null;
|
|
12
|
+
body.on("data", async (chunk) => {
|
|
13
|
+
const text = chunk.toString();
|
|
14
|
+
console.log("SSE Data:", text);
|
|
15
|
+
// Example event:
|
|
16
|
+
// event: endpoint
|
|
17
|
+
// data: /message?sessionId=...
|
|
18
|
+
const match = text.match(/sessionId=([a-f0-9-]+)/);
|
|
19
|
+
if (match && !sessionId) {
|
|
20
|
+
sessionId = match[1];
|
|
21
|
+
console.log("FOUND SESSION ID:", sessionId);
|
|
22
|
+
// 2. Test POST to /message
|
|
23
|
+
console.log(`2. Testing POST to /message?sessionId=${sessionId}...`);
|
|
24
|
+
const postResponse = await fetch(`http://localhost:3333/message?sessionId=${sessionId}`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json'
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
jsonrpc: "2.0",
|
|
31
|
+
id: 1,
|
|
32
|
+
method: "initialize",
|
|
33
|
+
params: {
|
|
34
|
+
clientInfo: { name: "test-client", version: "1.0.0" },
|
|
35
|
+
protocolVersion: "2024-11-05",
|
|
36
|
+
capabilities: {}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
console.log("POST Response Status:", postResponse.status);
|
|
41
|
+
const responseText = await postResponse.text();
|
|
42
|
+
console.log("POST Response Body:", responseText);
|
|
43
|
+
if (postResponse.status === 200 || postResponse.status === 202) {
|
|
44
|
+
console.log("SUCCESS: Stream was readable and message was accepted.");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error("FAILURE: Server returned error status.");
|
|
48
|
+
}
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
if (!sessionId) {
|
|
54
|
+
console.error("TIMEOUT: Did not receive sessionId from SSE.");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}, 10000);
|
|
58
|
+
}
|
|
59
|
+
test();
|
package/dist/test_sse.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
async function test() {
|
|
3
|
+
console.log("Connecting to http://localhost:3333/sse...");
|
|
4
|
+
const response = await fetch("http://localhost:3333/sse?apiKey=test-key");
|
|
5
|
+
if (!response.ok) {
|
|
6
|
+
console.error("Failed to connect:", response.status, await response.text());
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
console.log("Headers:", response.headers.raw());
|
|
10
|
+
const body = response.body;
|
|
11
|
+
body.on("data", (chunk) => {
|
|
12
|
+
console.log("Chunk received:", chunk.toString());
|
|
13
|
+
if (chunk.toString().includes("event: endpoint")) {
|
|
14
|
+
console.log("FOUND ENDPOINT EVENT!");
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
body.on("end", () => console.log("Connection closed"));
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
console.log("Closing test...");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}, 5000);
|
|
22
|
+
}
|
|
23
|
+
test();
|