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/__version__.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
mcp_ticketer/adapters/github.py
CHANGED
|
@@ -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(
|
|
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")
|
|
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
|
mcp_ticketer/adapters/jira.py
CHANGED
|
@@ -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(
|
|
38
|
-
return datetime.fromisoformat(date_str.replace(
|
|
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
|
|
39
|
+
if re.match(r".*[+-]\d{4}$", date_str):
|
|
42
40
|
# Insert colon in timezone: -0400 -> -04:00
|
|
43
|
-
date_str = re.sub(r
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|