@zzp123/mcp-zentao 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -990,49 +990,100 @@ server.tool("uploadFile", {
990
990
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
991
991
  });
992
992
  server.tool("uploadImageFromClipboard", {
993
- base64Data: z.string(),
994
993
  filename: z.string().optional(),
995
994
  uid: z.string().optional()
996
- }, async ({ base64Data, filename, uid }) => {
995
+ }, async ({ filename, uid }) => {
997
996
  if (!zentaoApi)
998
997
  throw new Error("Please initialize Zentao API first");
999
998
  const fs = await import('fs');
1000
999
  const path = await import('path');
1001
- // 创建 img 文件夹(如果不存在)
1002
- const imgDir = path.join(process.cwd(), 'img');
1003
- if (!fs.existsSync(imgDir)) {
1004
- fs.mkdirSync(imgDir, { recursive: true });
1005
- }
1006
- let fileBuffer;
1007
- let finalFilename;
1008
- // 处理 base64 数据
1009
- const matches = base64Data.match(/^data:image\/(\w+);base64,(.+)$/);
1010
- if (matches) {
1011
- const ext = matches[1];
1012
- const data = matches[2];
1013
- fileBuffer = Buffer.from(data, 'base64');
1014
- finalFilename = filename || `clipboard_${Date.now()}.${ext}`;
1000
+ const { execSync } = await import('child_process');
1001
+ try {
1002
+ // 读取系统剪贴板图片
1003
+ let base64Data;
1004
+ let fileBuffer;
1005
+ let finalFilename;
1006
+ // Windows: 使用 PowerShell 读取剪贴板
1007
+ if (process.platform === 'win32') {
1008
+ const psScript = `
1009
+ Add-Type -AssemblyName System.Windows.Forms
1010
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
1011
+ if ($img -ne $null) {
1012
+ $ms = New-Object System.IO.MemoryStream
1013
+ $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
1014
+ [Convert]::ToBase64String($ms.ToArray())
1015
+ } else {
1016
+ Write-Error "NoImage"
1017
+ }
1018
+ `;
1019
+ const result = execSync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, {
1020
+ encoding: 'utf8',
1021
+ maxBuffer: 10 * 1024 * 1024,
1022
+ stdio: ['pipe', 'pipe', 'pipe']
1023
+ }).trim();
1024
+ if (!result || result.includes('NoImage') || result.includes('Error')) {
1025
+ throw new Error('剪贴板中没有图片。请先复制图片,不要粘贴到输入框,直接告诉我"上传"。');
1026
+ }
1027
+ base64Data = result;
1028
+ fileBuffer = Buffer.from(base64Data, 'base64');
1029
+ finalFilename = filename || `clipboard_${Date.now()}.png`;
1030
+ }
1031
+ // macOS: 使用 pngpaste
1032
+ else if (process.platform === 'darwin') {
1033
+ const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
1034
+ try {
1035
+ execSync(`pngpaste "${tempFile}"`);
1036
+ fileBuffer = fs.readFileSync(tempFile);
1037
+ fs.unlinkSync(tempFile);
1038
+ finalFilename = filename || `clipboard_${Date.now()}.png`;
1039
+ }
1040
+ catch {
1041
+ throw new Error('剪贴板中没有图片。请先安装 pngpaste: brew install pngpaste');
1042
+ }
1043
+ }
1044
+ // Linux: 使用 xclip
1045
+ else {
1046
+ try {
1047
+ const buffer = execSync('xclip -selection clipboard -t image/png -o', {
1048
+ maxBuffer: 10 * 1024 * 1024
1049
+ });
1050
+ fileBuffer = buffer;
1051
+ finalFilename = filename || `clipboard_${Date.now()}.png`;
1052
+ }
1053
+ catch {
1054
+ throw new Error('剪贴板中没有图片。请先安装 xclip: sudo apt-get install xclip');
1055
+ }
1056
+ }
1057
+ // 创建 img 文件夹
1058
+ const imgDir = path.join(process.cwd(), 'img');
1059
+ if (!fs.existsSync(imgDir)) {
1060
+ fs.mkdirSync(imgDir, { recursive: true });
1061
+ }
1062
+ // 保存到 img 文件夹
1063
+ const savedPath = path.join(imgDir, finalFilename);
1064
+ fs.writeFileSync(savedPath, fileBuffer);
1065
+ // 上传到禅道
1066
+ const result = await zentaoApi.uploadFile({
1067
+ file: fileBuffer,
1068
+ filename: finalFilename,
1069
+ uid
1070
+ });
1071
+ const response = {
1072
+ upload: result,
1073
+ savedPath: savedPath,
1074
+ message: `图片已保存到本地并上传到禅道`
1075
+ };
1076
+ return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
1015
1077
  }
1016
- else {
1017
- // 直接的 base64 数据,没有 data URL 前缀
1018
- fileBuffer = Buffer.from(base64Data, 'base64');
1019
- finalFilename = filename || `clipboard_${Date.now()}.png`;
1078
+ catch (error) {
1079
+ return {
1080
+ content: [{
1081
+ type: "text",
1082
+ text: `错误: ${error.message}\n\n提示:\n1. 确保已复制图片到剪贴板\n2. 不要粘贴到输入框,直接说"上传剪贴板图片"\n3. 或使用命令行: upload.bat`
1083
+ }],
1084
+ isError: true
1085
+ };
1020
1086
  }
1021
- // 保存到 img 文件夹
1022
- const savedPath = path.join(imgDir, finalFilename);
1023
- fs.writeFileSync(savedPath, fileBuffer);
1024
- // 上传到禅道
1025
- const result = await zentaoApi.uploadFile({
1026
- file: fileBuffer,
1027
- filename: finalFilename,
1028
- uid
1029
- });
1030
- const response = {
1031
- upload: result,
1032
- savedPath: savedPath,
1033
- message: `图片已保存到本地并上传到禅道`
1034
- };
1035
- return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
1036
1087
  });
1037
1088
  server.tool("downloadFile", {
1038
1089
  fileId: z.number(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zzp123/mcp-zentao",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "禅道项目管理系统的高级API集成包,提供任务管理、Bug跟踪等功能的完整封装,专为Cursor IDE设计的MCP扩展",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 获取剪贴板图片的 base64 编码
5
+ * 可以在 Claude Code 中使用 Bash 工具调用此脚本
6
+ */
7
+
8
+ import { execSync } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import fs from 'fs';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ async function getClipboardBase64() {
17
+ try {
18
+ let base64Data;
19
+
20
+ // Windows: 使用 PowerShell
21
+ if (process.platform === 'win32') {
22
+ const psScript = `
23
+ Add-Type -AssemblyName System.Windows.Forms
24
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
25
+ if ($img -ne $null) {
26
+ $ms = New-Object System.IO.MemoryStream
27
+ $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
28
+ [Convert]::ToBase64String($ms.ToArray())
29
+ } else {
30
+ Write-Error "No image in clipboard"
31
+ exit 1
32
+ }
33
+ `;
34
+
35
+ try {
36
+ base64Data = execSync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, {
37
+ encoding: 'utf8',
38
+ maxBuffer: 10 * 1024 * 1024,
39
+ stdio: ['pipe', 'pipe', 'pipe']
40
+ }).trim();
41
+
42
+ if (!base64Data || base64Data.includes('Error')) {
43
+ console.error('ERROR: No image in clipboard');
44
+ process.exit(1);
45
+ }
46
+
47
+ // 输出完整的 data URL
48
+ console.log(`data:image/png;base64,${base64Data}`);
49
+
50
+ } catch (error) {
51
+ console.error('ERROR: Failed to read clipboard image');
52
+ process.exit(1);
53
+ }
54
+ }
55
+ // macOS: 使用 pngpaste
56
+ else if (process.platform === 'darwin') {
57
+ try {
58
+ execSync('which pngpaste', { stdio: 'ignore' });
59
+ const tempFile = join(__dirname, '.temp_clipboard.png');
60
+ execSync(`pngpaste "${tempFile}"`);
61
+ const buffer = fs.readFileSync(tempFile);
62
+ base64Data = buffer.toString('base64');
63
+ fs.unlinkSync(tempFile);
64
+ console.log(`data:image/png;base64,${base64Data}`);
65
+ } catch (error) {
66
+ console.error('ERROR: No image in clipboard or pngpaste not installed');
67
+ console.error('Install: brew install pngpaste');
68
+ process.exit(1);
69
+ }
70
+ }
71
+ // Linux: 使用 xclip
72
+ else {
73
+ try {
74
+ execSync('which xclip', { stdio: 'ignore' });
75
+ const buffer = execSync('xclip -selection clipboard -t image/png -o', {
76
+ maxBuffer: 10 * 1024 * 1024
77
+ });
78
+ base64Data = buffer.toString('base64');
79
+ console.log(`data:image/png;base64,${base64Data}`);
80
+ } catch (error) {
81
+ console.error('ERROR: No image in clipboard or xclip not installed');
82
+ console.error('Install: sudo apt-get install xclip');
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ } catch (error) {
88
+ console.error('ERROR:', error.message);
89
+ process.exit(1);
90
+ }
91
+ }
92
+
93
+ getClipboardBase64();