mcp-ticketer 0.1.26__py3-none-any.whl → 0.1.27__py3-none-any.whl

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.

Potentially problematic release.


This version of mcp-ticketer might be problematic. Click here for more details.

@@ -11,6 +11,7 @@ from dotenv import load_dotenv
11
11
  from ..core import AdapterRegistry
12
12
  from ..core.models import SearchQuery
13
13
  from ..queue import Queue, QueueStatus, WorkerManager
14
+ from ..queue.health_monitor import QueueHealthMonitor, HealthStatus
14
15
 
15
16
  # Import adapters module to trigger registration
16
17
  import mcp_ticketer.adapters # noqa: F401
@@ -92,6 +93,36 @@ class MCPTicketServer:
92
93
  result = await self._handle_create_pr(params)
93
94
  elif method == "ticket/link_pr":
94
95
  result = await self._handle_link_pr(params)
96
+ elif method == "queue/health":
97
+ result = await self._handle_queue_health(params)
98
+ # Hierarchy management tools
99
+ elif method == "epic/create":
100
+ result = await self._handle_epic_create(params)
101
+ elif method == "epic/list":
102
+ result = await self._handle_epic_list(params)
103
+ elif method == "epic/issues":
104
+ result = await self._handle_epic_issues(params)
105
+ elif method == "issue/create":
106
+ result = await self._handle_issue_create(params)
107
+ elif method == "issue/tasks":
108
+ result = await self._handle_issue_tasks(params)
109
+ elif method == "task/create":
110
+ result = await self._handle_task_create(params)
111
+ elif method == "hierarchy/tree":
112
+ result = await self._handle_hierarchy_tree(params)
113
+ # Bulk operations
114
+ elif method == "ticket/bulk_create":
115
+ result = await self._handle_bulk_create(params)
116
+ elif method == "ticket/bulk_update":
117
+ result = await self._handle_bulk_update(params)
118
+ # Advanced search
119
+ elif method == "ticket/search_hierarchy":
120
+ result = await self._handle_search_hierarchy(params)
121
+ # Attachment handling
122
+ elif method == "ticket/attach":
123
+ result = await self._handle_attach(params)
124
+ elif method == "ticket/attachments":
125
+ result = await self._handle_list_attachments(params)
95
126
  elif method == "tools/list":
96
127
  result = await self._handle_tools_list()
97
128
  elif method == "tools/call":
@@ -128,7 +159,30 @@ class MCPTicketServer:
128
159
 
129
160
  async def _handle_create(self, params: dict[str, Any]) -> dict[str, Any]:
130
161
  """Handle ticket creation."""
131
- # Queue the operation instead of direct execution
162
+ # Check queue health before proceeding
163
+ health_monitor = QueueHealthMonitor()
164
+ health = health_monitor.check_health()
165
+
166
+ # If queue is in critical state, try auto-repair
167
+ if health["status"] == HealthStatus.CRITICAL:
168
+ repair_result = health_monitor.auto_repair()
169
+ # Re-check health after repair
170
+ health = health_monitor.check_health()
171
+
172
+ # If still critical, return error immediately
173
+ if health["status"] == HealthStatus.CRITICAL:
174
+ critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
175
+ return {
176
+ "status": "error",
177
+ "error": "Queue system is in critical state",
178
+ "details": {
179
+ "health_status": health["status"],
180
+ "critical_issues": critical_alerts,
181
+ "repair_attempted": repair_result["actions_taken"]
182
+ }
183
+ }
184
+
185
+ # Queue the operation
132
186
  queue = Queue()
