fenix-mcp 1.7.0__tar.gz → 1.9.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.
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/PKG-INFO +1 -1
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/__init__.py +1 -1
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/knowledge.py +64 -10
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/README.md +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/presenters.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tool_base.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tool_registry.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/__init__.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/health.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/initialize.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/intelligence.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/productivity.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/application/tools/user_config.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/domain/initialization.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/domain/intelligence.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/domain/knowledge.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/domain/productivity.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/domain/user_config.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/infrastructure/config.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/infrastructure/context.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/infrastructure/fenix_api/client.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/infrastructure/http_client.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/infrastructure/logging.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/interface/mcp_server.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/interface/transports.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp/main.py +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/SOURCES.txt +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/requires.txt +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/fenix_mcp.egg-info/top_level.txt +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/pyproject.toml +0 -0
- {fenix_mcp-1.7.0 → fenix_mcp-1.9.0}/setup.cfg +0 -0
|
@@ -80,7 +80,7 @@ class KnowledgeAction(str, Enum):
|
|
|
80
80
|
)
|
|
81
81
|
WORK_BULK_CREATE = (
|
|
82
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'}]",
|
|
83
|
+
"Creates multiple work items atomically with hierarchy. Use temp_id as temporary identifier and parent_temp_id to reference parent in the same batch, or parent_key to reference an existing work item (e.g., TEMA-0056). Cannot use both parent_temp_id and parent_key on the same item. 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'}] or [{temp_id:'task-1', parent_key:'TEMA-0056', title:'My Task', item_type:'task', work_category:'backend'}]",
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
# Boards
|
|
@@ -269,7 +269,9 @@ class KnowledgeRequest(ToolRequest):
|
|
|
269
269
|
description=(
|
|
270
270
|
"List of work items for bulk create (max 50). Each item requires: "
|
|
271
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'
|
|
272
|
+
"Use parent_temp_id to set parent from same batch (e.g., parent_temp_id:'epic-1'), "
|
|
273
|
+
"OR parent_key to set parent from existing work item (e.g., parent_key:'TEMA-0056'). "
|
|
274
|
+
"Cannot use both parent_temp_id and parent_key on the same item. "
|
|
273
275
|
"Optional: description, priority (low/medium/high/critical), story_points, tags, due_date. "
|
|
274
276
|
'Example: [{"temp_id":"e1", "title":"Epic", "item_type":"epic", "work_category":"backend"}, '
|
|
275
277
|
'{"temp_id":"t1", "parent_temp_id":"e1", "title":"Task", "item_type":"task", "work_category":"backend"}]'
|
|
@@ -598,7 +600,7 @@ class KnowledgeTool(Tool):
|
|
|
598
600
|
if not payload.work_items:
|
|
599
601
|
return text(
|
|
600
602
|
"❌ 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."
|
|
603
|
+
"Optional: parent_temp_id OR parent_key (not both), description, priority, story_points, tags, due_date."
|
|
602
604
|
)
|
|
603
605
|
if len(payload.work_items) > 50:
|
|
604
606
|
return text("❌ Maximum 50 items per bulk create.")
|
|
@@ -613,21 +615,73 @@ class KnowledgeTool(Tool):
|
|
|
613
615
|
return text(f"❌ Item {i}: missing item_type.")
|
|
614
616
|
if not item.get("work_category"):
|
|
615
617
|
return text(f"❌ Item {i}: missing work_category.")
|
|
618
|
+
if item.get("parent_temp_id") and item.get("parent_key"):
|
|
619
|
+
return text(
|
|
620
|
+
f"❌ Item {i}: cannot have both parent_temp_id and parent_key. Use one or the other."
|
|
621
|
+
)
|
|
616
622
|
|
|
617
623
|
items = await self._service.work_bulk_create({"items": payload.work_items})
|
|
618
624
|
|
|
619
|
-
# Format response
|
|
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
|
+
# Format response as hierarchical tree
|
|
625
626
|
lines = [f"✅ **{len(items)} work items created**", ""]
|
|
627
|
+
|
|
628
|
+
# Build tree structure
|
|
629
|
+
items_by_id: Dict[str, Dict[str, Any]] = {}
|
|
630
|
+
children_map: Dict[str, List[str]] = {}
|
|
631
|
+
root_ids: List[str] = []
|
|
632
|
+
|
|
626
633
|
for item in items:
|
|
634
|
+
item_id = item.get("id", "")
|
|
635
|
+
items_by_id[item_id] = item
|
|
636
|
+
parent_id = item.get("parent_id")
|
|
637
|
+
if parent_id and parent_id in items_by_id:
|
|
638
|
+
if parent_id not in children_map:
|
|
639
|
+
children_map[parent_id] = []
|
|
640
|
+
children_map[parent_id].append(item_id)
|
|
641
|
+
else:
|
|
642
|
+
root_ids.append(item_id)
|
|
643
|
+
|
|
644
|
+
def format_tree_node(
|
|
645
|
+
item_id: str, prefix: str = "", is_last: bool = True
|
|
646
|
+
) -> List[str]:
|
|
647
|
+
"""Format a node and its children as tree lines."""
|
|
648
|
+
node_lines: List[str] = []
|
|
649
|
+
item = items_by_id.get(item_id, {})
|
|
627
650
|
key = item.get("key", "")
|
|
628
651
|
title = item.get("title", "Untitled")
|
|
629
652
|
item_type = item.get("item_type", "unknown")
|
|
630
|
-
|
|
653
|
+
|
|
654
|
+
# Determine connector
|
|
655
|
+
if prefix == "":
|
|
656
|
+
connector = ""
|
|
657
|
+
child_prefix = ""
|
|
658
|
+
else:
|
|
659
|
+
connector = "└── " if is_last else "├── "
|
|
660
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
661
|
+
|
|
662
|
+
node_lines.append(f"{prefix}{connector}{item_type}: {key} - {title}")
|
|
663
|
+
|
|
664
|
+
# Process children
|
|
665
|
+
child_ids = children_map.get(item_id, [])
|
|
666
|
+
for i, child_id in enumerate(child_ids):
|
|
667
|
+
is_last_child = i == len(child_ids) - 1
|
|
668
|
+
node_lines.extend(
|
|
669
|
+
format_tree_node(child_id, child_prefix, is_last_child)
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
return node_lines
|
|
673
|
+
|
|
674
|
+
# Format root items and their children
|
|
675
|
+
for i, root_id in enumerate(root_ids):
|
|
676
|
+
lines.extend(format_tree_node(root_id))
|
|
677
|
+
if i < len(root_ids) - 1:
|
|
678
|
+
lines.append("") # Add blank line between root trees
|
|
679
|
+
|
|
680
|
+
# Summary by type
|
|
681
|
+
type_counts: Dict[str, int] = {}
|
|
682
|
+
for item in items:
|
|
683
|
+
item_type = item.get("item_type", "unknown")
|
|
684
|
+
type_counts[item_type] = type_counts.get(item_type, 0) + 1
|
|
631
685
|
|
|
632
686
|
lines.append("")
|
|
633
687
|
lines.append("**Summary by type:**")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|