@bangdao-ai/zentao-mcp 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/package.json +4 -1
- package/src/__pycache__/zentao_nexus.cpython-313.pyc +0 -0
- package/src/zentao_nexus.py +99 -80
package/README.md
CHANGED
|
@@ -139,11 +139,12 @@ pip3 install -r requirements.txt
|
|
|
139
139
|
"@bangdao-ai/zentao-mcp@latest"
|
|
140
140
|
],
|
|
141
141
|
"env": {
|
|
142
|
-
"ZENTAO_PRODUCT_ID": "
|
|
143
|
-
"ZENTAO_OPENED_BY": "
|
|
144
|
-
"KEY": "
|
|
142
|
+
"ZENTAO_PRODUCT_ID": "your-product-id",
|
|
143
|
+
"ZENTAO_OPENED_BY": "your-user-id",
|
|
144
|
+
"KEY": "your-api-key",
|
|
145
145
|
"ZENTAO_KEYWORDS": "AI",
|
|
146
|
-
"ZENTAO_TITLE_PREFIX": ""
|
|
146
|
+
"ZENTAO_TITLE_PREFIX": "",
|
|
147
|
+
"ZENTAO_ROLE_TYPE": "test"
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
}
|
|
@@ -156,6 +157,7 @@ pip3 install -r requirements.txt
|
|
|
156
157
|
- `KEY`(必需):API认证密钥,作为API认证的key参数
|
|
157
158
|
- `ZENTAO_KEYWORDS`(可选):关键词,默认为空
|
|
158
159
|
- `ZENTAO_TITLE_PREFIX`(可选):标题前缀,默认为空
|
|
160
|
+
- `ZENTAO_ROLE_TYPE`(可选):角色类型,默认为"test"
|
|
159
161
|
|
|
160
162
|
### 兜底配置(可选)
|
|
161
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bangdao-ai/zentao-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "禅道Bug管理系统MCP工具",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,5 +30,8 @@
|
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
32
|
"url": "git+https://github.com/bangdao-ai/zentao-mcp.git"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
33
36
|
}
|
|
34
37
|
}
|
|
Binary file
|
package/src/zentao_nexus.py
CHANGED
|
@@ -167,67 +167,68 @@ class ZenTaoAPIClient:
|
|
|
167
167
|
'User-Agent': 'Python-ZenTao-Client/1.0'
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
170
|
+
# 打印请求信息(仅在非静默模式下)
|
|
171
|
+
if not self.silent:
|
|
172
|
+
print("\n" + "=" * 80)
|
|
173
|
+
print("【禅道API请求信息】")
|
|
174
|
+
print("=" * 80)
|
|
175
|
+
print(f"API URL: {api_url}")
|
|
176
|
+
print(f"\n【Headers】")
|
|
177
|
+
for key, value in headers.items():
|
|
178
|
+
print(f" {key}: {value}")
|
|
179
|
+
|
|
180
|
+
print(f"\n【Token生成信息】")
|
|
181
|
+
print(f" code: {self.code}")
|
|
182
|
+
print(f" key: {self.key[:10]}...{self.key[-10:] if len(self.key) > 20 else self.key}") # 只显示部分key,保护隐私
|
|
183
|
+
print(f" time: {current_timestamp}")
|
|
184
|
+
print(f" token生成公式: md5(code + key + time)")
|
|
185
|
+
print(f" token生成字符串: {self.code}{self.key}{current_timestamp}")
|
|
186
|
+
print(f" token: {token}")
|
|
187
|
+
|
|
188
|
+
print(f"\n【URL参数】")
|
|
189
|
+
for key, value in url_params.items():
|
|
190
|
+
print(f" {key}: {value}")
|
|
191
|
+
|
|
192
|
+
# 处理form_data用于显示
|
|
193
|
+
display_form_data = form_data
|
|
194
|
+
if isinstance(form_data, dict):
|
|
195
|
+
# 检查是否有数组类型的值
|
|
196
|
+
processed_data = []
|
|
197
|
+
for key, value in form_data.items():
|
|
198
|
+
if isinstance(value, (list, tuple)):
|
|
199
|
+
for item in value:
|
|
200
|
+
processed_data.append((key, str(item)))
|
|
201
|
+
else:
|
|
202
|
+
processed_data.append((key, str(value)))
|
|
203
|
+
display_form_data = processed_data
|
|
204
|
+
|
|
205
|
+
if display_form_data:
|
|
206
|
+
print(f"\n【Body (form-data)】")
|
|
207
|
+
if isinstance(display_form_data, list):
|
|
208
|
+
for item in display_form_data:
|
|
209
|
+
if isinstance(item, tuple):
|
|
210
|
+
key, value = item
|
|
211
|
+
# 如果是文件,只显示文件名
|
|
212
|
+
if hasattr(value, 'read'):
|
|
213
|
+
print(f" {key}: <文件对象>")
|
|
214
|
+
else:
|
|
215
|
+
print(f" {key}: {value}")
|
|
213
216
|
else:
|
|
214
|
-
print(f" {
|
|
217
|
+
print(f" {item}")
|
|
218
|
+
elif isinstance(display_form_data, dict):
|
|
219
|
+
for key, value in display_form_data.items():
|
|
220
|
+
print(f" {key}: {value}")
|
|
221
|
+
|
|
222
|
+
if files:
|
|
223
|
+
print(f"\n【Files】")
|
|
224
|
+
for key, file_info in files.items():
|
|
225
|
+
if isinstance(file_info, tuple):
|
|
226
|
+
filename = file_info[0] if len(file_info) > 0 else "unknown"
|
|
227
|
+
print(f" {key}: {filename}")
|
|
215
228
|
else:
|
|
216
|
-
print(f" {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
print(f" {key}: {value}")
|
|
220
|
-
|
|
221
|
-
if files:
|
|
222
|
-
print(f"\n【Files】")
|
|
223
|
-
for key, file_info in files.items():
|
|
224
|
-
if isinstance(file_info, tuple):
|
|
225
|
-
filename = file_info[0] if len(file_info) > 0 else "unknown"
|
|
226
|
-
print(f" {key}: {filename}")
|
|
227
|
-
else:
|
|
228
|
-
print(f" {key}: {file_info}")
|
|
229
|
-
|
|
230
|
-
print("=" * 80)
|
|
229
|
+
print(f" {key}: {file_info}")
|
|
230
|
+
|
|
231
|
+
print("=" * 80)
|
|
231
232
|
|
|
232
233
|
try:
|
|
233
234
|
if files:
|
|
@@ -252,24 +253,33 @@ class ZenTaoAPIClient:
|
|
|
252
253
|
else:
|
|
253
254
|
response = requests.post(api_url, params=url_params, headers=headers, timeout=30)
|
|
254
255
|
|
|
255
|
-
#
|
|
256
|
-
print("\n【禅道API响应信息】")
|
|
257
|
-
print("=" * 80)
|
|
258
|
-
print(f"状态码: {response.status_code}")
|
|
259
|
-
print(f"响应Headers:")
|
|
260
|
-
for key, value in response.headers.items():
|
|
261
|
-
print(f" {key}: {value}")
|
|
262
|
-
|
|
256
|
+
# 处理响应
|
|
263
257
|
try:
|
|
264
258
|
response_json = response.json()
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
259
|
+
# 打印响应信息(仅在非静默模式下)
|
|
260
|
+
if not self.silent:
|
|
261
|
+
print("\n【禅道API响应信息】")
|
|
262
|
+
print("=" * 80)
|
|
263
|
+
print(f"状态码: {response.status_code}")
|
|
264
|
+
print(f"响应Headers:")
|
|
265
|
+
for key, value in response.headers.items():
|
|
266
|
+
print(f" {key}: {value}")
|
|
267
|
+
print(f"\n响应Body (JSON):")
|
|
268
|
+
print(json.dumps(response_json, ensure_ascii=False, indent=2))
|
|
269
|
+
print("=" * 80 + "\n")
|
|
268
270
|
return response_json
|
|
269
271
|
except json.JSONDecodeError:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
# 打印响应信息(仅在非静默模式下)
|
|
273
|
+
if not self.silent:
|
|
274
|
+
print("\n【禅道API响应信息】")
|
|
275
|
+
print("=" * 80)
|
|
276
|
+
print(f"状态码: {response.status_code}")
|
|
277
|
+
print(f"响应Headers:")
|
|
278
|
+
for key, value in response.headers.items():
|
|
279
|
+
print(f" {key}: {value}")
|
|
280
|
+
print(f"\n响应Body (非JSON):")
|
|
281
|
+
print(response.text[:1000]) # 只显示前1000个字符
|
|
282
|
+
print("=" * 80 + "\n")
|
|
273
283
|
return {
|
|
274
284
|
"status": 0,
|
|
275
285
|
"message": "fail",
|
|
@@ -278,9 +288,11 @@ class ZenTaoAPIClient:
|
|
|
278
288
|
"raw_response": response.text
|
|
279
289
|
}
|
|
280
290
|
except requests.exceptions.RequestException as e:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
291
|
+
# 打印异常信息(仅在非静默模式下)
|
|
292
|
+
if not self.silent:
|
|
293
|
+
print(f"\n【请求异常】")
|
|
294
|
+
print(f"错误信息: {str(e)}")
|
|
295
|
+
print("=" * 80 + "\n")
|
|
284
296
|
return {
|
|
285
297
|
"status": 0,
|
|
286
298
|
"message": "fail",
|
|
@@ -669,13 +681,19 @@ class ZenTaoNexus:
|
|
|
669
681
|
LOWER_BUG = "0"
|
|
670
682
|
REGRESSION = "0"
|
|
671
683
|
|
|
672
|
-
#
|
|
684
|
+
# 职位类型到任务类型的映射(支持中英文)
|
|
673
685
|
ROLE_TO_TASK_TYPE = {
|
|
686
|
+
# 中文
|
|
674
687
|
"测试": "4", # 测试
|
|
675
688
|
"前端": "8", # 前端开发
|
|
676
689
|
"后端": "3", # 后端开发
|
|
677
690
|
"其它": "20", # 其他
|
|
678
|
-
"其他": "20"
|
|
691
|
+
"其他": "20", # 其他(兼容两种写法)
|
|
692
|
+
# 英文
|
|
693
|
+
"test": "4", # 测试
|
|
694
|
+
"frontend": "8", # 前端开发
|
|
695
|
+
"backend": "3", # 后端开发
|
|
696
|
+
"other": "20" # 其他
|
|
679
697
|
}
|
|
680
698
|
|
|
681
699
|
def __init__(self, config_path: str = None):
|
|
@@ -875,8 +893,9 @@ class ZenTaoNexus:
|
|
|
875
893
|
"""
|
|
876
894
|
# 如果 task_data 中没有指定 mold,且配置了职位类型,则自动设置
|
|
877
895
|
if 'mold' not in task_data or (task_data.get('mold') == '' or task_data.get('mold') is None):
|
|
878
|
-
if self.role_type
|
|
879
|
-
|
|
896
|
+
if self.role_type:
|
|
897
|
+
# 如果映射存在,使用映射值;否则使用"其它"对应的值 "20"
|
|
898
|
+
task_data['mold'] = self.ROLE_TO_TASK_TYPE.get(self.role_type, "20")
|
|
880
899
|
|
|
881
900
|
# 自动设置预计开始时间(当前日期)
|
|
882
901
|
if 'estStarted' not in task_data or (task_data.get('estStarted') == '' or task_data.get('estStarted') is None):
|