mcp-ticketer 0.3.1__py3-none-any.whl → 0.3.2__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.

Files changed (37) hide show
  1. mcp_ticketer/__version__.py +1 -1
  2. mcp_ticketer/adapters/aitrackdown.py +12 -15
  3. mcp_ticketer/adapters/github.py +7 -4
  4. mcp_ticketer/adapters/jira.py +23 -22
  5. mcp_ticketer/adapters/linear/__init__.py +1 -1
  6. mcp_ticketer/adapters/linear/adapter.py +88 -89
  7. mcp_ticketer/adapters/linear/client.py +71 -52
  8. mcp_ticketer/adapters/linear/mappers.py +88 -68
  9. mcp_ticketer/adapters/linear/queries.py +28 -7
  10. mcp_ticketer/adapters/linear/types.py +57 -50
  11. mcp_ticketer/adapters/linear.py +2 -2
  12. mcp_ticketer/cli/adapter_diagnostics.py +86 -51
  13. mcp_ticketer/cli/diagnostics.py +165 -72
  14. mcp_ticketer/cli/linear_commands.py +156 -113
  15. mcp_ticketer/cli/main.py +153 -82
  16. mcp_ticketer/cli/simple_health.py +73 -45
  17. mcp_ticketer/cli/utils.py +15 -10
  18. mcp_ticketer/core/config.py +23 -19
  19. mcp_ticketer/core/env_discovery.py +5 -4
  20. mcp_ticketer/core/env_loader.py +109 -86
  21. mcp_ticketer/core/exceptions.py +20 -18
  22. mcp_ticketer/core/models.py +9 -0
  23. mcp_ticketer/core/project_config.py +1 -1
  24. mcp_ticketer/mcp/server.py +294 -139
  25. mcp_ticketer/queue/health_monitor.py +152 -121
  26. mcp_ticketer/queue/manager.py +11 -4
  27. mcp_ticketer/queue/queue.py +15 -3
  28. mcp_ticketer/queue/run_worker.py +1 -1
  29. mcp_ticketer/queue/ticket_registry.py +190 -132
  30. mcp_ticketer/queue/worker.py +54 -25
  31. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/METADATA +1 -1
  32. mcp_ticketer-0.3.2.dist-info/RECORD +59 -0
  33. mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
  34. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
  35. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
  36. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
  37. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/top_level.txt +0 -0
@@ -8,13 +8,13 @@ from typing import Any, Optional
8
8
 
9
9
  from dotenv import load_dotenv
10
10
 
11
+ # Import adapters module to trigger registration
12
+ import mcp_ticketer.adapters # noqa: F401
13
+
11
14
  from ..core import AdapterRegistry
12
15
  from ..core.models import SearchQuery
13
16
  from ..queue import Queue, QueueStatus, WorkerManager
14
- from ..queue.health_monitor import QueueHealthMonitor, HealthStatus
15
-
16
- # Import adapters module to trigger registration
17
- import mcp_ticketer.adapters # noqa: F401
17
+ from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
18
18
 
19
19
  # Load environment variables early (prioritize .env.local)
20
20
  # Check for .env.local first (takes precedence)
@@ -171,15 +171,17 @@ class MCPTicketServer:
171
171
 
172
172
  # If still critical, return error immediately
173
173
  if health["status"] == HealthStatus.CRITICAL:
174
- critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
174
+ critical_alerts = [
175
+ alert for alert in health["alerts"] if alert["level"] == "critical"
176
+ ]
175
177
  return {
176
178
  "status": "error",
177
179
  "error": "Queue system is in critical state",
178
180
  "details": {
179
181
  "health_status": health["status"],
180
182
  "critical_issues": critical_alerts,
181
- "repair_attempted": repair_result["actions_taken"]
182
- }
183
+ "repair_attempted": repair_result["actions_taken"],
184
+ },
183
185
  }
184
186
 
185
187
  # Queue the operation
@@ -210,8 +212,8 @@ class MCPTicketServer:
210
212
  "queue_id": queue_id,
211
213
  "details": {
212
214
  "pending_count": queue.get_pending_count(),
213
- "action": "Worker process could not be started to process queued operations"
214
- }
215
+ "action": "Worker process could not be started to process queued operations",
216
+ },
215
217
  }
216
218
 
217
219
  # Check if async mode is requested (for backward compatibility)
