@zzp123/mcp-zentao 1.18.2 → 1.18.4
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/CHANGELOG.md +48 -0
- package/README.md +39 -0
- package/dist/api/zentaoApi.js +1 -266
- package/dist/config.js +1 -1
- package/dist/index-dev.js +4 -23
- package/dist/index-pm.js +70 -85
- package/dist/index-qa.js +1 -23
- package/dist/index.js +74 -130
- package/dist/mcpHelpers.d.ts +13 -0
- package/dist/mcpHelpers.js +24 -0
- package/package.json +1 -1
package/dist/config.js
CHANGED
package/dist/index-dev.js
CHANGED
|
@@ -14,15 +14,12 @@ if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
|
14
14
|
// 获取 --config 后面的 JSON 字符串并解析
|
|
15
15
|
const jsonStr = args[configIndex + 1];
|
|
16
16
|
configData = JSON.parse(jsonStr);
|
|
17
|
-
console.log('成功解析配置数据:', configData);
|
|
18
17
|
// 如果配置数据中包含 config 对象,则保存配置
|
|
19
18
|
if (configData.config) {
|
|
20
|
-
console.log('正在保存配置...');
|
|
21
19
|
saveConfig(configData.config);
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
catch (error) {
|
|
25
|
-
console.error('配置解析失败:', error);
|
|
26
23
|
process.exit(1);
|
|
27
24
|
}
|
|
28
25
|
}
|
|
@@ -34,10 +31,8 @@ const server = new McpServer({
|
|
|
34
31
|
// Initialize ZentaoAPI instance
|
|
35
32
|
let zentaoApi = null;
|
|
36
33
|
export default async function main(params) {
|
|
37
|
-
console.log('接收到的参数:', params);
|
|
38
34
|
// 如果传入了配置信息,就保存它
|
|
39
35
|
if (params.config) {
|
|
40
|
-
console.log('保存新的配置信息...');
|
|
41
36
|
saveConfig(params.config);
|
|
42
37
|
}
|
|
43
38
|
}
|
|
@@ -518,13 +513,11 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
518
513
|
const path = await import('path');
|
|
519
514
|
const { execSync } = await import('child_process');
|
|
520
515
|
try {
|
|
521
|
-
console.log('[uploadImageFromClipboard] 开始执行...');
|
|
522
516
|
// 读取系统剪贴板图片
|
|
523
517
|
let fileBuffer;
|
|
524
518
|
let finalFilename;
|
|
525
519
|
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
526
520
|
if (process.platform === 'win32') {
|
|
527
|
-
console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...');
|
|
528
521
|
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
529
522
|
const psScript = `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); $bytes = $ms.ToArray(); $ms.Close(); $base64 = [Convert]::ToBase64String($bytes); Write-Output $base64 } else { Write-Output 'NoImage' }`;
|
|
530
523
|
try {
|
|
@@ -536,7 +529,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
536
529
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
537
530
|
}).trim();
|
|
538
531
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
539
|
-
console.error('[uploadImageFromClipboard] 剪贴板中没有图片');
|
|
540
532
|
return {
|
|
541
533
|
content: [{
|
|
542
534
|
type: "text",
|
|
@@ -550,10 +542,8 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
550
542
|
}
|
|
551
543
|
fileBuffer = Buffer.from(result, 'base64');
|
|
552
544
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
553
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
554
545
|
}
|
|
555
546
|
catch (err) {
|
|
556
|
-
console.error('[uploadImageFromClipboard] Windows读取剪贴板失败:', err);
|
|
557
547
|
return {
|
|
558
548
|
content: [{
|
|
559
549
|
type: "text",
|
|
@@ -569,17 +559,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
569
559
|
}
|
|
570
560
|
// macOS: 使用 pngpaste
|
|
571
561
|
else if (process.platform === 'darwin') {
|
|
572
|
-
console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
|
|
573
562
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
574
563
|
try {
|
|
575
564
|
execSync(`pngpaste "${tempFile}"`);
|
|
576
565
|
fileBuffer = fs.readFileSync(tempFile);
|
|
577
566
|
fs.unlinkSync(tempFile);
|
|
578
567
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
579
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
580
568
|
}
|
|
581
569
|
catch (err) {
|
|
582
|
-
console.error('[uploadImageFromClipboard] macOS读取剪贴板失败:', err);
|
|
583
570
|
return {
|
|
584
571
|
content: [{
|
|
585
572
|
type: "text",
|
|
@@ -594,17 +581,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
594
581
|
}
|
|
595
582
|
// Linux: 使用 xclip
|
|
596
583
|
else {
|
|
597
|
-
console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
|
|
598
584
|
try {
|
|
599
585
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
600
586
|
maxBuffer: 10 * 1024 * 1024
|
|
601
587
|
});
|
|
602
588
|
fileBuffer = buffer;
|
|
603
589
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
604
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
605
590
|
}
|
|
606
591
|
catch (err) {
|
|
607
|
-
console.error('[uploadImageFromClipboard] Linux读取剪贴板失败:', err);
|
|
608
592
|
return {
|
|
609
593
|
content: [{
|
|
610
594
|
type: "text",
|
|
@@ -620,21 +604,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
620
604
|
// 创建 img 文件夹
|
|
621
605
|
const imgDir = path.join(process.cwd(), 'img');
|
|
622
606
|
if (!fs.existsSync(imgDir)) {
|
|
623
|
-
console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
|
|
624
607
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
625
608
|
}
|
|
626
609
|
// 保存到 img 文件夹
|
|
627
610
|
const savedPath = path.join(imgDir, finalFilename);
|
|
628
611
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
629
|
-
console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
|
|
630
612
|
// 上传到禅道
|
|
631
|
-
console.log('[uploadImageFromClipboard] 开始上传到禅道...');
|
|
632
613
|
const uploadResult = await zentaoApi.uploadFile({
|
|
633
614
|
file: fileBuffer,
|
|
634
615
|
filename: finalFilename,
|
|
635
616
|
uid
|
|
636
617
|
});
|
|
637
|
-
console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
|
|
638
618
|
// 生成禅道需要的 HTML 格式
|
|
639
619
|
const fileId = uploadResult.id;
|
|
640
620
|
const baseUrl = zentaoApi.getConfig().url;
|
|
@@ -652,7 +632,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
652
632
|
message: `图片已保存到本地并上传到禅道`,
|
|
653
633
|
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
654
634
|
};
|
|
655
|
-
console.log('[uploadImageFromClipboard] 返回结果:', response);
|
|
656
635
|
return {
|
|
657
636
|
content: [{
|
|
658
637
|
type: "text",
|
|
@@ -661,7 +640,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
661
640
|
};
|
|
662
641
|
}
|
|
663
642
|
catch (error) {
|
|
664
|
-
console.error('[uploadImageFromClipboard] 发生错误:', error);
|
|
665
643
|
const errorResponse = {
|
|
666
644
|
success: false,
|
|
667
645
|
error: error.message || String(error),
|
|
@@ -807,4 +785,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
807
785
|
});
|
|
808
786
|
// Start receiving messages on stdin and sending messages on stdout
|
|
809
787
|
const transport = new StdioServerTransport();
|
|
810
|
-
await server.connect(transport).catch(
|
|
788
|
+
await server.connect(transport).catch(err => {
|
|
789
|
+
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
790
|
+
process.exit(1);
|
|
791
|
+
});
|
package/dist/index-pm.js
CHANGED
|
@@ -14,15 +14,15 @@ if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
|
14
14
|
// 获取 --config 后面的 JSON 字符串并解析
|
|
15
15
|
const jsonStr = args[configIndex + 1];
|
|
16
16
|
configData = JSON.parse(jsonStr);
|
|
17
|
-
|
|
17
|
+
process.stdout.write('成功解析配置数据: ' + JSON.stringify(configData) + '\n');
|
|
18
18
|
// 如果配置数据中包含 config 对象,则保存配置
|
|
19
19
|
if (configData.config) {
|
|
20
|
-
|
|
20
|
+
process.stdout.write('正在保存配置...\n');
|
|
21
21
|
saveConfig(configData.config);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
25
|
-
|
|
25
|
+
process.stderr.write('配置解析失败: ' + String(error) + '\n');
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -34,10 +34,10 @@ const server = new McpServer({
|
|
|
34
34
|
// Initialize ZentaoAPI instance
|
|
35
35
|
let zentaoApi = null;
|
|
36
36
|
export default async function main(params) {
|
|
37
|
-
|
|
37
|
+
process.stdout.write('接收到的参数: ' + JSON.stringify(params) + '\n');
|
|
38
38
|
// 如果传入了配置信息,就保存它
|
|
39
39
|
if (params.config) {
|
|
40
|
-
|
|
40
|
+
process.stdout.write('保存新的配置信息...\n');
|
|
41
41
|
saveConfig(params.config);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -375,76 +375,58 @@ server.tool("createPlan", {
|
|
|
375
375
|
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
376
376
|
};
|
|
377
377
|
});
|
|
378
|
-
server.tool("changeStory", "需求变更 - 支持软件需求(story)和用户需求(requirement)
|
|
378
|
+
server.tool("changeStory", "需求变更 - 支持软件需求(story)和用户需求(requirement),自动识别类型", {
|
|
379
379
|
storyId: z.number().describe("需求ID(可以是story或requirement类型)"),
|
|
380
|
-
update: z.
|
|
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
|
-
z.string()
|
|
424
|
-
]).describe(`更新需求(使用 PUT 接口)
|
|
380
|
+
update: z.object({
|
|
381
|
+
// 基本信息
|
|
382
|
+
title: z.string().optional(),
|
|
383
|
+
product: z.number().optional(),
|
|
384
|
+
parent: z.number().optional(),
|
|
385
|
+
module: z.number().optional(),
|
|
386
|
+
branch: z.number().optional(),
|
|
387
|
+
plan: z.union([z.number(), z.array(z.number())]).optional(),
|
|
388
|
+
type: z.string().optional(),
|
|
389
|
+
// 来源信息
|
|
390
|
+
source: z.string().optional(),
|
|
391
|
+
sourceNote: z.string().optional(),
|
|
392
|
+
// 分类与优先级
|
|
393
|
+
category: z.string().optional(),
|
|
394
|
+
pri: z.number().optional(),
|
|
395
|
+
estimate: z.number().optional(),
|
|
396
|
+
// 状态与阶段
|
|
397
|
+
stage: z.string().optional(),
|
|
398
|
+
status: z.string().optional(),
|
|
399
|
+
// 关键词与标识
|
|
400
|
+
keywords: z.string().optional(),
|
|
401
|
+
color: z.string().optional(),
|
|
402
|
+
grade: z.number().optional(),
|
|
403
|
+
// 人员相关
|
|
404
|
+
mailto: z.array(z.string()).optional(),
|
|
405
|
+
reviewer: z.array(z.string()).optional().describe("评审人员列表(通常为必填,除非设置needNotReview=true跳过评审)"),
|
|
406
|
+
assignedTo: z.string().optional(),
|
|
407
|
+
closedBy: z.string().optional(),
|
|
408
|
+
feedbackBy: z.string().optional(),
|
|
409
|
+
// 关闭相关
|
|
410
|
+
closedReason: z.enum(['done', 'subdivided', 'duplicate', 'postponed', 'willnotdo', 'cancel', 'bydesign']).optional(),
|
|
411
|
+
duplicateStory: z.number().optional(),
|
|
412
|
+
// 评审相关
|
|
413
|
+
needNotReview: z.boolean().optional().describe("是否跳过评审,如果不提供reviewer则应设置为true"),
|
|
414
|
+
// 通知相关
|
|
415
|
+
notifyEmail: z.string().optional(),
|
|
416
|
+
// 描述内容
|
|
417
|
+
spec: z.string().optional(),
|
|
418
|
+
verify: z.string().optional(),
|
|
419
|
+
// 备注
|
|
420
|
+
comment: z.string().optional()
|
|
421
|
+
}).describe(`更新需求(使用 PUT 接口)
|
|
425
422
|
|
|
426
423
|
此工具使用标准的"修改需求其他字段"接口(PUT /stories/:id),支持修改29个字段。
|
|
427
424
|
自动处理评审人问题,无需手动设置 needNotReview。
|
|
428
|
-
|
|
429
|
-
支持对象或JSON字符串格式。
|
|
430
425
|
`.trim())
|
|
431
426
|
}, async ({ storyId, update }) => {
|
|
432
427
|
if (!zentaoApi)
|
|
433
428
|
throw new Error("Please initialize Zentao API first");
|
|
434
|
-
|
|
435
|
-
let updateData;
|
|
436
|
-
if (typeof update === 'string') {
|
|
437
|
-
try {
|
|
438
|
-
updateData = JSON.parse(update);
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
throw new Error(`Invalid JSON string: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
updateData = update;
|
|
446
|
-
}
|
|
447
|
-
const story = await zentaoApi.changeStory(storyId, updateData);
|
|
429
|
+
const story = await zentaoApi.changeStory(storyId, update);
|
|
448
430
|
return {
|
|
449
431
|
content: [{ type: "text", text: JSON.stringify(story, null, 2) }]
|
|
450
432
|
};
|
|
@@ -639,13 +621,13 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
639
621
|
const path = await import('path');
|
|
640
622
|
const { execSync } = await import('child_process');
|
|
641
623
|
try {
|
|
642
|
-
|
|
624
|
+
process.stdout.write('[uploadImageFromClipboard] 开始执行...\n');
|
|
643
625
|
// 读取系统剪贴板图片
|
|
644
626
|
let fileBuffer;
|
|
645
627
|
let finalFilename;
|
|
646
628
|
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
647
629
|
if (process.platform === 'win32') {
|
|
648
|
-
|
|
630
|
+
process.stdout.write('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...\n');
|
|
649
631
|
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
650
632
|
const psScript = `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); $bytes = $ms.ToArray(); $ms.Close(); $base64 = [Convert]::ToBase64String($bytes); Write-Output $base64 } else { Write-Output 'NoImage' }`;
|
|
651
633
|
try {
|
|
@@ -657,7 +639,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
657
639
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
658
640
|
}).trim();
|
|
659
641
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
660
|
-
|
|
642
|
+
process.stderr.write('[uploadImageFromClipboard] 剪贴板中没有图片\n');
|
|
661
643
|
return {
|
|
662
644
|
content: [{
|
|
663
645
|
type: "text",
|
|
@@ -671,10 +653,10 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
671
653
|
}
|
|
672
654
|
fileBuffer = Buffer.from(result, 'base64');
|
|
673
655
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
674
|
-
|
|
656
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
675
657
|
}
|
|
676
658
|
catch (err) {
|
|
677
|
-
|
|
659
|
+
process.stderr.write('[uploadImageFromClipboard] Windows读取剪贴板失败: ' + String(err) + '\n');
|
|
678
660
|
return {
|
|
679
661
|
content: [{
|
|
680
662
|
type: "text",
|
|
@@ -690,17 +672,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
690
672
|
}
|
|
691
673
|
// macOS: 使用 pngpaste
|
|
692
674
|
else if (process.platform === 'darwin') {
|
|
693
|
-
|
|
675
|
+
process.stdout.write('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...\n');
|
|
694
676
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
695
677
|
try {
|
|
696
678
|
execSync(`pngpaste "${tempFile}"`);
|
|
697
679
|
fileBuffer = fs.readFileSync(tempFile);
|
|
698
680
|
fs.unlinkSync(tempFile);
|
|
699
681
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
700
|
-
|
|
682
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
701
683
|
}
|
|
702
684
|
catch (err) {
|
|
703
|
-
|
|
685
|
+
process.stderr.write('[uploadImageFromClipboard] macOS读取剪贴板失败: ' + String(err) + '\n');
|
|
704
686
|
return {
|
|
705
687
|
content: [{
|
|
706
688
|
type: "text",
|
|
@@ -715,17 +697,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
715
697
|
}
|
|
716
698
|
// Linux: 使用 xclip
|
|
717
699
|
else {
|
|
718
|
-
|
|
700
|
+
process.stdout.write('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...\n');
|
|
719
701
|
try {
|
|
720
702
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
721
703
|
maxBuffer: 10 * 1024 * 1024
|
|
722
704
|
});
|
|
723
705
|
fileBuffer = buffer;
|
|
724
706
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
725
|
-
|
|
707
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
726
708
|
}
|
|
727
709
|
catch (err) {
|
|
728
|
-
|
|
710
|
+
process.stderr.write('[uploadImageFromClipboard] Linux读取剪贴板失败: ' + String(err) + '\n');
|
|
729
711
|
return {
|
|
730
712
|
content: [{
|
|
731
713
|
type: "text",
|
|
@@ -741,21 +723,21 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
741
723
|
// 创建 img 文件夹
|
|
742
724
|
const imgDir = path.join(process.cwd(), 'img');
|
|
743
725
|
if (!fs.existsSync(imgDir)) {
|
|
744
|
-
|
|
726
|
+
process.stdout.write(`[uploadImageFromClipboard] 创建目录: ${imgDir}\n`);
|
|
745
727
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
746
728
|
}
|
|
747
729
|
// 保存到 img 文件夹
|
|
748
730
|
const savedPath = path.join(imgDir, finalFilename);
|
|
749
731
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
750
|
-
|
|
732
|
+
process.stdout.write(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}\n`);
|
|
751
733
|
// 上传到禅道
|
|
752
|
-
|
|
734
|
+
process.stdout.write('[uploadImageFromClipboard] 开始上传到禅道...\n');
|
|
753
735
|
const uploadResult = await zentaoApi.uploadFile({
|
|
754
736
|
file: fileBuffer,
|
|
755
737
|
filename: finalFilename,
|
|
756
738
|
uid
|
|
757
739
|
});
|
|
758
|
-
|
|
740
|
+
process.stdout.write('[uploadImageFromClipboard] 上传成功,结果: ' + JSON.stringify(uploadResult) + '\n');
|
|
759
741
|
// 生成禅道需要的 HTML 格式
|
|
760
742
|
const fileId = uploadResult.id;
|
|
761
743
|
const baseUrl = zentaoApi.getConfig().url;
|
|
@@ -773,7 +755,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
773
755
|
message: `图片已保存到本地并上传到禅道`,
|
|
774
756
|
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
775
757
|
};
|
|
776
|
-
|
|
758
|
+
process.stdout.write('[uploadImageFromClipboard] 返回结果: ' + JSON.stringify(response) + '\n');
|
|
777
759
|
return {
|
|
778
760
|
content: [{
|
|
779
761
|
type: "text",
|
|
@@ -782,7 +764,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
782
764
|
};
|
|
783
765
|
}
|
|
784
766
|
catch (error) {
|
|
785
|
-
|
|
767
|
+
process.stderr.write('[uploadImageFromClipboard] 发生错误: ' + String(error) + '\n');
|
|
786
768
|
const errorResponse = {
|
|
787
769
|
success: false,
|
|
788
770
|
error: error.message || String(error),
|
|
@@ -928,4 +910,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
928
910
|
});
|
|
929
911
|
// Start receiving messages on stdin and sending messages on stdout
|
|
930
912
|
const transport = new StdioServerTransport();
|
|
931
|
-
await server.connect(transport).catch(
|
|
913
|
+
await server.connect(transport).catch(err => {
|
|
914
|
+
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
915
|
+
process.exit(1);
|
|
916
|
+
});
|
package/dist/index-qa.js
CHANGED
|
@@ -14,15 +14,12 @@ if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
|
14
14
|
// 获取 --config 后面的 JSON 字符串并解析
|
|
15
15
|
const jsonStr = args[configIndex + 1];
|
|
16
16
|
configData = JSON.parse(jsonStr);
|
|
17
|
-
console.log('成功解析配置数据:', configData);
|
|
18
17
|
// 如果配置数据中包含 config 对象,则保存配置
|
|
19
18
|
if (configData.config) {
|
|
20
|
-
console.log('正在保存配置...');
|
|
21
19
|
saveConfig(configData.config);
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
catch (error) {
|
|
25
|
-
console.error('配置解析失败:', error);
|
|
26
23
|
process.exit(1);
|
|
27
24
|
}
|
|
28
25
|
}
|
|
@@ -34,10 +31,8 @@ const server = new McpServer({
|
|
|
34
31
|
// Initialize ZentaoAPI instance
|
|
35
32
|
let zentaoApi = null;
|
|
36
33
|
export default async function main(params) {
|
|
37
|
-
console.log('接收到的参数:', params);
|
|
38
34
|
// 如果传入了配置信息,就保存它
|
|
39
35
|
if (params.config) {
|
|
40
|
-
console.log('保存新的配置信息...');
|
|
41
36
|
saveConfig(params.config);
|
|
42
37
|
}
|
|
43
38
|
}
|
|
@@ -455,13 +450,11 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
455
450
|
const path = await import('path');
|
|
456
451
|
const { execSync } = await import('child_process');
|
|
457
452
|
try {
|
|
458
|
-
console.log('[uploadImageFromClipboard] 开始执行...');
|
|
459
453
|
// 读取系统剪贴板图片
|
|
460
454
|
let fileBuffer;
|
|
461
455
|
let finalFilename;
|
|
462
456
|
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
463
457
|
if (process.platform === 'win32') {
|
|
464
|
-
console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...');
|
|
465
458
|
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
466
459
|
const psScript = `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); $bytes = $ms.ToArray(); $ms.Close(); $base64 = [Convert]::ToBase64String($bytes); Write-Output $base64 } else { Write-Output 'NoImage' }`;
|
|
467
460
|
try {
|
|
@@ -473,7 +466,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
473
466
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
474
467
|
}).trim();
|
|
475
468
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
476
|
-
console.error('[uploadImageFromClipboard] 剪贴板中没有图片');
|
|
477
469
|
return {
|
|
478
470
|
content: [{
|
|
479
471
|
type: "text",
|
|
@@ -487,10 +479,8 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
487
479
|
}
|
|
488
480
|
fileBuffer = Buffer.from(result, 'base64');
|
|
489
481
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
490
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
491
482
|
}
|
|
492
483
|
catch (err) {
|
|
493
|
-
console.error('[uploadImageFromClipboard] Windows读取剪贴板失败:', err);
|
|
494
484
|
return {
|
|
495
485
|
content: [{
|
|
496
486
|
type: "text",
|
|
@@ -506,17 +496,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
506
496
|
}
|
|
507
497
|
// macOS: 使用 pngpaste
|
|
508
498
|
else if (process.platform === 'darwin') {
|
|
509
|
-
console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
|
|
510
499
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
511
500
|
try {
|
|
512
501
|
execSync(`pngpaste "${tempFile}"`);
|
|
513
502
|
fileBuffer = fs.readFileSync(tempFile);
|
|
514
503
|
fs.unlinkSync(tempFile);
|
|
515
504
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
516
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
517
505
|
}
|
|
518
506
|
catch (err) {
|
|
519
|
-
console.error('[uploadImageFromClipboard] macOS读取剪贴板失败:', err);
|
|
520
507
|
return {
|
|
521
508
|
content: [{
|
|
522
509
|
type: "text",
|
|
@@ -531,17 +518,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
531
518
|
}
|
|
532
519
|
// Linux: 使用 xclip
|
|
533
520
|
else {
|
|
534
|
-
console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
|
|
535
521
|
try {
|
|
536
522
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
537
523
|
maxBuffer: 10 * 1024 * 1024
|
|
538
524
|
});
|
|
539
525
|
fileBuffer = buffer;
|
|
540
526
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
541
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
542
527
|
}
|
|
543
528
|
catch (err) {
|
|
544
|
-
console.error('[uploadImageFromClipboard] Linux读取剪贴板失败:', err);
|
|
545
529
|
return {
|
|
546
530
|
content: [{
|
|
547
531
|
type: "text",
|
|
@@ -557,21 +541,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
557
541
|
// 创建 img 文件夹
|
|
558
542
|
const imgDir = path.join(process.cwd(), 'img');
|
|
559
543
|
if (!fs.existsSync(imgDir)) {
|
|
560
|
-
console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
|
|
561
544
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
562
545
|
}
|
|
563
546
|
// 保存到 img 文件夹
|
|
564
547
|
const savedPath = path.join(imgDir, finalFilename);
|
|
565
548
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
566
|
-
console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
|
|
567
549
|
// 上传到禅道
|
|
568
|
-
console.log('[uploadImageFromClipboard] 开始上传到禅道...');
|
|
569
550
|
const uploadResult = await zentaoApi.uploadFile({
|
|
570
551
|
file: fileBuffer,
|
|
571
552
|
filename: finalFilename,
|
|
572
553
|
uid
|
|
573
554
|
});
|
|
574
|
-
console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
|
|
575
555
|
// 生成禅道需要的 HTML 格式
|
|
576
556
|
const fileId = uploadResult.id;
|
|
577
557
|
const baseUrl = zentaoApi.getConfig().url;
|
|
@@ -589,7 +569,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
589
569
|
message: `图片已保存到本地并上传到禅道`,
|
|
590
570
|
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
591
571
|
};
|
|
592
|
-
console.log('[uploadImageFromClipboard] 返回结果:', response);
|
|
593
572
|
return {
|
|
594
573
|
content: [{
|
|
595
574
|
type: "text",
|
|
@@ -598,7 +577,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
598
577
|
};
|
|
599
578
|
}
|
|
600
579
|
catch (error) {
|
|
601
|
-
console.error('[uploadImageFromClipboard] 发生错误:', error);
|
|
602
580
|
const errorResponse = {
|
|
603
581
|
success: false,
|
|
604
582
|
error: error.message || String(error),
|
|
@@ -744,4 +722,4 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
744
722
|
});
|
|
745
723
|
// Start receiving messages on stdin and sending messages on stdout
|
|
746
724
|
const transport = new StdioServerTransport();
|
|
747
|
-
await server.connect(transport).catch(
|
|
725
|
+
await server.connect(transport).catch(err => { process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n'); process.exit(1); });
|