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
@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.2"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -245,15 +245,14 @@ class AITrackdownAdapter(BaseAdapter[Task]):
245
245
 
246
246
  Returns:
247
247
  Created Epic instance
248
+
248
249
  """
249
- epic = Epic(
250
- title=title,
251
- description=description,
252
- **kwargs
253
- )
250
+ epic = Epic(title=title, description=description, **kwargs)
254
251
  return await self.create(epic)
255
252
 
256
- async def create_issue(self, title: str, parent_epic: str = None, description: str = None, **kwargs) -> Task:
253
+ async def create_issue(
254
+ self, title: str, parent_epic: str = None, description: str = None, **kwargs
255
+ ) -> Task:
257
256
  """Create a new issue.
258
257
 
259
258
  Args:
@@ -264,16 +263,16 @@ class AITrackdownAdapter(BaseAdapter[Task]):
264
263
 
265
264
  Returns:
266
265
  Created Task instance (representing an issue)
266
+
267
267
  """
268
268
  task = Task(
269
- title=title,
270
- description=description,
271
- parent_epic=parent_epic,
272
- **kwargs
269
+ title=title, description=description, parent_epic=parent_epic, **kwargs
273
270
  )
274
271
  return await self.create(task)
275
272
 
276
- async def create_task(self, title: str, parent_id: str, description: str = None, **kwargs) -> Task:
273
+ async def create_task(
274
+ self, title: str, parent_id: str, description: str = None, **kwargs
275
+ ) -> Task:
277
276
  """Create a new task under an issue.
278
277
 
279
278
  Args:
@@ -284,12 +283,10 @@ class AITrackdownAdapter(BaseAdapter[Task]):
284
283
 
285
284
  Returns:
286
285
  Created Task instance
286
+
287
287
  """
288
288
  task = Task(
289
- title=title,
290
- description=description,
291
- parent_issue=parent_id,
292
- **kwargs
289
+ title=title, description=description, parent_issue=parent_id, **kwargs
293
290
  )
294
291
  return await self.create(task)
295
292
 
@@ -1,7 +1,6 @@
1
1
  """GitHub adapter implementation using REST API v3 and GraphQL API v4."""
2
2
 
3
3
  import builtins
4
- import os
5
4
  import re
6
5
  from datetime import datetime
7
6
  from typing import Any, Dict, List, Optional
@@ -9,9 +8,9 @@ from typing import Any, Dict, List, Optional
9
8
  import httpx
10
9
 
11
10
  from ..core.adapter import BaseAdapter
11
+ from ..core.env_loader import load_adapter_config, validate_adapter_config
12
12
  from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
13
13
  from ..core.registry import AdapterRegistry
14
- from ..core.env_loader import load_adapter_config, validate_adapter_config
15
14
 
16
15
 
17
16
  class GitHubStateMapping:
@@ -158,11 +157,15 @@ class GitHubAdapter(BaseAdapter[Task]):
158
157
  # Validate required configuration
159
158
  missing_keys = validate_adapter_config("github", full_config)
160
159
  if missing_keys:
161
- raise ValueError(f"GitHub adapter missing required configuration: {', '.join(missing_keys)}")
160
+ raise ValueError(
161
+ f"GitHub adapter missing required configuration: {', '.join(missing_keys)}"
162
+ )
162
163
 
163
164
  # Get authentication token - support both 'api_key' and 'token' for compatibility
164
165
  self.token = (
165
- full_config.get("api_key") or full_config.get("token") or full_config.get("token")
166
+ full_config.get("api_key")
167
+ or full_config.get("token")
168
+ or full_config.get("token")
166
169
  )
167
170
 
168
171
  # Get repository information
@@ -3,7 +3,6 @@
3
3
  import asyncio
4
4
  import builtins
5
5
  import logging
6
- import os
7
6
  import re
8
7
  from datetime import datetime
9
8
  from enum import Enum
@@ -13,16 +12,15 @@ import httpx
13
12
  from httpx import AsyncClient, HTTPStatusError, TimeoutException
14
13
 
15
14
  from ..core.adapter import BaseAdapter
15
+ from ..core.env_loader import load_adapter_config, validate_adapter_config
16
16
  from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
17
17
  from ..core.registry import AdapterRegistry
18
- from ..core.env_loader import load_adapter_config, validate_adapter_config
19
18
 
20
19
  logger = logging.getLogger(__name__)
21
20
 
22
21
 
23
22
  def parse_jira_datetime(date_str: str) -> Optional[datetime]:
24
- """
25
- Parse JIRA datetime strings which can be in various formats.
23
+ """Parse JIRA datetime strings which can be in various formats.
26
24
 
27
25
  JIRA can return dates in formats like:
28
26
  - 2025-10-24T14:12:18.771-0400
@@ -34,13 +32,13 @@ def parse_jira_datetime(date_str: str) -> Optional[datetime]:
34
32
 
35
33
  try:
36
34
  # Handle Z timezone
37
- if date_str.endswith('Z'):
38
- return datetime.fromisoformat(date_str.replace('Z', '+00:00'))
35
+ if date_str.endswith("Z"):
36
+ return datetime.fromisoformat(date_str.replace("Z", "+00:00"))
39
37
 
40
38
  # Handle timezone formats like -0400, +0500 (need to add colon)
41
- if re.match(r'.*[+-]\d{4}$', date_str):
39
+ if re.match(r".*[+-]\d{4}$", date_str):
42
40
  # Insert colon in timezone: -0400 -> -04:00
43
- date_str = re.sub(r'([+-]\d{2})(\d{2})$', r'\1:\2', date_str)
41
+ date_str = re.sub(r"([+-]\d{2})(\d{2})$", r"\1:\2", date_str)
44
42
 
45
43
  return datetime.fromisoformat(date_str)
46
44
 
@@ -50,14 +48,14 @@ def parse_jira_datetime(date_str: str) -> Optional[datetime]:
50
48
 
51
49
 
52
50
  def extract_text_from_adf(adf_content: Union[str, Dict[str, Any]]) -> str:
53
- """
54
- Extract plain text from Atlassian Document Format (ADF).
51
+ """Extract plain text from Atlassian Document Format (ADF).
55
52
 
56
53
  Args:
57
54
  adf_content: Either a string (already plain text) or ADF document dict
58
55
 
59
56
  Returns:
60
57
  Plain text string extracted from the ADF content
58
+
61
59
  """