@@ -551,7 +553,10 @@ class MCPTicketServer:
551
553
 
552
554
  # Add auto-repair option
553
555
  auto_repair = params.get("auto_repair", False)
554
- if auto_repair and health["status"] in [HealthStatus.CRITICAL, HealthStatus.WARNING]:
556
+ if auto_repair and health["status"] in [
557
+ HealthStatus.CRITICAL,
558
+ HealthStatus.WARNING,
559
+ ]:
555
560
  repair_result = health_monitor.auto_repair()
556
561
  health["auto_repair"] = repair_result
557
562
  # Re-check health after repair
@@ -572,15 +577,17 @@ class MCPTicketServer:
572
577
  health = health_monitor.check_health()
573
578
 
574
579
  if health["status"] == HealthStatus.CRITICAL:
575
- critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
580
+ critical_alerts = [
581
+ alert for alert in health["alerts"] if alert["level"] == "critical"
582
+ ]
576
583
  return {
577
584
  "status": "error",
578
585
  "error": "Queue system is in critical state",
579
586
  "details": {
580
587
  "health_status": health["status"],
581
588
  "critical_issues": critical_alerts,
582
- "repair_attempted": repair_result["actions_taken"]
583
- }
589
+ "repair_attempted": repair_result["actions_taken"],
590
+ },
584
591
  }
585
592
 
586
593
  # Queue the epic creation
@@ -610,15 +617,15 @@ class MCPTicketServer:
610
617
  "queue_id": queue_id,
611
618
  "details": {
612
619
  "pending_count": queue.get_pending_count(),
613
- "action": "Worker process could not be started to process queued operations"
614
- }
620
+ "action": "Worker process could not be started to process queued operations",
621
+ },
615
622
  }
616
623
 
617
624
  return {
618
625
  "queue_id": queue_id,
619
626
  "status": "queued",
620
627
  "message": f"Epic creation queued with ID: {queue_id}",
621
- "epic_data": epic_data
628
+ "epic_data": epic_data,
622
629
  }
623
630
 
624
631
  async def _handle_epic_list(self, params: dict[str, Any]) -> list[dict[str, Any]]:
@@ -626,7 +633,7 @@ class MCPTicketServer:
626
633
  epics = await self.adapter.list_epics(
627
634
  limit=params.get("limit", 10),
628
635
  offset=params.get("offset", 0),
629
- **{k: v for k, v in params.items() if k not in ["limit", "offset"]}
636
+ **{k: v for k, v in params.items() if k not in ["limit", "offset"]},
630
637
  )
631
638
  return [epic.model_dump() for epic in epics]
632
639
 
@@ -647,15 +654,17 @@ class MCPTicketServer:
647
654
  health = health_monitor.check_health()
648
655
 
649
656
  if health["status"] == HealthStatus.CRITICAL:
650
- critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
657
+ critical_alerts = [
658
+ alert for alert in health["alerts"] if alert["level"] == "critical"
659
+ ]
651
660
  return {
652
661
  "status": "error",
653
662
  "error": "Queue system is in critical state",
654
663
  "details": {
655
664
  "health_status": health["status"],
656
665
  "critical_issues": critical_alerts,
657
- "repair_attempted": repair_result["actions_taken"]
658
- }
666
+ "repair_attempted": repair_result["actions_taken"],
667
+ },
659
668
  }
660
669
 
661
670
  # Queue the issue creation
@@ -687,15 +696,15 @@ class MCPTicketServer:
687
696
  "queue_id": queue_id,
688
697
  "details": {
689
698
  "pending_count": queue.get_pending_count(),
690
- "action": "Worker process could not be started to process queued operations"
691
- }
699
+ "action": "Worker process could not be started to process queued operations",
700
+ },
692
701
  }
693
702
 
694
703
  return {
695
704
  "queue_id": queue_id,
696
705
  "status": "queued",
697
706
  "message": f"Issue creation queued with ID: {queue_id}",
698
- "issue_data": issue_data
707
+ "issue_data": issue_data,
699
708
  }
700
709
 
701
710
  async def _handle_issue_tasks(self, params: dict[str, Any]) -> list[dict[str, Any]]:
@@ -715,15 +724,17 @@ class MCPTicketServer:
715
724
  health = health_monitor.check_health()
716
725
 
