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
|
@@ -238,7 +238,9 @@ WORKFLOW_STATES_QUERY = """
|
|
|
238
238
|
}
|
|
239
239
|
"""
|
|
240
240
|
|
|
241
|
-
CREATE_ISSUE_MUTATION =
|
|
241
|
+
CREATE_ISSUE_MUTATION = (
|
|
242
|
+
ALL_FRAGMENTS
|
|
243
|
+
+ """
|
|
242
244
|
mutation CreateIssue($input: IssueCreateInput!) {
|
|
243
245
|
issueCreate(input: $input) {
|
|
244
246
|
success
|
|
@@ -248,8 +250,11 @@ CREATE_ISSUE_MUTATION = ALL_FRAGMENTS + """
|
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
"""
|
|
253
|
+
)
|
|
251
254
|
|
|
252
|
-
UPDATE_ISSUE_MUTATION =
|
|
255
|
+
UPDATE_ISSUE_MUTATION = (
|
|
256
|
+
ALL_FRAGMENTS
|
|
257
|
+
+ """
|
|
253
258
|
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
|
|
254
259
|
issueUpdate(id: $id, input: $input) {
|
|
255
260
|
success
|
|
@@ -259,8 +264,11 @@ UPDATE_ISSUE_MUTATION = ALL_FRAGMENTS + """
|
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
"""
|
|
267
|
+
)
|
|
262
268
|
|
|
263
|
-
LIST_ISSUES_QUERY =
|
|
269
|
+
LIST_ISSUES_QUERY = (
|
|
270
|
+
ISSUE_LIST_FRAGMENTS
|
|
271
|
+
+ """
|
|
264
272
|
query ListIssues($filter: IssueFilter, $first: Int!) {
|
|
265
273
|
issues(
|
|
266
274
|
filter: $filter
|
|
@@ -277,8 +285,11 @@ LIST_ISSUES_QUERY = ISSUE_LIST_FRAGMENTS + """
|
|
|
277
285
|
}
|
|
278
286
|
}
|
|
279
287
|
"""
|
|
288
|
+
)
|
|
280
289
|
|
|
281
|
-
SEARCH_ISSUES_QUERY =
|
|
290
|
+
SEARCH_ISSUES_QUERY = (
|
|
291
|
+
ISSUE_LIST_FRAGMENTS
|
|
292
|
+
+ """
|
|
282
293
|
query SearchIssues($filter: IssueFilter, $first: Int!) {
|
|
283
294
|
issues(
|
|
284
295
|
filter: $filter
|
|
@@ -291,6 +302,7 @@ SEARCH_ISSUES_QUERY = ISSUE_LIST_FRAGMENTS + """
|
|
|
291
302
|
}
|
|
292
303
|
}
|
|
293
304
|
"""
|
|
305
|
+
)
|
|
294
306
|
|
|
295
307
|
GET_CYCLES_QUERY = """
|
|
296
308
|
query GetCycles($filter: CycleFilter) {
|
|
@@ -336,7 +348,9 @@ SEARCH_ISSUE_BY_IDENTIFIER_QUERY = """
|
|
|
336
348
|
}
|
|
337
349
|
"""
|
|
338
350
|
|
|
339
|
-
LIST_PROJECTS_QUERY =
|
|
351
|
+
LIST_PROJECTS_QUERY = (
|
|
352
|
+
PROJECT_FRAGMENT
|
|
353
|
+
+ """
|
|
340
354
|
query ListProjects($filter: ProjectFilter, $first: Int!) {
|
|
341
355
|
projects(filter: $filter, first: $first, orderBy: updatedAt) {
|
|
342
356
|
nodes {
|
|
@@ -345,8 +359,11 @@ LIST_PROJECTS_QUERY = PROJECT_FRAGMENT + """
|
|
|
345
359
|
}
|
|
346
360
|
}
|
|
347
361
|
"""
|
|
362
|
+
)
|
|
348
363
|
|
|
349
|
-
CREATE_SUB_ISSUE_MUTATION =
|
|
364
|
+
CREATE_SUB_ISSUE_MUTATION = (
|
|
365
|
+
ALL_FRAGMENTS
|
|
366
|
+
+ """
|
|
350
367
|
mutation CreateSubIssue($input: IssueCreateInput!) {
|
|
351
368
|
issueCreate(input: $input) {
|
|
352
369
|
success
|
|
@@ -356,11 +373,15 @@ CREATE_SUB_ISSUE_MUTATION = ALL_FRAGMENTS + """
|
|
|
356
373
|
}
|
|
357
374
|
}
|
|
358
375
|
"""
|
|
376
|
+
)
|
|
359
377
|
|
|
360
|
-
GET_CURRENT_USER_QUERY =
|
|
378
|
+
GET_CURRENT_USER_QUERY = (
|
|
379
|
+
USER_FRAGMENT
|
|
380
|
+
+ """
|
|
361
381
|
query GetCurrentUser {
|
|
362
382
|
viewer {
|
|
363
383
|
...UserFields
|
|
364
384
|
}
|
|
365
385
|
}
|
|
366
386
|
"""
|
|
387
|
+
)
|
|
@@ -10,39 +10,39 @@ from mcp_ticketer.core.models import Priority, TicketState
|
|
|
10
10
|
|
|
11
11
|
class LinearPriorityMapping:
|
|
12
12
|
"""Mapping between universal Priority and Linear priority values."""
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# Linear uses numeric priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
|
|
15
15
|
TO_LINEAR: Dict[Priority, int] = {
|
|
16
16
|
Priority.CRITICAL: 1, # Urgent
|
|
17
|
-
Priority.HIGH: 2,
|
|
18
|
-
Priority.MEDIUM: 3,
|
|
19
|
-
Priority.LOW: 4,
|
|
17
|
+
Priority.HIGH: 2, # High
|
|
18
|
+
Priority.MEDIUM: 3, # Medium
|
|
19
|
+
Priority.LOW: 4, # Low
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
FROM_LINEAR: Dict[int, Priority] = {
|
|
23
|
-
0: Priority.LOW,
|
|
23
|
+
0: Priority.LOW, # No priority -> Low
|
|
24
24
|
1: Priority.CRITICAL, # Urgent -> Critical
|
|
25
|
-
2: Priority.HIGH,
|
|
26
|
-
3: Priority.MEDIUM,
|
|
27
|
-
4: Priority.LOW,
|
|
25
|
+
2: Priority.HIGH, # High -> High
|
|
26
|
+
3: Priority.MEDIUM, # Medium -> Medium
|
|
27
|
+
4: Priority.LOW, # Low -> Low
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class LinearStateMapping:
|
|
32
32
|
"""Mapping between universal TicketState and Linear workflow state types."""
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
# Linear workflow state types
|
|
35
35
|
TO_LINEAR: Dict[TicketState, str] = {
|
|
36
36
|
TicketState.OPEN: "unstarted",
|
|
37
37
|
TicketState.IN_PROGRESS: "started",
|
|
38
38
|
TicketState.READY: "unstarted", # No direct equivalent, use unstarted
|
|
39
|
-
TicketState.TESTED: "started",
|
|
39
|
+
TicketState.TESTED: "started", # No direct equivalent, use started
|
|
40
40
|
TicketState.DONE: "completed",
|
|
41
41
|
TicketState.CLOSED: "canceled",
|
|
42
42
|
TicketState.WAITING: "unstarted",
|
|
43
43
|
TicketState.BLOCKED: "unstarted",
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
FROM_LINEAR: Dict[str, TicketState] = {
|
|
47
47
|
"backlog": TicketState.OPEN,
|
|
48
48
|
"unstarted": TicketState.OPEN,
|
|
@@ -54,7 +54,7 @@ class LinearStateMapping:
|
|
|
54
54
|
|
|
55
55
|
class LinearWorkflowStateType(Enum):
|
|
56
56
|
"""Linear workflow state types."""
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
BACKLOG = "backlog"
|
|
59
59
|
UNSTARTED = "unstarted"
|
|
60
60
|
STARTED = "started"
|
|
@@ -64,7 +64,7 @@ class LinearWorkflowStateType(Enum):
|
|
|
64
64
|
|
|
65
65
|
class LinearProjectState(Enum):
|
|
66
66
|
"""Linear project states."""
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
PLANNED = "planned"
|
|
69
69
|
STARTED = "started"
|
|
70
70
|
COMPLETED = "completed"
|
|
@@ -74,7 +74,7 @@ class LinearProjectState(Enum):
|
|
|
74
74
|
|
|
75
75
|
class LinearIssueRelationType(Enum):
|
|
76
76
|
"""Linear issue relation types."""
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
BLOCKS = "blocks"
|
|
79
79
|
BLOCKED_BY = "blockedBy"
|
|
80
80
|
DUPLICATE = "duplicate"
|
|
@@ -84,55 +84,59 @@ class LinearIssueRelationType(Enum):
|
|
|
84
84
|
|
|
85
85
|
class LinearCommentType(Enum):
|
|
86
86
|
"""Linear comment types."""
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
COMMENT = "comment"
|
|
89
89
|
SYSTEM = "system"
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def get_linear_priority(priority: Priority) -> int:
|
|
93
93
|
"""Convert universal Priority to Linear priority value.
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
Args:
|
|
96
96
|
priority: Universal priority enum
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
Returns:
|
|
99
99
|
Linear priority integer (0-4)
|
|
100
|
+
|
|
100
101
|
"""
|
|
101
102
|
return LinearPriorityMapping.TO_LINEAR.get(priority, 3) # Default to Medium
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
def get_universal_priority(linear_priority: int) -> Priority:
|
|
105
106
|
"""Convert Linear priority value to universal Priority.
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
Args:
|
|
108
109
|
linear_priority: Linear priority integer (0-4)
|
|
109
|
-
|
|
110
|
+
|
|
110
111
|
Returns:
|
|
111
112
|
Universal priority enum
|
|
113
|
+
|
|
112
114
|
"""
|
|
113
115
|
return LinearPriorityMapping.FROM_LINEAR.get(linear_priority, Priority.MEDIUM)
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
def get_linear_state_type(state: TicketState) -> str:
|
|
117
119
|
"""Convert universal TicketState to Linear workflow state type.
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
Args:
|
|
120
122
|
state: Universal ticket state enum
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
Returns:
|
|
123
125
|
Linear workflow state type string
|
|
126
|
+
|
|
124
127
|
"""
|
|
125
128
|
return LinearStateMapping.TO_LINEAR.get(state, "unstarted")
|
|
126
129
|
|
|
127
130
|
|
|
128
131
|
def get_universal_state(linear_state_type: str) -> TicketState:
|
|
129
132
|
"""Convert Linear workflow state type to universal TicketState.
|
|
130
|
-
|
|
133
|
+
|
|
131
134
|
Args:
|
|
132
135
|
linear_state_type: Linear workflow state type string
|
|
133
|
-
|
|
136
|
+
|
|
134
137
|
Returns:
|
|
135
138
|
Universal ticket state enum
|
|
139
|
+
|
|
136
140
|
"""
|
|
137
141
|
return LinearStateMapping.FROM_LINEAR.get(linear_state_type, TicketState.OPEN)
|
|
138
142
|
|
|
@@ -150,7 +154,7 @@ def build_issue_filter(
|
|
|
150
154
|
include_archived: bool = False,
|
|
151
155
|
) -> Dict[str, Any]:
|
|
152
156
|
"""Build a Linear issue filter from parameters.
|
|
153
|
-
|
|
157
|
+
|
|
154
158
|
Args:
|
|
155
159
|
state: Filter by ticket state
|
|
156
160
|
assignee_id: Filter by assignee Linear user ID
|
|
@@ -162,38 +166,39 @@ def build_issue_filter(
|
|
|
162
166
|
updated_after: Filter by update date (ISO string)
|
|
163
167
|
due_before: Filter by due date (ISO string)
|
|
164
168
|
include_archived: Whether to include archived issues
|
|
165
|
-
|
|
169
|
+
|
|
166
170
|
Returns:
|
|
167
171
|
Linear GraphQL filter object
|
|
172
|
+
|
|
168
173
|
"""
|
|
169
174
|
issue_filter: Dict[str, Any] = {}
|
|
170
|
-
|
|
175
|
+
|
|
171
176
|
# Team filter (required for most operations)
|
|
172
177
|
if team_id:
|
|
173
178
|
issue_filter["team"] = {"id": {"eq": team_id}}
|
|
174
|
-
|
|
179
|
+
|
|
175
180
|
# State filter
|
|
176
181
|
if state:
|
|
177
182
|
state_type = get_linear_state_type(state)
|
|
178
183
|
issue_filter["state"] = {"type": {"eq": state_type}}
|
|
179
|
-
|
|
184
|
+
|
|
180
185
|
# Assignee filter
|
|
181
186
|
if assignee_id:
|
|
182
187
|
issue_filter["assignee"] = {"id": {"eq": assignee_id}}
|
|
183
|
-
|
|
188
|
+
|
|
184
189
|
# Priority filter
|
|
185
190
|
if priority:
|
|
186
191
|
linear_priority = get_linear_priority(priority)
|
|
187
192
|
issue_filter["priority"] = {"eq": linear_priority}
|
|
188
|
-
|
|
193
|
+
|
|
189
194
|
# Project filter
|
|
190
195
|
if project_id:
|
|
191
196
|
issue_filter["project"] = {"id": {"eq": project_id}}
|
|
192
|
-
|
|
197
|
+
|
|
193
198
|
# Labels filter
|
|
194
199
|
if labels:
|
|
195
200
|
issue_filter["labels"] = {"some": {"name": {"in": labels}}}
|
|
196
|
-
|
|
201
|
+
|
|
197
202
|
# Date filters
|
|
198
203
|
if created_after:
|
|
199
204
|
issue_filter["createdAt"] = {"gte": created_after}
|
|
@@ -201,11 +206,11 @@ def build_issue_filter(
|
|
|
201
206
|
issue_filter["updatedAt"] = {"gte": updated_after}
|
|
202
207
|
if due_before:
|
|
203
208
|
issue_filter["dueDate"] = {"lte": due_before}
|
|
204
|
-
|
|
209
|
+
|
|
205
210
|
# Archived filter
|
|
206
211
|
if not include_archived:
|
|
207
212
|
issue_filter["archivedAt"] = {"null": True}
|
|
208
|
-
|
|
213
|
+
|
|
209
214
|
return issue_filter
|
|
210
215
|
|
|
211
216
|
|
|
@@ -215,63 +220,65 @@ def build_project_filter(
|
|
|
215
220
|
include_completed: bool = True,
|
|
216
221
|
) -> Dict[str, Any]:
|
|
217
222
|
"""Build a Linear project filter from parameters.
|
|
218
|
-
|
|
223
|
+
|
|
219
224
|
Args:
|
|
220
225
|
state: Filter by project state
|
|
221
226
|
team_id: Filter by team ID
|
|
222
227
|
include_completed: Whether to include completed projects
|
|
223
|
-
|
|
228
|
+
|
|
224
229
|
Returns:
|
|
225
230
|
Linear GraphQL filter object
|
|
231
|
+
|
|
226
232
|
"""
|
|
227
233
|
project_filter: Dict[str, Any] = {}
|
|
228
|
-
|
|
234
|
+
|
|
229
235
|
# Team filter
|
|
230
236
|
if team_id:
|
|
231
237
|
project_filter["teams"] = {"some": {"id": {"eq": team_id}}}
|
|
232
|
-
|
|
238
|
+
|
|
233
239
|
# State filter
|
|
234
240
|
if state:
|
|
235
241
|
project_filter["state"] = {"eq": state}
|
|
236
242
|
elif not include_completed:
|
|
237
243
|
# Exclude completed projects by default
|
|
238
244
|
project_filter["state"] = {"neq": "completed"}
|
|
239
|
-
|
|
245
|
+
|
|
240
246
|
return project_filter
|
|
241
247
|
|
|
242
248
|
|
|
243
249
|
def extract_linear_metadata(issue_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
244
250
|
"""Extract Linear-specific metadata from issue data.
|
|
245
|
-
|
|
251
|
+
|
|
246
252
|
Args:
|
|
247
253
|
issue_data: Raw Linear issue data from GraphQL
|
|
248
|
-
|
|
254
|
+
|
|
249
255
|
Returns:
|
|
250
256
|
Dictionary of Linear-specific metadata
|
|
257
|
+
|
|
251
258
|
"""
|
|
252
259
|
metadata = {}
|
|
253
|
-
|
|
260
|
+
|
|
254
261
|
# Extract Linear-specific fields
|
|
255
262
|
if "dueDate" in issue_data and issue_data["dueDate"]:
|
|
256
263
|
metadata["due_date"] = issue_data["dueDate"]
|
|
257
|
-
|
|
264
|
+
|
|
258
265
|
if "cycle" in issue_data and issue_data["cycle"]:
|
|
259
266
|
metadata["cycle_id"] = issue_data["cycle"]["id"]
|
|
260
267
|
metadata["cycle_name"] = issue_data["cycle"]["name"]
|
|
261
|
-
|
|
268
|
+
|
|
262
269
|
if "estimate" in issue_data and issue_data["estimate"]:
|
|
263
270
|
metadata["estimate"] = issue_data["estimate"]
|
|
264
|
-
|
|
271
|
+
|
|
265
272
|
if "branchName" in issue_data and issue_data["branchName"]:
|
|
266
273
|
metadata["branch_name"] = issue_data["branchName"]
|
|
267
|
-
|
|
274
|
+
|
|
268
275
|
if "url" in issue_data:
|
|
269
276
|
metadata["linear_url"] = issue_data["url"]
|
|
270
|
-
|
|
277
|
+
|
|
271
278
|
if "slaBreachesAt" in issue_data and issue_data["slaBreachesAt"]:
|
|
272
279
|
metadata["sla_breaches_at"] = issue_data["slaBreachesAt"]
|
|
273
|
-
|
|
280
|
+
|
|
274
281
|
if "customerTicketCount" in issue_data:
|
|
275
282
|
metadata["customer_ticket_count"] = issue_data["customerTicketCount"]
|
|
276
|
-
|
|
283
|
+
|
|
277
284
|
return metadata
|
mcp_ticketer/adapters/linear.py
CHANGED
|
@@ -8,8 +8,8 @@ For new code, import directly from the linear package:
|
|
|
8
8
|
from mcp_ticketer.adapters.linear import LinearAdapter
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
# Import the refactored adapter
|
|
12
|
-
from .linear import LinearAdapter
|
|
11
|
+
# Import the refactored adapter from the modular structure
|
|
12
|
+
from .linear.adapter import LinearAdapter
|
|
13
13
|
|
|
14
14
|
# Re-export for backward compatibility
|
|
15
15
|
__all__ = ["LinearAdapter"]
|