@zzp123/mcp-zentao 1.18.3 → 1.18.5
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 +62 -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 +26 -23
- package/dist/index-qa.js +1 -23
- package/dist/index.js +12 -33
- package/dist/mcpHelpers.d.ts +13 -0
- package/dist/mcpHelpers.js +24 -0
- package/package.json +5 -2
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
|
}
|
|
@@ -621,13 +621,13 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
621
621
|
const path = await import('path');
|
|
622
622
|
const { execSync } = await import('child_process');
|
|
623
623
|
try {
|
|
624
|
-
|
|
624
|
+
process.stdout.write('[uploadImageFromClipboard] 开始执行...\n');
|
|
625
625
|
// 读取系统剪贴板图片
|
|
626
626
|
let fileBuffer;
|
|
627
627
|
let finalFilename;
|
|
628
628
|
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
629
629
|
if (process.platform === 'win32') {
|
|
630
|
-
|
|
630
|
+
process.stdout.write('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...\n');
|
|
631
631
|
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
632
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' }`;
|
|
633
633
|
try {
|
|
@@ -639,7 +639,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
639
639
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
640
640
|
}).trim();
|
|
641
641
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
642
|
-
|
|
642
|
+
process.stderr.write('[uploadImageFromClipboard] 剪贴板中没有图片\n');
|
|
643
643
|
return {
|
|
644
644
|
content: [{
|
|
645
645
|
type: "text",
|
|
@@ -653,10 +653,10 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
653
653
|
}
|
|
654
654
|
fileBuffer = Buffer.from(result, 'base64');
|
|
655
655
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
656
|
-
|
|
656
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
657
657
|
}
|
|
658
658
|
catch (err) {
|
|
659
|
-
|
|
659
|
+
process.stderr.write('[uploadImageFromClipboard] Windows读取剪贴板失败: ' + String(err) + '\n');
|
|
660
660
|
return {
|
|
661
661
|
content: [{
|
|
662
662
|
type: "text",
|
|
@@ -672,17 +672,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
672
672
|
}
|
|
673
673
|
// macOS: 使用 pngpaste
|
|
674
674
|
else if (process.platform === 'darwin') {
|
|
675
|
-
|
|
675
|
+
process.stdout.write('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...\n');
|
|
676
676
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
677
677
|
try {
|
|
678
678
|
execSync(`pngpaste "${tempFile}"`);
|
|
679
679
|
fileBuffer = fs.readFileSync(tempFile);
|
|
680
680
|
fs.unlinkSync(tempFile);
|
|
681
681
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
682
|
-
|
|
682
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
683
683
|
}
|
|
684
684
|
catch (err) {
|
|
685
|
-
|
|
685
|
+
process.stderr.write('[uploadImageFromClipboard] macOS读取剪贴板失败: ' + String(err) + '\n');
|
|
686
686
|
return {
|
|
687
687
|
content: [{
|
|
688
688
|
type: "text",
|
|
@@ -697,17 +697,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
697
697
|
}
|
|
698
698
|
// Linux: 使用 xclip
|
|
699
699
|
else {
|
|
700
|
-
|
|
700
|
+
process.stdout.write('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...\n');
|
|
701
701
|
try {
|
|
702
702
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
703
703
|
maxBuffer: 10 * 1024 * 1024
|
|
704
704
|
});
|
|
705
705
|
fileBuffer = buffer;
|
|
706
706
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
707
|
-
|
|
707
|
+
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
708
708
|
}
|
|
709
709
|
catch (err) {
|
|
710
|
-
|
|
710
|
+
process.stderr.write('[uploadImageFromClipboard] Linux读取剪贴板失败: ' + String(err) + '\n');
|
|
711
711
|
return {
|
|
712
712
|
content: [{
|
|
713
713
|
type: "text",
|
|
@@ -723,21 +723,21 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
723
723
|
// 创建 img 文件夹
|
|
724
724
|
const imgDir = path.join(process.cwd(), 'img');
|
|
725
725
|
if (!fs.existsSync(imgDir)) {
|
|
726
|
-
|
|
726
|
+
process.stdout.write(`[uploadImageFromClipboard] 创建目录: ${imgDir}\n`);
|
|
727
727
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
728
728
|
}
|
|
729
729
|
// 保存到 img 文件夹
|
|
730
730
|
const savedPath = path.join(imgDir, finalFilename);
|
|
731
731
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
732
|
-
|
|
732
|
+
process.stdout.write(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}\n`);
|
|
733
733
|
// 上传到禅道
|
|
734
|
-
|
|
734
|
+
process.stdout.write('[uploadImageFromClipboard] 开始上传到禅道...\n');
|
|
735
735
|
const uploadResult = await zentaoApi.uploadFile({
|
|
736
736
|
file: fileBuffer,
|
|
737
737
|
filename: finalFilename,
|
|
738
738
|
uid
|
|
739
739
|
});
|
|
740
|
-
|
|
740
|
+
process.stdout.write('[uploadImageFromClipboard] 上传成功,结果: ' + JSON.stringify(uploadResult) + '\n');
|
|
741
741
|
// 生成禅道需要的 HTML 格式
|
|
742
742
|
const fileId = uploadResult.id;
|
|
743
743
|
const baseUrl = zentaoApi.getConfig().url;
|
|
@@ -755,7 +755,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
755
755
|
message: `图片已保存到本地并上传到禅道`,
|
|
756
756
|
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
757
757
|
};
|
|
758
|
-
|
|
758
|
+
process.stdout.write('[uploadImageFromClipboard] 返回结果: ' + JSON.stringify(response) + '\n');
|
|
759
759
|
return {
|
|
760
760
|
content: [{
|
|
761
761
|
type: "text",
|
|
@@ -764,7 +764,7 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
764
764
|
};
|
|
765
765
|
}
|
|
766
766
|
catch (error) {
|
|
767
|
-
|
|
767
|
+
process.stderr.write('[uploadImageFromClipboard] 发生错误: ' + String(error) + '\n');
|
|
768
768
|
const errorResponse = {
|
|
769
769
|
success: false,
|
|
770
770
|
error: error.message || String(error),
|
|
@@ -910,4 +910,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
910
910
|
});
|
|
911
911
|
// Start receiving messages on stdin and sending messages on stdout
|
|
912
912
|
const transport = new StdioServerTransport();
|
|
913
|
-
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); });
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { ZentaoAPI } from './api/zentaoApi.js';
|
|
6
6
|
import { loadConfig, saveConfig } from './config.js';
|
|
7
|
+
import { withApi } from './mcpHelpers.js';
|
|
7
8
|
// 解析命令行参数
|
|
8
9
|
const args = process.argv.slice(2);
|
|
9
10
|
let configData = null;
|
|
@@ -14,15 +15,12 @@ if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
|
14
15
|
// 获取 --config 后面的 JSON 字符串并解析
|
|
15
16
|
const jsonStr = args[configIndex + 1];
|
|
16
17
|
configData = JSON.parse(jsonStr);
|
|
17
|
-
console.log('成功解析配置数据:', configData);
|
|
18
18
|
// 如果配置数据中包含 config 对象,则保存配置
|
|
19
19
|
if (configData.config) {
|
|
20
|
-
console.log('正在保存配置...');
|
|
21
20
|
saveConfig(configData.config);
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
catch (error) {
|
|
25
|
-
console.error('配置解析失败:', error);
|
|
26
24
|
process.exit(1);
|
|
27
25
|
}
|
|
28
26
|
}
|
|
@@ -33,11 +31,10 @@ const server = new McpServer({
|
|
|
33
31
|
});
|
|
34
32
|
// Initialize ZentaoAPI instance
|
|
35
33
|
let zentaoApi = null;
|
|
34
|
+
const getApi = () => zentaoApi;
|
|
36
35
|
export default async function main(params) {
|
|
37
|
-
console.log('接收到的参数:', params);
|
|
38
36
|
// 如果传入了配置信息,就保存它
|
|
39
37
|
if (params.config) {
|
|
40
|
-
console.log('保存新的配置信息...');
|
|
41
38
|
saveConfig(params.config);
|
|
42
39
|
}
|
|
43
40
|
}
|
|
@@ -58,25 +55,21 @@ server.tool("initZentao", {}, async ({}) => {
|
|
|
58
55
|
// Add getMyTasks tool
|
|
59
56
|
server.tool("getMyTasks", {
|
|
60
57
|
status: z.enum(['wait', 'doing', 'done', 'all']).optional()
|
|
61
|
-
}, async ({ status }) => {
|
|
62
|
-
|
|
63
|
-
throw new Error("Please initialize Zentao API first");
|
|
64
|
-
const tasks = await zentaoApi.getMyTasks(status);
|
|
58
|
+
}, withApi(getApi, async (api, { status }) => {
|
|
59
|
+
const tasks = await api.getMyTasks(status);
|
|
65
60
|
return {
|
|
66
61
|
content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }]
|
|
67
62
|
};
|
|
68
|
-
});
|
|
63
|
+
}));
|
|
69
64
|
// Add getTaskDetail tool
|
|
70
65
|
server.tool("getTaskDetail", {
|
|
71
66
|
taskId: z.number()
|
|
72
|
-
}, async ({ taskId }) => {
|
|
73
|
-
|
|
74
|
-
throw new Error("Please initialize Zentao API first");
|
|
75
|
-
const task = await zentaoApi.getTaskDetail(taskId);
|
|
67
|
+
}, withApi(getApi, async (api, { taskId }) => {
|
|
68
|
+
const task = await api.getTaskDetail(taskId);
|
|
76
69
|
return {
|
|
77
70
|
content: [{ type: "text", text: JSON.stringify(task, null, 2) }]
|
|
78
71
|
};
|
|
79
|
-
});
|
|
72
|
+
}));
|
|
80
73
|
// Add getProducts tool
|
|
81
74
|
server.tool("getProducts", "获取产品列表 - 只返回核心字段(id, 名称, 负责人)", {
|
|
82
75
|
fields: z.enum(['basic', 'default', 'full']).optional().describe("返回字段级别:'basic'(id/名称/状态)、'default'(+负责人/创建人,默认)、'full'(完整字段)")
|
|
@@ -1276,13 +1269,11 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1276
1269
|
const path = await import('path');
|
|
1277
1270
|
const { execSync } = await import('child_process');
|
|
1278
1271
|
try {
|
|
1279
|
-
console.log('[uploadImageFromClipboard] 开始执行...');
|
|
1280
1272
|
// 读取系统剪贴板图片
|
|
1281
1273
|
let fileBuffer;
|
|
1282
1274
|
let finalFilename;
|
|
1283
1275
|
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
1284
1276
|
if (process.platform === 'win32') {
|
|
1285
|
-
console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...');
|
|
1286
1277
|
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
1287
1278
|
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' }`;
|
|
1288
1279
|
try {
|
|
@@ -1294,7 +1285,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1294
1285
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
1295
1286
|
}).trim();
|
|
1296
1287
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
1297
|
-
console.error('[uploadImageFromClipboard] 剪贴板中没有图片');
|
|
1298
1288
|
return {
|
|
1299
1289
|
content: [{
|
|
1300
1290
|
type: "text",
|
|
@@ -1308,10 +1298,8 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1308
1298
|
}
|
|
1309
1299
|
fileBuffer = Buffer.from(result, 'base64');
|
|
1310
1300
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1311
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1312
1301
|
}
|
|
1313
1302
|
catch (err) {
|
|
1314
|
-
console.error('[uploadImageFromClipboard] Windows读取剪贴板失败:', err);
|
|
1315
1303
|
return {
|
|
1316
1304
|
content: [{
|
|
1317
1305
|
type: "text",
|
|
@@ -1327,17 +1315,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1327
1315
|
}
|
|
1328
1316
|
// macOS: 使用 pngpaste
|
|
1329
1317
|
else if (process.platform === 'darwin') {
|
|
1330
|
-
console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
|
|
1331
1318
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
1332
1319
|
try {
|
|
1333
1320
|
execSync(`pngpaste "${tempFile}"`);
|
|
1334
1321
|
fileBuffer = fs.readFileSync(tempFile);
|
|
1335
1322
|
fs.unlinkSync(tempFile);
|
|
1336
1323
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1337
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1338
1324
|
}
|
|
1339
1325
|
catch (err) {
|
|
1340
|
-
console.error('[uploadImageFromClipboard] macOS读取剪贴板失败:', err);
|
|
1341
1326
|
return {
|
|
1342
1327
|
content: [{
|
|
1343
1328
|
type: "text",
|
|
@@ -1352,17 +1337,14 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1352
1337
|
}
|
|
1353
1338
|
// Linux: 使用 xclip
|
|
1354
1339
|
else {
|
|
1355
|
-
console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
|
|
1356
1340
|
try {
|
|
1357
1341
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
1358
1342
|
maxBuffer: 10 * 1024 * 1024
|
|
1359
1343
|
});
|
|
1360
1344
|
fileBuffer = buffer;
|
|
1361
1345
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1362
|
-
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1363
1346
|
}
|
|
1364
1347
|
catch (err) {
|
|
1365
|
-
console.error('[uploadImageFromClipboard] Linux读取剪贴板失败:', err);
|
|
1366
1348
|
return {
|
|
1367
1349
|
content: [{
|
|
1368
1350
|
type: "text",
|
|
@@ -1378,21 +1360,17 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1378
1360
|
// 创建 img 文件夹
|
|
1379
1361
|
const imgDir = path.join(process.cwd(), 'img');
|
|
1380
1362
|
if (!fs.existsSync(imgDir)) {
|
|
1381
|
-
console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
|
|
1382
1363
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
1383
1364
|
}
|
|
1384
1365
|
// 保存到 img 文件夹
|
|
1385
1366
|
const savedPath = path.join(imgDir, finalFilename);
|
|
1386
1367
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
1387
|
-
console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
|
|
1388
1368
|
// 上传到禅道
|
|
1389
|
-
console.log('[uploadImageFromClipboard] 开始上传到禅道...');
|
|
1390
1369
|
const uploadResult = await zentaoApi.uploadFile({
|
|
1391
1370
|
file: fileBuffer,
|
|
1392
1371
|
filename: finalFilename,
|
|
1393
1372
|
uid
|
|
1394
1373
|
});
|
|
1395
|
-
console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
|
|
1396
1374
|
// 生成禅道需要的 HTML 格式
|
|
1397
1375
|
const fileId = uploadResult.id;
|
|
1398
1376
|
const baseUrl = zentaoApi.getConfig().url;
|
|
@@ -1410,7 +1388,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1410
1388
|
message: `图片已保存到本地并上传到禅道`,
|
|
1411
1389
|
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
1412
1390
|
};
|
|
1413
|
-
console.log('[uploadImageFromClipboard] 返回结果:', response);
|
|
1414
1391
|
return {
|
|
1415
1392
|
content: [{
|
|
1416
1393
|
type: "text",
|
|
@@ -1419,7 +1396,6 @@ server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动
|
|
|
1419
1396
|
};
|
|
1420
1397
|
}
|
|
1421
1398
|
catch (error) {
|
|
1422
|
-
console.error('[uploadImageFromClipboard] 发生错误:', error);
|
|
1423
1399
|
const errorResponse = {
|
|
1424
1400
|
success: false,
|
|
1425
1401
|
error: error.message || String(error),
|
|
@@ -1566,4 +1542,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
1566
1542
|
});
|
|
1567
1543
|
// Start receiving messages on stdin and sending messages on stdout
|
|
1568
1544
|
const transport = new StdioServerTransport();
|
|
1569
|
-
await server.connect(transport).catch(
|
|
1545
|
+
await server.connect(transport).catch(err => {
|
|
1546
|
+
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
1547
|
+
process.exit(1);
|
|
1548
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ZentaoAPI } from "./api/zentaoApi.js";
|
|
3
|
+
export type ApiGetter = () => ZentaoAPI | null;
|
|
4
|
+
export declare function ensureApi(getApi: ApiGetter): ZentaoAPI;
|
|
5
|
+
export declare function withApi<TArgs, TResult>(getApi: ApiGetter, handler: (api: ZentaoAPI, args: TArgs) => Promise<TResult>): (args: TArgs) => Promise<TResult>;
|
|
6
|
+
export type ToolHandler<TArgs = any> = (args: TArgs) => Promise<any>;
|
|
7
|
+
export interface ToolDefinition<TArgs = any> {
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
schema: any;
|
|
11
|
+
handler: ToolHandler<TArgs>;
|
|
12
|
+
}
|
|
13
|
+
export declare function registerTools(server: McpServer, tools: ToolDefinition[]): void;
|