fenix-mcp 1.5.0__tar.gz → 1.7.0__tar.gz

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.
Files changed (34) hide show
  1. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/PKG-INFO +1 -1
  2. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/__init__.py +1 -1
  3. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/knowledge.py +159 -43
  4. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/knowledge.py +11 -0
  5. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/fenix_api/client.py +9 -0
  6. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
  7. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/README.md +0 -0
  8. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/presenters.py +0 -0
  9. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tool_base.py +0 -0
  10. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tool_registry.py +0 -0
  11. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/__init__.py +0 -0
  12. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/health.py +0 -0
  13. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/initialize.py +0 -0
  14. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/intelligence.py +0 -0
  15. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/productivity.py +0 -0
  16. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/user_config.py +0 -0
  17. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/initialization.py +0 -0
  18. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/intelligence.py +0 -0
  19. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/productivity.py +0 -0
  20. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/user_config.py +0 -0
  21. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/config.py +0 -0
  22. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/context.py +0 -0
  23. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/http_client.py +0 -0
  24. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/logging.py +0 -0
  25. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/interface/mcp_server.py +0 -0
  26. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/interface/transports.py +0 -0
  27. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/main.py +0 -0
  28. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/SOURCES.txt +0 -0
  29. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
  30. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
  31. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/requires.txt +0 -0
  32. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/top_level.txt +0 -0
  33. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/pyproject.toml +0 -0
  34. {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 1.5.0
3
+ Version: 1.7.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
@@ -8,4 +8,4 @@ Fênix Cloud MCP Server (Python edition).
8
8
  __all__ = ["__version__"]
9
9
 
10
10
 
11
- __version__ = "1.5.0"
11
+ __version__ = "1.7.0"
@@ -39,18 +39,20 @@ class KnowledgeAction(str, Enum):
39
39
  # Work items
40
40
  WORK_CREATE = (
41
41
  "work_create",
42
- "Creates a work item with title, status and optional links.",
42
+ "Creates a work item with title, status and optional links. Use parent_key (e.g., DVPT-0001) to set parent.",
43
43
  )
44
44
  WORK_LIST = (
45
45
  "work_list",
46
46
  "Lists work items with status, priority and context filters.",
47
47
  )
48
- WORK_GET = ("work_get", "Gets full details of a work item by ID.")
48
+ WORK_GET = (
49
+ "work_get",
50
+ "Gets full details of a work item by ID or key (e.g., DVPT-0001).",
51
+ )
49
52
  WORK_UPDATE = (
50
53
  "work_update",
51
- "Updates specific fields of an existing work item.",
54
+ "Updates allowed fields of an existing work item (title, description, priority, story_points, tags, due_date).",
52
55
  )
53
- WORK_DELETE = ("work_delete", "Permanently removes a work item.")
54
56
  WORK_BACKLOG = ("work_backlog", "Lists backlog items for a team.")
55
57
  WORK_SEARCH = (
56
58
  "work_search",
@@ -60,7 +62,10 @@ class KnowledgeAction(str, Enum):
60
62
  WORK_BY_BOARD = ("work_by_board", "Lists work items associated with a board.")
61
63
  WORK_BY_SPRINT = ("work_by_sprint", "Lists work items associated with a sprint.")
62
64
  WORK_BY_EPIC = ("work_by_epic", "Lists work items associated with an epic.")
63
- WORK_CHILDREN = ("work_children", "Lists child work items of a parent item.")
65
+ WORK_CHILDREN = (
66
+ "work_children",
67
+ "Lists child work items of a parent item by ID or key (e.g., DVPT-0001).",
68
+ )
64
69
  WORK_STATUS_UPDATE = (
65
70
  "work_status_update",
66
71
  "Updates only the status of a work item.",
@@ -69,6 +74,14 @@ class KnowledgeAction(str, Enum):
69
74
  "work_assign_sprint",
70
75
  "Assigns work items to a sprint.",
71
76
  )
77
+ WORK_ASSIGN_TO_ME = (
78
+ "work_assign_to_me",
79
+ "Assigns a work item to the current user.",
80
+ )
81
+ WORK_BULK_CREATE = (
82
+ "work_bulk_create",
83
+ "Creates multiple work items atomically with hierarchy. Use temp_id as temporary identifier and parent_temp_id to reference parent. Example: [{temp_id:'epic-1', title:'My Epic', item_type:'epic', work_category:'backend'}, {temp_id:'task-1', parent_temp_id:'epic-1', title:'My Task', item_type:'task', work_category:'backend'}]",
84
+ )
72
85
 
73
86
  # Boards
74
87
  BOARD_LIST = ("board_list", "Lists available boards with optional filters.")
@@ -203,6 +216,10 @@ class KnowledgeRequest(ToolRequest):
203
216
  )
204
217
 
205
218
  # Work item fields
219
+ work_key: Optional[str] = Field(
220
+ default=None,
221
+ description="Work item key (e.g., DVPT-0001). Use this instead of id for work_get and work_children.",
222
+ )
206
223
  work_title: Optional[TitleStr] = Field(default=None, description="Work item title.")
207
224
  work_description: Optional[MarkdownStr] = Field(
208
225
  default=None, description="Work item description (Markdown)."
@@ -213,7 +230,7 @@ class KnowledgeRequest(ToolRequest):
213
230
  )
214
231
  work_status: Optional[str] = Field(
215
232
  default=None,
216
- description="Work item status (backlog, todo, in_progress, review, testing, done, cancelled).",
233
+ description="Work item status ID (UUID of TeamStatus).",
217
234
  )
218
235
  work_priority: Optional[str] = Field(
219
236
  default=None,
@@ -230,7 +247,12 @@ class KnowledgeRequest(ToolRequest):
230
247
  default=None, description="Assignee ID (UUID)."
231
248
  )
232
249
  parent_id: Optional[UUIDStr] = Field(
233
- default=None, description="Parent item ID (UUID)."
250
+ default=None,
251
+ description="Parent item ID (UUID). Prefer using parent_key instead.",
252
+ )
253
+ parent_key: Optional[str] = Field(
254
+ default=None,
255
+ description="Parent item key (e.g., DVPT-0001). Use this to set the parent of a work item.",
234
256
  )
235
257
  work_due_date: Optional[DateTimeStr] = Field(
236
258
  default=None, description="Work item due date (ISO 8601)."
@@ -242,6 +264,17 @@ class KnowledgeRequest(ToolRequest):
242
264
  default=None,
243
265
  description="List of work item IDs for batch operations (UUIDs).",
244
266
  )
267
+ work_items: Optional[List[Dict[str, Any]]] = Field(
268
+ default=None,
269
+ description=(
270
+ "List of work items for bulk create (max 50). Each item requires: "
271
+ "temp_id (your temporary ID like 'epic-1', 'task-a'), title, item_type, work_category. "
272
+ "Use parent_temp_id to set parent (e.g., parent_temp_id:'epic-1' makes it child of epic-1). "
273
+ "Optional: description, priority (low/medium/high/critical), story_points, tags, due_date. "
274
+ 'Example: [{"temp_id":"e1", "title":"Epic", "item_type":"epic", "work_category":"backend"}, '
275
+ '{"temp_id":"t1", "parent_temp_id":"e1", "title":"Task", "item_type":"task", "work_category":"backend"}]'
276
+ ),
277
+ )
245
278
 
246
279
  # Mode fields
247
280
  mode_id: Optional[UUIDStr] = Field(
@@ -372,19 +405,25 @@ class KnowledgeTool(Tool):
372
405
  return text(
373
406
  "❌ Provide work_category to create the item. Values: backend, frontend, mobile, fullstack, devops, infra, platform, sre, database, security, data, analytics, ai_ml, qa, automation, design, research, product, project, agile, support, operations, documentation, training, architecture, planning, development."
374
407
  )
408
+
409
+ # Resolve parent_key to parent_id if provided
410
+ parent_id = payload.parent_id
411
+ if payload.parent_key and not parent_id:
412
+ parent_work = await self._service.work_get_by_key(payload.parent_key)
413
+ parent_id = parent_work.get("id")
414
+
375
415
  work = await self._service.work_create(
376
416
  {
377
417
  "title": payload.work_title,
378
418
  "description": payload.work_description,
379
419
  "item_type": payload.work_type,
380
- "status": payload.work_status,
420
+ "status_id": payload.work_status,
381
421
  "priority": payload.work_priority,
382
422
  "work_category": payload.work_category,
383
423
  "story_points": payload.story_points,
384
424
  "assignee_id": payload.assignee_id,
385
425
  "sprint_id": payload.sprint_id,
386
- "board_id": payload.board_id,
387
- "parent_id": payload.parent_id,
426
+ "parent_id": parent_id,
388
427
  "due_date": payload.work_due_date,
389
428
  "tags": payload.work_tags,
390
429
  }
@@ -399,7 +438,6 @@ class KnowledgeTool(Tool):
399
438
  type=payload.work_type,
400
439
  assignee=payload.assignee_id,
401
440
  sprint=payload.sprint_id,
402
- board=payload.board_id,
403
441
  )
404
442
  if not items:
405
443
  return text("🎯 No work items found.")
@@ -407,41 +445,51 @@ class KnowledgeTool(Tool):
407
445
  return text(f"🎯 **Work items ({len(items)}):**\n\n{body}")
408
446
 
409
447
  if action is KnowledgeAction.WORK_GET:
410
- if not payload.id:
411
- return text("❌ Provide the work item ID.")
412
- work = await self._service.work_get(payload.id)
448
+ # Support both id and work_key
449
+ if payload.work_key:
450
+ work = await self._service.work_get_by_key(payload.work_key)
451
+ elif payload.id:
452
+ work = await self._service.work_get(payload.id)
453
+ else:
454
+ return text("❌ Provide id or work_key to get the work item.")
413
455
  return text(
414
456
  _format_work(work, header="🎯 Work item details", show_description=True)
415
457
  )
416
458
 
417
459
  if action is KnowledgeAction.WORK_UPDATE:
418
- if not payload.id:
419
- return text("❌ Provide the work item ID.")
460
+ # Resolve work_key to id if needed
461
+ work_id = payload.id
462
+ if payload.work_key and not work_id:
463
+ work_item = await self._service.work_get_by_key(payload.work_key)
464
+ work_id = work_item.get("id")
465
+ if not work_id:
466
+ return text("❌ Provide id or work_key to update the work item.")
467
+
468
+ # Only allow safe fields to be updated via MCP
469
+ # Excluded: item_type, status_id, assignee_id, sprint_id, parent_id, work_category
420
470
  work = await self._service.work_update(
421
- payload.id,
471
+ work_id,
422
472
  {
423
473
  "title": payload.work_title,
424
474
  "description": payload.work_description,
425
- "item_type": payload.work_type,
426
- "status": payload.work_status,
427
475
  "priority": payload.work_priority,
428
- "work_category": payload.work_category,
429
476
  "story_points": payload.story_points,
430
- "assignee_id": payload.assignee_id,
431
- "sprint_id": payload.sprint_id,
432
- "board_id": payload.board_id,
433
- "parent_id": payload.parent_id,
434
477
  "due_date": payload.work_due_date,
435
478
  "tags": payload.work_tags,
436
479
  },
437
480
  )
438
481
  return text(_format_work(work, header="✅ Work item updated"))
439
482
 
440
- if action is KnowledgeAction.WORK_DELETE:
441
- if not payload.id:
442
- return text("❌ Provide the work item ID.")
443
- await self._service.work_delete(payload.id)
444
- return text(f"🗑️ Work item {payload.id} removed.")
483
+ if action is KnowledgeAction.WORK_ASSIGN_TO_ME:
484
+ # Resolve work_key to id if needed
485
+ work_id = payload.id
486
+ if payload.work_key and not work_id:
487
+ work_item = await self._service.work_get_by_key(payload.work_key)
488
+ work_id = work_item.get("id")
489
+ if not work_id:
490
+ return text("❌ Provide id or work_key to assign the work item.")
491
+ work = await self._service.work_assign_to_me(work_id)
492
+ return text(_format_work(work, header="✅ Work item assigned to you"))
445
493
 
446
494
  if action is KnowledgeAction.WORK_BACKLOG:
447
495
  items = await self._service.work_backlog()
@@ -503,22 +551,32 @@ class KnowledgeTool(Tool):
503
551
  return text(f"📦 **Epic work items ({len(items)}):**\n\n{body}")
504
552
 
505
553
  if action is KnowledgeAction.WORK_CHILDREN:
506
- if not payload.id:
507
- return text("❌ Provide the parent work item ID.")
508
- items = await self._service.work_children(payload.id)
554
+ # Support both id and work_key
555
+ work_id = payload.id
556
+ if payload.work_key and not work_id:
557
+ work_item = await self._service.work_get_by_key(payload.work_key)
558
+ work_id = work_item.get("id")
559
+ if not work_id:
560
+ return text("❌ Provide id or work_key to list children.")
561
+ items = await self._service.work_children(work_id)
509
562
  if not items:
510
563
  return text("👶 No child items found.")
511
564
  body = "\n\n".join(_format_work(item) for item in items)
512
565
  return text(f"👶 **Child work items ({len(items)}):**\n\n{body}")
513
566
 
514
567
  if action is KnowledgeAction.WORK_STATUS_UPDATE:
515
- if not payload.id:
516
- return text("❌ Provide the work item ID.")
568
+ # Resolve work_key to id if needed
569
+ work_id = payload.id
570
+ if payload.work_key and not work_id:
571
+ work_item = await self._service.work_get_by_key(payload.work_key)
572
+ work_id = work_item.get("id")
573
+ if not work_id:
574
+ return text("❌ Provide id or work_key to update status.")
517
575
  if not payload.work_status:
518
- return text("❌ Provide work_status to update.")
576
+ return text("❌ Provide work_status (status_id) to update.")
519
577
  work = await self._service.work_update_status(
520
- payload.id,
521
- {"status": payload.work_status},
578
+ work_id,
579
+ {"status_id": payload.work_status},
522
580
  )
523
581
  return text(_format_work(work, header="✅ Status updated"))
524
582
 
@@ -536,6 +594,48 @@ class KnowledgeTool(Tool):
536
594
  count = len(payload.work_item_ids)
537
595
  return text(f"✅ {count} work item(s) assigned to sprint.")
538
596
 
597
+ if action is KnowledgeAction.WORK_BULK_CREATE:
598
+ if not payload.work_items:
599
+ return text(
600
+ "❌ Provide work_items array. Each item requires: temp_id, title, item_type, work_category. "
601
+ "Optional: parent_temp_id, description, priority, story_points, tags, due_date."
602
+ )
603
+ if len(payload.work_items) > 50:
604
+ return text("❌ Maximum 50 items per bulk create.")
605
+
606
+ # Validate required fields
607
+ for i, item in enumerate(payload.work_items):
608
+ if not item.get("temp_id"):
609
+ return text(f"❌ Item {i}: missing temp_id.")
610
+ if not item.get("title"):
611
+ return text(f"❌ Item {i}: missing title.")
612
+ if not item.get("item_type"):
613
+ return text(f"❌ Item {i}: missing item_type.")
614
+ if not item.get("work_category"):
615
+ return text(f"❌ Item {i}: missing work_category.")
616
+
617
+ items = await self._service.work_bulk_create({"items": payload.work_items})
618
+
619
+ # Format response with summary
620
+ type_counts: Dict[str, int] = {}
621
+ for item in items:
622
+ item_type = item.get("item_type", "unknown")
623
+ type_counts[item_type] = type_counts.get(item_type, 0) + 1
624
+
625
+ lines = [f"✅ **{len(items)} work items created**", ""]
626
+ for item in items:
627
+ key = item.get("key", "")
628
+ title = item.get("title", "Untitled")
629
+ item_type = item.get("item_type", "unknown")
630
+ lines.append(f"- [{key}] {title} ({item_type})")
631
+
632
+ lines.append("")
633
+ lines.append("**Summary by type:**")
634
+ for item_type, count in sorted(type_counts.items()):
635
+ lines.append(f"- {item_type}: {count}")
636
+
637
+ return text("\n".join(lines))
638
+
539
639
  return text(
540
640
  "❌ Unsupported work item action.\n\nChoose one of the values:\n"
541
641
  + "\n".join(
@@ -1084,14 +1184,21 @@ def _format_work(
1084
1184
  lines.append(header)
1085
1185
  lines.append("")
1086
1186
 
1187
+ # Extract key (e.g., DVPT-0001)
1188
+ key = item.get("key", "")
1189
+
1087
1190
  # Extract title
1088
1191
  title = item.get("title") or item.get("name") or "Untitled"
1089
1192
 
1090
1193
  # Extract type
1091
1194
  item_type = item.get("item_type") or item.get("type") or "unknown"
1092
1195
 
1093
- # Extract status
1094
- status = item.get("status") or item.get("state") or "unknown"
1196
+ # Extract status - prefer status object with name, fallback to status_id
1197
+ status_obj = item.get("status")
1198
+ if isinstance(status_obj, dict):
1199
+ status = status_obj.get("name", "unknown")
1200
+ else:
1201
+ status = item.get("status_id") or status_obj or "unknown"
1095
1202
 
1096
1203
  # Extract priority
1097
1204
  priority = item.get("priority") or item.get("priority_level") or "undefined"
@@ -1102,15 +1209,19 @@ def _format_work(
1102
1209
  # Extract assignee - check both assignee_id and assignee object
1103
1210
  assignee = item.get("assignee_id")
1104
1211
  if not assignee and item.get("assignee"):
1105
- assignee = item.get("assignee", {}).get("name") or item.get("assignee", {}).get(
1106
- "id"
1107
- )
1212
+ assignee_obj = item.get("assignee", {})
1213
+ assignee = assignee_obj.get("name") or assignee_obj.get("id")
1108
1214
  if not assignee:
1109
1215
  assignee = "N/A"
1110
1216
 
1217
+ # Format title line with key if available
1218
+ if key:
1219
+ lines.append(f"🎯 **[{key}] {title}**")
1220
+ else:
1221
+ lines.append(f"🎯 **{title}**")
1222
+
1111
1223
  lines.extend(
1112
1224
  [
1113
- f"🎯 **{title}**",
1114
1225
  f"ID: {item_id}",
1115
1226
  f"Type: {item_type}",
1116
1227
  f"Status: {status}",
@@ -1118,6 +1229,11 @@ def _format_work(
1118
1229
  f"Assignee: {assignee}",
1119
1230
  ]
1120
1231
  )
1232
+
1233
+ # Add key as separate line if present (for easy reference)
1234
+ if key:
1235
+ lines.append(f"Key: {key}")
1236
+
1121
1237
  if item.get("due_date") or item.get("dueDate"):
1122
1238
  lines.append(
1123
1239
  f"Due date: {_format_date(item.get('due_date') or item.get('dueDate'))}"
@@ -72,6 +72,12 @@ class KnowledgeService:
72
72
  async def work_get(self, work_id: str) -> Dict[str, Any]:
73
73
  return await self._call_dict(self.api.get_work_item, work_id)
74
74
 
75
+ async def work_get_by_key(self, key: str) -> Dict[str, Any]:
76
+ return await self._call_dict(self.api.get_work_item_by_key, key)
77
+
78
+ async def work_assign_to_me(self, work_id: str) -> Dict[str, Any]:
79
+ return await self._call_dict(self.api.assign_work_item_to_me, work_id)
80
+
75
81
  async def work_update(
76
82
  self, work_id: str, payload: Dict[str, Any]
77
83
  ) -> Dict[str, Any]:
@@ -175,6 +181,11 @@ class KnowledgeService:
175
181
  async def work_bulk_update(self, payload: Dict[str, Any]) -> Dict[str, Any]:
176
182
  return await self._call(self.api.bulk_update_work_items, _strip_none(payload))
177
183
 
184
+ async def work_bulk_create(self, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
185
+ return await self._call_list(
186
+ self.api.bulk_create_work_items, _strip_none(payload)
187
+ )
188
+
178
189
  # ------------------------------------------------------------------
179
190
  # Work boards
180
191
  # ------------------------------------------------------------------
@@ -492,6 +492,12 @@ class FenixApiClient:
492
492
  def get_work_item(self, item_id: str) -> Any:
493
493
  return self._request("GET", f"/api/work-items/{item_id}")
494
494
 
495
+ def get_work_item_by_key(self, key: str) -> Any:
496
+ return self._request("GET", f"/api/work-items/by-key/{key}")
497
+
498
+ def assign_work_item_to_me(self, item_id: str) -> Any:
499
+ return self._request("POST", f"/api/work-items/{item_id}/assign-to-me")
500
+
495
501
  def update_work_item(self, item_id: str, payload: Mapping[str, Any]) -> Any:
496
502
  return self._request("PATCH", f"/api/work-items/{item_id}", json=payload)
497
503
 
@@ -567,6 +573,9 @@ class FenixApiClient:
567
573
  def bulk_update_work_items(self, payload: Mapping[str, Any]) -> Any:
568
574
  return self._request("PATCH", "/api/work-items/bulk-update", json=payload)
569
575
 
576
+ def bulk_create_work_items(self, payload: Mapping[str, Any]) -> Any:
577
+ return self._request("POST", "/api/work-items/bulk-create", json=payload)
578
+
570
579
  # ------------------------------------------------------------------
571
580
  # Knowledge: boards
572
581
  # ------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 1.5.0
3
+ Version: 1.7.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
File without changes
File without changes
File without changes
File without changes