717
726
  if health["status"] == HealthStatus.CRITICAL:
718
- critical_alerts = [alert for alert in health["alerts"] if alert["level"] == "critical"]
727
+ critical_alerts = [
728
+ alert for alert in health["alerts"] if alert["level"] == "critical"
729
+ ]
719
730
  return {
720
731
  "status": "error",
721
732
  "error": "Queue system is in critical state",
722
733
  "details": {
723
734
  "health_status": health["status"],
724
735
  "critical_issues": critical_alerts,
725
- "repair_attempted": repair_result["actions_taken"]
726
- }
736
+ "repair_attempted": repair_result["actions_taken"],
737
+ },
727
738
  }
728
739
 
729
740
  # Validate required parent_id
@@ -731,7 +742,7 @@ class MCPTicketServer:
731
742
  return {
732
743
  "status": "error",
733
744
  "error": "Tasks must have a parent_id (issue identifier)",
734
- "details": {"required_field": "parent_id"}
745
+ "details": {"required_field": "parent_id"},
735
746
  }
736
747
 
737
748
  # Queue the task creation
@@ -763,15 +774,15 @@ class MCPTicketServer:
763
774
  "queue_id": queue_id,
764
775
  "details": {
765
776
  "pending_count": queue.get_pending_count(),
766
- "action": "Worker process could not be started to process queued operations"
767
- }
777
+ "action": "Worker process could not be started to process queued operations",
778
+ },
768
779
  }
769
780
 
770
781
  return {
771
782
  "queue_id": queue_id,
772
783
  "status": "queued",
773
784
  "message": f"Task creation queued with ID: {queue_id}",
774
- "task_data": task_data
785
+ "task_data": task_data,
775
786
  }
776
787
 
777
788
  async def _handle_hierarchy_tree(self, params: dict[str, Any]) -> dict[str, Any]:
@@ -786,18 +797,12 @@ class MCPTicketServer:
786
797
  return {"error": f"Epic {epic_id} not found"}
787
798
 
788
799
  # Build tree structure
789
- tree = {
790
- "epic": epic.model_dump(),
791
- "issues": []
792
- }
800
+ tree = {"epic": epic.model_dump(), "issues": []}
793
801
 
794
802
  # Get issues in epic
795
803
  issues = await self.adapter.list_issues_by_epic(epic_id)
796
804
  for issue in issues:
797
- issue_node = {
798
- "issue": issue.model_dump(),
799
- "tasks": []
800
- }
805
+ issue_node = {"issue": issue.model_dump(), "tasks": []}
801
806
 
802
807
  # Get tasks in issue if depth allows
803
808
  if max_depth > 2:
@@ -813,7 +818,9 @@ class MCPTicketServer:
813
818
  trees = []
814
819
 
815
820
  for epic in epics:
816
- tree = await self._handle_hierarchy_tree({"epic_id": epic.id, "max_depth": max_depth})
821
+ tree = await self._handle_hierarchy_tree(
822
+ {"epic_id": epic.id, "max_depth": max_depth}
823
+ )
817
824
  trees.append(tree)
818
825
 
819
826
  return {"trees": trees}
@@ -836,7 +843,7 @@ class MCPTicketServer:
836
843
  return {
837
844
  "status": "error",
838
845
  "error": "Queue system is in critical state - cannot process bulk operations",
839
- "details": {"health_status": health["status"]}
846
+ "details": {"health_status": health["status"]},
840
847
  }
841
848
 
842
849
  # Queue all tickets
@@ -847,7 +854,7 @@ class MCPTicketServer:
847
854
  if not ticket_data.get("title"):
848
855
  return {
849
856
  "status": "error",
850
- "error": f"Ticket {i} missing required 'title' field"
857
+ "error": f"Ticket {i} missing required 'title' field",
851
858
  }
852
859
 
853
860
  queue_id = queue.add(
@@ -865,7 +872,7 @@ class MCPTicketServer:
865
872
  "queue_ids": queue_ids,
866
873
  "status": "queued",
867
874
  "message": f"Bulk creation of {len(tickets)} tickets queued",
868
- "count": len(tickets)
875
+ "count": len(tickets),
869
876
  }
870
877
 
871
878
  async def _handle_bulk_update(self, params: dict[str, Any]) -> dict[str, Any]:
@@ -886,7 +893,7 @@ class MCPTicketServer:
886
893
  return {
887
894
  "status": "error",
888
895
  "error": "Queue system is in critical state - cannot process bulk operations",
889
- "details": {"health_status": health["status"]}
896
+ "details": {"health_status": health["status"]},
890
897
  }
891
898
 
892
899
  # Queue all updates
@@ -897,7 +904,7 @@ class MCPTicketServer:
897
904
  if not update_data.get("ticket_id"):
898
905
  return {
899
906
  "status": "error",
900
- "error": f"Update {i} missing required 'ticket_id' field"
907
+ "error": f"Update {i} missing required 'ticket_id' field",
901
908
  }
902
909
 
903
910
  queue_id = queue.add(
@@ -915,7 +922,7 @@ class MCPTicketServer:
915
922
  "queue_ids": queue_ids,
916
923
  "status": "queued",
917
924
  "message": f"Bulk update of {len(updates)} tickets queued",
918
- "count": len(updates)
925
+ "count": len(updates),
919
926
  }
920
927
 
921
928
  async def _handle_search_hierarchy(self, params: dict[str, Any]) -> dict[str, Any]:
@@ -929,7 +936,7 @@ class MCPTicketServer:
929
936
  query=query,
930
937
  state=params.get("state"),
931
938
  priority=params.get("priority"),
932
- limit=params.get("limit", 50)
939
+ limit=params.get("limit", 50),
933
940
  )
934
941
 
935
942
  tickets = await self.adapter.search(search_query)
@@ -937,19 +944,16 @@ class MCPTicketServer:
937
944
  # Enhance with hierarchy information
938
945
  enhanced_results = []
939
946
  for ticket in tickets:
940
- result = {
941
- "ticket": ticket.model_dump(),
942
- "hierarchy": {}
943
- }
947
+ result = {"ticket": ticket.model_dump(), "hierarchy": {}}
944
948
 
945
949
  # Add parent information
946
950
  if include_parents:
947
- if hasattr(ticket, 'parent_epic') and ticket.parent_epic:
951
+ if hasattr(ticket, "parent_epic") and ticket.parent_epic:
948
952
  parent_epic = await self.adapter.get_epic(ticket.parent_epic)
949
953
  if parent_epic:
950
954
  result["hierarchy"]["epic"] = parent_epic.model_dump()
951
955
 
952
- if hasattr(ticket, 'parent_issue') and ticket.parent_issue:
956
+ if hasattr(ticket, "parent_issue") and ticket.parent_issue:
953
957
  parent_issue = await self.adapter.read(ticket.parent_issue)
954
958
  if parent_issue:
955
959
  result["hierarchy"]["parent_issue"] = parent_issue.model_dump()
@@ -958,7 +962,9 @@ class MCPTicketServer:
958
962
  if include_children:
959
963
  if ticket.ticket_type == "epic":
960
964
  issues = await self.adapter.list_issues_by_epic(ticket.id)
961
- result["hierarchy"]["issues"] = [issue.model_dump() for issue in issues]
965
+ result["hierarchy"]["issues"] = [
966
+ issue.model_dump() for issue in issues
967
+ ]
962
968
  elif ticket.ticket_type == "issue":
963
969
  tasks = await self.adapter.list_tasks_by_issue(ticket.id)
964
970
  result["hierarchy"]["tasks"] = [task.model_dump() for task in tasks]
@@ -968,7 +974,7 @@ class MCPTicketServer:
968
974
  return {
969
975
  "results": enhanced_results,
970
976
  "count": len(enhanced_results),
971
- "query": query
977
+ "query": query,
972
978
  }
973
979
 
974
980
  async def _handle_attach(self, params: dict[str, Any]) -> dict[str, Any]:
@@ -981,11 +987,16 @@ class MCPTicketServer:
981
987
  "ticket_id": params.get("ticket_id"),
982
988
  "details": {
983
989
  "reason": "File attachments require adapter-specific implementation",
984
- "alternatives": ["Add file URLs in comments", "Use external file storage"]
985
- }
990
+ "alternatives": [
991
+ "Add file URLs in comments",
992
+ "Use external file storage",
993
+ ],
994
+ },
986
995
  }
987
996
 