62
60
  if isinstance(adf_content, str):
63
61
  return adf_content
@@ -136,7 +134,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
136
134
  # Validate required configuration
137
135
  missing_keys = validate_adapter_config("jira", full_config)
138
136
  if missing_keys:
139
- raise ValueError(f"JIRA adapter missing required configuration: {', '.join(missing_keys)}")
137
+ raise ValueError(
138
+ f"JIRA adapter missing required configuration: {', '.join(missing_keys)}"
139
+ )
140
140
 
141
141
  # Configuration
142
142
  self.server = full_config.get("server", "")
@@ -803,14 +803,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
803
803
  "content": [
804
804
  {
805
805
  "type": "paragraph",
806
- "content": [
807
- {
808
- "type": "text",
809
- "text": comment.content
810
- }
811
- ]
806
+ "content": [{"type": "text", "text": comment.content}],
812
807
  }
813
- ]
808
+ ],
814
809
  }
815
810
  }
816
811
 
@@ -821,7 +816,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
821
816
 
822
817
  # Update comment with JIRA data
823
818
  comment.id = result.get("id")
824
- comment.created_at = parse_jira_datetime(result.get("created")) or datetime.now()
819
+ comment.created_at = (
820
+ parse_jira_datetime(result.get("created")) or datetime.now()
821
+ )
825
822
  comment.author = result.get("author", {}).get("displayName", comment.author)
826
823
  comment.metadata["jira"] = result
827
824
 
@@ -950,7 +947,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
950
947
 
951
948
  try:
952
949
  # Get project role users
953
- project_data = await self._make_request("GET", f"project/{self.project_key}")
950
+ project_data = await self._make_request(
951
+ "GET", f"project/{self.project_key}"
952
+ )
954
953
 
955
954
  # Get users from project roles
956
955
  users = []
@@ -959,7 +958,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
959
958
  # Extract role ID from URL
960
959
  role_id = role_url.split("/")[-1]
961
960
  try:
962
- role_data = await self._make_request("GET", f"project/{self.project_key}/role/{role_id}")
961
+ role_data = await self._make_request(
962
+ "GET", f"project/{self.project_key}/role/{role_id}"
963
+ )
963
964
  if "actors" in role_data:
964
965
  for actor in role_data["actors"]:
965
966
  if actor.get("type") == "atlassian-user-role-actor":
@@ -985,7 +986,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
985
986
  users_data = await self._make_request(
986
987
  "GET",
987
988
  "user/assignable/search",
988
- params={"project": self.project_key, "maxResults": 50}
989
+ params={"project": self.project_key, "maxResults": 50},
989
990
  )
990
991
  return users_data if isinstance(users_data, list) else []
991
992
  except Exception:
@@ -11,7 +11,7 @@ The adapter is split into multiple modules for better organization:
11
11
 
12
12
  Usage:
13
13
  from mcp_ticketer.adapters.linear import LinearAdapter
14
-
14
+
15
15
  config = {
16
16
  "api_key": "your_linear_api_key",
17
17
  "team_id": "your_team_id"