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 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 based on '_ia_fix_instruction'. 3. IMMEDIATELY call 'mark_annotation_ready' with the results for EACH ticket you fixed. Use 'outerHtml' and 'source' (file:line) to locate the exact code block without manual searching. IMPORTANT: All AI analysis and comments MUST be in the 'preferredLanguage' specified in the output. Failure to follow the language setting is UNACCEPTABLE.",
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 of statuses to fetch (e.g. 'pending,bug_fixing,in_progress'). Use 'all' to fetch from any state.", default: "pending,bug_fixing" },
57
- search: { type: "string", description: "Search term to filter annotations by text in message or file paths." },
58
- key: { type: "string", description: "Human-readable key (e.g. VG-B62B4E) to find a specific annotation ID." }
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 for annotations. It marks tickets as 'Ready for Review'. REQUIREMENT: Use the internal database ID (e.g. 675ba...), NOT the human key (VG-XXXX). If you only have the key, use 'get_annotations' with 'key' filter first. IMPORTANT: The 'appliedChanges' summary MUST be written in the 'preferredLanguage' specified by 'get_annotations'.",
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 what was changed in the code for this specific ticket. MUST be in the preferredLanguage." }
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. CALL WITHOUT ARGUMENTS to fetch backlog tickets that need analysis. CALL WITH 'results' to submit AI analysis in batch. Rules: complejidad (1-3), incertidumbre (1-3), impacto (1-3), riesgo (1-3), tipo (AI-friendly|AI-assisted|Human-critical). IMPORTANT: All AI analysis, 'aiAnalysis' descriptions, and comments MUST be in the 'preferredLanguage' specified in the output.",
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", description: "The ticket/annotation ID" },
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", description: "Reasoning or summary of the analysis. MUST be in the preferredLanguage." }
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. Call ONLY if the user explicitly asks to 'sync endpoints' or 'update routes'. DO NOT call this automatically during other workflows.",
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", description: "The relative path of the endpoint" },
124
- method: { type: "string", description: "HTTP method (GET, POST, etc.)" },
125
- description: { type: "string", description: "Human-readable description of what the endpoint does. MUST be in the preferredLanguage." },
126
- hash: { type: "string", description: "Optional SHA256 hash of the endpoint definition for efficient diffing." },
127
- contract: { type: "string", description: "Optional JSON string or description of the request/response contract (payload)." }
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
- type: "boolean",
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 for this project. Use this to perform diffing and avoid redundant synchronization calls.",
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 (unused fields). WORKFLOW: 1. Get optimizations, 2. Locate the backend code for the endpoint, 3. Remove unused fields from the response, 4. Call 'mark_optimization_applied' with the ID.",
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 in the backend after you have modified the code to remove unused fields.",
147
+ description: "Marks a payload optimization as applied.",
163
148
  inputSchema: {
164
149
  type: "object",
165
150
  properties: {
166
- id: { type: "string", description: "The ID of the optimization report to mark as applied" }
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
- switch (request.params.name) {
176
- case "get_annotations": {
177
- try {
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 search and key (in case backend doesn't support them yet)
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
- const prioA = priorityMap[(a.priority || 'medium').toLowerCase()] || 0;
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, // Include full history
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
- parentContext: ann.reference?.parentContext,
267
- componentPath: ann.reference?.componentPath,
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
- ### 🔬 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, "'")}...\`.
238
+ ${pendingCorrection ? `### ⚠️ PENDING CORRECTION\nUser feedback: "${pendingCorrection.text}"` : ''}
286
239
 
287
- IMPORTANT: All your analysis and implementation comments MUST BE IN ${rawData.preferredLanguage === 'es' ? 'SPANISH' : 'ENGLISH'}.
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
- 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}` : ''}`
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
- catch (error) {
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
- let lastError = null;
321
- for (let attempt = 1; attempt <= 3; attempt++) {
322
- try {
323
- const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-ready`, {
324
- method: 'PATCH',
325
- headers: {
326
- 'Content-Type': 'application/json',
327
- 'x-api-key': apiKey,
328
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
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
- if (!args.results) {
360
- // Fetch Backlog Mode
361
- const response = await fetch(`${BACKEND_URL}/api/mcp/backlog`, {
362
- headers: {
363
- 'x-api-key': apiKey,
364
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
365
- }
366
- });
367
- if (!response.ok) {
368
- throw new Error(`Backend responded with ${response.status}`);
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
- 'Content-Type': 'application/json',
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
- const result = (await response.json());
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
- const data = (await response.json());
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
- catch (error) {
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
- 'x-api-key': apiKey,
499
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
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 result = await response.json();
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
- default:
517
- throw new Error("Unknown tool");
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 in STDIO mode.");
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
- const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : '*';
544
- app.use(cors({
545
- origin: ALLOWED_ORIGINS,
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
  }
@@ -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();
@@ -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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viewgate-mcp",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "viewgate-mcp": "./dist/index.js"