988
- async def _handle_list_attachments(self, params: dict[str, Any]) -> list[dict[str, Any]]:
997
+ async def _handle_list_attachments(
998
+ self, params: dict[str, Any]
999
+ ) -> list[dict[str, Any]]:
989
1000
  """Handle listing ticket attachments."""
990
1001
  # Note: This is a placeholder for attachment functionality
991
1002
  return []
@@ -1160,13 +1171,26 @@ class MCPTicketServer:
1160
1171
  "type": "object",
1161
1172
  "properties": {
1162
1173
  "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"}
1174
+ "description": {
1175
+ "type": "string",
1176
+ "description": "Epic description",
1177
+ },
1178
+ "target_date": {
1179
+ "type": "string",
1180
+ "description": "Target completion date (ISO format)",
1181
+ },
1182
+ "lead_id": {
1183
+ "type": "string",
1184
+ "description": "Epic lead/owner ID",
1185
+ },
1186
+ "child_issues": {
1187
+ "type": "array",
1188
+ "items": {"type": "string"},
1189
+ "description": "Initial child issue IDs",
1190
+ },
1167
1191
  },
1168
- "required": ["title"]
1169
- }
1192
+ "required": ["title"],
1193
+ },
1170
1194
  },
1171
1195
  {
1172
1196
  "name": "epic_list",
@@ -1174,10 +1198,18 @@ class MCPTicketServer:
1174
1198
  "inputSchema": {
1175
1199
  "type": "object",
1176
1200
  "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
- }
1201
+ "limit": {
1202
+ "type": "integer",
1203
+ "default": 10,
1204
+ "description": "Maximum number of epics to return",
1205
+ },
1206
+ "offset": {
1207
+ "type": "integer",
1208
+ "default": 0,
1209
+ "description": "Number of epics to skip",
1210
+ },
1211
+ },
1212
+ },
1181
1213
  },
1182
1214
  {
1183
1215
  "name": "epic_issues",
@@ -1185,10 +1217,13 @@ class MCPTicketServer:
1185
1217
  "inputSchema": {
1186
1218
  "type": "object",
1187
1219
  "properties": {
1188
- "epic_id": {"type": "string", "description": "Epic ID to get issues for"}
1220
+ "epic_id": {
1221
+ "type": "string",
1222
+ "description": "Epic ID to get issues for",
1223
+ }
1189
1224
  },
1190
- "required": ["epic_id"]
1191
- }
1225
+ "required": ["epic_id"],
1226
+ },
1192
1227
  },
1193
1228
  {
1194
1229
  "name": "issue_create",
@@ -1197,15 +1232,35 @@ class MCPTicketServer:
1197
1232
  "type": "object",
1198
1233
  "properties": {
1199
1234
  "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"}
1235
+ "description": {
1236
+ "type": "string",
1237
+ "description": "Issue description",
1238
+ },
1239
+ "epic_id": {
1240
+ "type": "string",
1241
+ "description": "Parent epic ID",
1242
+ },
1243
+ "priority": {
1244
+ "type": "string",
1245
+ "enum": ["low", "medium", "high", "critical"],
1246
+ "default": "medium",
1247
+ },
1248
+ "assignee": {
1249
+ "type": "string",
1250
+ "description": "Assignee username",
1251
+ },
1252
+ "tags": {
1253
+ "type": "array",
1254
+ "items": {"type": "string"},
1255
+ "description": "Issue tags",
1256
+ },
1257
+ "estimated_hours": {
1258
+ "type": "number",
1259
+ "description": "Estimated hours to complete",
1260
+ },
1206
1261
  },
1207
- "required": ["title"]
1208
- }
1262
+ "required": ["title"],
1263
+ },
1209
1264
  },
1210
1265
  {
1211
1266
  "name": "issue_tasks",
@@ -1213,10 +1268,13 @@ class MCPTicketServer:
1213
1268
  "inputSchema": {
1214
1269
  "type": "object",
1215
1270
  "properties": {
1216
- "issue_id": {"type": "string", "description": "Issue ID to get tasks for"}
1271
+ "issue_id": {
1272
+ "type": "string",
1273
+ "description": "Issue ID to get tasks for",
1274
+ }
1217
1275
  },
1218
- "required": ["issue_id"]
1219
- }
1276
+ "required": ["issue_id"],
1277
+ },
1220
1278
  },
