mcp-ticketer 2.0.1__py3-none-any.whl → 2.2.13__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 (73) hide show
  1. mcp_ticketer/__version__.py +1 -1
  2. mcp_ticketer/_version_scm.py +1 -0
  3. mcp_ticketer/adapters/aitrackdown.py +122 -0
  4. mcp_ticketer/adapters/asana/adapter.py +121 -0
  5. mcp_ticketer/adapters/github/__init__.py +26 -0
  6. mcp_ticketer/adapters/{github.py → github/adapter.py} +1506 -365
  7. mcp_ticketer/adapters/github/client.py +335 -0
  8. mcp_ticketer/adapters/github/mappers.py +797 -0
  9. mcp_ticketer/adapters/github/queries.py +692 -0
  10. mcp_ticketer/adapters/github/types.py +460 -0
  11. mcp_ticketer/adapters/jira/__init__.py +35 -0
  12. mcp_ticketer/adapters/{jira.py → jira/adapter.py} +250 -678
  13. mcp_ticketer/adapters/jira/client.py +271 -0
  14. mcp_ticketer/adapters/jira/mappers.py +246 -0
  15. mcp_ticketer/adapters/jira/queries.py +216 -0
  16. mcp_ticketer/adapters/jira/types.py +304 -0
  17. mcp_ticketer/adapters/linear/adapter.py +1000 -92
  18. mcp_ticketer/adapters/linear/client.py +91 -1
  19. mcp_ticketer/adapters/linear/mappers.py +107 -0
  20. mcp_ticketer/adapters/linear/queries.py +112 -2
  21. mcp_ticketer/adapters/linear/types.py +50 -10
  22. mcp_ticketer/cli/configure.py +524 -89
  23. mcp_ticketer/cli/install_mcp_server.py +418 -0
  24. mcp_ticketer/cli/main.py +10 -0
  25. mcp_ticketer/cli/mcp_configure.py +177 -49
  26. mcp_ticketer/cli/platform_installer.py +9 -0
  27. mcp_ticketer/cli/setup_command.py +157 -1
  28. mcp_ticketer/cli/ticket_commands.py +443 -81
  29. mcp_ticketer/cli/utils.py +113 -0
  30. mcp_ticketer/core/__init__.py +28 -0
  31. mcp_ticketer/core/adapter.py +367 -1
  32. mcp_ticketer/core/milestone_manager.py +252 -0
  33. mcp_ticketer/core/models.py +345 -0
  34. mcp_ticketer/core/project_utils.py +281 -0
  35. mcp_ticketer/core/project_validator.py +376 -0
  36. mcp_ticketer/core/session_state.py +6 -1
  37. mcp_ticketer/core/state_matcher.py +36 -3
  38. mcp_ticketer/mcp/server/__main__.py +2 -1
  39. mcp_ticketer/mcp/server/routing.py +68 -0
  40. mcp_ticketer/mcp/server/tools/__init__.py +7 -4
  41. mcp_ticketer/mcp/server/tools/attachment_tools.py +3 -1
  42. mcp_ticketer/mcp/server/tools/config_tools.py +233 -35
  43. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  44. mcp_ticketer/mcp/server/tools/search_tools.py +30 -1
  45. mcp_ticketer/mcp/server/tools/ticket_tools.py +37 -1
  46. mcp_ticketer/queue/queue.py +68 -0
  47. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/METADATA +33 -3
  48. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/RECORD +72 -36
  49. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  50. py_mcp_installer/examples/phase3_demo.py +178 -0
  51. py_mcp_installer/scripts/manage_version.py +54 -0
  52. py_mcp_installer/setup.py +6 -0
  53. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  54. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  55. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  56. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  57. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  58. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  59. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  60. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  61. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  62. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  63. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  64. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  65. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  66. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  67. py_mcp_installer/tests/__init__.py +0 -0
  68. py_mcp_installer/tests/platforms/__init__.py +0 -0
  69. py_mcp_installer/tests/test_platform_detector.py +17 -0
  70. mcp_ticketer-2.0.1.dist-info/top_level.txt +0 -1
  71. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  72. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  73. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
mcp_ticketer/cli/utils.py CHANGED
@@ -5,6 +5,7 @@ import json
5
5
  import logging
6
6
  import os
7
7
  from collections.abc import Callable
8
+ from datetime import datetime
8
9
  from functools import wraps
9
10
  from pathlib import Path