133
187
  task_data = {
134
188
  "title": params["title"],
@@ -146,7 +200,19 @@ class MCPTicketServer:
146
200
 
147
201
  # Start worker if needed
148
202
  manager = WorkerManager()
149
- manager.start_if_needed()
203
+ worker_started = manager.start_if_needed()
204
+
205
+ # If worker failed to start and we have pending items, that's critical
206
+ if not worker_started and queue.get_pending_count() > 0:
207
+ return {
208
+ "status": "error",
209
+ "error": "Failed to start worker process",
210
+ "queue_id": queue_id,
211
+ "details": {
212
+ "pending_count": queue.get_pending_count(),
213
+ "action": "Worker process could not be started to process queued operations"
214
+ }
215
+ }
150
216
 
151
217
  # Check if async mode is requested (for backward compatibility)
152
218
  if params.get("async_mode", False):
@@ -478,6 +544,452 @@ class MCPTicketServer:
478
544
 
479
545
  return response
480
546
 
547
+ async def _handle_queue_health(self, params: dict[str, Any]) -> dict[str, Any]:
548
+ """Handle queue health check."""
549
+ health_monitor = QueueHealthMonitor()
550
+ health = health_monitor.check_health()
551
+
552
+ # Add auto-repair option
553
+ auto_repair = params.get("auto_repair", False)
554
+ if auto_repair and health["status"] in [HealthStatus.CRITICAL, HealthStatus.WARNING]:
555
+ repair_result = health_monitor.auto_repair()
556
+ health["auto_repair"] = repair_result
557
+ # Re-check health after repair
558
+ health.update(health_monitor.check_health())
559
+
560
+ return health
561
+
562
+ # Hierarchy Management Handlers
563
+
564
+ async def _handle_epic_create(self, params: dict[str, Any]) -> dict[str, Any]:
565
+ """Handle epic creation."""
566
+ # Check queue health before proceeding
567
+ health_monitor = QueueHealthMonitor()
568
+ health = health_monitor.check_health()
569
+
570
+ if health["status"] == HealthStatus.CRITICAL:
571
+ repair_result = health_monitor.auto_repair()
572
+ health = health_monitor.check_health()
573
+
574
+ if health["status"] == HealthStatus.CRITICAL:
575
+ critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
576
+ return {
577
+ "status": "error",
578
+ "error": "Queue system is in critical state",
579
+ "details": {
580
+ "health_status": health["status"],
581
+ "critical_issues": critical_alerts,
582
+ "repair_attempted": repair_result["actions_taken"]
583
+ }
584
+ }
585
+
586
+ # Queue the epic creation
587
+ queue = Queue()
588
+ epic_data = {
589
+ "title": params["title"],
590
+ "description": params.get("description"),
591
+ "child_issues": params.get("child_issues", []),
592
+ "target_date": params.get("target_date"),
593
+ "lead_id": params.get("lead_id"),
594
+ }
595
+
596
+ queue_id = queue.add(
597
+ ticket_data=epic_data,
598
+ adapter=self.adapter.__class__.__name__.lower().replace("adapter", ""),
599
+ operation="create_epic",
600
+ )
601
+
602
+ # Start worker if needed
603
+ manager = WorkerManager()
604
+ worker_started = manager.start_if_needed()
605
+
606
+ if not worker_started and queue.get_pending_count() > 0:
607
+ return {
608
+ "status": "error",
609
+ "error": "Failed to start worker process",
610
+ "queue_id": queue_id,
611
+ "details": {
612
+ "pending_count": queue.get_pending_count(),
613
+ "action": "Worker process could not be started to process queued operations"
614
+ }
615
+ }
616
+
617
+ return {
618
+ "queue_id": queue_id,
619
+ "status": "queued",
620
+ "message": f"Epic creation queued with ID: {queue_id}",
621
+ "epic_data": epic_data
622
+ }
623
+
624
+ async def _handle_epic_list(self, params: dict[str, Any]) -> list[dict[str, Any]]:
625
+ """Handle epic listing."""
626
+ epics = await self.adapter.list_epics(
627
+ limit=params.get("limit", 10),
628
+ offset=params.get("offset", 0),
629
+ **{k: v for k, v in params.items() if k not in ["limit", "offset"]}
630
+ )
631
+ return [epic.model_dump() for epic in epics]
632
+
633
+ async def _handle_epic_issues(self, params: dict[str, Any]) -> list[dict[str, Any]]:
634
+ """Handle listing issues in an epic."""
635
+ epic_id = params["epic_id"]
636
+ issues = await self.adapter.list_issues_by_epic(epic_id)
637
+ return [issue.model_dump() for issue in issues]
638
+
639
+ async def _handle_issue_create(self, params: dict[str, Any]) -> dict[str, Any]:
640
+ """Handle issue creation."""
641
+ # Check queue health
642
+ health_monitor = QueueHealthMonitor()
643
+ health = health_monitor.check_health()
644
+
645
+ if health["status"] == HealthStatus.CRITICAL:
646
+ repair_result = health_monitor.auto_repair()
647
+ health = health_monitor.check_health()
648
+
649
+ if health["status"] == HealthStatus.CRITICAL:
650
+ critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
651
+ return {
652
+ "status": "error",
653
+ "error": "Queue system is in critical state",
654
+ "details": {
655
+ "health_status": health["status"],
656
+ "critical_issues": critical_alerts,
657
+ "repair_attempted": repair_result["actions_taken"]
658
+ }
659
+ }
660
+
661
+ # Queue the issue creation
662
+ queue = Queue()
663
+ issue_data = {
664
+ "title": params["title"],
665
+ "description": params.get("description"),
666
+ "epic_id": params.get("epic_id"),
667
+ "priority": params.get("priority", "medium"),
668
+ "assignee": params.get("assignee"),
669
+ "tags": params.get("tags", []),
670
+ "estimated_hours": params.get("estimated_hours"),
671
+ }
672
+
673
+ queue_id = queue.add(
674
+ ticket_data=issue_data,
675
+ adapter=self.adapter.__class__.__name__.lower().replace("adapter", ""),
676
+ operation="create_issue",
677
+ )
678
+
679
+ # Start worker if needed
680
+ manager = WorkerManager()
681
+ worker_started = manager.start_if_needed()
682
+
683
+ if not worker_started and queue.get_pending_count() > 0:
684
+ return {
685
+ "status": "error",
686
+ "error": "Failed to start worker process",
687
+ "queue_id": queue_id,
688
+ "details": {
689
+ "pending_count": queue.get_pending_count(),
690
+ "action": "Worker process could not be started to process queued operations"
691
+ }
692
+ }
693
+
694
+ return {
695
+ "queue_id": queue_id,
696
+ "status": "queued",
697
+ "message": f"Issue creation queued with ID: {queue_id}",
698
+ "issue_data": issue_data
699
+ }
700
+
701
+ async def _handle_issue_tasks(self, params: dict[str, Any]) -> list[dict[str, Any]]:
702
+ """Handle listing tasks in an issue."""
703
+ issue_id = params["issue_id"]
704
+ tasks = await self.adapter.list_tasks_by_issue(issue_id)
705
+ return [task.model_dump() for task in tasks]
706
+
707
+ async def _handle_task_create(self, params: dict[str, Any]) -> dict[str, Any]:
708
+ """Handle task creation."""
709
+ # Check queue health
710
+ health_monitor = QueueHealthMonitor()
711
+ health = health_monitor.check_health()
712
+
713
+ if health["status"] == HealthStatus.CRITICAL:
714
+ repair_result = health_monitor.auto_repair()
715
+ health = health_monitor.check_health()
716
+
717
+ if health["status"] == HealthStatus.CRITICAL:
718
+ critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
719
+ return {
720
+ "status": "error",
721
+ "error": "Queue system is in critical state",
722
+ "details": {
723
+ "health_status": health["status"],
724
+ "critical_issues": critical_alerts,
725
+ "repair_attempted": repair_result["actions_taken"]
726
+ }
727
+ }
728
+
729
+ # Validate required parent_id
730
+ if not params.get("parent_id"):
731
+ return {
732
+ "status": "error",
733
+ "error": "Tasks must have a parent_id (issue identifier)",
734
+ "details": {"required_field": "parent_id"}
735
+ }
736
+
737
+ # Queue the task creation
738
+ queue = Queue()
739
+ task_data = {
740
+ "title": params["title"],
741
+ "parent_id": params["parent_id"],
742
+ "description": params.get("description"),
743
+ "priority": params.get("priority", "medium"),
744
+ "assignee": params.get("assignee"),
745
+ "tags": params.get("tags", []),
746
+ "estimated_hours": params.get("estimated_hours"),
747
+ }
748
+
749
+ queue_id = queue.add(
750
+ ticket_data=task_data,
751
+ adapter=self.adapter.__class__.__name__.lower().replace("adapter", ""),
752
+ operation="create_task",
753
+ )
754
+
755
+ # Start worker if needed
756
+ manager = WorkerManager()
757
+ worker_started = manager.start_if_needed()
758
+
759
+ if not worker_started and queue.get_pending_count() > 0:
760
+ return {
761
+ "status": "error",
762
+ "error": "Failed to start worker process",
763
+ "queue_id": queue_id,
764
+ "details": {
765
+ "pending_count": queue.get_pending_count(),
766
+ "action": "Worker process could not be started to process queued operations"
767
+ }
768
+ }
769
+
770
+ return {
771
+ "queue_id": queue_id,
772
+ "status": "queued",
773
+ "message": f"Task creation queued with ID: {queue_id}",
774
+ "task_data": task_data
775
+ }
776
+
777
+ async def _handle_hierarchy_tree(self, params: dict[str, Any]) -> dict[str, Any]:
778
+ """Handle hierarchy tree visualization."""
779
+ epic_id = params.get("epic_id")
780
+ max_depth = params.get("max_depth", 3)
781
+
782
+ if epic_id:
783
+ # Get specific epic tree
784
+ epic = await self.adapter.get_epic(epic_id)
785
+ if not epic:
786
+ return {"error": f"Epic {epic_id} not found"}
787
+
788
+ # Build tree structure
789
+ tree = {
790
+ "epic": epic.model_dump(),
791
+ "issues": []
792
+ }
793
+
794
+ # Get issues in epic
795
+ issues = await self.adapter.list_issues_by_epic(epic_id)
796
+ for issue in issues:
797
+ issue_node = {
798
+ "issue": issue.model_dump(),
799
+ "tasks": []
800
+ }
801
+
802
+ # Get tasks in issue if depth allows
803
+ if max_depth > 2:
804
+ tasks = await self.adapter.list_tasks_by_issue(issue.id)
805
+ issue_node["tasks"] = [task.model_dump() for task in tasks]
806
+
807
+ tree["issues"].append(issue_node)
808
+
809
+ return tree
810
+ else:
811
+ # Get all epics with their hierarchies
812
+ epics = await self.adapter.list_epics(limit=params.get("limit", 10))
813
+ trees = []
814
+
815
+ for epic in epics:
816
+ tree = await self._handle_hierarchy_tree({"epic_id": epic.id, "max_depth": max_depth})
817
+ trees.append(tree)
818
+
819
+ return {"trees": trees}
820
+
821
+ async def _handle_bulk_create(self, params: dict[str, Any]) -> dict[str, Any]:
822
+ """Handle bulk ticket creation."""
823
+ tickets = params.get("tickets", [])
824
+ if not tickets:
825
+ return {"error": "No tickets provided for bulk creation"}
826
+
827
+ # Check queue health
828
+ health_monitor = QueueHealthMonitor()
829
+ health = health_monitor.check_health()
830
+
831
+ if health["status"] == HealthStatus.CRITICAL:
832
+ repair_result = health_monitor.auto_repair()
833
+ health = health_monitor.check_health()
834
+
835
+ if health["status"] == HealthStatus.CRITICAL:
836
+ return {
837
+ "status": "error",
838
+ "error": "Queue system is in critical state - cannot process bulk operations",
839
+ "details": {"health_status": health["status"]}
840
+ }
841
+
842
+ # Queue all tickets
843
+ queue = Queue()
844
+ queue_ids = []
845
+
846
+ for i, ticket_data in enumerate(tickets):
847
+ if not ticket_data.get("title"):
848
+ return {
849
+ "status": "error",
850
+ "error": f"Ticket {i} missing required 'title' field"
851
+ }
852
+
853
+ queue_id = queue.add(
854
+ ticket_data=ticket_data,
855
+ adapter=self.adapter.__class__.__name__.lower().replace("adapter", ""),
856
+ operation=ticket_data.get("operation", "create"),
857
+ )
858
+ queue_ids.append(queue_id)
859
+
860
+ # Start worker if needed
861
+ manager = WorkerManager()
862
+ manager.start_if_needed()
863
+
864
+ return {
865
+ "queue_ids": queue_ids,
866
+ "status": "queued",
867
+ "message": f"Bulk creation of {len(tickets)} tickets queued",
868
+ "count": len(tickets)
869
+ }
870
+
871
+ async def _handle_bulk_update(self, params: dict[str, Any]) -> dict[str, Any]:
872
+ """Handle bulk ticket updates."""
873
+ updates = params.get("updates", [])
874
+ if not updates:
875
+ return {"error": "No updates provided for bulk operation"}
876
+
877
+ # Check queue health
878
+ health_monitor = QueueHealthMonitor()
879
+ health = health_monitor.check_health()
880
+
881
+ if health["status"] == HealthStatus.CRITICAL:
882
+ repair_result = health_monitor.auto_repair()
883
+ health = health_monitor.check_health()
884
+
885
+ if health["status"] == HealthStatus.CRITICAL:
886
+ return {
887
+ "status": "error",
888
+ "error": "Queue system is in critical state - cannot process bulk operations",
889
+ "details": {"health_status": health["status"]}
890
+ }
891
+
892
+ # Queue all updates
893
+ queue = Queue()
894
+ queue_ids = []
895
+
896
+ for i, update_data in enumerate(updates):
897
+ if not update_data.get("ticket_id"):
898
+ return {
899
+ "status": "error",
900
+ "error": f"Update {i} missing required 'ticket_id' field"
901
+ }
902
+
903
+ queue_id = queue.add(
904
+ ticket_data=update_data,
905
+ adapter=self.adapter.__class__.__name__.lower().replace("adapter", ""),
906
+ operation="update",
907
+ )
908
+ queue_ids.append(queue_id)
909
+
910
+ # Start worker if needed
911
+ manager = WorkerManager()
912
+ manager.start_if_needed()
913
+
914
+ return {
915
+ "queue_ids": queue_ids,
916
+ "status": "queued",
917
+ "message": f"Bulk update of {len(updates)} tickets queued",
918
+ "count": len(updates)
919
+ }
920
+
921
+ async def _handle_search_hierarchy(self, params: dict[str, Any]) -> dict[str, Any]:
922
+ """Handle hierarchy-aware search."""
923
+ query = params.get("query", "")
924
+ include_children = params.get("include_children", True)
925
+ include_parents = params.get("include_parents", True)
926
+
927
+ # Perform basic search
928
+ search_query = SearchQuery(
929
+ query=query,
930
+ state=params.get("state"),
931
+ priority=params.get("priority"),
932
+ limit=params.get("limit", 50)
933
+ )
934
+
935
+ tickets = await self.adapter.search(search_query)
936
+
937
+ # Enhance with hierarchy information
938
+ enhanced_results = []
939
+ for ticket in tickets:
940
+ result = {
941
+ "ticket": ticket.model_dump(),
942
+ "hierarchy": {}
943
+ }
944
+
945
+ # Add parent information
946
+ if include_parents:
947
+ if hasattr(ticket, 'parent_epic') and ticket.parent_epic:
948
+ parent_epic = await self.adapter.get_epic(ticket.parent_epic)
949
+ if parent_epic:
950
+ result["hierarchy"]["epic"] = parent_epic.model_dump()
951
+
952
+ if hasattr(ticket, 'parent_issue') and ticket.parent_issue:
953
+ parent_issue = await self.adapter.read(ticket.parent_issue)
954
+ if parent_issue:
955
+ result["hierarchy"]["parent_issue"] = parent_issue.model_dump()
956
+
957
+ # Add children information
958
+ if include_children:
959
+ if ticket.ticket_type == "epic":
960
+ issues = await self.adapter.list_issues_by_epic(ticket.id)
961
+ result["hierarchy"]["issues"] = [issue.model_dump() for issue in issues]
962
+ elif ticket.ticket_type == "issue":
963
+ tasks = await self.adapter.list_tasks_by_issue(ticket.id)
964
+ result["hierarchy"]["tasks"] = [task.model_dump() for task in tasks]
965
+
966
+ enhanced_results.append(result)
967
+
968
+ return {
969
+ "results": enhanced_results,
970
+ "count": len(enhanced_results),
971
+ "query": query
972
+ }
973
+
974
+ async def _handle_attach(self, params: dict[str, Any]) -> dict[str, Any]:
975
+ """Handle file attachment to ticket."""
976
+ # Note: This is a placeholder for attachment functionality
977
+ # Most adapters don't support file attachments directly
978
+ return {
979
+ "status": "not_implemented",
980
+ "error": "Attachment functionality not yet implemented",
981
+ "ticket_id": params.get("ticket_id"),
982
+ "details": {
983
+ "reason": "File attachments require adapter-specific implementation",
984
+ "alternatives": ["Add file URLs in comments", "Use external file storage"]
985
+ }
986
+ }
987
+
988
+ async def _handle_list_attachments(self, params: dict[str, Any]) -> list[dict[str, Any]]:
989
+ """Handle listing ticket attachments."""
990
+ # Note: This is a placeholder for attachment functionality
991
+ return []
992
+
481
993
  async def _handle_create_pr(self, params: dict[str, Any]) -> dict[str, Any]:
482
994
  """Handle PR creation for a ticket."""
483
995
  ticket_id = params.get("ticket_id")
@@ -640,6 +1152,172 @@ class MCPTicketServer:
640
1152
  """List available MCP tools."""
641
1153
  return {
642
1154
  "tools": [
1155
+ # Hierarchy Management Tools
1156
+ {
1157
+ "name": "epic_create",
1158
+ "description": "Create a new epic (top-level project/milestone)",
1159
+ "inputSchema": {
1160
+ "type": "object",
1161
+ "properties": {
1162
+ "title": {"type": "string", "description": "Epic title"},
1163
+ "description": {"type": "string", "description": "Epic description"},
1164
+ "target_date": {"type": "string", "description": "Target completion date (ISO format)"},
1165
+ "lead_id": {"type": "string", "description": "Epic lead/owner ID"},
1166
+ "child_issues": {"type": "array", "items": {"type": "string"}, "description": "Initial child issue IDs"}
1167
+ },
1168
+ "required": ["title"]
1169
+ }
1170
+ },
1171
+ {
1172
+ "name": "epic_list",
1173
+ "description": "List all epics",
1174
+ "inputSchema": {
1175
+ "type": "object",
1176
+ "properties": {
1177
+ "limit": {"type": "integer", "default": 10, "description": "Maximum number of epics to return"},
1178
+ "offset": {"type": "integer", "default": 0, "description": "Number of epics to skip"}
1179
+ }
1180
+ }
1181
+ },
1182
+ {
1183
+ "name": "epic_issues",
1184
+ "description": "List all issues in an epic",
1185
+ "inputSchema": {
1186
+ "type": "object",
1187
+ "properties": {
1188
+ "epic_id": {"type": "string", "description": "Epic ID to get issues for"}
1189
+ },
1190
+ "required": ["epic_id"]
1191
+ }
1192
+ },
1193
+ {
1194
+ "name": "issue_create",
1195
+ "description": "Create a new issue (work item)",
1196
+ "inputSchema": {
1197
+ "type": "object",
1198
+ "properties": {
1199
+ "title": {"type": "string", "description": "Issue title"},
1200
+ "description": {"type": "string", "description": "Issue description"},
1201
+ "epic_id": {"type": "string", "description": "Parent epic ID"},
1202
+ "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"], "default": "medium"},
1203
+ "assignee": {"type": "string", "description": "Assignee username"},
1204
+ "tags": {"type": "array", "items": {"type": "string"}, "description": "Issue tags"},
1205
+ "estimated_hours": {"type": "number", "description": "Estimated hours to complete"}
1206
+ },
1207
+ "required": ["title"]
1208
+ }
1209
+ },
1210
+ {
1211
+ "name": "issue_tasks",
1212
+ "description": "List all tasks in an issue",
1213
+ "inputSchema": {
1214
+ "type": "object",
1215
+ "properties": {
1216
+ "issue_id": {"type": "string", "description": "Issue ID to get tasks for"}
1217
+ },
1218
+ "required": ["issue_id"]
1219
+ }
1220
+ },
1221
+ {
1222
+ "name": "task_create",
1223
+ "description": "Create a new task (sub-item under an issue)",
1224
+ "inputSchema": {
1225
+ "type": "object",
1226
+ "properties": {
1227
+ "title": {"type": "string", "description": "Task title"},
1228
+ "parent_id": {"type": "string", "description": "Parent issue ID (required)"},
1229
+ "description": {"type": "string", "description": "Task description"},
1230
+ "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"], "default": "medium"},
1231
+ "assignee": {"type": "string", "description": "Assignee username"},
1232
+ "tags": {"type": "array", "items": {"type": "string"}, "description": "Task tags"},
1233
+ "estimated_hours": {"type": "number", "description": "Estimated hours to complete"}
1234
+ },
1235
+ "required": ["title", "parent_id"]
1236
+ }
1237
+ },
1238
+ {
1239
+ "name": "hierarchy_tree",
1240
+ "description": "Get hierarchy tree view of epic/issues/tasks",
1241
+ "inputSchema": {
1242
+ "type": "object",
1243
+ "properties": {
1244
+ "epic_id": {"type": "string", "description": "Specific epic ID (optional - if not provided, returns all epics)"},
1245
+ "max_depth": {"type": "integer", "default": 3, "description": "Maximum depth to traverse (1=epics only, 2=epics+issues, 3=full tree)"},
1246
+ "limit": {"type": "integer", "default": 10, "description": "Maximum number of epics to return (when epic_id not specified)"}
1247
+ }
1248
+ }
1249
+ },
1250
+ # Bulk Operations
1251
+ {
1252
+ "name": "ticket_bulk_create",
1253
+ "description": "Create multiple tickets in one operation",
1254
+ "inputSchema": {
1255
+ "type": "object",
1256
+ "properties": {
1257
+ "tickets": {
1258
+ "type": "array",
1259
+ "items": {
1260
+ "type": "object",
1261
+ "properties": {
1262
+ "title": {"type": "string"},
1263
+ "description": {"type": "string"},
1264
+ "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
1265
+ "operation": {"type": "string", "enum": ["create", "create_epic", "create_issue", "create_task"], "default": "create"},
1266
+ "epic_id": {"type": "string", "description": "For issues"},
1267
+ "parent_id": {"type": "string", "description": "For tasks"}
1268
+ },
1269
+ "required": ["title"]
1270
+ },
1271
+ "description": "Array of tickets to create"
1272
+ }
1273
+ },
1274
+ "required": ["tickets"]
1275
+ }
1276
+ },
1277
+ {
1278
+ "name": "ticket_bulk_update",
1279
+ "description": "Update multiple tickets in one operation",
1280
+ "inputSchema": {
1281
+ "type": "object",
1282
+ "properties": {
1283
+ "updates": {
1284
+ "type": "array",
1285
+ "items": {
1286
+ "type": "object",
1287
+ "properties": {
1288
+ "ticket_id": {"type": "string"},
1289
+ "title": {"type": "string"},
1290
+ "description": {"type": "string"},
1291
+ "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
1292
+ "state": {"type": "string"},
1293
+ "assignee": {"type": "string"}
1294
+ },
1295
+ "required": ["ticket_id"]
1296
+ },
1297
+ "description": "Array of ticket updates"
1298
+ }
1299
+ },
1300
+ "required": ["updates"]
1301
+ }
1302
+ },
1303
+ # Advanced Search
1304
+ {
1305
+ "name": "ticket_search_hierarchy",
1306
+ "description": "Search tickets with hierarchy context",
1307
+ "inputSchema": {
1308
+ "type": "object",
1309
+ "properties": {
1310
+ "query": {"type": "string", "description": "Search query"},
1311
+ "state": {"type": "string", "description": "Filter by state"},
1312
+ "priority": {"type": "string", "description": "Filter by priority"},
1313
+ "limit": {"type": "integer", "default": 50, "description": "Maximum results"},
1314
+ "include_children": {"type": "boolean", "default": True, "description": "Include child items in results"},
1315
+ "include_parents": {"type": "boolean", "default": True, "description": "Include parent context in results"}
1316
+ },
1317
+ "required": ["query"]
1318
+ }
1319
+ },
1320
+ # PR Integration
643
1321
  {
644
1322
  "name": "ticket_create_pr",
645
1323
  "description": "Create a GitHub PR linked to a ticket",
@@ -676,6 +1354,7 @@ class MCPTicketServer:
676
1354
  "required": ["ticket_id"],
677
1355
  },
