viewgate-mcp 1.0.44 → 1.0.45
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 +203 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -46,6 +46,187 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
46
46
|
tools: {},
|
|
47
47
|
},
|
|
48
48
|
});
|
|
49
|
+
const FLOW_TTL_MS = 10 * 60 * 1000;
|
|
50
|
+
const guard = {
|
|
51
|
+
flow: "idle",
|
|
52
|
+
step: 0,
|
|
53
|
+
startedAt: Date.now(),
|
|
54
|
+
lastActivityAt: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
function resetGuard() {
|
|
57
|
+
guard.flow = "idle";
|
|
58
|
+
guard.step = 0;
|
|
59
|
+
guard.lastTool = undefined;
|
|
60
|
+
guard.startedAt = Date.now();
|
|
61
|
+
guard.lastActivityAt = Date.now();
|
|
62
|
+
}
|
|
63
|
+
function getToolFlow(toolName) {
|
|
64
|
+
switch (toolName) {
|
|
65
|
+
case "get_ui_components":
|
|
66
|
+
case "generate_ui_components":
|
|
67
|
+
case "mark_ui_component_generated":
|
|
68
|
+
return "ui_components";
|
|
69
|
+
case "get_annotations":
|
|
70
|
+
case "mark_annotation_ready":
|
|
71
|
+
case "mark_annotations_as_live":
|
|
72
|
+
return "annotations";
|
|
73
|
+
case "get_ui_improvements":
|
|
74
|
+
return "ui_improvements";
|
|
75
|
+
case "planning":
|
|
76
|
+
return "planning";
|
|
77
|
+
case "sync_endpoints":
|
|
78
|
+
case "get_synced_endpoints":
|
|
79
|
+
return "sync";
|
|
80
|
+
case "get_ai_resolved_tickets":
|
|
81
|
+
return "idle";
|
|
82
|
+
default:
|
|
83
|
+
return "idle";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function enforceWorkflow(toolName, args) {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
if (guard.flow !== "idle" && now - guard.lastActivityAt > FLOW_TTL_MS) {
|
|
89
|
+
resetGuard();
|
|
90
|
+
}
|
|
91
|
+
const desiredFlow = getToolFlow(toolName);
|
|
92
|
+
if (toolName === "get_ai_resolved_tickets") {
|
|
93
|
+
if (guard.flow !== "idle") {
|
|
94
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
95
|
+
}
|
|
96
|
+
guard.lastTool = toolName;
|
|
97
|
+
guard.lastActivityAt = now;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (guard.flow === "idle") {
|
|
101
|
+
switch (toolName) {
|
|
102
|
+
case "get_ui_components":
|
|
103
|
+
guard.flow = "ui_components";
|
|
104
|
+
guard.step = 1;
|
|
105
|
+
break;
|
|
106
|
+
case "get_annotations":
|
|
107
|
+
guard.flow = "annotations";
|
|
108
|
+
guard.step = 1;
|
|
109
|
+
break;
|
|
110
|
+
case "get_ui_improvements":
|
|
111
|
+
guard.flow = "ui_improvements";
|
|
112
|
+
guard.step = 1;
|
|
113
|
+
break;
|
|
114
|
+
case "planning":
|
|
115
|
+
if (args?.results) {
|
|
116
|
+
throw new Error("TOOL_CALL_BLOCKED: planning submit requires prior fetch");
|
|
117
|
+
}
|
|
118
|
+
guard.flow = "planning";
|
|
119
|
+
guard.step = 1;
|
|
120
|
+
break;
|
|
121
|
+
case "sync_endpoints":
|
|
122
|
+
guard.flow = "sync";
|
|
123
|
+
guard.step = 1;
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in idle");
|
|
127
|
+
}
|
|
128
|
+
guard.startedAt = now;
|
|
129
|
+
guard.lastTool = toolName;
|
|
130
|
+
guard.lastActivityAt = now;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (desiredFlow !== guard.flow) {
|
|
134
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
135
|
+
}
|
|
136
|
+
if (guard.flow === "ui_components") {
|
|
137
|
+
if (toolName === "get_ui_components") {
|
|
138
|
+
if (guard.step > 2)
|
|
139
|
+
throw new Error("TOOL_CALL_BLOCKED: cannot restart flow at this step");
|
|
140
|
+
guard.step = 1;
|
|
141
|
+
}
|
|
142
|
+
else if (toolName === "generate_ui_components") {
|
|
143
|
+
if (guard.step !== 1)
|
|
144
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
145
|
+
guard.step = 2;
|
|
146
|
+
}
|
|
147
|
+
else if (toolName === "mark_ui_component_generated") {
|
|
148
|
+
if (guard.step !== 2)
|
|
149
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
150
|
+
resetGuard();
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (guard.flow === "annotations") {
|
|
157
|
+
if (toolName === "get_annotations") {
|
|
158
|
+
if (guard.step > 2)
|
|
159
|
+
throw new Error("TOOL_CALL_BLOCKED: cannot restart flow at this step");
|
|
160
|
+
guard.step = 1;
|
|
161
|
+
}
|
|
162
|
+
else if (toolName === "mark_annotation_ready") {
|
|
163
|
+
if (guard.step !== 1)
|
|
164
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
165
|
+
guard.step = 2;
|
|
166
|
+
}
|
|
167
|
+
else if (toolName === "mark_annotations_as_live") {
|
|
168
|
+
if (guard.step !== 2)
|
|
169
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
170
|
+
resetGuard();
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (guard.flow === "ui_improvements") {
|
|
177
|
+
if (toolName === "get_ui_improvements") {
|
|
178
|
+
if (guard.step > 2)
|
|
179
|
+
throw new Error("TOOL_CALL_BLOCKED: cannot restart flow at this step");
|
|
180
|
+
guard.step = 1;
|
|
181
|
+
}
|
|
182
|
+
else if (toolName === "mark_annotation_ready") {
|
|
183
|
+
if (guard.step !== 1)
|
|
184
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
185
|
+
guard.step = 2;
|
|
186
|
+
}
|
|
187
|
+
else if (toolName === "mark_annotations_as_live") {
|
|
188
|
+
if (guard.step !== 2)
|
|
189
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
190
|
+
resetGuard();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (guard.flow === "planning") {
|
|
197
|
+
if (toolName !== "planning") {
|
|
198
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
199
|
+
}
|
|
200
|
+
const isSubmit = Array.isArray(args?.results);
|
|
201
|
+
if (!isSubmit) {
|
|
202
|
+
if (guard.step !== 1)
|
|
203
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
204
|
+
guard.step = 1;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
if (guard.step !== 1)
|
|
208
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
209
|
+
resetGuard();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (guard.flow === "sync") {
|
|
213
|
+
if (toolName === "sync_endpoints") {
|
|
214
|
+
if (guard.step !== 1)
|
|
215
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
216
|
+
guard.step = 2;
|
|
217
|
+
}
|
|
218
|
+
else if (toolName === "get_synced_endpoints") {
|
|
219
|
+
if (guard.step !== 2)
|
|
220
|
+
throw new Error("TOOL_CALL_BLOCKED: unexpected step");
|
|
221
|
+
resetGuard();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw new Error("TOOL_CALL_BLOCKED: tool not allowed in active flow");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
guard.lastTool = toolName;
|
|
228
|
+
guard.lastActivityAt = now;
|
|
229
|
+
}
|
|
49
230
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
50
231
|
return {
|
|
51
232
|
tools: [
|
|
@@ -216,10 +397,12 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
216
397
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
217
398
|
const toolName = request.params.name;
|
|
218
399
|
console.error(`[MCP] Handling tool call: ${toolName}`);
|
|
400
|
+
const argsAny = request.params.arguments;
|
|
219
401
|
try {
|
|
402
|
+
enforceWorkflow(toolName, argsAny);
|
|
220
403
|
switch (toolName) {
|
|
221
404
|
case "get_ui_components": {
|
|
222
|
-
const args =
|
|
405
|
+
const args = argsAny;
|
|
223
406
|
const limit = args.limit || 1;
|
|
224
407
|
const status = args.status || 'pending';
|
|
225
408
|
const fetchUrl = new URL(`${BACKEND_URL}/api/mcp/components`);
|
|
@@ -228,6 +411,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
228
411
|
const response = await fetch(fetchUrl, {
|
|
229
412
|
headers: {
|
|
230
413
|
'x-api-key': apiKey,
|
|
414
|
+
'x-mcp-tool-name': toolName,
|
|
231
415
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
232
416
|
}
|
|
233
417
|
});
|
|
@@ -239,7 +423,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
239
423
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
240
424
|
}
|
|
241
425
|
case "generate_ui_components": {
|
|
242
|
-
const args =
|
|
426
|
+
const args = argsAny;
|
|
243
427
|
const limit = Math.min(args.limit || 1, 10);
|
|
244
428
|
const fetchUrl = new URL(`${BACKEND_URL}/api/mcp/components`);
|
|
245
429
|
fetchUrl.searchParams.append("limit", limit.toString());
|
|
@@ -247,6 +431,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
247
431
|
const response = await fetch(fetchUrl, {
|
|
248
432
|
headers: {
|
|
249
433
|
'x-api-key': apiKey,
|
|
434
|
+
'x-mcp-tool-name': toolName,
|
|
250
435
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
251
436
|
}
|
|
252
437
|
});
|
|
@@ -302,7 +487,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
302
487
|
};
|
|
303
488
|
}
|
|
304
489
|
case "mark_ui_component_generated": {
|
|
305
|
-
const args =
|
|
490
|
+
const args = argsAny;
|
|
306
491
|
if (!args?.id || !args?.code) {
|
|
307
492
|
throw new Error("id and code are required");
|
|
308
493
|
}
|
|
@@ -311,6 +496,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
311
496
|
headers: {
|
|
312
497
|
'Content-Type': 'application/json',
|
|
313
498
|
'x-api-key': apiKey,
|
|
499
|
+
'x-mcp-tool-name': toolName,
|
|
314
500
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
315
501
|
},
|
|
316
502
|
body: JSON.stringify({ code: args.code, props: args.props })
|
|
@@ -323,7 +509,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
323
509
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
324
510
|
}
|
|
325
511
|
case "get_annotations": {
|
|
326
|
-
const args =
|
|
512
|
+
const args = argsAny;
|
|
327
513
|
const limit = args.limit || 3;
|
|
328
514
|
// Strictly enforce allowed statuses: pending and bug_fixing
|
|
329
515
|
const allowedStatuses = ['pending', 'bug_fixing'];
|
|
@@ -358,6 +544,7 @@ function createMcpServer(apiKey, personalKey) {
|
|
|
358
544
|
const response = await fetch(fetchUrl, {
|
|
359
545
|
headers: {
|
|
360
546
|
'x-api-key': apiKey,
|
|
547
|
+
'x-mcp-tool-name': toolName,
|
|
361
548
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
362
549
|
}
|
|
363
550
|
});
|
|
@@ -436,7 +623,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
436
623
|
};
|
|
437
624
|
}
|
|
438
625
|
case "mark_annotation_ready": {
|
|
439
|
-
const args =
|
|
626
|
+
const args = argsAny;
|
|
440
627
|
const results = args.results;
|
|
441
628
|
console.error(`[mark_annotation_ready] Submitting results for ${results?.length} items`);
|
|
442
629
|
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/batch-ready`, {
|
|
@@ -444,6 +631,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
444
631
|
headers: {
|
|
445
632
|
'Content-Type': 'application/json',
|
|
446
633
|
'x-api-key': apiKey,
|
|
634
|
+
'x-mcp-tool-name': toolName,
|
|
447
635
|
'x-personal-key': personalKey || ''
|
|
448
636
|
},
|
|
449
637
|
body: JSON.stringify({
|
|
@@ -458,7 +646,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
458
646
|
};
|
|
459
647
|
}
|
|
460
648
|
case "mark_annotations_as_live": {
|
|
461
|
-
const args =
|
|
649
|
+
const args = argsAny;
|
|
462
650
|
const ids = args.ids;
|
|
463
651
|
console.error(`[mark_annotations_as_live] Marking ${ids?.length} items as live`);
|
|
464
652
|
const response = await fetch(`${BACKEND_URL}/api/mcp/annotations/mark-as-live`, {
|
|
@@ -466,6 +654,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
466
654
|
headers: {
|
|
467
655
|
'Content-Type': 'application/json',
|
|
468
656
|
'x-api-key': apiKey,
|
|
657
|
+
'x-mcp-tool-name': toolName,
|
|
469
658
|
'x-personal-key': personalKey || ''
|
|
470
659
|
},
|
|
471
660
|
body: JSON.stringify({ ids })
|
|
@@ -478,12 +667,12 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
478
667
|
};
|
|
479
668
|
}
|
|
480
669
|
case "planning": {
|
|
481
|
-
const args =
|
|
670
|
+
const args = argsAny;
|
|
482
671
|
const url = args.results ? `${BACKEND_URL}/api/mcp/annotations/batch-planning` : `${BACKEND_URL}/api/mcp/backlog`;
|
|
483
672
|
const method = args.results ? 'PATCH' : 'GET';
|
|
484
673
|
const response = await fetch(url, {
|
|
485
674
|
method,
|
|
486
|
-
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
675
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'x-mcp-tool-name': toolName, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
487
676
|
body: args.results ? JSON.stringify({ results: args.results, force: args.force }) : undefined
|
|
488
677
|
});
|
|
489
678
|
if (!response.ok)
|
|
@@ -495,7 +684,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
495
684
|
return { content: [{ type: "text", text: langHint + JSON.stringify(data, null, 2) }] };
|
|
496
685
|
}
|
|
497
686
|
case "get_ui_improvements": {
|
|
498
|
-
const args =
|
|
687
|
+
const args = argsAny;
|
|
499
688
|
const fetchUrl = new URL(`${BACKEND_URL}/api/mcp/ui-improvements`);
|
|
500
689
|
if (args.limit)
|
|
501
690
|
fetchUrl.searchParams.append("limit", args.limit.toString());
|
|
@@ -508,6 +697,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
508
697
|
const response = await fetch(fetchUrl, {
|
|
509
698
|
headers: {
|
|
510
699
|
'x-api-key': apiKey,
|
|
700
|
+
'x-mcp-tool-name': toolName,
|
|
511
701
|
...(personalKey ? { 'x-personal-key': personalKey } : {})
|
|
512
702
|
}
|
|
513
703
|
});
|
|
@@ -522,10 +712,10 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
522
712
|
return { content: [{ type: "text", text: langHint + JSON.stringify(data, null, 2) }] };
|
|
523
713
|
}
|
|
524
714
|
case "sync_endpoints": {
|
|
525
|
-
const args =
|
|
715
|
+
const args = argsAny;
|
|
526
716
|
const response = await fetch(`${BACKEND_URL}/api/mcp/sync-endpoints`, {
|
|
527
717
|
method: 'POST',
|
|
528
|
-
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
718
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'x-mcp-tool-name': toolName, ...(personalKey ? { 'x-personal-key': personalKey } : {}) },
|
|
529
719
|
body: JSON.stringify(args)
|
|
530
720
|
});
|
|
531
721
|
if (!response.ok)
|
|
@@ -535,7 +725,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
535
725
|
}
|
|
536
726
|
case "get_synced_endpoints": {
|
|
537
727
|
const response = await fetch(`${BACKEND_URL}/api/projects/endpoints`, {
|
|
538
|
-
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
728
|
+
headers: { 'x-api-key': apiKey, 'x-mcp-tool-name': toolName, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
539
729
|
});
|
|
540
730
|
if (!response.ok)
|
|
541
731
|
throw new Error(`Backend responded with ${response.status}`);
|
|
@@ -544,7 +734,7 @@ Lang: ${rawData.preferredLanguage === 'es' ? 'ES' : 'EN'}
|
|
|
544
734
|
}
|
|
545
735
|
case "get_ai_resolved_tickets": {
|
|
546
736
|
const response = await fetch(`${BACKEND_URL}/api/mcp/resolved-tickets`, {
|
|
547
|
-
headers: { 'x-api-key': apiKey, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
737
|
+
headers: { 'x-api-key': apiKey, 'x-mcp-tool-name': toolName, ...(personalKey ? { 'x-personal-key': personalKey } : {}) }
|
|
548
738
|
});
|
|
549
739
|
if (!response.ok)
|
|
550
740
|
throw new Error(`Backend responded with ${response.status}`);
|