mcp-ticketer 0.3.5__py3-none-any.whl → 0.3.7__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/linear/adapter.py +44 -14
- mcp_ticketer/adapters/linear/queries.py +9 -7
- mcp_ticketer/mcp/server.py +40 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/METADATA +1 -1
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/RECORD +10 -10
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.3.7.dist-info}/top_level.txt +0 -0
mcp_ticketer/__version__.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
5
7
|
import os
|
|
6
8
|
from typing import Any
|
|
7
9
|
|
|
@@ -205,7 +207,7 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
205
207
|
)
|
|
206
208
|
|
|
207
209
|
workflow_states = {}
|
|
208
|
-
for state in result["
|
|
210
|
+
for state in result["team"]["states"]["nodes"]:
|
|
209
211
|
state_type = state["type"].lower()
|
|
210
212
|
if state_type not in workflow_states:
|
|
211
213
|
workflow_states[state_type] = state
|
|
@@ -218,12 +220,14 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
218
220
|
raise ValueError(f"Failed to load workflow states: {e}")
|
|
219
221
|
|
|
220
222
|
async def _load_team_labels(self, team_id: str) -> None:
|
|
221
|
-
"""Load and cache labels for the team.
|
|
223
|
+
"""Load and cache labels for the team with retry logic.
|
|
222
224
|
|
|
223
225
|
Args:
|
|
224
226
|
team_id: Linear team ID
|
|
225
227
|
|
|
226
228
|
"""
|
|
229
|
+
logger = logging.getLogger(__name__)
|
|
230
|
+
|
|
227
231
|
query = """
|
|
228
232
|
query GetTeamLabels($teamId: String!) {
|
|
229
233
|
team(id: $teamId) {
|
|
@@ -239,15 +243,32 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
239
243
|
}
|
|
240
244
|
"""
|
|
241
245
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
max_retries = 3
|
|
247
|
+
for attempt in range(max_retries):
|
|
248
|
+
try:
|
|
249
|
+
result = await self.client.execute_query(query, {"teamId": team_id})
|
|
250
|
+
labels = result.get("team", {}).get("labels", {}).get("nodes", [])
|
|
251
|
+
self._labels_cache = labels
|
|
252
|
+
logger.info(f"Loaded {len(labels)} labels for team {team_id}")
|
|
253
|
+
return # Success
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
if attempt < max_retries - 1:
|
|
257
|
+
wait_time = 2**attempt
|
|
258
|
+
logger.warning(
|
|
259
|
+
f"Failed to load labels (attempt {attempt + 1}/{max_retries}): {e}. "
|
|
260
|
+
f"Retrying in {wait_time}s..."
|
|
261
|
+
)
|
|
262
|
+
await asyncio.sleep(wait_time)
|
|
263
|
+
else:
|
|
264
|
+
logger.error(
|
|
265
|
+
f"Failed to load team labels after {max_retries} attempts: {e}",
|
|
266
|
+
exc_info=True,
|
|
267
|
+
)
|
|
268
|
+
self._labels_cache = [] # Explicitly empty on failure
|
|
248
269
|
|
|
249
270
|
async def _resolve_label_ids(self, label_names: list[str]) -> list[str]:
|
|
250
|
-
"""Resolve label names to Linear label IDs.
|
|
271
|
+
"""Resolve label names to Linear label IDs with proper None vs empty list handling.
|
|
251
272
|
|
|
252
273
|
Args:
|
|
253
274
|
label_names: List of label names
|
|
@@ -256,16 +277,25 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
256
277
|
List of Linear label IDs that exist
|
|
257
278
|
|
|
258
279
|
"""
|
|
259
|
-
import logging
|
|
260
|
-
|
|
261
280
|
logger = logging.getLogger(__name__)
|
|
262
281
|
|
|
263
|
-
|
|
282
|
+
# None = not loaded yet, [] = loaded but empty or failed
|
|
283
|
+
if self._labels_cache is None:
|
|
264
284
|
team_id = await self._ensure_team_id()
|
|
265
285
|
await self._load_team_labels(team_id)
|
|
266
286
|
|
|
287
|
+
if self._labels_cache is None:
|
|
288
|
+
# Still None after load attempt - should not happen
|
|
289
|
+
logger.error(
|
|
290
|
+
"Label cache is None after load attempt. Tags will be skipped."
|
|
291
|
+
)
|
|
292
|
+
return []
|
|
293
|
+
|
|
267
294
|
if not self._labels_cache:
|
|
268
|
-
|
|
295
|
+
# Empty list - either no labels in team or load failed
|
|
296
|
+
logger.warning(
|
|
297
|
+
f"Team has no labels available. Cannot resolve tags: {label_names}"
|
|
298
|
+
)
|
|
269
299
|
return []
|
|
270
300
|
|
|
271
301
|
# Create name -> ID mapping (case-insensitive)
|
|
@@ -835,7 +865,7 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
835
865
|
|
|
836
866
|
comment_input = {
|
|
837
867
|
"issueId": linear_id,
|
|
838
|
-
"body": comment.
|
|
868
|
+
"body": comment.content,
|
|
839
869
|
}
|
|
840
870
|
|
|
841
871
|
result = await self.client.execute_mutation(
|
|
@@ -226,13 +226,15 @@ ISSUE_LIST_FRAGMENTS = (
|
|
|
226
226
|
|
|
227
227
|
WORKFLOW_STATES_QUERY = """
|
|
228
228
|
query WorkflowStates($teamId: String!) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
team(id: $teamId) {
|
|
230
|
+
states {
|
|
231
|
+
nodes {
|
|
232
|
+
id
|
|
233
|
+
name
|
|
234
|
+
type
|
|
235
|
+
position
|
|
236
|
+
color
|
|
237
|
+
}
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
}
|
mcp_ticketer/mcp/server.py
CHANGED
|
@@ -861,6 +861,44 @@ class MCPTicketServer:
|
|
|
861
861
|
"required": ["title"],
|
|
862
862
|
},
|
|
863
863
|
},
|
|
864
|
+
{
|
|
865
|
+
"name": "ticket_comment",
|
|
866
|
+
"description": "Add or list comments on a ticket",
|
|
867
|
+
"inputSchema": {
|
|
868
|
+
"type": "object",
|
|
869
|
+
"properties": {
|
|
870
|
+
"operation": {
|
|
871
|
+
"type": "string",
|
|
872
|
+
"enum": ["add", "list"],
|
|
873
|
+
"description": "Operation to perform: 'add' to create a comment, 'list' to retrieve comments",
|
|
874
|
+
"default": "add",
|
|
875
|
+
},
|
|
876
|
+
"ticket_id": {
|
|
877
|
+
"type": "string",
|
|
878
|
+
"description": "Ticket ID to comment on",
|
|
879
|
+
},
|
|
880
|
+
"content": {
|
|
881
|
+
"type": "string",
|
|
882
|
+
"description": "Comment content (required for 'add' operation)",
|
|
883
|
+
},
|
|
884
|
+
"author": {
|
|
885
|
+
"type": "string",
|
|
886
|
+
"description": "Comment author (optional for 'add' operation)",
|
|
887
|
+
},
|
|
888
|
+
"limit": {
|
|
889
|
+
"type": "integer",
|
|
890
|
+
"default": 10,
|
|
891
|
+
"description": "Maximum number of comments to return (for 'list' operation)",
|
|
892
|
+
},
|
|
893
|
+
"offset": {
|
|
894
|
+
"type": "integer",
|
|
895
|
+
"default": 0,
|
|
896
|
+
"description": "Number of comments to skip (for 'list' operation)",
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
"required": ["ticket_id"],
|
|
900
|
+
},
|
|
901
|
+
},
|
|
864
902
|
]
|
|
865
903
|
}
|
|
866
904
|
|
|
@@ -913,6 +951,8 @@ class MCPTicketServer:
|
|
|
913
951
|
result = await self._handle_transition(arguments)
|
|
914
952
|
elif tool_name == "ticket_search":
|
|
915
953
|
result = await self._handle_search(arguments)
|
|
954
|
+
elif tool_name == "ticket_comment":
|
|
955
|
+
result = await self._handle_comment(arguments)
|
|
916
956
|
# PR integration
|
|
917
957
|
elif tool_name == "ticket_create_pr":
|
|
918
958
|
result = await self._handle_create_pr(arguments)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-ticketer
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Universal ticket management interface for AI agents with MCP support
|
|
5
5
|
Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
6
6
|
Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
|
|
2
|
-
mcp_ticketer/__version__.py,sha256=
|
|
2
|
+
mcp_ticketer/__version__.py,sha256=s7BXKaEC9wzrKYxOdqIb1UHlYxMSgLWk-8vzfr8WEIc,1117
|
|
3
3
|
mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
|
|
5
5
|
mcp_ticketer/adapters/aitrackdown.py,sha256=Ecw2SQAGVQs5yMH6m2pj61LxCJsuy-g2bvF8uwTpLUE,22588
|
|
@@ -8,10 +8,10 @@ mcp_ticketer/adapters/hybrid.py,sha256=UADYZLc_UNw0xHPSbgguBNzvUCnuYn12Qi9ea-zdl
|
|
|
8
8
|
mcp_ticketer/adapters/jira.py,sha256=labZFqOy_mmMmizC-RD1EQbu9m4LLtJywwZ956-_x5E,35347
|
|
9
9
|
mcp_ticketer/adapters/linear.py,sha256=trm6ZhmlUl80sj51WAPAox_R2HQZXZ-h1QXJsrFYDCQ,587
|
|
10
10
|
mcp_ticketer/adapters/linear/__init__.py,sha256=6l0ZoR6ZHSRcytLfps2AZuk5R189Pq1GfR5-YDQt8-Q,731
|
|
11
|
-
mcp_ticketer/adapters/linear/adapter.py,sha256=
|
|
11
|
+
mcp_ticketer/adapters/linear/adapter.py,sha256=ZCj6ZM5RiWbdiGrhA3NsMTgFImvrzaB0jBIr_3ZCcO8,30959
|
|
12
12
|
mcp_ticketer/adapters/linear/client.py,sha256=0UmWlSEcRiwnSMFYKL89KMrPPL8S8uZ5V6rIY_KFOQU,8803
|
|
13
13
|
mcp_ticketer/adapters/linear/mappers.py,sha256=GN1X7bOcU-5dhDW3dAtSEGivinhFBc8hoKYot8c5tCo,9631
|
|
14
|
-
mcp_ticketer/adapters/linear/queries.py,sha256=
|
|
14
|
+
mcp_ticketer/adapters/linear/queries.py,sha256=K8y7xc3iH-q9LEUmg-0YDBhh546LAwLZDvVLkzx3yY4,7223
|
|
15
15
|
mcp_ticketer/adapters/linear/types.py,sha256=ugXtRGLljDw6yoCnEVgdFs0xLR9ErLdnv4ffh9EAUhk,7874
|
|
16
16
|
mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d4,138
|
|
17
17
|
mcp_ticketer/cache/memory.py,sha256=2yBqGi9i0SanlUhJoOC7nijWjoMa3_ntPe-V-AV-LfU,5042
|
|
@@ -45,7 +45,7 @@ mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,
|
|
|
45
45
|
mcp_ticketer/mcp/constants.py,sha256=EBGsJtBPaTCvAm5rOMknckrXActrNIls7lRklnh1L4s,2072
|
|
46
46
|
mcp_ticketer/mcp/dto.py,sha256=fUNAdCnPNp80s6RYLFqSmgqQZX04BHYry4GArmFkdG0,7336
|
|
47
47
|
mcp_ticketer/mcp/response_builder.py,sha256=sEYiwQddlfQmIOcbQ-yBsDvH1EJfbTDwCEUJNf7q5Vk,4946
|
|
48
|
-
mcp_ticketer/mcp/server.py,sha256=
|
|
48
|
+
mcp_ticketer/mcp/server.py,sha256=SZ8lHdXJJAfV3jVRiZcR2uNYpHY-Lcbt2xXy_Tc728E,48848
|
|
49
49
|
mcp_ticketer/queue/__init__.py,sha256=1YIaCpZpFqPcqvDEQXiEvDLiw94DXRdCJkBaVIFQrms,231
|
|
50
50
|
mcp_ticketer/queue/__main__.py,sha256=gc_tE9NUdK07OJfTZuD4t6KeBD_vxFQIhknGTQUG_jk,109
|
|
51
51
|
mcp_ticketer/queue/health_monitor.py,sha256=KFOzksomUFnS94XKBiuHFPmGK6b4QXWzsrjwhHkR9vI,12245
|
|
@@ -54,9 +54,9 @@ mcp_ticketer/queue/queue.py,sha256=PIB_8gOE4rCb5_tBNKw9qD6YhSgH3Ei3IzVrUSY3F_o,1
|
|
|
54
54
|
mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
|
|
55
55
|
mcp_ticketer/queue/ticket_registry.py,sha256=FE6W_D8NA-66cJQ6VqghChF3JasYW845JVfEZdiqLbA,15449
|
|
56
56
|
mcp_ticketer/queue/worker.py,sha256=AF6W1bdxWnHiJd6-iBWqTHkZ4lFflsS65CAtgFPR0FA,20983
|
|
57
|
-
mcp_ticketer-0.3.
|
|
58
|
-
mcp_ticketer-0.3.
|
|
59
|
-
mcp_ticketer-0.3.
|
|
60
|
-
mcp_ticketer-0.3.
|
|
61
|
-
mcp_ticketer-0.3.
|
|
62
|
-
mcp_ticketer-0.3.
|
|
57
|
+
mcp_ticketer-0.3.7.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
|
|
58
|
+
mcp_ticketer-0.3.7.dist-info/METADATA,sha256=ivW854H301BPY_YzUtzYhhhQ15sQKF_u-owFjOq6e24,13219
|
|
59
|
+
mcp_ticketer-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
60
|
+
mcp_ticketer-0.3.7.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
|
|
61
|
+
mcp_ticketer-0.3.7.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
|
|
62
|
+
mcp_ticketer-0.3.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|