678
1356
  },
1357
+ # Standard Ticket Operations
679
1358
  {
680
1359
  "name": "ticket_link_pr",
681
1360
  "description": "Link an existing PR to a ticket",
@@ -799,7 +1478,31 @@ class MCPTicketServer:
799
1478
 
800
1479
  try:
801
1480
  # Route to appropriate handler based on tool name
802
- if tool_name == "ticket_create":
1481
+ # Hierarchy management tools
1482
+ if tool_name == "epic_create":
1483
+ result = await self._handle_epic_create(arguments)
1484
+ elif tool_name == "epic_list":
1485
+ result = await self._handle_epic_list(arguments)
1486
+ elif tool_name == "epic_issues":
1487
+ result = await self._handle_epic_issues(arguments)
1488
+ elif tool_name == "issue_create":
1489
+ result = await self._handle_issue_create(arguments)
1490
+ elif tool_name == "issue_tasks":
1491
+ result = await self._handle_issue_tasks(arguments)
1492
+ elif tool_name == "task_create":
1493
+ result = await self._handle_task_create(arguments)
1494
+ elif tool_name == "hierarchy_tree":
1495
+ result = await self._handle_hierarchy_tree(arguments)
1496
+ # Bulk operations
1497
+ elif tool_name == "ticket_bulk_create":
1498
+ result = await self._handle_bulk_create(arguments)
1499
+ elif tool_name == "ticket_bulk_update":
1500
+ result = await self._handle_bulk_update(arguments)
1501
+ # Advanced search
1502
+ elif tool_name == "ticket_search_hierarchy":
1503
+ result = await self._handle_search_hierarchy(arguments)
1504
+ # Standard ticket operations
1505
+ elif tool_name == "ticket_create":
803
1506
  result = await self._handle_create(arguments)
804
1507
  elif tool_name == "ticket_list":
805
1508
  result = await self._handle_list(arguments)
@@ -811,6 +1514,7 @@ class MCPTicketServer:
811
1514
  result = await self._handle_search(arguments)
812
1515
  elif tool_name == "ticket_status":
813
1516
  result = await self._handle_queue_status(arguments)
1517
+ # PR integration
814
1518
  elif tool_name == "ticket_create_pr":
815
1519
  result = await self._handle_create_pr(arguments)
816
1520
  elif tool_name == "ticket_link_pr":