@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/dist/config.js CHANGED
@@ -22,7 +22,7 @@ export function loadConfig() {
22
22
  }
23
23
  }
24
24
  catch (error) {
25
- console.error('读取配置文件失败:', error);
25
+ process.stderr.write('[ERROR] Failed to load config: ' + String(error) + '\n');
26
26
  }
27
27
  return null;
28
28
  }
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(console.error);
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
- console.log('成功解析配置数据:', configData);
17
+ process.stdout.write('成功解析配置数据: ' + JSON.stringify(configData) + '\n');
18
18
  // 如果配置数据中包含 config 对象,则保存配置
19
19
  if (configData.config) {
20
- console.log('正在保存配置...');
20
+ process.stdout.write('正在保存配置...\n');
21
21
  saveConfig(configData.config);
22
22
  }
23
23
  }
24
24
  catch (error) {
25
- console.error('配置解析失败:', error);
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
- console.log('接收到的参数:', params);
37
+ process.stdout.write('接收到的参数: ' + JSON.stringify(params) + '\n');
38
38
  // 如果传入了配置信息,就保存它
39
39
  if (params.config) {
40
- console.log('保存新的配置信息...');
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
- console.log('[uploadImageFromClipboard] 开始执行...');
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
- console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...');
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
- console.error('[uploadImageFromClipboard] 剪贴板中没有图片');
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
- console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
656
+ process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
657
657
  }
658
658
  catch (err) {
659
- console.error('[uploadImageFromClipboard] Windows读取剪贴板失败:', err);
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
- console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
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
- console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
682
+ process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
683
683
  }
684
684
  catch (err) {
685
- console.error('[uploadImageFromClipboard] macOS读取剪贴板失败:', err);
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
- console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
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
- console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
707
+ process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
708
708
  }
709
709
  catch (err) {
710
- console.error('[uploadImageFromClipboard] Linux读取剪贴板失败:', err);
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
- console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
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
- console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
732
+ process.stdout.write(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}\n`);
733
733
  // 上传到禅道
734
- console.log('[uploadImageFromClipboard] 开始上传到禅道...');
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
- console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
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
- console.log('[uploadImageFromClipboard] 返回结果:', response);
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
- console.error('[uploadImageFromClipboard] 发生错误:', error);
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(console.error);
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(console.error);
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
- if (!zentaoApi)
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
- if (!zentaoApi)
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(console.error);
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;