10
11
  from typing import Any, TypeVar
@@ -23,6 +24,118 @@ T = TypeVar("T")
23
24
  console = Console()
24
25
  logger = logging.getLogger(__name__)
25
26
 
27
+ # Get version from package
28
+ try:
29
+ from importlib.metadata import version
30
+
31
+ __version__ = version("mcp-ticketer")
32
+ except Exception:
33
+ __version__ = "2.2.2" # Fallback to known version
34
+
35
+
36
+ def format_json_response(status: str, data: Any, message: str | None = None) -> str:
37
+ """Format response as JSON with standard structure.
38
+
39
+ Args:
40
+ status: Response status - "success" or "error"
41
+ data: Response data (dict, list, or any JSON-serializable type)
42
+ message: Optional human-readable message
43
+
44
+ Returns:
45
+ JSON string with standard format
46
+
47
+ Example:
48
+ >>> format_json_response("success", {"id": "1M-123", "title": "Fix bug"})
49
+ {
50
+ "status": "success",
51
+ "data": {"id": "1M-123", "title": "Fix bug"},
52
+ "metadata": {
53
+ "timestamp": "2025-12-05T10:30:00Z",
54
+ "version": "2.2.2"
55
+ }
56
+ }
57
+ """
58
+ response = {
59
+ "status": status,
60
+ "data": data,
61
+ "metadata": {
62
+ "timestamp": datetime.utcnow().isoformat() + "Z",
63
+ "version": __version__,
64
+ },
65
+ }
66
+ if message:
67
+ response["message"] = message
68
+ return json.dumps(response, indent=2, default=str)
69
+
70
+
71
+ def format_error_json(error: str | Exception, ticket_id: str | None = None) -> str:
72
+ """Format error response as JSON.
73
+
74
+ Args:
75
+ error: Error message or exception
76
+ ticket_id: Optional ticket ID that caused the error
77
+
78
+ Returns:
79
+ JSON error response
80
+ """
81
+ error_msg = str(error)
82
+ data = {"error": error_msg}
83
+ if ticket_id:
84
+ data["ticket_id"] = ticket_id
85
+ return format_json_response("error", data, message=error_msg)
86
+
87
+
88
+ def serialize_task(task: Task) -> dict[str, Any]:
89
+ """Serialize Task object to JSON-compatible dict.
90
+
91
+ Args:
92
+ task: Task object to serialize
93
+
94
+ Returns:
95
+ Dictionary with task fields
96
+ """
97
+ task_dict = {
98
+ "id": task.id,
99
+ "title": task.title,
100
+ "state": task.state,
101
+ "priority": task.priority,
102
+ "description": task.description,
103
+ "tags": task.tags or [],
104
+ "assignee": task.assignee,
105
+ }
106
+
107
+ # Add timestamps if available
108
+ if task.created_at:
109
+ task_dict["created_at"] = (
110
+ task.created_at.isoformat()
111
+ if hasattr(task.created_at, "isoformat")
112
+ else str(task.created_at)
113
+ )
114
+ if task.updated_at:
115
+ task_dict["updated_at"] = (
116
+ task.updated_at.isoformat()
117
+ if hasattr(task.updated_at, "isoformat")
118
+ else str(task.updated_at)
119
+ )
120
+
121
+ # Add parent relationships
122
+ if hasattr(task, "parent_epic") and task.parent_epic:
123
+ task_dict["parent_epic"] = task.parent_epic
124
+ if hasattr(task, "parent_issue") and task.parent_issue:
125
+ task_dict["parent_issue"] = task.parent_issue
126
+
127
+ # Add URL from metadata if available
128
+ if task.metadata:
129
+ if isinstance(task.metadata, dict):
130
+ # Linear metadata structure
131
+ if "linear" in task.metadata and "url" in task.metadata["linear"]:
132
+ task_dict["url"] = task.metadata["linear"]["url"]
133
+ # Generic url field
134
+ elif "url" in task.metadata:
135
+ task_dict["url"] = task.metadata["url"]
136
+
137
+ return task_dict
138
+
26
139
 
27
140
  class CommonPatterns:
28
141
  """Common CLI patterns and utilities."""
@@ -8,17 +8,28 @@ from .instructions import (
8
8
  TicketInstructionsManager,
9
9
  get_instructions,
10
10
  )
