viewgate-mcp 1.0.24 → 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.
Files changed (2) hide show
  1. package/dist/index.js +100 -311
  2. 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 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: 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 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. 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,50 +117,38 @@ 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
  },
@@ -172,9 +157,11 @@ function createMcpServer(apiKey, personalKey) {
172
157
  };
173
158
  });
174
159
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
175
- switch (request.params.name) {
176
- case "get_annotations": {
177
- try {
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';
@@ -189,25 +176,20 @@ function createMcpServer(apiKey, personalKey) {
189
176
  fetchUrl += `&search=${encodeURIComponent(search)}`;
190
177
  if (key)
191
178
  fetchUrl += `&key=${encodeURIComponent(key)}`;
192
- console.error(`[MCP] Fetching annotations from: ${fetchUrl}`);
193
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 search and key (in case backend doesn't support them yet)
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,317 +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
- const prioA = priorityMap[(a.priority || 'medium').toLowerCase()] || 0;
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, // Include full history
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
- 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.` : ''}
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
- 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
- };
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
- 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
- };
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) }] };
407
273
  }
408
- }
409
- case "sync_endpoints": {
410
- try {
274
+ case "sync_endpoints": {
411
275
  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
276
  const response = await fetch(`${BACKEND_URL}/api/mcp/sync-endpoints`, {
416
277
  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
- })
278
+ headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
279
+ body: JSON.stringify(args)
427
280
  });
428
- if (!response.ok) {
281
+ if (!response.ok)
429
282
  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
- };
283
+ const result = await response.json();
284
+ return { content: [{ type: "text", text: `Sync results: ${JSON.stringify(result)}` }] };
441
285
  }
442
- }
443
- case "get_synced_endpoints": {
444
- try {
286
+ case "get_synced_endpoints": {
445
287
  const response = await fetch(`${BACKEND_URL}/api/projects/endpoints`, {
446
- headers: {
447
- 'x-api-key': apiKey,
448
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
449
- }
288
+ headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
450
289
  });
451
- if (!response.ok) {
290
+ if (!response.ok)
452
291
  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
- };
292
+ const data = await response.json();
293
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
468
294
  }
469
- }
470
- case "get_optimizations": {
471
- try {
295
+ case "get_optimizations": {
472
296
  const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations`, {
473
- headers: {
474
- 'x-api-key': apiKey,
475
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
476
- }
297
+ headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
477
298
  });
478
299
  if (!response.ok)
479
300
  throw new Error(`Backend responded with ${response.status}`);
480
301
  const data = await response.json();
481
- return {
482
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
483
- };
484
- }
485
- catch (error) {
486
- return {
487
- content: [{ type: "text", text: `Error fetching optimizations: ${error.message}` }],
488
- isError: true
489
- };
302
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
490
303
  }
491
- }
492
- case "mark_optimization_applied": {
493
- try {
304
+ case "mark_optimization_applied": {
494
305
  const args = request.params.arguments;
495
306
  const response = await fetch(`${BACKEND_URL}/api/mcp/optimizations/${args.id}/applied`, {
496
307
  method: 'PATCH',
497
- headers: {
498
- 'x-api-key': apiKey,
499
- ...(personalKey ? { 'x-personal-key': personalKey } : {})
500
- }
308
+ headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
501
309
  });
502
310
  if (!response.ok)
503
311
  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
- };
312
+ return { content: [{ type: "text", text: "Optimization marked as applied." }] };
514
313
  }
314
+ default:
315
+ throw new Error("Unknown tool");
515
316
  }
516
- default:
517
- throw new Error("Unknown tool");
317
+ }
318
+ catch (error) {
319
+ console.error(`[MCP Error] ${toolName}:`, error.message);
320
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
518
321
  }
519
322
  });
520
323
  return server;
521
324
  }
522
- // --- TRANSPORT SELECTION ---
523
325
  const useSSE = process.argv.includes("--sse") || process.env.MCP_TRANSPORT === "sse";
524
326
  if (!useSSE) {
525
- // STDIO Mode (NPM Package Default)
526
- // In Stdio mode, we use the API key from environment variable
527
327
  const apiKey = process.env.VIEWGATE_API_KEY || process.env.API_KEY || "";
528
328
  const personalKey = process.env.VIEWGATE_PERSONAL_KEY || "";
529
329
  if (!apiKey) {
530
- console.error("Error: VIEWGATE_API_KEY environment variable is required in STDIO mode.");
330
+ console.error("Error: VIEWGATE_API_KEY environment variable is required.");
531
331
  process.exit(1);
532
332
  }
533
333
  const server = createMcpServer(apiKey, personalKey);
@@ -538,20 +338,10 @@ if (!useSSE) {
538
338
  });
539
339
  }
540
340
  else {
541
- // SSE Mode (Express/Remote)
542
341
  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
- });
342
+ app.use(cors());
343
+ app.use((req, res, next) => { if (req.path === "/message")
344
+ return next(); express.json()(req, res, next); });
555
345
  app.get("/sse", async (req, res) => {
556
346
  const apiKey = req.query.apiKey || req.headers['x-api-key'];
557
347
  const personalKey = req.query.personalKey || req.headers['x-personal-key'];
@@ -575,6 +365,5 @@ else {
575
365
  }
576
366
  await session.transport.handlePostMessage(req, res);
577
367
  });
578
- app.listen(port, () => {
579
- });
368
+ app.listen(port, () => { console.error(`[MCP SSE] Listening on port ${port}`); });
580
369
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viewgate-mcp",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "viewgate-mcp": "./dist/index.js"