mcp-ticketer 0.3.2__py3-none-any.whl → 0.3.4__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 +152 -21
- mcp_ticketer/adapters/github.py +4 -4
- mcp_ticketer/adapters/jira.py +6 -6
- mcp_ticketer/adapters/linear/adapter.py +121 -17
- mcp_ticketer/adapters/linear/client.py +7 -7
- mcp_ticketer/adapters/linear/mappers.py +9 -10
- mcp_ticketer/adapters/linear/types.py +10 -10
- mcp_ticketer/cli/adapter_diagnostics.py +2 -2
- mcp_ticketer/cli/codex_configure.py +6 -6
- mcp_ticketer/cli/diagnostics.py +17 -18
- mcp_ticketer/cli/main.py +15 -0
- mcp_ticketer/cli/simple_health.py +5 -10
- mcp_ticketer/core/env_loader.py +13 -13
- mcp_ticketer/core/exceptions.py +5 -5
- mcp_ticketer/core/models.py +30 -2
- mcp_ticketer/mcp/constants.py +58 -0
- mcp_ticketer/mcp/dto.py +195 -0
- mcp_ticketer/mcp/response_builder.py +206 -0
- mcp_ticketer/mcp/server.py +311 -1287
- mcp_ticketer/queue/health_monitor.py +14 -14
- mcp_ticketer/queue/manager.py +59 -15
- mcp_ticketer/queue/queue.py +9 -2
- mcp_ticketer/queue/ticket_registry.py +15 -15
- mcp_ticketer/queue/worker.py +25 -18
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/METADATA +1 -1
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/RECORD +31 -28
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.4.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import time
|
|
5
5
|
from datetime import datetime, timedelta
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
|
|
9
9
|
import psutil
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ class HealthAlert:
|
|
|
30
30
|
self,
|
|
31
31
|
level: HealthStatus,
|
|
32
32
|
message: str,
|
|
33
|
-
details: Optional[
|
|
33
|
+
details: Optional[dict[str, Any]] = None,
|
|
34
34
|
timestamp: Optional[datetime] = None,
|
|
35
35
|
):
|
|
36
36
|
self.level = level
|
|
@@ -62,9 +62,9 @@ class QueueHealthMonitor:
|
|
|
62
62
|
self.queue = queue or Queue()
|
|
63
63
|
self.manager = WorkerManager()
|
|
64
64
|
self.last_check = datetime.now()
|
|
65
|
-
self.alerts:
|
|
65
|
+
self.alerts: list[HealthAlert] = []
|
|
66
66
|
|
|
67
|
-
def check_health(self) ->
|
|
67
|
+
def check_health(self) -> dict[str, Any]:
|
|
68
68
|
"""Perform comprehensive health check.
|
|
69
69
|
|
|
70
70
|
Returns:
|
|
@@ -110,7 +110,7 @@ class QueueHealthMonitor:
|
|
|
110
110
|
self.last_check = datetime.now()
|
|
111
111
|
return health_report
|
|
112
112
|
|
|
113
|
-
def _check_worker_health(self) ->
|
|
113
|
+
def _check_worker_health(self) -> dict[str, Any]:
|
|
114
114
|
"""Check worker process health."""
|
|
115
115
|
worker_status = self.manager.get_status()
|
|
116
116
|
|
|
@@ -145,7 +145,7 @@ class QueueHealthMonitor:
|
|
|
145
145
|
pid = worker_status.get("pid")
|
|
146
146
|
if pid:
|
|
147
147
|
try:
|
|
148
|
-
|
|
148
|
+
psutil.Process(pid)
|
|
149
149
|
# Check if worker has been idle too long with pending items
|
|
150
150
|
pending_count = self.queue.get_pending_count()
|
|
151
151
|
if pending_count > 0:
|
|
@@ -173,7 +173,7 @@ class QueueHealthMonitor:
|
|
|
173
173
|
|
|
174
174
|
return metrics
|
|
175
175
|
|
|
176
|
-
def _check_queue_health(self) ->
|
|
176
|
+
def _check_queue_health(self) -> dict[str, Any]:
|
|
177
177
|
"""Check queue status and backlog."""
|
|
178
178
|
stats = self.queue.get_stats()
|
|
179
179
|
|
|
@@ -220,7 +220,7 @@ class QueueHealthMonitor:
|
|
|
220
220
|
|
|
221
221
|
return metrics
|
|
222
222
|
|
|
223
|
-
def _check_stuck_items(self) ->
|
|
223
|
+
def _check_stuck_items(self) -> dict[str, Any]:
|
|
224
224
|
"""Check for items stuck in processing state."""
|
|
225
225
|
# Reset stuck items first
|
|
226
226
|
self.queue.reset_stuck_items(timeout_minutes=5) # 5 minute timeout
|
|
@@ -247,7 +247,7 @@ class QueueHealthMonitor:
|
|
|
247
247
|
|
|
248
248
|
return metrics
|
|
249
249
|
|
|
250
|
-
def _check_failure_rates(self) ->
|
|
250
|
+
def _check_failure_rates(self) -> dict[str, Any]:
|
|
251
251
|
"""Check recent failure rates."""
|
|
252
252
|
stats = self.queue.get_stats()
|
|
253
253
|
|
|
@@ -288,21 +288,21 @@ class QueueHealthMonitor:
|
|
|
288
288
|
|
|
289
289
|
return HealthStatus.HEALTHY
|
|
290
290
|
|
|
291
|
-
def _get_old_pending_items(self) ->
|
|
291
|
+
def _get_old_pending_items(self) -> list:
|
|
292
292
|
"""Get items that have been pending for too long."""
|
|
293
293
|
cutoff_time = datetime.now() - timedelta(seconds=self.WORKER_TIMEOUT_SECONDS)
|
|
294
294
|
|
|
295
295
|
items = self.queue.list_items(status=QueueStatus.PENDING, limit=100)
|
|
296
296
|
return [item for item in items if item.created_at < cutoff_time]
|
|
297
297
|
|
|
298
|
-
def _get_stuck_processing_items(self) ->
|
|
298
|
+
def _get_stuck_processing_items(self) -> list:
|
|
299
299
|
"""Get items stuck in processing state."""
|
|
300
300
|
cutoff_time = datetime.now() - timedelta(seconds=self.STUCK_ITEM_THRESHOLD)
|
|
301
301
|
|
|
302
302
|
items = self.queue.list_items(status=QueueStatus.PROCESSING, limit=100)
|
|
303
303
|
return [item for item in items if item.created_at < cutoff_time]
|
|
304
304
|
|
|
305
|
-
def get_immediate_alerts(self) ->
|
|
305
|
+
def get_immediate_alerts(self) -> list[HealthAlert]:
|
|
306
306
|
"""Get alerts that require immediate attention."""
|
|
307
307
|
return [
|
|
308
308
|
alert
|
|
@@ -310,12 +310,12 @@ class QueueHealthMonitor:
|
|
|
310
310
|
if alert.level in [HealthStatus.CRITICAL, HealthStatus.FAILED]
|
|
311
311
|
]
|
|
312
312
|
|
|
313
|
-
def auto_repair(self) ->
|
|
313
|
+
def auto_repair(self) -> dict[str, Any]:
|
|
314
314
|
"""Attempt automatic repair of detected issues."""
|
|
315
315
|
repair_actions = []
|
|
316
316
|
|
|
317
317
|
# Check health first
|
|
318
|
-
|
|
318
|
+
self.check_health()
|
|
319
319
|
|
|
320
320
|
for alert in self.alerts:
|
|
321
321
|
action = alert.details.get("action")
|
mcp_ticketer/queue/manager.py
CHANGED
|
@@ -81,12 +81,22 @@ class WorkerManager:
|
|
|
81
81
|
# Try to start worker
|
|
82
82
|
return self.start()
|
|
83
83
|
|
|
84
|
-
def start(self) -> bool:
|
|
84
|
+
def start(self, timeout: float = 30.0) -> bool:
|
|
85
85
|
"""Start the worker process.
|
|
86
86
|
|
|
87
|
+
Args:
|
|
88
|
+
timeout: Maximum seconds to wait for worker to become fully operational (default: 30)
|
|
89
|
+
|
|
87
90
|
Returns:
|
|
88
91
|
True if started successfully, False otherwise
|
|
89
92
|
|
|
93
|
+
Raises:
|
|
94
|
+
TimeoutError: If worker doesn't start within timeout period
|
|
95
|
+
|
|
96
|
+
Note:
|
|
97
|
+
If timeout occurs, a worker may already be running in another process.
|
|
98
|
+
Check for stale lock files or processes using `get_status()`.
|
|
99
|
+
|
|
90
100
|
"""
|
|
91
101
|
# Check if already running
|
|
92
102
|
if self.is_running():
|
|
@@ -96,7 +106,19 @@ class WorkerManager:
|
|
|
96
106
|
# Try to acquire lock
|
|
97
107
|
if not self._acquire_lock():
|
|
98
108
|
logger.warning("Could not acquire lock - another worker may be running")
|
|
99
|
-
|
|
109
|
+
# Wait briefly to see if worker becomes operational
|
|
110
|
+
start_time = time.time()
|
|
111
|
+
while time.time() - start_time < timeout:
|
|
112
|
+
if self.is_running():
|
|
113
|
+
logger.info("Worker started by another process")
|
|
114
|
+
return True
|
|
115
|
+
time.sleep(1)
|
|
116
|
+
|
|
117
|
+
raise TimeoutError(
|
|
118
|
+
f"Worker start timed out after {timeout}s. "
|
|
119
|
+
f"Lock is held but worker is not running. "
|
|
120
|
+
f"Check for stale lock files or zombie processes."
|
|
121
|
+
)
|
|
100
122
|
|
|
101
123
|
try:
|
|
102
124
|
# Start worker in subprocess using the same Python executable as the CLI
|
|
@@ -133,20 +155,42 @@ class WorkerManager:
|
|
|
133
155
|
# Save PID
|
|
134
156
|
self.pid_file.write_text(str(process.pid))
|
|
135
157
|
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
# Wait for worker to become fully operational with timeout
|
|
159
|
+
start_time = time.time()
|
|
160
|
+
while time.time() - start_time < timeout:
|
|
161
|
+
# Check if process exists
|
|
162
|
+
if not psutil.pid_exists(process.pid):
|
|
163
|
+
logger.error("Worker process died during startup")
|
|
164
|
+
self._cleanup()
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
# Check if worker is fully running (not just process exists)
|
|
168
|
+
if self.is_running():
|
|
169
|
+
elapsed = time.time() - start_time
|
|
170
|
+
logger.info(
|
|
171
|
+
f"Worker started successfully in {elapsed:.1f}s with PID {process.pid}"
|
|
172
|
+
)
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
# Log progress for long startups
|
|
176
|
+
elapsed = time.time() - start_time
|
|
177
|
+
if elapsed > 5 and int(elapsed) % 5 == 0:
|
|
178
|
+
logger.debug(
|
|
179
|
+
f"Waiting for worker to start, elapsed: {elapsed:.1f}s"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
time.sleep(0.5)
|
|
183
|
+
|
|
184
|
+
# Timeout reached
|
|
185
|
+
raise TimeoutError(
|
|
186
|
+
f"Worker start timed out after {timeout}s. "
|
|
187
|
+
f"Process spawned (PID {process.pid}) but worker did not become operational. "
|
|
188
|
+
f"Check worker logs for startup errors."
|
|
189
|
+
)
|
|
149
190
|
|
|
191
|
+
except TimeoutError:
|
|
192
|
+
# Re-raise timeout errors with context preserved
|
|
193
|
+
raise
|
|
150
194
|
except Exception as e:
|
|
151
195
|
logger.error(f"Failed to start worker: {e}")
|
|
152
196
|
self._release_lock()
|
mcp_ticketer/queue/queue.py
CHANGED
|
@@ -35,6 +35,7 @@ class QueueItem:
|
|
|
35
35
|
retry_count: int = 0
|
|
36
36
|
result: Optional[dict[str, Any]] = None
|
|
37
37
|
project_dir: Optional[str] = None
|
|
38
|
+
adapter_config: Optional[dict[str, Any]] = None # Adapter configuration
|
|
38
39
|
|
|
39
40
|
def to_dict(self) -> dict:
|
|
40
41
|
"""Convert to dictionary for storage."""
|
|
@@ -59,6 +60,7 @@ class QueueItem:
|
|
|
59
60
|
retry_count=row[8],
|
|
60
61
|
result=json.loads(row[9]) if row[9] else None,
|
|
61
62
|
project_dir=row[10] if len(row) > 10 else None,
|
|
63
|
+
adapter_config=json.loads(row[11]) if len(row) > 11 and row[11] else None,
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
|
|
@@ -127,6 +129,8 @@ class Queue:
|
|
|
127
129
|
columns = [row[1] for row in cursor.fetchall()]
|
|
128
130
|
if "project_dir" not in columns:
|
|
129
131
|
conn.execute("ALTER TABLE queue ADD COLUMN project_dir TEXT")
|
|
132
|
+
if "adapter_config" not in columns:
|
|
133
|
+
conn.execute("ALTER TABLE queue ADD COLUMN adapter_config TEXT")
|
|
130
134
|
|
|
131
135
|
conn.commit()
|
|
132
136
|
|
|
@@ -136,6 +140,7 @@ class Queue:
|
|
|
136
140
|
adapter: str,
|
|
137
141
|
operation: str,
|
|
138
142
|
project_dir: Optional[str] = None,
|
|
143
|
+
adapter_config: Optional[dict[str, Any]] = None,
|
|
139
144
|
) -> str:
|
|
140
145
|
"""Add item to queue.
|
|
141
146
|
|
|
@@ -144,6 +149,7 @@ class Queue:
|
|
|
144
149
|
adapter: Name of the adapter to use
|
|
145
150
|
operation: Operation to perform (create, update, delete, etc.)
|
|
146
151
|
project_dir: Project directory for config resolution (defaults to current directory)
|
|
152
|
+
adapter_config: Adapter configuration to use (optional, for explicit config passing)
|
|
147
153
|
|
|
148
154
|
Returns:
|
|
149
155
|
Queue ID for tracking
|
|
@@ -161,8 +167,8 @@ class Queue:
|
|
|
161
167
|
"""
|
|
162
168
|
INSERT INTO queue (
|
|
163
169
|
id, ticket_data, adapter, operation,
|
|
164
|
-
status, created_at, retry_count, project_dir
|
|
165
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
170
|
+
status, created_at, retry_count, project_dir, adapter_config
|
|
171
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
166
172
|
""",
|
|
167
173
|
(
|
|
168
174
|
queue_id,
|
|
@@ -173,6 +179,7 @@ class Queue:
|
|
|
173
179
|
datetime.now().isoformat(),
|
|
174
180
|
0,
|
|
175
181
|
project_dir,
|
|
182
|
+
json.dumps(adapter_config) if adapter_config else None,
|
|
176
183
|
),
|
|
177
184
|
)
|
|
178
185
|
conn.commit()
|
|
@@ -5,7 +5,7 @@ import sqlite3
|
|
|
5
5
|
import threading
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class TicketRegistry:
|
|
@@ -91,7 +91,7 @@ class TicketRegistry:
|
|
|
91
91
|
adapter: str,
|
|
92
92
|
operation: str,
|
|
93
93
|
title: str,
|
|
94
|
-
ticket_data:
|
|
94
|
+
ticket_data: dict[str, Any],
|
|
95
95
|
) -> None:
|
|
96
96
|
"""Register a new ticket operation.
|
|
97
97
|
|
|
@@ -131,7 +131,7 @@ class TicketRegistry:
|
|
|
131
131
|
queue_id: str,
|
|
132
132
|
status: str,
|
|
133
133
|
ticket_id: Optional[str] = None,
|
|
134
|
-
result_data: Optional[
|
|
134
|
+
result_data: Optional[dict[str, Any]] = None,
|
|
135
135
|
error_message: Optional[str] = None,
|
|
136
136
|
retry_count: Optional[int] = None,
|
|
137
137
|
) -> None:
|
|
@@ -179,7 +179,7 @@ class TicketRegistry:
|
|
|
179
179
|
)
|
|
180
180
|
conn.commit()
|
|
181
181
|
|
|
182
|
-
def get_ticket_info(self, queue_id: str) -> Optional[
|
|
182
|
+
def get_ticket_info(self, queue_id: str) -> Optional[dict[str, Any]]:
|
|
183
183
|
"""Get ticket information by queue ID.
|
|
184
184
|
|
|
185
185
|
Args:
|
|
@@ -212,7 +212,7 @@ class TicketRegistry:
|
|
|
212
212
|
|
|
213
213
|
return ticket_info
|
|
214
214
|
|
|
215
|
-
def find_tickets_by_id(self, ticket_id: str) ->
|
|
215
|
+
def find_tickets_by_id(self, ticket_id: str) -> list[dict[str, Any]]:
|
|
216
216
|
"""Find all operations for a specific ticket ID.
|
|
217
217
|
|
|
218
218
|
Args:
|
|
@@ -225,7 +225,7 @@ class TicketRegistry:
|
|
|
225
225
|
with sqlite3.connect(self.db_path) as conn:
|
|
226
226
|
cursor = conn.execute(
|
|
227
227
|
"""
|
|
228
|
-
SELECT * FROM ticket_registry
|
|
228
|
+
SELECT * FROM ticket_registry
|
|
229
229
|
WHERE ticket_id = ?
|
|
230
230
|
ORDER BY created_at DESC
|
|
231
231
|
""",
|
|
@@ -248,7 +248,7 @@ class TicketRegistry:
|
|
|
248
248
|
|
|
249
249
|
return results
|
|
250
250
|
|
|
251
|
-
def get_failed_operations(self, limit: int = 50) ->
|
|
251
|
+
def get_failed_operations(self, limit: int = 50) -> list[dict[str, Any]]:
|
|
252
252
|
"""Get failed operations that might need recovery.
|
|
253
253
|
|
|
254
254
|
Args:
|
|
@@ -261,7 +261,7 @@ class TicketRegistry:
|
|
|
261
261
|
with sqlite3.connect(self.db_path) as conn:
|
|
262
262
|
cursor = conn.execute(
|
|
263
263
|
"""
|
|
264
|
-
SELECT * FROM ticket_registry
|
|
264
|
+
SELECT * FROM ticket_registry
|
|
265
265
|
WHERE status = 'failed'
|
|
266
266
|
ORDER BY updated_at DESC
|
|
267
267
|
LIMIT ?
|
|
@@ -285,7 +285,7 @@ class TicketRegistry:
|
|
|
285
285
|
|
|
286
286
|
return results
|
|
287
287
|
|
|
288
|
-
def get_orphaned_tickets(self) ->
|
|
288
|
+
def get_orphaned_tickets(self) -> list[dict[str, Any]]:
|
|
289
289
|
"""Get tickets that were created but queue operation failed.
|
|
290
290
|
|
|
291
291
|
Returns:
|
|
@@ -295,8 +295,8 @@ class TicketRegistry:
|
|
|
295
295
|
with sqlite3.connect(self.db_path) as conn:
|
|
296
296
|
cursor = conn.execute(
|
|
297
297
|
"""
|
|
298
|
-
SELECT * FROM ticket_registry
|
|
299
|
-
WHERE ticket_id IS NOT NULL
|
|
298
|
+
SELECT * FROM ticket_registry
|
|
299
|
+
WHERE ticket_id IS NOT NULL
|
|
300
300
|
AND status IN ('processing', 'failed')
|
|
301
301
|
ORDER BY updated_at DESC
|
|
302
302
|
"""
|
|
@@ -318,7 +318,7 @@ class TicketRegistry:
|
|
|
318
318
|
|
|
319
319
|
return results
|
|
320
320
|
|
|
321
|
-
def attempt_recovery(self, queue_id: str, recovery_type: str) ->
|
|
321
|
+
def attempt_recovery(self, queue_id: str, recovery_type: str) -> dict[str, Any]:
|
|
322
322
|
"""Attempt to recover a failed operation.
|
|
323
323
|
|
|
324
324
|
Args:
|
|
@@ -390,7 +390,7 @@ class TicketRegistry:
|
|
|
390
390
|
self,
|
|
391
391
|
queue_id: str,
|
|
392
392
|
recovery_type: str,
|
|
393
|
-
recovery_data:
|
|
393
|
+
recovery_data: dict[str, Any],
|
|
394
394
|
success: bool,
|
|
395
395
|
) -> None:
|
|
396
396
|
"""Log recovery attempt."""
|
|
@@ -412,7 +412,7 @@ class TicketRegistry:
|
|
|
412
412
|
)
|
|
413
413
|
conn.commit()
|
|
414
414
|
|
|
415
|
-
def get_recovery_history(self, queue_id: str) ->
|
|
415
|
+
def get_recovery_history(self, queue_id: str) -> list[dict[str, Any]]:
|
|
416
416
|
"""Get recovery history for a queue operation.
|
|
417
417
|
|
|
418
418
|
Args:
|
|
@@ -425,7 +425,7 @@ class TicketRegistry:
|
|
|
425
425
|
with sqlite3.connect(self.db_path) as conn:
|
|
426
426
|
cursor = conn.execute(
|
|
427
427
|
"""
|
|
428
|
-
SELECT * FROM recovery_log
|
|
428
|
+
SELECT * FROM recovery_log
|
|
429
429
|
WHERE queue_id = ?
|
|
430
430
|
ORDER BY timestamp DESC
|
|
431
431
|
""",
|
mcp_ticketer/queue/worker.py
CHANGED
|
@@ -373,24 +373,31 @@ class Worker:
|
|
|
373
373
|
|
|
374
374
|
from ..cli.main import load_config
|
|
375
375
|
|
|
376
|
-
# Use item
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
376
|
+
# PRIORITY 1: Use adapter_config from queue item if available (explicit config)
|
|
377
|
+
if item.adapter_config:
|
|
378
|
+
logger.info("Worker using explicit adapter_config from queue item")
|
|
379
|
+
adapter_config = item.adapter_config
|
|
380
|
+
logger.info(f"Worker adapter config for {item.adapter}: {adapter_config}")
|
|
381
|
+
else:
|
|
382
|
+
# PRIORITY 2: Load from project config file
|
|
383
|
+
# Use item's project_dir if available, otherwise use current directory
|
|
384
|
+
project_path = Path(item.project_dir) if item.project_dir else None
|
|
385
|
+
|
|
386
|
+
# Load environment variables from project directory's .env.local if it exists
|
|
387
|
+
if project_path:
|
|
388
|
+
env_file = project_path / ".env.local"
|
|
389
|
+
if env_file.exists():
|
|
390
|
+
logger.info(f"Worker loading environment from {env_file}")
|
|
391
|
+
load_dotenv(env_file)
|
|
392
|
+
|
|
393
|
+
logger.info(f"Worker project_path: {project_path}")
|
|
394
|
+
logger.info(f"Worker current working directory: {os.getcwd()}")
|
|
395
|
+
|
|
396
|
+
config = load_config(project_dir=project_path)
|
|
397
|
+
logger.info(f"Worker loaded config: {config}")
|
|
398
|
+
adapters_config = config.get("adapters", {})
|
|
399
|
+
adapter_config = adapters_config.get(item.adapter, {})
|
|
400
|
+
logger.info(f"Worker adapter config for {item.adapter}: {adapter_config}")
|
|
394
401
|
|
|
395
402
|
# Add environment variables for authentication
|
|
396
403
|
if item.adapter == "linear":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-ticketer
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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,59 +1,62 @@
|
|
|
1
1
|
mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
|
|
2
|
-
mcp_ticketer/__version__.py,sha256=
|
|
2
|
+
mcp_ticketer/__version__.py,sha256=XrTye_cA_opF1wX2csBbZnPCpEQ57tGLh94P_i6prws,1117
|
|
3
3
|
mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
|
|
5
|
-
mcp_ticketer/adapters/aitrackdown.py,sha256=
|
|
6
|
-
mcp_ticketer/adapters/github.py,sha256=
|
|
5
|
+
mcp_ticketer/adapters/aitrackdown.py,sha256=Ecw2SQAGVQs5yMH6m2pj61LxCJsuy-g2bvF8uwTpLUE,22588
|
|
6
|
+
mcp_ticketer/adapters/github.py,sha256=YbZ8hj4nOy4pGIbZ_0zwciNbNnwK69zqfljb-wc2tSY,47384
|
|
7
7
|
mcp_ticketer/adapters/hybrid.py,sha256=UADYZLc_UNw0xHPSbgguBNzvUCnuYn12Qi9ea-zdlMk,19086
|
|
8
|
-
mcp_ticketer/adapters/jira.py,sha256=
|
|
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=
|
|
12
|
-
mcp_ticketer/adapters/linear/client.py,sha256=
|
|
13
|
-
mcp_ticketer/adapters/linear/mappers.py,sha256=
|
|
11
|
+
mcp_ticketer/adapters/linear/adapter.py,sha256=IwmMCm9UFss1NcOnf3gdAlBJ8AeiVTG3FbeqW36gpVg,29606
|
|
12
|
+
mcp_ticketer/adapters/linear/client.py,sha256=0UmWlSEcRiwnSMFYKL89KMrPPL8S8uZ5V6rIY_KFOQU,8803
|
|
13
|
+
mcp_ticketer/adapters/linear/mappers.py,sha256=GN1X7bOcU-5dhDW3dAtSEGivinhFBc8hoKYot8c5tCo,9631
|
|
14
14
|
mcp_ticketer/adapters/linear/queries.py,sha256=LScSwVb9QA6gPiJJ1vHHoTwA6NA3TEPPD7dUzd1zA4g,7196
|
|
15
|
-
mcp_ticketer/adapters/linear/types.py,sha256=
|
|
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
|
|
18
18
|
mcp_ticketer/cli/__init__.py,sha256=l9Q8iKmfGkTu0cssHBVqNZTsL4eAtFzOB25AED_0G6g,89
|
|
19
|
-
mcp_ticketer/cli/adapter_diagnostics.py,sha256=
|
|
19
|
+
mcp_ticketer/cli/adapter_diagnostics.py,sha256=pQDdtDgBwSW04wdFEPVzwbul3KgfB9g6ZMS85qpYulY,14988
|
|
20
20
|
mcp_ticketer/cli/auggie_configure.py,sha256=MXKzLtqe3K_UTQ2GacHAWbvf_B0779KL325smiAKE0Q,8212
|
|
21
|
-
mcp_ticketer/cli/codex_configure.py,sha256=
|
|
21
|
+
mcp_ticketer/cli/codex_configure.py,sha256=k7-q5d7NICjm7Vxa-z0mejJNAG-bicAHKcwqrzdRDyU,9080
|
|
22
22
|
mcp_ticketer/cli/configure.py,sha256=BsA_pSHQMQS0t1bJO_wMM8LWsd5sWJDASjEPRHvwC18,16198
|
|
23
|
-
mcp_ticketer/cli/diagnostics.py,sha256=
|
|
23
|
+
mcp_ticketer/cli/diagnostics.py,sha256=jHF68ydW3RNVGumBnHUjUmq6YOjQD2UDkx0O7M__xv0,29965
|
|
24
24
|
mcp_ticketer/cli/discover.py,sha256=AF_qlQc1Oo0UkWayoF5pmRChS5J3fJjH6f2YZzd_k8w,13188
|
|
25
25
|
mcp_ticketer/cli/gemini_configure.py,sha256=ZNSA1lBW-itVToza-JxW95Po7daVXKiZAh7lp6pmXMU,9343
|
|
26
26
|
mcp_ticketer/cli/linear_commands.py,sha256=_8f8ze_1MbiUweU6RFHpldgfHLirysIdPjHr2_S0YhI,17319
|
|
27
|
-
mcp_ticketer/cli/main.py,sha256=
|
|
27
|
+
mcp_ticketer/cli/main.py,sha256=BFo7QYU0tTOHWSfTmegzDqflSYFytCE8Jw6QGB7TwaY,74470
|
|
28
28
|
mcp_ticketer/cli/mcp_configure.py,sha256=RzV50UjXgOmvMp-9S0zS39psuvjffVByaMrqrUaAGAM,9594
|
|
29
29
|
mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
|
|
30
30
|
mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
|
|
31
|
-
mcp_ticketer/cli/simple_health.py,sha256=
|
|
31
|
+
mcp_ticketer/cli/simple_health.py,sha256=GlOLRRFoifCna995NoHuKpb3xmFkLi2b3Ke1hyeDvq4,7950
|
|
32
32
|
mcp_ticketer/cli/utils.py,sha256=IOycMmhwtCDpL3RN_wVEZkYMg9FHyDr4mg5M9Bxzfbo,23041
|
|
33
33
|
mcp_ticketer/core/__init__.py,sha256=eXovsaJymQRP2AwOBuOy6mFtI3I68D7gGenZ5V-IMqo,349
|
|
34
34
|
mcp_ticketer/core/adapter.py,sha256=q64LxOInIno7EIbmuxItf8KEsd-g9grCs__Z4uwZHto,10273
|
|
35
35
|
mcp_ticketer/core/config.py,sha256=ZaooOtim4ZgaQrDPjL5TWJ-K03N9hb0s63YehCsNqR0,19372
|
|
36
36
|
mcp_ticketer/core/env_discovery.py,sha256=bHOfj7YVExhqFVxto3G-zZ8p6T12BOhF0J4IXCjdll8,19971
|
|
37
|
-
mcp_ticketer/core/env_loader.py,sha256=
|
|
38
|
-
mcp_ticketer/core/exceptions.py,sha256=
|
|
37
|
+
mcp_ticketer/core/env_loader.py,sha256=VLCQhK50quM5e3LkrAGHD5on5vTBj18Z2IgNMNES14M,11866
|
|
38
|
+
mcp_ticketer/core/exceptions.py,sha256=H1gUmNiOjVXn4CT-JLQcGXmjWxHaxxdFvwcpJLTrs-U,3621
|
|
39
39
|
mcp_ticketer/core/http_client.py,sha256=s5ikMiwEJ8TJjNn73wu3gv3OdAtyBEpAqPnSroRMW2k,13971
|
|
40
40
|
mcp_ticketer/core/mappers.py,sha256=1aG1jFsHTCwmGRVgOlXW-VOSTGzc86gv7qjDfiR1ups,17462
|
|
41
|
-
mcp_ticketer/core/models.py,sha256=
|
|
41
|
+
mcp_ticketer/core/models.py,sha256=r3BqH0pK2ag2_7c7SSKMZe2G0K7eQID6qy2tkC-GvfI,13155
|
|
42
42
|
mcp_ticketer/core/project_config.py,sha256=5W9YvDBBASEo5fBcSF-rlA1W3LwzkTv6_CEJXxnsOjc,23346
|
|
43
43
|
mcp_ticketer/core/registry.py,sha256=ShYLDPE62KFJpB0kj_zFyQzRxSH3LkQEEuo1jaakb1k,3483
|
|
44
44
|
mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,123
|
|
45
|
-
mcp_ticketer/mcp/
|
|
45
|
+
mcp_ticketer/mcp/constants.py,sha256=EBGsJtBPaTCvAm5rOMknckrXActrNIls7lRklnh1L4s,2072
|
|
46
|
+
mcp_ticketer/mcp/dto.py,sha256=fUNAdCnPNp80s6RYLFqSmgqQZX04BHYry4GArmFkdG0,7336
|
|
47
|
+
mcp_ticketer/mcp/response_builder.py,sha256=sEYiwQddlfQmIOcbQ-yBsDvH1EJfbTDwCEUJNf7q5Vk,4946
|
|
48
|
+
mcp_ticketer/mcp/server.py,sha256=l9DQ4e9pzdIyIS3Y-gKQwzOzBiVxDi0YJO0U6EeCz5M,46825
|
|
46
49
|
mcp_ticketer/queue/__init__.py,sha256=1YIaCpZpFqPcqvDEQXiEvDLiw94DXRdCJkBaVIFQrms,231
|
|
47
50
|
mcp_ticketer/queue/__main__.py,sha256=gc_tE9NUdK07OJfTZuD4t6KeBD_vxFQIhknGTQUG_jk,109
|
|
48
|
-
mcp_ticketer/queue/health_monitor.py,sha256=
|
|
49
|
-
mcp_ticketer/queue/manager.py,sha256=
|
|
50
|
-
mcp_ticketer/queue/queue.py,sha256=
|
|
51
|
+
mcp_ticketer/queue/health_monitor.py,sha256=KFOzksomUFnS94XKBiuHFPmGK6b4QXWzsrjwhHkR9vI,12245
|
|
52
|
+
mcp_ticketer/queue/manager.py,sha256=qo7splhIIqzPxINpD8Yflr335sRpuqXtWr2JlsjB8Us,12246
|
|
53
|
+
mcp_ticketer/queue/queue.py,sha256=PIB_8gOE4rCb5_tBNKw9qD6YhSgH3Ei3IzVrUSY3F_o,17978
|
|
51
54
|
mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
|
|
52
|
-
mcp_ticketer/queue/ticket_registry.py,sha256=
|
|
53
|
-
mcp_ticketer/queue/worker.py,sha256=
|
|
54
|
-
mcp_ticketer-0.3.
|
|
55
|
-
mcp_ticketer-0.3.
|
|
56
|
-
mcp_ticketer-0.3.
|
|
57
|
-
mcp_ticketer-0.3.
|
|
58
|
-
mcp_ticketer-0.3.
|
|
59
|
-
mcp_ticketer-0.3.
|
|
55
|
+
mcp_ticketer/queue/ticket_registry.py,sha256=FE6W_D8NA-66cJQ6VqghChF3JasYW845JVfEZdiqLbA,15449
|
|
56
|
+
mcp_ticketer/queue/worker.py,sha256=AF6W1bdxWnHiJd6-iBWqTHkZ4lFflsS65CAtgFPR0FA,20983
|
|
57
|
+
mcp_ticketer-0.3.4.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
|
|
58
|
+
mcp_ticketer-0.3.4.dist-info/METADATA,sha256=mcdoktp4RGFdnMy0_fMGrCQXJ72rTjCtVsF3fqrpDOg,13219
|
|
59
|
+
mcp_ticketer-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
60
|
+
mcp_ticketer-0.3.4.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
|
|
61
|
+
mcp_ticketer-0.3.4.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
|
|
62
|
+
mcp_ticketer-0.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|