1221
1279
  {
1222
1280
  "name": "task_create",
@@ -1225,15 +1283,35 @@ class MCPTicketServer:
1225
1283
  "type": "object",
1226
1284
  "properties": {
1227
1285
  "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"}
1286
+ "parent_id": {
1287
+ "type": "string",
1288
+ "description": "Parent issue ID (required)",
1289
+ },
1290
+ "description": {
1291
+ "type": "string",
1292
+ "description": "Task description",
1293
+ },
1294
+ "priority": {
1295
+ "type": "string",
1296
+ "enum": ["low", "medium", "high", "critical"],
1297
+ "default": "medium",
1298
+ },
1299
+ "assignee": {
1300
+ "type": "string",
1301
+ "description": "Assignee username",
1302
+ },
1303
+ "tags": {
1304
+ "type": "array",
1305
+ "items": {"type": "string"},
1306
+ "description": "Task tags",
1307
+ },
1308
+ "estimated_hours": {
1309
+ "type": "number",
1310
+ "description": "Estimated hours to complete",
1311
+ },
1234
1312
  },
1235
- "required": ["title", "parent_id"]
1236
- }
1313
+ "required": ["title", "parent_id"],
1314
+ },
1237
1315
  },
1238
1316
  {
1239
1317
  "name": "hierarchy_tree",
@@ -1241,11 +1319,22 @@ class MCPTicketServer:
1241
1319
  "inputSchema": {
1242
1320
  "type": "object",
1243
1321
  "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
- }
1322
+ "epic_id": {
1323
+ "type": "string",
1324
+ "description": "Specific epic ID (optional - if not provided, returns all epics)",
1325
+ },
1326
+ "max_depth": {
1327
+ "type": "integer",
1328
+ "default": 3,
1329
+ "description": "Maximum depth to traverse (1=epics only, 2=epics+issues, 3=full tree)",
1330
+ },
1331
+ "limit": {
1332
+ "type": "integer",
1333
+ "default": 10,
1334
+ "description": "Maximum number of epics to return (when epic_id not specified)",
1335
+ },
1336
+ },
1337
+ },
1249
1338
  },
1250
1339
  # Bulk Operations
1251
1340
  {
@@ -1261,18 +1350,41 @@ class MCPTicketServer:
1261
1350
  "properties": {
1262
1351
  "title": {"type": "string"},
1263
1352
  "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"}
1353
+ "priority": {
1354
+ "type": "string",
1355
+ "enum": [
1356
+ "low",
1357
+ "medium",
1358
+ "high",
1359
+ "critical",
1360
+ ],
1361
+ },
1362
+ "operation": {
1363
+ "type": "string",
1364
+ "enum": [
1365
+ "create",
1366
+ "create_epic",
1367
+ "create_issue",
1368
+ "create_task",
1369
+ ],
1370
+ "default": "create",
1371
+ },
1372
+ "epic_id": {
1373
+ "type": "string",
1374
+ "description": "For issues",
1375
+ },
1376
+ "parent_id": {
1377
+ "type": "string",
1378
+ "description": "For tasks",
1379
+ },
1268
1380
  },
1269
- "required": ["title"]
1381
+ "required": ["title"],
1270
1382
  },
1271
- "description": "Array of tickets to create"
1383
+ "description": "Array of tickets to create",
1272
1384
  }
1273
1385
  },
1274
- "required": ["tickets"]
1275
- }
1386
+ "required": ["tickets"],
1387
+ },
1276
1388
  },
1277
1389
  {
1278
1390
  "name": "ticket_bulk_update",
@@ -1288,17 +1400,25 @@ class MCPTicketServer:
1288
1400
  "ticket_id": {"type": "string"},
1289
1401
  "title": {"type": "string"},
1290
1402
  "description": {"type": "string"},
1291
- "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
1403
+ "priority": {
1404
+ "type": "string",
1405
+ "enum": [
1406
+ "low",
1407
+ "medium",
1408
+ "high",
1409
+ "critical",
1410
+ ],
1411
+ },
1292
1412
  "state": {"type": "string"},
1293
- "assignee": {"type": "string"}
1413
+ "assignee": {"type": "string"},
1294
1414
  },
1295
- "required": ["ticket_id"]
1415
+ "required": ["ticket_id"],
1296
1416
  },
1297
- "description": "Array of ticket updates"
1417
+ "description": "Array of ticket updates",
1298
1418
  }
1299
1419
  },