11
+ from .milestone_manager import MilestoneManager
11
12
  from .models import (
12
13
  Attachment,
13
14
  Comment,
14
15
  Epic,
16
+ Milestone,
15
17
  Priority,
18
+ Project,
19
+ ProjectScope,
20
+ ProjectState,
21
+ ProjectStatistics,
16
22
  ProjectUpdate,
17
23
  ProjectUpdateHealth,
24
+ ProjectVisibility,
18
25
  Task,
19
26
  TicketState,
20
27
  TicketType,
21
28
  )
29
+ from .project_utils import (
30
+ epic_to_project,
31
+ project_to_epic,
32
+ )
22
33
  from .registry import AdapterRegistry
23
34
  from .state_matcher import (
24
35
  SemanticStateMatcher,
@@ -28,22 +39,39 @@ from .state_matcher import (
28
39
  )
29
40
 
30
41
  __all__ = [
42
+ # Core ticket models
31
43
  "Epic",
32
44
  "Task",
33
45
  "Comment",
34
46
  "Attachment",
47
+ "Milestone",
48
+ # Project models
49
+ "Project",
50
+ "ProjectScope",
51
+ "ProjectState",
52
+ "ProjectStatistics",
53
+ "ProjectVisibility",
35
54
  "ProjectUpdate",
36
55
  "ProjectUpdateHealth",
56
+ # Project utilities
57
+ "epic_to_project",
58
+ "project_to_epic",
59
+ # Enums
37
60
  "TicketState",
38
61
  "Priority",
39
62
  "TicketType",
63
+ # Adapters
40
64
  "BaseAdapter",
41
65
  "AdapterRegistry",
66
+ # Managers
67
+ "MilestoneManager",
42
68
  "TicketInstructionsManager",
69
+ # Instructions
43
70
  "InstructionsError",
44
71
  "InstructionsNotFoundError",
45
72
  "InstructionsValidationError",
46
73
  "get_instructions",
74
+ # State matching
47
75
  "SemanticStateMatcher",
48
76
  "StateMatchResult",
49
77
  "ValidationResult",
@@ -4,9 +4,22 @@ from __future__ import annotations
4
4
 
5
5
  import builtins
6
6
  from abc import ABC, abstractmethod
7
+ from datetime import datetime
7
8
  from typing import TYPE_CHECKING, Any, Generic, TypeVar
8
9
 
9
- from .models import Comment, Epic, SearchQuery, Task, TicketState, TicketType
10
+ from .models import (
11
+ Comment,
12
+ Epic,
13
+ Milestone,
14
+ Project,
15
+ ProjectScope,
16
+ ProjectState,
17
+ ProjectStatistics,
18
+ SearchQuery,
19
+ Task,
20
+ TicketState,
21
+ TicketType,
22
+ )
10
23
  from .state_matcher import get_state_matcher
11
24
 
12
25
  if TYPE_CHECKING:
@@ -612,3 +625,356 @@ class BaseAdapter(ABC, Generic[T]):
612
625
  async def close(self) -> None:
613
626
  """Close adapter and cleanup resources."""
614
627
  pass
628
+
629
+ # Milestone Operations (Phase 1 - Abstract methods)
630
+
631
+ @abstractmethod
632
+ async def milestone_create(
633
+ self,
634
+ name: str,
635
+ target_date: datetime | None = None,
636
+ labels: list[str] | None = None,
637
+ description: str = "",
638
+ project_id: str | None = None,
639
+ ) -> Milestone:
640
+ """Create a new milestone.
641
+
642
+ Args:
643
+ ----
644
+ name: Milestone name
645
+ target_date: Target completion date (ISO format: YYYY-MM-DD)
646
+ labels: Labels that define this milestone
647
+ description: Milestone description
648
+ project_id: Associated project ID
649
+
650
+ Returns:
651
+ -------
652
+ Created Milestone object
653
+
654
+ """
655
+ pass
656
+
657
+ @abstractmethod
658
+ async def milestone_get(self, milestone_id: str) -> Milestone | None:
659
+ """Get milestone by ID with progress calculation.
660
+
661
+ Args:
662
+ ----
663
+ milestone_id: Milestone identifier
664
+
665
+ Returns:
666
+ -------
667
+ Milestone object with calculated progress, None if not found
668
+
669
+ """
670
+ pass
671
+
672
+ @abstractmethod
673
+ async def milestone_list(
674
+ self,
675
+ project_id: str | None = None,
676
+ state: str | None = None,
677
+ ) -> builtins.list[Milestone]:
678
+ """List milestones with optional filters.
679
+
680
+ Args:
681
+ ----
682
+ project_id: Filter by project
683
+ state: Filter by state (open, active, completed, closed)
684
+
685
+ Returns:
686
+ -------
687
+ List of Milestone objects
688
+
689
+ """
690
+ pass
691
+
692
+ @abstractmethod
693
+ async def milestone_update(
694
+ self,
695
+ milestone_id: str,
696
+ name: str | None = None,
697
+ target_date: datetime | None = None,
698
+ state: str | None = None,
699
+ labels: list[str] | None = None,
700
+ description: str | None = None,
701
+ ) -> Milestone | None:
702
+ """Update milestone properties.
703
+
704
+ Args:
705
+ ----
706
+ milestone_id: Milestone identifier
707
+ name: New name (optional)
708
+ target_date: New target date (optional)
709
+ state: New state (optional)
710
+ labels: New labels (optional)
711
+ description: New description (optional)
712
+
713
+ Returns:
714
+ -------
715
+ Updated Milestone object, None if not found
716
+
717
+ """
718
+ pass
719
+
720
+ @abstractmethod
721
+ async def milestone_delete(self, milestone_id: str) -> bool:
722
+ """Delete milestone.
723
+
724
+ Args:
725
+ ----
726
+ milestone_id: Milestone identifier
727
+
728
+ Returns:
729
+ -------
730
+ True if deleted successfully, False otherwise
731
+
732
+ """
733
+ pass
734
+
735
+ @abstractmethod
736
+ async def milestone_get_issues(
737
+ self,
738
+ milestone_id: str,
739
+ state: str | None = None,
740
+ ) -> builtins.list[Task]:
741
+ """Get issues associated with milestone.
742
+
743
+ Args:
744
+ ----
745
+ milestone_id: Milestone identifier
746
+ state: Filter by issue state (optional)
747
+
748
+ Returns:
749
+ -------
750
+ List of Task objects (issues)
751
+
752
+ """
753
+ pass
754
+
755
+ # Project Operations (Phase 1 - Abstract methods)
756
+ # These methods are optional - adapters that don't support projects
757
+ # can raise NotImplementedError with a helpful message
758
+
759
+ async def project_list(
760
+ self,
761
+ scope: ProjectScope | None = None,
762
+ state: ProjectState | None = None,
763
+ limit: int = 50,
764
+ offset: int = 0,
765
+ ) -> builtins.list[Project]:
766
+ """List projects with optional filters.
767
+
768
+ Args:
769
+ ----
770
+ scope: Filter by project scope (user, team, org, repo)
771
+ state: Filter by project state
772
+ limit: Maximum results (default: 50)
773
+ offset: Pagination offset (default: 0)
774
+
775
+ Returns:
776
+ -------
777
+ List of Project objects
778
+
779
+ Raises:
780
+ ------
781
+ NotImplementedError: If adapter doesn't support projects
782
+
783
+ """
784
+ raise NotImplementedError(
785
+ f"{self.__class__.__name__} does not support project operations. "
786
+ "Use Epic operations for this adapter."
787
+ )
788
+
789
+ async def project_get(self, project_id: str) -> Project | None:
790
+ """Get project by ID.
791
+
792
+ Args:
793
+ ----
794
+ project_id: Project identifier (platform-specific or unified)
795
+
796
+ Returns:
797
+ -------
798
+ Project object if found, None otherwise
799
+
800
+ Raises:
801
+ ------
802
+ NotImplementedError: If adapter doesn't support projects
803
+
804
+ """
805
+ raise NotImplementedError(
806
+ f"{self.__class__.__name__} does not support project operations. "
807
+ "Use get_epic() for this adapter."
808
+ )
809
+
810
+ async def project_create(
811
+ self,
812
+ name: str,
813
+ description: str | None = None,
814
+ state: ProjectState = ProjectState.PLANNED,
815
+ target_date: datetime | None = None,
816
+ **kwargs: Any,
817
+ ) -> Project:
818
+ """Create new project.
819
+
820
+ Args:
821
+ ----
822
+ name: Project name (required)
823
+ description: Project description
824
+ state: Initial project state (default: PLANNED)
825
+ target_date: Target completion date
826
+ **kwargs: Platform-specific additional fields
827
+
828
+ Returns:
829
+ -------
830
+ Created Project object
831
+
832
+ Raises:
833
+ ------
834
+ NotImplementedError: If adapter doesn't support projects
835
+
836
+ """
837
+ raise NotImplementedError(
838
+ f"{self.__class__.__name__} does not support project operations. "
839
+ "Use create_epic() for this adapter."
840
+ )
841
+
842
+ async def project_update(
843
+ self,
844
+ project_id: str,
845
+ name: str | None = None,
846
+ description: str | None = None,
847
+ state: ProjectState | None = None,
848
+ **kwargs: Any,
849
+ ) -> Project | None:
850
+ """Update project properties.
851
+
852
+ Args:
853
+ ----
854
+ project_id: Project identifier
855
+ name: New name (optional)
856
+ description: New description (optional)
857
+ state: New state (optional)
858
+ **kwargs: Platform-specific fields to update
859
+
860
+ Returns:
861
+ -------
862
+ Updated Project object, None if not found
863
+
864
+ Raises:
865
+ ------
866
+ NotImplementedError: If adapter doesn't support projects
867
+
868
+ """
869
+ raise NotImplementedError(
870
+ f"{self.__class__.__name__} does not support project operations."
871
+ )
872
+
873
+ async def project_delete(self, project_id: str) -> bool:
874
+ """Delete or archive project.
875
+
876
+ Args:
877
+ ----
878
+ project_id: Project identifier
879
+
880
+ Returns:
881
+ -------
882
+ True if deleted successfully, False otherwise
883
+
884
+ Raises:
885
+ ------
886
+ NotImplementedError: If adapter doesn't support projects
887
+
888
+ """
889
+ raise NotImplementedError(
890
+ f"{self.__class__.__name__} does not support project operations."
891
+ )
892
+
893
+ async def project_get_issues(
894
+ self, project_id: str, state: TicketState | None = None
895
+ ) -> builtins.list[Task]:
896
+ """Get all issues in project.
897
+
898
+ Args:
899
+ ----
900
+ project_id: Project identifier
901
+ state: Filter by issue state (optional)
902
+
903
+ Returns:
904
+ -------
905
+ List of Task objects (issues in project)
906
+
907
+ Raises:
908
+ ------
909
+ NotImplementedError: If adapter doesn't support projects
910
+
911
+ """
912
+ raise NotImplementedError(
913
+ f"{self.__class__.__name__} does not support project operations. "
914
+ "Use list_issues_by_epic() for this adapter."
915
+ )
916
+
917
+ async def project_add_issue(self, project_id: str, issue_id: str) -> bool:
918
+ """Add issue to project.
919
+
920
+ Args:
921
+ ----
922
+ project_id: Project identifier
923
+ issue_id: Issue identifier to add
924
+
925
+ Returns:
926
+ -------
927
+ True if added successfully, False otherwise
928
+
929
+ Raises:
930
+ ------
931
+ NotImplementedError: If adapter doesn't support projects
932
+
933
+ """
934
+ raise NotImplementedError(
935
+ f"{self.__class__.__name__} does not support project operations."
936
+ )
937
+
938
+ async def project_remove_issue(self, project_id: str, issue_id: str) -> bool:
939
+ """Remove issue from project.
940
+
941
+ Args:
942
+ ----
943
+ project_id: Project identifier
944
+ issue_id: Issue identifier to remove
945
+
946
+ Returns:
947
+ -------
948
+ True if removed successfully, False otherwise
949
+
950
+ Raises:
951
+ ------
952
+ NotImplementedError: If adapter doesn't support projects
953
+
954
+ """
955
+ raise NotImplementedError(
956
+ f"{self.__class__.__name__} does not support project operations."
957
+ )
958
+
959
+ async def project_get_statistics(self, project_id: str) -> ProjectStatistics:
960
+ """Get project statistics and metrics.
961
+
962
+ Calculates or retrieves statistics including issue counts by state,
963
+ progress percentage, and velocity metrics.
964
+
965
+ Args:
966
+ ----
967
+ project_id: Project identifier
968
+
969
+ Returns:
970
+ -------
971
+ ProjectStatistics object with calculated metrics
972
+
973
+ Raises:
974
+ ------
975
+ NotImplementedError: If adapter doesn't support projects
976
+
977
+ """
978
+ raise NotImplementedError(
979
+ f"{self.__class__.__name__} does not support project statistics."
980
+ )