@cqsjjb/meter-sphere-mcp-server 1.0.1 → 1.0.3
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/README.md +35 -20
- package/mcp-config.example.json +22 -20
- package/mcp-server.mjs +998 -927
- package/package.json +5 -2
package/mcp-server.mjs
CHANGED
|
@@ -1,928 +1,999 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import http from 'http';
|
|
10
|
-
import https from 'https';
|
|
11
|
-
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
*/
|
|
91
|
-
function
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
'
|
|
370
|
-
'
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
},
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
]
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
)
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
]
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import http from 'http';
|
|
10
|
+
import https from 'https';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取平台配置,从环境变量读取(扁平化格式)
|
|
17
|
+
*/
|
|
18
|
+
function getPlatformConfig() {
|
|
19
|
+
const xAuthToken = process.env.PLATFORM_X_AUTH_TOKEN;
|
|
20
|
+
const csrfToken = process.env.PLATFORM_CSRF_TOKEN;
|
|
21
|
+
const project = process.env.PLATFORM_PROJECT;
|
|
22
|
+
const organization = process.env.PLATFORM_ORGANIZATION;
|
|
23
|
+
const moduleIdsStr = process.env.PLATFORM_MODULE_IDS || '[]';
|
|
24
|
+
|
|
25
|
+
if (!xAuthToken || !csrfToken || !project || !organization) {
|
|
26
|
+
throw new Error('缺少必要配置。请在 Cursor 的 MCP 配置中通过环境变量设置 PLATFORM_X_AUTH_TOKEN、PLATFORM_CSRF_TOKEN、PLATFORM_PROJECT、PLATFORM_ORGANIZATION');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let moduleIds = [];
|
|
30
|
+
try {
|
|
31
|
+
moduleIds = JSON.parse(moduleIdsStr);
|
|
32
|
+
if (!Array.isArray(moduleIds)) {
|
|
33
|
+
moduleIds = [];
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// 如果解析失败,使用空数组
|
|
37
|
+
moduleIds = [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
xAuthToken,
|
|
42
|
+
csrfToken,
|
|
43
|
+
project,
|
|
44
|
+
organization,
|
|
45
|
+
moduleIds
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 获取默认的AI分析提示语模板
|
|
51
|
+
*/
|
|
52
|
+
function getDefaultAnalysisPromptTemplate() {
|
|
53
|
+
return `你是一个专业的测试用例分析专家。请仔细分析以下测试用例,将其中的测试步骤按照以下三个类别进行分类:
|
|
54
|
+
|
|
55
|
+
**重要说明:**
|
|
56
|
+
- 此工具仅用于**静态代码分析**,通过分析代码结构、逻辑、配置等来验证测试用例
|
|
57
|
+
- **不涉及实际交互操作**,如页面点击、表单提交、实际运行等
|
|
58
|
+
- 对于涉及用户交互、界面操作、实际运行验证的测试步骤,需要明确提示用户手动完成测试验证
|
|
59
|
+
|
|
60
|
+
**分类说明:**
|
|
61
|
+
1. **配置优化** - 涉及系统配置、参数设置、选项调整、权限配置、环境配置等(可通过静态代码分析验证)
|
|
62
|
+
2. **前端交互功能** - 涉及用户界面操作、页面交互、表单填写、按钮点击、数据展示、样式验证等前端功能(需要用户手动验证交互效果)
|
|
63
|
+
3. **后端数据** - 涉及接口调用、数据处理、数据库操作、数据同步、API请求、数据查询等后端功能(可通过静态代码分析验证接口调用逻辑)
|
|
64
|
+
|
|
65
|
+
**测试用例内容:**
|
|
66
|
+
{{PROMPT_TEXT}}
|
|
67
|
+
|
|
68
|
+
**要求:**
|
|
69
|
+
1. 仔细分析每个测试步骤,判断它主要属于哪个类别
|
|
70
|
+
2. 对于每个测试步骤,明确标注其类别(配置优化/前端交互功能/后端数据)
|
|
71
|
+
3. 如果某个步骤涉及多个方面,选择最主要的一个类别
|
|
72
|
+
4. 最后给出整体分类结论:这个用例主要属于哪个类别
|
|
73
|
+
5. **对于涉及交互的步骤,必须明确标注"需要用户手动验证交互效果"**
|
|
74
|
+
6. 明确指出哪些步骤可以通过静态代码分析完成,哪些需要用户手动测试验证
|
|
75
|
+
|
|
76
|
+
**输出格式:**
|
|
77
|
+
请按照以下格式输出:
|
|
78
|
+
|
|
79
|
+
1. 测试点:[测试点名称]
|
|
80
|
+
2. 用例名称:[用例名称]
|
|
81
|
+
3. 测试类别:[前端交互功能 / 配置优化 / 后端数据]
|
|
82
|
+
4. 测试步骤分析
|
|
83
|
+
- 可通过静态代码分析验证的步骤:[列出可通过代码分析验证的步骤]
|
|
84
|
+
- 需要用户手动验证交互的步骤:[列出需要实际交互验证的步骤,并明确提示用户手动完成测试]
|
|
85
|
+
5. 静态代码分析建议:[针对可通过静态代码分析的部分,给出具体的代码检查建议]`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 获取AI模型配置,从环境变量读取(扁平化格式)
|
|
90
|
+
*/
|
|
91
|
+
function getModelConfig() {
|
|
92
|
+
const baseURL = process.env.MODEL_BASE_URL;
|
|
93
|
+
const apiKey = process.env.MODEL_API_KEY;
|
|
94
|
+
const model = process.env.MODEL_ID;
|
|
95
|
+
const analysisPromptTemplate = process.env.MODEL_ANALYSIS_PROMPT || getDefaultAnalysisPromptTemplate();
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
baseURL: baseURL || null,
|
|
99
|
+
apiKey: apiKey || null,
|
|
100
|
+
model: model || null,
|
|
101
|
+
analysisPromptTemplate: analysisPromptTemplate
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 获取进度文件路径
|
|
107
|
+
* 优先级:环境变量 > 当前工作目录 > 用户主目录下的配置目录
|
|
108
|
+
*/
|
|
109
|
+
function getProgressFilePath() {
|
|
110
|
+
// 优先使用环境变量指定的目录
|
|
111
|
+
if (process.env.PROGRESS_FILE_DIR) {
|
|
112
|
+
return path.join(process.env.PROGRESS_FILE_DIR, 'test-progress.json');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 其次使用当前工作目录(用户运行命令的目录,通常是项目根目录)
|
|
116
|
+
// 这对于npm包来说是最合适的,因为进度文件应该跟随项目
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
if (cwd && cwd !== '/') {
|
|
119
|
+
return path.join(cwd, 'test-progress.json');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 最后回退到用户主目录下的配置目录
|
|
123
|
+
const homeDir = os.homedir();
|
|
124
|
+
const configDir = path.join(homeDir, '.config', 'meter-sphere-mcp');
|
|
125
|
+
|
|
126
|
+
// 确保配置目录存在
|
|
127
|
+
try {
|
|
128
|
+
if (!fs.existsSync(configDir)) {
|
|
129
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('创建配置目录失败:', error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return path.join(configDir, 'test-progress.json');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 加载测试进度
|
|
140
|
+
*/
|
|
141
|
+
function loadProgress() {
|
|
142
|
+
const progressPath = getProgressFilePath();
|
|
143
|
+
try {
|
|
144
|
+
if (fs.existsSync(progressPath)) {
|
|
145
|
+
const content = fs.readFileSync(progressPath, 'utf8');
|
|
146
|
+
const progress = JSON.parse(content);
|
|
147
|
+
// 向后兼容:如果旧进度文件没有 knownTestCaseIds 字段,初始化它
|
|
148
|
+
if (!progress.knownTestCaseIds) {
|
|
149
|
+
progress.knownTestCaseIds = [];
|
|
150
|
+
// 如果已有已完成用例,将它们添加到 knownTestCaseIds 中
|
|
151
|
+
if (progress.completed && Array.isArray(progress.completed)) {
|
|
152
|
+
progress.completed.forEach(item => {
|
|
153
|
+
if (item.testCaseId && !progress.knownTestCaseIds.includes(item.testCaseId)) {
|
|
154
|
+
progress.knownTestCaseIds.push(item.testCaseId);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// 更新 total 为 knownTestCaseIds 的长度
|
|
159
|
+
progress.total = progress.knownTestCaseIds.length;
|
|
160
|
+
// 保存更新后的进度,确保向后兼容的修改被持久化
|
|
161
|
+
try {
|
|
162
|
+
saveProgress(progress);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// 如果保存失败,不影响加载,只记录错误
|
|
165
|
+
console.error('保存向后兼容更新失败:', error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return progress;
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('加载进度文件失败:', error);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
lastUpdate: null,
|
|
175
|
+
completed: [],
|
|
176
|
+
total: 0,
|
|
177
|
+
completedCount: 0,
|
|
178
|
+
knownTestCaseIds: [] // 记录所有已知的用例ID(用于准确计算总数)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 保存测试进度
|
|
184
|
+
*/
|
|
185
|
+
function saveProgress(progress) {
|
|
186
|
+
const progressPath = getProgressFilePath();
|
|
187
|
+
try {
|
|
188
|
+
progress.lastUpdate = new Date().toISOString();
|
|
189
|
+
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('保存进度文件失败:', error);
|
|
192
|
+
throw new Error(`保存进度失败: ${error.message}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 检查测试用例是否已完成
|
|
198
|
+
*/
|
|
199
|
+
function isCompleted(testCaseId, progress) {
|
|
200
|
+
return progress.completed.some(item => item.testCaseId === testCaseId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 标记测试用例为已完成
|
|
205
|
+
*/
|
|
206
|
+
function markCompleted(testCaseId, priority, progress) {
|
|
207
|
+
if (!isCompleted(testCaseId, progress)) {
|
|
208
|
+
progress.completed.push({
|
|
209
|
+
testCaseId: testCaseId,
|
|
210
|
+
priority: priority,
|
|
211
|
+
completedAt: new Date().toISOString()
|
|
212
|
+
});
|
|
213
|
+
progress.completedCount = progress.completed.length;
|
|
214
|
+
|
|
215
|
+
// 确保 knownTestCaseIds 存在
|
|
216
|
+
if (!progress.knownTestCaseIds) {
|
|
217
|
+
progress.knownTestCaseIds = [];
|
|
218
|
+
}
|
|
219
|
+
// 将已完成的用例ID添加到已知集合中(如果不存在)
|
|
220
|
+
if (!progress.knownTestCaseIds.includes(testCaseId)) {
|
|
221
|
+
progress.knownTestCaseIds.push(testCaseId);
|
|
222
|
+
}
|
|
223
|
+
// 更新总数
|
|
224
|
+
progress.total = progress.knownTestCaseIds.length;
|
|
225
|
+
|
|
226
|
+
saveProgress(progress);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 重置测试进度文件
|
|
232
|
+
*/
|
|
233
|
+
function resetProgress() {
|
|
234
|
+
const newProgress = {
|
|
235
|
+
lastUpdate: null,
|
|
236
|
+
completed: [],
|
|
237
|
+
total: 0,
|
|
238
|
+
completedCount: 0,
|
|
239
|
+
knownTestCaseIds: []
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
saveProgress(newProgress);
|
|
244
|
+
return newProgress;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('重置进度文件失败:', error);
|
|
247
|
+
throw new Error(`重置进度失败: ${error.message}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 发送 HTTP POST 请求
|
|
253
|
+
*/
|
|
254
|
+
function httpPost(url, headers = {}, data = {}) {
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const urlObj = new URL(url);
|
|
257
|
+
const postData = JSON.stringify(data);
|
|
258
|
+
|
|
259
|
+
const requestHeaders = {
|
|
260
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
|
261
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
262
|
+
...headers
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const options = {
|
|
266
|
+
hostname: urlObj.hostname,
|
|
267
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
268
|
+
path: urlObj.pathname + (urlObj.search || ''),
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: requestHeaders
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const req = http.request(options, res => {
|
|
274
|
+
const chunks = [];
|
|
275
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
276
|
+
res.on('end', () => {
|
|
277
|
+
const buffer = Buffer.concat(chunks);
|
|
278
|
+
const responseData = buffer.toString('utf8');
|
|
279
|
+
resolve(responseData);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
req.on('error', error => reject(error));
|
|
284
|
+
req.write(postData);
|
|
285
|
+
req.end();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 发送 HTTP GET 请求
|
|
291
|
+
*/
|
|
292
|
+
function httpGet(url, headers = {}) {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
const urlObj = new URL(url);
|
|
295
|
+
const options = {
|
|
296
|
+
hostname: urlObj.hostname,
|
|
297
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
298
|
+
path: urlObj.pathname + (urlObj.search || ''),
|
|
299
|
+
method: 'GET',
|
|
300
|
+
headers: headers
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const req = http.request(options, res => {
|
|
304
|
+
const chunks = [];
|
|
305
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
306
|
+
res.on('end', () => {
|
|
307
|
+
const buffer = Buffer.concat(chunks);
|
|
308
|
+
const responseData = buffer.toString('utf8');
|
|
309
|
+
resolve(responseData);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
req.on('error', error => reject(error));
|
|
314
|
+
req.end();
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 生成AI测试提示语
|
|
320
|
+
*/
|
|
321
|
+
function generateAIPrompt(detailData, testPlanCollectionName) {
|
|
322
|
+
try {
|
|
323
|
+
const name = detailData.name || '';
|
|
324
|
+
const caseEditType = detailData.caseEditType || 'STEP';
|
|
325
|
+
|
|
326
|
+
let prompt = `测试点:${testPlanCollectionName || ''}\n用例名称:${name}\n测试步骤:\n`;
|
|
327
|
+
let hasSteps = false;
|
|
328
|
+
|
|
329
|
+
// 处理 STEP 类型:从 steps 字段解析
|
|
330
|
+
if (caseEditType === 'STEP') {
|
|
331
|
+
try {
|
|
332
|
+
const steps = detailData.steps || [];
|
|
333
|
+
let stepsArray = [];
|
|
334
|
+
|
|
335
|
+
if (typeof steps === 'string') {
|
|
336
|
+
try {
|
|
337
|
+
stepsArray = JSON.parse(steps);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
// 如果解析失败,尝试作为空数组处理
|
|
340
|
+
stepsArray = [];
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
stepsArray = steps;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (Array.isArray(stepsArray) && stepsArray.length > 0) {
|
|
347
|
+
const sortedSteps = stepsArray.sort((a, b) => (a.num || 0) - (b.num || 0));
|
|
348
|
+
|
|
349
|
+
sortedSteps.forEach((step, index) => {
|
|
350
|
+
const stepNum = index + 1;
|
|
351
|
+
const desc = step.desc || '';
|
|
352
|
+
const result = step.result || '';
|
|
353
|
+
const stepText = `${desc}${result ? ' 期望结果: ' + result : ''}`;
|
|
354
|
+
prompt += `${stepNum}. ${stepText}\n`;
|
|
355
|
+
hasSteps = true;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
console.error('解析STEP类型步骤失败:', e);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// 处理 TEXT 类型:从 textDescription 和 expectedResult 字段解析
|
|
363
|
+
else if (caseEditType === 'TEXT') {
|
|
364
|
+
const textDescription = detailData.textDescription || '';
|
|
365
|
+
const expectedResult = detailData.expectedResult || '';
|
|
366
|
+
|
|
367
|
+
if (textDescription && textDescription.trim()) {
|
|
368
|
+
// 解析 textDescription,格式类似 "[1]步骤1\n[2]步骤2\n..."
|
|
369
|
+
const descLines = textDescription.split('\n').filter(line => line.trim());
|
|
370
|
+
const resultLines = expectedResult && expectedResult.trim() ? expectedResult.split('\n').filter(line => line.trim()) : [];
|
|
371
|
+
|
|
372
|
+
if (descLines.length > 0) {
|
|
373
|
+
descLines.forEach((line, index) => {
|
|
374
|
+
// 移除开头的 [数字] 标记,支持多种格式
|
|
375
|
+
const cleanLine = line.replace(/^\[\d+\]\s*/, '').trim();
|
|
376
|
+
const resultLine = resultLines[index] ? resultLines[index].replace(/^\[\d+\]\s*/, '').trim() : '';
|
|
377
|
+
const stepText = `${cleanLine}${resultLine ? ' 期望结果: ' + resultLine : ''}`;
|
|
378
|
+
prompt += `${index + 1}. ${stepText}\n`;
|
|
379
|
+
hasSteps = true;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!hasSteps) {
|
|
386
|
+
return `测试点:${testPlanCollectionName || ''}\n用例名称:${name}\n测试步骤:暂无测试步骤`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return prompt.trim();
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error('生成AI提示语错误:', error);
|
|
392
|
+
return '生成提示语失败';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 获取优先级等级(用于排序)
|
|
398
|
+
*/
|
|
399
|
+
function getPriorityLevel(priority) {
|
|
400
|
+
const priorityMap = {
|
|
401
|
+
'P0': 0,
|
|
402
|
+
'P1': 1,
|
|
403
|
+
'P2': 2,
|
|
404
|
+
'P3': 3
|
|
405
|
+
};
|
|
406
|
+
return priorityMap[priority] !== undefined ? priorityMap[priority] : 999;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 调用 AI 模型 API 进行流式分析
|
|
411
|
+
*/
|
|
412
|
+
/**
|
|
413
|
+
* 调用 AI 模型 API 进行流式分析
|
|
414
|
+
* @param {string} promptText - 测试用例提示语
|
|
415
|
+
* @returns {Promise<string|null>} 返回分析结果,如果未配置MODEL则返回null
|
|
416
|
+
*/
|
|
417
|
+
async function analyzeWithModel(promptText) {
|
|
418
|
+
const config = getModelConfig();
|
|
419
|
+
|
|
420
|
+
// 如果MODEL配置不完整,返回null表示跳过AI分析
|
|
421
|
+
if (!config.baseURL || !config.apiKey || !config.model) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 使用配置的提示语模板,将 {{PROMPT_TEXT}} 替换为实际的测试用例内容
|
|
426
|
+
// 如果模板中没有 {{PROMPT_TEXT}} 占位符,则在末尾追加测试用例内容
|
|
427
|
+
let analysisPrompt = config.analysisPromptTemplate;
|
|
428
|
+
if (analysisPrompt.includes('{{PROMPT_TEXT}}')) {
|
|
429
|
+
analysisPrompt = analysisPrompt.replace('{{PROMPT_TEXT}}', promptText);
|
|
430
|
+
} else {
|
|
431
|
+
analysisPrompt = `${analysisPrompt}\n\n**测试用例内容:**\n${promptText}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
435
|
+
const urlObj = new URL(`${config.baseURL}/chat/completions`);
|
|
436
|
+
const postData = JSON.stringify({
|
|
437
|
+
model: config.model,
|
|
438
|
+
messages: [
|
|
439
|
+
{
|
|
440
|
+
role: 'user',
|
|
441
|
+
content: analysisPrompt
|
|
442
|
+
}
|
|
443
|
+
],
|
|
444
|
+
stream: true,
|
|
445
|
+
temperature: 0.3
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const options = {
|
|
449
|
+
hostname: urlObj.hostname,
|
|
450
|
+
port: urlObj.port || 443,
|
|
451
|
+
path: urlObj.pathname,
|
|
452
|
+
method: 'POST',
|
|
453
|
+
headers: {
|
|
454
|
+
'Content-Type': 'application/json',
|
|
455
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
456
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const req = https.request(options, (res) => {
|
|
461
|
+
if (res.statusCode !== 200) {
|
|
462
|
+
let errorData = '';
|
|
463
|
+
res.on('data', chunk => errorData += chunk.toString());
|
|
464
|
+
res.on('end', () => {
|
|
465
|
+
reject(new Error(`AI模型API请求失败: ${res.statusCode} - ${errorData}`));
|
|
466
|
+
});
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let buffer = '';
|
|
471
|
+
let fullContent = '';
|
|
472
|
+
|
|
473
|
+
res.on('data', (chunk) => {
|
|
474
|
+
buffer += chunk.toString();
|
|
475
|
+
const lines = buffer.split('\n');
|
|
476
|
+
buffer = lines.pop() || '';
|
|
477
|
+
|
|
478
|
+
for (const line of lines) {
|
|
479
|
+
if (line.trim() === '') continue;
|
|
480
|
+
if (line.startsWith('data: ')) {
|
|
481
|
+
const data = line.slice(6);
|
|
482
|
+
if (data === '[DONE]') {
|
|
483
|
+
resolve(fullContent);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const json = JSON.parse(data);
|
|
488
|
+
const content = json.choices?.[0]?.delta?.content || '';
|
|
489
|
+
if (content) {
|
|
490
|
+
fullContent += content;
|
|
491
|
+
}
|
|
492
|
+
} catch (e) {
|
|
493
|
+
// 忽略JSON解析错误
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
res.on('end', () => {
|
|
500
|
+
if (fullContent) {
|
|
501
|
+
resolve(fullContent);
|
|
502
|
+
} else {
|
|
503
|
+
reject(new Error('AI模型API响应为空'));
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
req.on('error', (error) => {
|
|
509
|
+
reject(new Error(`AI模型API请求错误: ${error.message}`));
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
req.write(postData);
|
|
513
|
+
req.end();
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
class MeterSphereMCPServer {
|
|
518
|
+
constructor() {
|
|
519
|
+
this.server = new Server(
|
|
520
|
+
{
|
|
521
|
+
name: 'meter-sphere-test-cases',
|
|
522
|
+
version: '1.0.0',
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
capabilities: {
|
|
526
|
+
tools: {},
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
this.setupHandlers();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
setupHandlers() {
|
|
535
|
+
// 列出可用工具
|
|
536
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
537
|
+
return {
|
|
538
|
+
tools: [
|
|
539
|
+
{
|
|
540
|
+
name: 'get_test_list',
|
|
541
|
+
description: '获取所有优先级的测试用例列表,按优先级排序(P0 > P1 > P2 > P3),生成TODO清单。支持过滤已完成用例和显示完成状态。配置信息从环境变量读取(PLATFORM配置必需,MODEL配置可选)。重要提示:此工具仅用于静态代码分析,不涉及实际交互测试。当用户要求"继续测试"、"继续自检"、"跳过已完成的"或类似需求时,必须设置 excludeCompleted=true 来排除已完成的测试用例,只返回未完成的用例列表。',
|
|
542
|
+
inputSchema: {
|
|
543
|
+
type: 'object',
|
|
544
|
+
properties: {
|
|
545
|
+
excludeCompleted: {
|
|
546
|
+
type: 'boolean',
|
|
547
|
+
description: '是否排除已完成的测试用例。默认为false(显示所有用例)。当用户要求继续测试、跳过已完成用例时,必须设置为true。设置为true后,返回的列表将只包含未完成的测试用例,方便继续测试工作。',
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
required: [],
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
name: 'get_test_detail',
|
|
555
|
+
description: '根据测试用例ID获取详细信息,包括测试步骤和AI测试提示语。如果配置了MODEL,会自动调用AI模型进行测试用例分析;如果未配置MODEL,则只返回测试用例详情。此工具仅用于静态代码分析,通过分析代码结构、逻辑、配置等来验证测试用例,不涉及实际交互操作。对于涉及用户交互、界面操作、实际运行验证的测试用例,需要提示用户手动完成测试验证。配置信息从环境变量读取(PLATFORM配置必需,MODEL配置可选)。',
|
|
556
|
+
inputSchema: {
|
|
557
|
+
type: 'object',
|
|
558
|
+
properties: {
|
|
559
|
+
testCaseId: {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: '测试用例ID',
|
|
562
|
+
},
|
|
563
|
+
testPlanCollectionName: {
|
|
564
|
+
type: 'string',
|
|
565
|
+
description: '测试点名称(从get_test_list返回)',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
required: ['testCaseId', 'testPlanCollectionName'],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'mark_test_completed',
|
|
573
|
+
description: '标记指定测试用例为已完成状态,用于进度跟踪。',
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: 'object',
|
|
576
|
+
properties: {
|
|
577
|
+
testCaseId: {
|
|
578
|
+
type: 'string',
|
|
579
|
+
description: '测试用例ID',
|
|
580
|
+
},
|
|
581
|
+
priority: {
|
|
582
|
+
type: 'string',
|
|
583
|
+
description: '测试用例优先级(P0/P1/P2/P3)',
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
required: ['testCaseId', 'priority'],
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: 'get_test_progress',
|
|
591
|
+
description: '获取当前测试进度,包括已完成和未完成的测试用例统计信息。',
|
|
592
|
+
inputSchema: {
|
|
593
|
+
type: 'object',
|
|
594
|
+
properties: {},
|
|
595
|
+
required: [],
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: 'reset_test_progress',
|
|
600
|
+
description: '重置测试进度文件(test-progress.json),清空所有已完成的测试用例记录。',
|
|
601
|
+
inputSchema: {
|
|
602
|
+
type: 'object',
|
|
603
|
+
properties: {},
|
|
604
|
+
required: [],
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// 处理工具调用
|
|
612
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
613
|
+
const { name, arguments: args } = request.params;
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
if (name === 'get_test_list') {
|
|
617
|
+
return await this.handleGetTestList(args);
|
|
618
|
+
} else if (name === 'get_test_detail') {
|
|
619
|
+
return await this.handleGetTestDetail(args);
|
|
620
|
+
} else if (name === 'mark_test_completed') {
|
|
621
|
+
return await this.handleMarkTestCompleted(args);
|
|
622
|
+
} else if (name === 'get_test_progress') {
|
|
623
|
+
return await this.handleGetTestProgress(args);
|
|
624
|
+
} else if (name === 'reset_test_progress') {
|
|
625
|
+
return await this.handleResetTestProgress(args);
|
|
626
|
+
} else {
|
|
627
|
+
throw new Error(`未知工具: ${name}`);
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return {
|
|
631
|
+
content: [
|
|
632
|
+
{
|
|
633
|
+
type: 'text',
|
|
634
|
+
text: `错误: ${error.message}\n${error.stack || ''}`,
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
isError: true,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async handleGetTestList(args) {
|
|
644
|
+
const { excludeCompleted = false } = args || {};
|
|
645
|
+
|
|
646
|
+
// 从环境变量读取配置
|
|
647
|
+
const platformConfig = getPlatformConfig();
|
|
648
|
+
|
|
649
|
+
// 加载进度
|
|
650
|
+
const progress = loadProgress();
|
|
651
|
+
|
|
652
|
+
// 构建目标API URL(使用默认的 API 基础 URL)
|
|
653
|
+
const apiBaseUrl = 'http://192.168.3.26:8081';
|
|
654
|
+
const targetUrl = `${apiBaseUrl}/functional/case/page`;
|
|
655
|
+
|
|
656
|
+
const headers = {
|
|
657
|
+
'Csrf-token': platformConfig.csrfToken,
|
|
658
|
+
'organization': platformConfig.organization,
|
|
659
|
+
'project': platformConfig.project,
|
|
660
|
+
'x-auth-token': platformConfig.xAuthToken,
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
const requestData = {
|
|
664
|
+
current: 1,
|
|
665
|
+
pageSize: 500,
|
|
666
|
+
combineSearch: {
|
|
667
|
+
searchMode: 'AND',
|
|
668
|
+
conditions: [],
|
|
669
|
+
},
|
|
670
|
+
projectId: platformConfig.project,
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
// 如果配置了 MODULE_IDS,添加到请求参数中
|
|
675
|
+
if (platformConfig.moduleIds && platformConfig.moduleIds.length > 0) {
|
|
676
|
+
requestData.moduleIds = platformConfig.moduleIds;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const response = await httpPost(targetUrl, headers, requestData);
|
|
680
|
+
console.log(response);
|
|
681
|
+
const data = JSON.parse(response);
|
|
682
|
+
|
|
683
|
+
if (!data || !data.data || !data.data.list) {
|
|
684
|
+
throw new Error('获取测试用例列表失败:响应数据格式不正确');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const testCases = data.data.list;
|
|
688
|
+
|
|
689
|
+
// 处理所有测试用例,提取优先级并按优先级排序
|
|
690
|
+
let processedTestCases = testCases
|
|
691
|
+
.map(item => {
|
|
692
|
+
const priority = (item.customFields && item.customFields[0] && item.customFields[0].defaultValue) || '-';
|
|
693
|
+
return {
|
|
694
|
+
id: item.id,
|
|
695
|
+
name: item.name,
|
|
696
|
+
priority: priority.toUpperCase(),
|
|
697
|
+
testPlanCollectionName: item.testPlanCollectionName || '',
|
|
698
|
+
createUserName: item.createUserName || '',
|
|
699
|
+
moduleName: item.moduleName || '',
|
|
700
|
+
};
|
|
701
|
+
})
|
|
702
|
+
.sort((a, b) => {
|
|
703
|
+
return getPriorityLevel(a.priority) - getPriorityLevel(b.priority);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// 更新已知用例ID集合(合并当前查询到的用例ID)
|
|
707
|
+
if (!progress.knownTestCaseIds) {
|
|
708
|
+
progress.knownTestCaseIds = [];
|
|
709
|
+
}
|
|
710
|
+
const currentTestCaseIds = processedTestCases.map(tc => tc.id);
|
|
711
|
+
// 合并去重:将当前查询到的用例ID添加到已知集合中
|
|
712
|
+
currentTestCaseIds.forEach(id => {
|
|
713
|
+
if (!progress.knownTestCaseIds.includes(id)) {
|
|
714
|
+
progress.knownTestCaseIds.push(id);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
// 基于已知用例ID集合计算总数
|
|
718
|
+
progress.total = progress.knownTestCaseIds.length;
|
|
719
|
+
saveProgress(progress);
|
|
720
|
+
|
|
721
|
+
// 如果excludeCompleted为true,过滤已完成的用例
|
|
722
|
+
if (excludeCompleted) {
|
|
723
|
+
processedTestCases = processedTestCases.filter(testCase => {
|
|
724
|
+
return !isCompleted(testCase.id, progress);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// 统计各优先级的数量
|
|
729
|
+
const priorityCounts = {
|
|
730
|
+
P0: 0,
|
|
731
|
+
P1: 0,
|
|
732
|
+
P2: 0,
|
|
733
|
+
P3: 0,
|
|
734
|
+
other: 0
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
processedTestCases.forEach(testCase => {
|
|
738
|
+
const priority = testCase.priority;
|
|
739
|
+
if (priorityCounts.hasOwnProperty(priority)) {
|
|
740
|
+
priorityCounts[priority]++;
|
|
741
|
+
} else {
|
|
742
|
+
priorityCounts.other++;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const totalCount = processedTestCases.length;
|
|
747
|
+
|
|
748
|
+
// 生成统计信息
|
|
749
|
+
const stats = [
|
|
750
|
+
`共找到 (${totalCount}) 个测试用例;`,
|
|
751
|
+
`P0: ${priorityCounts.P0}个`,
|
|
752
|
+
`P1: ${priorityCounts.P1}个`,
|
|
753
|
+
`P2: ${priorityCounts.P2}个`,
|
|
754
|
+
`P3: ${priorityCounts.P3}个`
|
|
755
|
+
].join('\n');
|
|
756
|
+
|
|
757
|
+
// 生成TODO清单格式:✅/⬜ 用例ID:1245456454654545(P0)
|
|
758
|
+
const todoList = processedTestCases.map(testCase => {
|
|
759
|
+
const status = isCompleted(testCase.id, progress) ? '✅' : '⬜';
|
|
760
|
+
return `${status} 用例ID:${testCase.id}(${testCase.priority})`;
|
|
761
|
+
}).join('\n');
|
|
762
|
+
|
|
763
|
+
const progressInfo = excludeCompleted
|
|
764
|
+
? `\n(已过滤已完成用例,剩余 ${processedTestCases.length} 个未完成用例)\n\n⚠️ **提示**:此工具仅用于静态代码分析,不涉及实际交互测试。对于涉及交互的测试用例,需要用户手动完成测试验证。`
|
|
765
|
+
: `\n(已完成: ${progress.completedCount}/${progress.total})\n\n⚠️ **提示**:此工具仅用于静态代码分析,不涉及实际交互测试。对于涉及交互的测试用例,需要用户手动完成测试验证。\n如需继续分析未完成的用例,请使用 excludeCompleted=true 参数调用此工具`;
|
|
766
|
+
|
|
767
|
+
const result = `${stats}${progressInfo}\n\nTODO:\n${todoList}`;
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: 'text',
|
|
773
|
+
text: result,
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async handleGetTestDetail(args) {
|
|
780
|
+
const { testCaseId, testPlanCollectionName } = args;
|
|
781
|
+
|
|
782
|
+
// 检查用例是否已完成
|
|
783
|
+
const progress = loadProgress();
|
|
784
|
+
const isCaseCompleted = isCompleted(testCaseId, progress);
|
|
785
|
+
let completedInfo = '';
|
|
786
|
+
if (isCaseCompleted) {
|
|
787
|
+
const completedItem = progress.completed.find(item => item.testCaseId === testCaseId);
|
|
788
|
+
const completedTime = completedItem ? new Date(completedItem.completedAt).toLocaleString('zh-CN') : '';
|
|
789
|
+
completedInfo = `\n\n⚠️ **注意:此测试用例已完成** ✅\n完成时间:${completedTime}\n如需继续测试其他用例,请使用 get_test_list 工具并设置 excludeCompleted=true 获取未完成的用例列表。\n`;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// 从环境变量读取配置
|
|
793
|
+
const platformConfig = getPlatformConfig();
|
|
794
|
+
|
|
795
|
+
// 构建目标API URL(使用默认的 API 基础 URL)
|
|
796
|
+
const apiBaseUrl = 'http://192.168.3.26:8081';
|
|
797
|
+
const targetUrl = `${apiBaseUrl}/functional/case/detail/${testCaseId}`;
|
|
798
|
+
|
|
799
|
+
const headers = {
|
|
800
|
+
'Csrf-token': platformConfig.csrfToken,
|
|
801
|
+
'organization': platformConfig.organization,
|
|
802
|
+
'project': platformConfig.project,
|
|
803
|
+
'x-auth-token': platformConfig.xAuthToken,
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const response = await httpGet(targetUrl, headers);
|
|
807
|
+
const data = JSON.parse(response);
|
|
808
|
+
|
|
809
|
+
if (!data || !data.data) {
|
|
810
|
+
throw new Error('获取测试用例详情失败:响应数据格式不正确');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const detailData = data.data;
|
|
814
|
+
const aiPrompt = generateAIPrompt(detailData, testPlanCollectionName);
|
|
815
|
+
|
|
816
|
+
// 尝试调用AI模型进行分析(如果配置了MODEL)
|
|
817
|
+
let modelAnalysis = null;
|
|
818
|
+
try {
|
|
819
|
+
modelAnalysis = await analyzeWithModel(aiPrompt);
|
|
820
|
+
// 如果返回null,说明未配置MODEL,跳过AI分析
|
|
821
|
+
if (modelAnalysis === null) {
|
|
822
|
+
modelAnalysis = '';
|
|
823
|
+
}
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.error('AI模型分析失败:', error);
|
|
826
|
+
modelAnalysis = `\n\n⚠️ AI模型分析失败: ${error.message}`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// 添加AI测试提示语前缀,强调静态代码分析
|
|
830
|
+
const fullPrompt = '**重要说明:此工具仅用于静态代码分析,通过分析代码结构、逻辑、配置等来验证测试用例,不涉及实际交互操作。**\n\n请根据下列提示语中的测试点、用例名称、测试类别、测试步骤、分析说明找到当前项目中对应的代码位置,进行静态代码检查:\n- 检查代码逻辑是否正确实现\n- 检查配置参数是否正确设置\n- 检查接口调用是否符合预期\n- 检查数据结构是否正确\n\n**对于涉及用户交互、界面操作、实际运行验证的测试步骤,请明确提示用户需要手动完成测试验证。**\n\n' + aiPrompt;
|
|
831
|
+
|
|
832
|
+
let resultText = `## 测试用例详情\n\n` +
|
|
833
|
+
`**用例ID**: ${testCaseId}${completedInfo}` +
|
|
834
|
+
`## ⚠️ 重要提示\n\n**此工具仅用于静态代码分析**,通过分析代码结构、逻辑、配置等来验证测试用例,不涉及实际交互操作。对于涉及用户交互、界面操作、实际运行验证的测试步骤,需要用户手动完成测试验证。\n\n` +
|
|
835
|
+
`## 测试内容\n\n\`\`\`\n${fullPrompt}\n\`\`\`\n`;
|
|
836
|
+
|
|
837
|
+
// 只有当AI分析成功时才显示AI分析结果
|
|
838
|
+
if (modelAnalysis && modelAnalysis.trim() && !modelAnalysis.includes('AI模型分析失败')) {
|
|
839
|
+
resultText += `\n## AI分析结果\n\n\`\`\`\n${modelAnalysis}\n\`\`\`\n`;
|
|
840
|
+
} else if (!modelAnalysis || modelAnalysis === '') {
|
|
841
|
+
// 如果未配置MODEL,添加提示信息
|
|
842
|
+
resultText += `\n\n💡 **提示**:未配置MODEL相关环境变量,AI分析功能已禁用。如需启用AI分析,请在配置中设置 MODEL_BASE_URL、MODEL_API_KEY、MODEL_ID。`;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
content: [
|
|
847
|
+
{
|
|
848
|
+
type: 'text',
|
|
849
|
+
text: resultText
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async handleMarkTestCompleted(args) {
|
|
856
|
+
const { testCaseId, priority } = args;
|
|
857
|
+
|
|
858
|
+
if (!testCaseId || !priority) {
|
|
859
|
+
throw new Error('缺少必要参数:testCaseId 和 priority');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const progress = loadProgress();
|
|
863
|
+
markCompleted(testCaseId, priority, progress);
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
content: [
|
|
867
|
+
{
|
|
868
|
+
type: 'text',
|
|
869
|
+
text: `✅ 已标记测试用例 ${testCaseId}(${priority}) 为已完成\n\n当前进度: ${progress.completedCount}/${progress.total}`
|
|
870
|
+
},
|
|
871
|
+
],
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async handleGetTestProgress(args) {
|
|
876
|
+
const progress = loadProgress();
|
|
877
|
+
|
|
878
|
+
// 确保 knownTestCaseIds 存在
|
|
879
|
+
if (!progress.knownTestCaseIds) {
|
|
880
|
+
progress.knownTestCaseIds = [];
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const completedCount = progress.completedCount;
|
|
884
|
+
// 确保 total 至少等于 completedCount(避免出现负数)
|
|
885
|
+
// 如果 knownTestCaseIds 存在,使用它的长度;否则使用 completedCount
|
|
886
|
+
let total = progress.total;
|
|
887
|
+
if (progress.knownTestCaseIds && progress.knownTestCaseIds.length > 0) {
|
|
888
|
+
total = progress.knownTestCaseIds.length;
|
|
889
|
+
} else if (total < completedCount) {
|
|
890
|
+
// 如果 total 小于已完成数,说明数据不一致,使用已完成数作为总数
|
|
891
|
+
total = completedCount;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (total === 0 && completedCount === 0) {
|
|
895
|
+
return {
|
|
896
|
+
content: [
|
|
897
|
+
{
|
|
898
|
+
type: 'text',
|
|
899
|
+
text: '暂无测试进度记录。请先调用 get_test_list 获取测试用例列表。'
|
|
900
|
+
},
|
|
901
|
+
],
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const remaining = total - completedCount;
|
|
906
|
+
const percentage = total > 0 ? Math.round((completedCount / total) * 100) : 0;
|
|
907
|
+
|
|
908
|
+
// 按优先级统计已完成用例
|
|
909
|
+
const completedByPriority = {
|
|
910
|
+
P0: 0,
|
|
911
|
+
P1: 0,
|
|
912
|
+
P2: 0,
|
|
913
|
+
P3: 0,
|
|
914
|
+
other: 0
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
progress.completed.forEach(item => {
|
|
918
|
+
const priority = item.priority || 'other';
|
|
919
|
+
if (completedByPriority.hasOwnProperty(priority)) {
|
|
920
|
+
completedByPriority[priority]++;
|
|
921
|
+
} else {
|
|
922
|
+
completedByPriority.other++;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
const progressText = [
|
|
927
|
+
`## 测试进度统计`,
|
|
928
|
+
``,
|
|
929
|
+
`总用例数: ${total}`,
|
|
930
|
+
`已完成: ${completedCount}`,
|
|
931
|
+
`剩余: ${remaining}`,
|
|
932
|
+
`完成率: ${percentage}%`,
|
|
933
|
+
``,
|
|
934
|
+
`### 已完成用例按优先级分布:`,
|
|
935
|
+
`P0: ${completedByPriority.P0}个`,
|
|
936
|
+
`P1: ${completedByPriority.P1}个`,
|
|
937
|
+
`P2: ${completedByPriority.P2}个`,
|
|
938
|
+
`P3: ${completedByPriority.P3}个`,
|
|
939
|
+
``,
|
|
940
|
+
`### 已完成用例列表:`,
|
|
941
|
+
...(progress.completed.length > 0
|
|
942
|
+
? progress.completed.map(item =>
|
|
943
|
+
`✅ 用例ID:${item.testCaseId}(${item.priority}) - ${new Date(item.completedAt).toLocaleString('zh-CN')}`
|
|
944
|
+
)
|
|
945
|
+
: ['暂无']
|
|
946
|
+
)
|
|
947
|
+
].join('\n');
|
|
948
|
+
|
|
949
|
+
return {
|
|
950
|
+
content: [
|
|
951
|
+
{
|
|
952
|
+
type: 'text',
|
|
953
|
+
text: progressText
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async handleResetTestProgress(args) {
|
|
960
|
+
const progressPath = getProgressFilePath();
|
|
961
|
+
const oldProgress = loadProgress();
|
|
962
|
+
|
|
963
|
+
// 重置进度
|
|
964
|
+
const newProgress = resetProgress();
|
|
965
|
+
|
|
966
|
+
const resetInfo = [
|
|
967
|
+
`✅ 测试进度文件已重置`,
|
|
968
|
+
``,
|
|
969
|
+
`重置前状态:`,
|
|
970
|
+
`- 已完成用例数: ${oldProgress.completedCount}`,
|
|
971
|
+
`- 总用例数: ${oldProgress.total}`,
|
|
972
|
+
``,
|
|
973
|
+
`重置后状态:`,
|
|
974
|
+
`- 已完成用例数: ${newProgress.completedCount}`,
|
|
975
|
+
`- 总用例数: ${newProgress.total}`,
|
|
976
|
+
``,
|
|
977
|
+
`进度文件路径: ${progressPath}`
|
|
978
|
+
].join('\n');
|
|
979
|
+
|
|
980
|
+
return {
|
|
981
|
+
content: [
|
|
982
|
+
{
|
|
983
|
+
type: 'text',
|
|
984
|
+
text: resetInfo
|
|
985
|
+
},
|
|
986
|
+
],
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async run() {
|
|
991
|
+
const transport = new StdioServerTransport();
|
|
992
|
+
await this.server.connect(transport);
|
|
993
|
+
console.error('MeterSphere MCP Server 已启动');
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// 启动服务器
|
|
998
|
+
const server = new MeterSphereMCPServer();
|
|
928
999
|
server.run().catch(console.error);
|