fenix-mcp 1.8.0__tar.gz → 1.10.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.8.0 → fenix_mcp-1.10.0}/PKG-INFO +1 -1
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/__init__.py +1 -1
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/knowledge.py +69 -7
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/domain/knowledge.py +7 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/infrastructure/fenix_api/client.py +4 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/README.md +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/presenters.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tool_base.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tool_registry.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/__init__.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/health.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/initialize.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/intelligence.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/productivity.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/application/tools/user_config.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/domain/initialization.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/domain/intelligence.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/domain/productivity.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/domain/user_config.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/infrastructure/config.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/infrastructure/context.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/infrastructure/http_client.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/infrastructure/logging.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/interface/mcp_server.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/interface/transports.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp/main.py +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/SOURCES.txt +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/requires.txt +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/fenix_mcp.egg-info/top_level.txt +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/pyproject.toml +0 -0
- {fenix_mcp-1.8.0 → fenix_mcp-1.10.0}/setup.cfg +0 -0
|
@@ -78,6 +78,10 @@ class KnowledgeAction(str, Enum):
|
|
|
78
78
|
"work_assign_to_me",
|
|
79
79
|
"Assigns a work item to the current user.",
|
|
80
80
|
)
|
|
81
|
+
WORK_MINE = (
|
|
82
|
+
"work_mine",
|
|
83
|
+
"Lists work items assigned to the current user. Automatically excludes items with status 'done' or 'cancelled'. Supports pagination via limit and offset parameters.",
|
|
84
|
+
)
|
|
81
85
|
WORK_BULK_CREATE = (
|
|
82
86
|
"work_bulk_create",
|
|
83
87
|
"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'}]",
|
|
@@ -493,6 +497,16 @@ class KnowledgeTool(Tool):
|
|
|
493
497
|
work = await self._service.work_assign_to_me(work_id)
|
|
494
498
|
return text(_format_work(work, header="✅ Work item assigned to you"))
|
|
495
499
|
|
|
500
|
+
if action is KnowledgeAction.WORK_MINE:
|
|
501
|
+
items = await self._service.work_mine(
|
|
502
|
+
limit=payload.limit,
|
|
503
|
+
offset=payload.offset,
|
|
504
|
+
)
|
|
505
|
+
if not items:
|
|
506
|
+
return text("🎯 No work items assigned to you.")
|
|
507
|
+
body = "\n\n".join(_format_work(item) for item in items)
|
|
508
|
+
return text(f"🎯 **Your work items ({len(items)}):**\n\n{body}")
|
|
509
|
+
|
|
496
510
|
if action is KnowledgeAction.WORK_BACKLOG:
|
|
497
511
|
items = await self._service.work_backlog()
|
|
498
512
|
if not items:
|
|
@@ -622,18 +636,66 @@ class KnowledgeTool(Tool):
|
|
|
622
636
|
|
|
623
637
|
items = await self._service.work_bulk_create({"items": payload.work_items})
|
|
624
638
|
|
|
625
|
-
# Format response
|
|
626
|
-
type_counts: Dict[str, int] = {}
|
|
627
|
-
for item in items:
|
|
628
|
-
item_type = item.get("item_type", "unknown")
|
|
629
|
-
type_counts[item_type] = type_counts.get(item_type, 0) + 1
|
|
630
|
-
|
|
639
|
+
# Format response as hierarchical tree
|
|
631
640
|
lines = [f"✅ **{len(items)} work items created**", ""]
|
|
641
|
+
|
|
642
|
+
# Build tree structure
|
|
643
|
+
items_by_id: Dict[str, Dict[str, Any]] = {}
|
|
644
|
+
children_map: Dict[str, List[str]] = {}
|
|
645
|
+
root_ids: List[str] = []
|
|
646
|
+
|
|
632
647
|
for item in items:
|
|
648
|
+
item_id = item.get("id", "")
|
|
649
|
+
items_by_id[item_id] = item
|
|
650
|
+
parent_id = item.get("parent_id")
|
|
651
|
+
if parent_id and parent_id in items_by_id:
|
|
652
|
+
if parent_id not in children_map:
|
|
653
|
+
children_map[parent_id] = []
|
|
654
|
+
children_map[parent_id].append(item_id)
|
|
655
|
+
else:
|
|
656
|
+
root_ids.append(item_id)
|
|
657
|
+
|
|
658
|
+
def format_tree_node(
|
|
659
|
+
item_id: str, prefix: str = "", is_last: bool = True
|
|
660
|
+
) -> List[str]:
|
|
661
|
+
"""Format a node and its children as tree lines."""
|
|
662
|
+
node_lines: List[str] = []
|
|
663
|
+
item = items_by_id.get(item_id, {})
|
|
633
664
|
key = item.get("key", "")
|
|
634
665
|
title = item.get("title", "Untitled")
|
|
635
666
|
item_type = item.get("item_type", "unknown")
|
|
636
|
-
|
|
667
|
+
|
|
668
|
+
# Determine connector
|
|
669
|
+
if prefix == "":
|
|
670
|
+
connector = ""
|
|
671
|
+
child_prefix = ""
|
|
672
|
+
else:
|
|
673
|
+
connector = "└── " if is_last else "├── "
|
|
674
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
675
|
+
|
|
676
|
+
node_lines.append(f"{prefix}{connector}{item_type}: {key} - {title}")
|
|
677
|
+
|
|
678
|
+
# Process children
|
|
679
|
+
child_ids = children_map.get(item_id, [])
|
|
680
|
+
for i, child_id in enumerate(child_ids):
|
|
681
|
+
is_last_child = i == len(child_ids) - 1
|
|
682
|
+
node_lines.extend(
|
|
683
|
+
format_tree_node(child_id, child_prefix, is_last_child)
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
return node_lines
|
|
687
|
+
|
|
688
|
+
# Format root items and their children
|
|
689
|
+
for i, root_id in enumerate(root_ids):
|
|
690
|
+
lines.extend(format_tree_node(root_id))
|
|
691
|
+
if i < len(root_ids) - 1:
|
|
692
|
+
lines.append("") # Add blank line between root trees
|
|
693
|
+
|
|
694
|
+
# Summary by type
|
|
695
|
+
type_counts: Dict[str, int] = {}
|
|
696
|
+
for item in items:
|
|
697
|
+
item_type = item.get("item_type", "unknown")
|
|
698
|
+
type_counts[item_type] = type_counts.get(item_type, 0) + 1
|
|
637
699
|
|
|
638
700
|
lines.append("")
|
|
639
701
|
lines.append("**Summary by type:**")
|
|
@@ -78,6 +78,13 @@ class KnowledgeService:
|
|
|
78
78
|
async def work_assign_to_me(self, work_id: str) -> Dict[str, Any]:
|
|
79
79
|
return await self._call_dict(self.api.assign_work_item_to_me, work_id)
|
|
80
80
|
|
|
81
|
+
async def work_mine(
|
|
82
|
+
self, *, limit: int = 50, offset: int = 0
|
|
83
|
+
) -> List[Dict[str, Any]]:
|
|
84
|
+
return await self._call_list(
|
|
85
|
+
self.api.get_work_items_mine, limit=limit, offset=offset
|
|
86
|
+
)
|
|
87
|
+
|
|
81
88
|
async def work_update(
|
|
82
89
|
self, work_id: str, payload: Dict[str, Any]
|
|
83
90
|
) -> Dict[str, Any]:
|
|
@@ -498,6 +498,10 @@ class FenixApiClient:
|
|
|
498
498
|
def assign_work_item_to_me(self, item_id: str) -> Any:
|
|
499
499
|
return self._request("POST", f"/api/work-items/{item_id}/assign-to-me")
|
|
500
500
|
|
|
501
|
+
def get_work_items_mine(self, *, limit: int = 50, offset: int = 0) -> Any:
|
|
502
|
+
params = self._build_params(optional={"limit": limit, "offset": offset})
|
|
503
|
+
return self._request("GET", "/api/work-items/mine", params=params)
|
|
504
|
+
|
|
501
505
|
def update_work_item(self, item_id: str, payload: Mapping[str, Any]) -> Any:
|
|
502
506
|
return self._request("PATCH", f"/api/work-items/{item_id}", json=payload)
|
|
503
507
|
|
|
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
|