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.
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +12 -15
- mcp_ticketer/adapters/github.py +7 -4
- mcp_ticketer/adapters/jira.py +23 -22
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +88 -89
- mcp_ticketer/adapters/linear/client.py +71 -52
- mcp_ticketer/adapters/linear/mappers.py +88 -68
- mcp_ticketer/adapters/linear/queries.py +28 -7
- mcp_ticketer/adapters/linear/types.py +57 -50
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/cli/adapter_diagnostics.py +86 -51
- mcp_ticketer/cli/diagnostics.py +165 -72
- mcp_ticketer/cli/linear_commands.py +156 -113
- mcp_ticketer/cli/main.py +153 -82
- mcp_ticketer/cli/simple_health.py +73 -45
- mcp_ticketer/cli/utils.py +15 -10
- mcp_ticketer/core/config.py +23 -19
- mcp_ticketer/core/env_discovery.py +5 -4
- mcp_ticketer/core/env_loader.py +109 -86
- mcp_ticketer/core/exceptions.py +20 -18
- mcp_ticketer/core/models.py +9 -0
- mcp_ticketer/core/project_config.py +1 -1
- mcp_ticketer/mcp/server.py +294 -139
- mcp_ticketer/queue/health_monitor.py +152 -121
- mcp_ticketer/queue/manager.py +11 -4
- mcp_ticketer/queue/queue.py +15 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +190 -132
- mcp_ticketer/queue/worker.py +54 -25
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/METADATA +1 -1
- mcp_ticketer-0.3.2.dist-info/RECORD +59 -0
- mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/top_level.txt +0 -0
mcp_ticketer/mcp/server.py
CHANGED
|
@@ -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
|
|
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 = [
|
|
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 [
|
|
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 = [
|
|
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 = [
|
|
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 = [
|
|
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(
|
|
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,
|
|
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,
|
|
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"] = [
|
|
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": [
|
|
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(
|
|
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": {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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": {
|
|
1178
|
-
|
|
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": {
|
|
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": {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
"
|
|
1205
|
-
|
|
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": {
|
|
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": {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
"
|
|
1233
|
-
|
|
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": {
|
|
1245
|
-
|
|
1246
|
-
|
|
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": {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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": {
|
|
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": {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
"
|
|
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
|
|
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(
|
|
1745
|
-
key, value = line.split(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
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[
|
|
2152
|
+
if report["recommendations"]:
|
|
1998
2153
|
summary += "RECOMMENDATIONS:\n"
|
|
1999
|
-
for rec in report[
|
|
2154
|
+
for rec in report["recommendations"]:
|
|
2000
2155
|
summary += f"{rec}\n"
|
|
2001
2156
|
|
|
2002
2157
|
return {
|