1300
- "required": ["updates"]
1301
- }
1420
+ "required": ["updates"],
1421
+ },
1302
1422
  },
1303
1423
  # Advanced Search
1304
1424
  {
@@ -1308,14 +1428,32 @@ class MCPTicketServer:
1308
1428
  "type": "object",
1309
1429
  "properties": {
1310
1430
  "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"}
1431
+ "state": {
1432
+ "type": "string",
1433
+ "description": "Filter by state",
1434
+ },
1435
+ "priority": {
1436
+ "type": "string",
1437
+ "description": "Filter by priority",
1438
+ },
1439
+ "limit": {
1440
+ "type": "integer",
1441
+ "default": 50,
1442
+ "description": "Maximum results",
1443
+ },
1444
+ "include_children": {
1445
+ "type": "boolean",
1446
+ "default": True,
1447
+ "description": "Include child items in results",
1448
+ },
1449
+ "include_parents": {
1450
+ "type": "boolean",
1451
+ "default": True,
1452
+ "description": "Include parent context in results",
1453
+ },
1316
1454
  },
1317
- "required": ["query"]
1318
- }
1455
+ "required": ["query"],
1456
+ },
1319
1457
  },
1320
1458
  # PR Integration
1321
1459
  {
@@ -1652,7 +1790,6 @@ async def main():
1652
1790
  # Load configuration
1653
1791
  import json
1654
1792
  import logging
1655
- import os
1656
1793
  from pathlib import Path
1657
1794
 
1658
1795
  logger = logging.getLogger(__name__)
@@ -1726,6 +1863,7 @@ def _load_env_configuration() -> Optional[dict[str, Any]]:
1726
1863
 
1727
1864
  Returns:
1728
1865
  Dictionary with 'adapter_type' and 'adapter_config' keys, or None if no config found
1866
+
1729
1867
  """
1730
1868
  from pathlib import Path
1731
1869
 
@@ -1738,11 +1876,11 @@ def _load_env_configuration() -> Optional[dict[str, Any]]:
1738
1876
  if env_path.exists():
1739
1877
  try:
1740
1878
  # Parse .env file manually to avoid external dependencies
1741
- with open(env_path, 'r') as f:
1879
+ with open(env_path) as f:
1742
1880
  for line in f:
1743
1881
  line = line.strip()
1744
- if line and not line.startswith('#') and '=' in line:
1745
- key, value = line.split('=', 1)
1882
+ if line and not line.startswith("#") and "=" in line:
1883
+ key, value = line.split("=", 1)
1746
1884
  key = key.strip()
1747
1885
  value = value.strip().strip('"').strip("'")
1748
1886
  if value: # Only add non-empty values
@@ -1772,13 +1910,12 @@ def _load_env_configuration() -> Optional[dict[str, Any]]:
1772
1910
  if not adapter_config:
1773
1911
  return None
1774
1912
 
1775
- return {
1776
- "adapter_type": adapter_type,
1777
- "adapter_config": adapter_config
1778
- }
1913
+ return {"adapter_type": adapter_type, "adapter_config": adapter_config}
1779
1914
 
1780
1915
 
1781
- def _build_adapter_config_from_env_vars(adapter_type: str, env_vars: dict[str, str]) -> dict[str, Any]:
1916
+ def _build_adapter_config_from_env_vars(
1917
+ adapter_type: str, env_vars: dict[str, str]
1918
+ ) -> dict[str, Any]:
1782
1919
  """Build adapter configuration from parsed environment variables.
1783
1920
 
1784
1921
  Args:
@@ -1787,6 +1924,7 @@ def _build_adapter_config_from_env_vars(adapter_type: str, env_vars: dict[str, s
1787
1924
 
1788
1925
  Returns:
1789
1926
  Dictionary of adapter configuration
1927
+
1790
1928
  """
1791
1929
  config = {}
1792
1930
 
@@ -1834,9 +1972,6 @@ def _build_adapter_config_from_env_vars(adapter_type: str, env_vars: dict[str, s
1834
1972
  return config
1835
1973
 
1836
1974
 
1837
-
1838
-
1839
-
1840
1975
  # Add diagnostic handler methods to MCPTicketServer class
1841
1976
  async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, Any]:
1842
1977
  """Handle system health check."""
@@ -1856,6 +1991,7 @@ async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, An
1856
1991
  # Check configuration
1857
1992
  try:
1858
1993
  from ..core.config import get_config
1994
+
1859
1995
  config = get_config()
1860
1996
  adapters = config.get_enabled_adapters()
1861
1997
  if adapters:
@@ -1881,6 +2017,7 @@ async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, An
1881
2017
  # Check queue system
1882
2018
  try:
1883
2019
  from ..queue.manager import WorkerManager
2020
+
1884
2021
  worker_manager = WorkerManager()
1885
2022
  worker_status = worker_manager.get_status()
1886
2023
  stats = worker_manager.queue.get_stats()
@@ -1904,11 +2041,15 @@ async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, An
1904
2041
  health_status["overall_status"] = "critical"
1905
2042
  elif failure_rate > 50:
1906
2043
  queue_health["status"] = "degraded"
1907
- health_status["issues"].append(f"High queue failure rate: {failure_rate:.1f}%")
2044
+ health_status["issues"].append(
2045
+ f"High queue failure rate: {failure_rate:.1f}%"
2046
+ )
1908
2047
  health_status["overall_status"] = "critical"
1909
2048
  elif failure_rate > 20:
1910
2049
  queue_health["status"] = "warning"
1911
- health_status["warnings"].append(f"Elevated queue failure rate: {failure_rate:.1f}%")
2050
+ health_status["warnings"].append(
2051
+ f"Elevated queue failure rate: {failure_rate:.1f}%"
2052
+ )
1912
2053
  if health_status["overall_status"] == "healthy":
1913
2054
  health_status["overall_status"] = "warning"
1914
2055
 
@@ -1926,14 +2067,28 @@ async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, An
1926
2067
  "content": [
1927
2068
  {
1928
2069
  "type": "text",
1929
- "text": f"System Health Status: {health_status['overall_status'].upper()}\n\n" +
1930
- f"Configuration: {health_status['components'].get('configuration', {}).get('status', 'unknown')}\n" +
1931
- f"Queue System: {health_status['components'].get('queue_system', {}).get('status', 'unknown')}\n\n" +
1932
- f"Issues: {len(health_status['issues'])}\n" +
1933
- f"Warnings: {len(health_status['warnings'])}\n\n" +
1934
- (f"Critical Issues:\n" + "\n".join(f"• {issue}" for issue in health_status['issues']) + "\n\n" if health_status['issues'] else "") +
1935
- (f"Warnings:\n" + "\n".join(f"• {warning}" for warning in health_status['warnings']) + "\n\n" if health_status['warnings'] else "") +
1936
- "For detailed diagnosis, use system_diagnose tool.",
2070
+ "text": f"System Health Status: {health_status['overall_status'].upper()}\n\n"
2071
+ + f"Configuration: {health_status['components'].get('configuration', {}).get('status', 'unknown')}\n"
2072
+ + f"Queue System: {health_status['components'].get('queue_system', {}).get('status', 'unknown')}\n\n"
2073
+ + f"Issues: {len(health_status['issues'])}\n"
2074
+ + f"Warnings: {len(health_status['warnings'])}\n\n"
2075
+ + (
2076
+ "Critical Issues:\n"
2077
+ + "\n".join(f"• {issue}" for issue in health_status["issues"])
2078
+ + "\n\n"
2079
+ if health_status["issues"]
2080
+ else ""
2081
+ )
2082
+ + (
2083
+ "Warnings:\n"
2084
+ + "\n".join(
2085
+ f"• {warning}" for warning in health_status["warnings"]
2086
+ )
2087
+ + "\n\n"
2088
+ if health_status["warnings"]
2089
+ else ""
2090
+ )
2091
+ + "For detailed diagnosis, use system_diagnose tool.",
1937
2092
  }
1938
2093
  ],
1939
2094
  "isError": health_status["overall_status"] == "critical",
@@ -1994,9 +2149,9 @@ STATISTICS:
1994
2149
  summary += f"• {warning}\n"
1995
2150
  summary += "\n"
1996
2151
 
1997
- if report['recommendations']:
2152
+ if report["recommendations"]:
1998
2153
  summary += "RECOMMENDATIONS:\n"
1999
- for rec in report['recommendations']:
2154
+ for rec in report["recommendations"]:
2000
2155
  summary += f"{rec}\n"
2001
2156
 
2002
2157
  return {