mcp-ticketer 0.12.0__py3-none-any.whl → 2.0.1__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/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +385 -6
- mcp_ticketer/adapters/asana/adapter.py +108 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github.py +525 -11
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +521 -0
- mcp_ticketer/adapters/linear/adapter.py +1784 -101
- mcp_ticketer/adapters/linear/client.py +85 -3
- mcp_ticketer/adapters/linear/mappers.py +96 -8
- mcp_ticketer/adapters/linear/queries.py +168 -1
- mcp_ticketer/adapters/linear/types.py +80 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +851 -103
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +233 -3151
- mcp_ticketer/cli/mcp_configure.py +672 -98
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +264 -24
- mcp_ticketer/core/__init__.py +28 -6
- mcp_ticketer/core/adapter.py +166 -1
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/models.py +135 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/session_state.py +171 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +31 -12
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +6 -8
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1184 -136
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1070 -123
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Automatic project update posting on ticket transitions.
|
|
2
|
+
|
|
3
|
+
This module implements automatic project status updates that trigger when
|
|
4
|
+
tickets are transitioned, generating real-time epic/project status summaries.
|
|
5
|
+
|
|
6
|
+
Design Decision: Reuse StatusAnalyzer from 1M-316
|
|
7
|
+
------------------------------------------------
|
|
8
|
+
The project_status tool (1M-316) already provides comprehensive analysis via
|
|
9
|
+
StatusAnalyzer. This module focuses on:
|
|
10
|
+
1. Hooking into ticket transitions
|
|
11
|
+
2. Formatting analysis as concise updates
|
|
12
|
+
3. Posting to Linear automatically
|
|
13
|
+
|
|
14
|
+
Related Tickets:
|
|
15
|
+
- 1M-315: Automatic project update posting
|
|
16
|
+
- 1M-316: project_status tool (provides StatusAnalyzer)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from ..analysis.project_status import StatusAnalyzer
|
|
24
|
+
from ..core.adapter import BaseAdapter
|
|
25
|
+
from ..core.models import ProjectUpdateHealth, Task
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AutoProjectUpdateManager:
|
|
31
|
+
"""Manager for automatic project status updates on ticket transitions.
|
|
32
|
+
|
|
33
|
+
This class hooks into ticket transitions and automatically generates
|
|
34
|
+
project status updates using the StatusAnalyzer from 1M-316.
|
|
35
|
+
|
|
36
|
+
Design Principles:
|
|
37
|
+
- Fail gracefully: Update failures don't block ticket transitions
|
|
38
|
+
- Reuse existing analysis: Leverage StatusAnalyzer for consistency
|
|
39
|
+
- Concise summaries: Focus on key information for readability
|
|
40
|
+
- Health tracking: Include health status in all updates
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
config: Project configuration dictionary
|
|
44
|
+
adapter: Ticket adapter instance
|
|
45
|
+
analyzer: StatusAnalyzer for project analysis
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: dict[str, Any], adapter: BaseAdapter):
|
|
50
|
+
"""Initialize the auto project update manager.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
config: Project configuration dictionary
|
|
54
|
+
adapter: Ticket adapter instance
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
self.config = config
|
|
58
|
+
self.adapter = adapter
|
|
59
|
+
self.analyzer = StatusAnalyzer()
|
|
60
|
+
|
|
61
|
+
def is_enabled(self) -> bool:
|
|
62
|
+
"""Check if automatic project updates are enabled.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if auto_project_updates.enabled is True in config
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
auto_updates_config = self.config.get("auto_project_updates", {})
|
|
69
|
+
return auto_updates_config.get("enabled", False)
|
|
70
|
+
|
|
71
|
+
def get_update_frequency(self) -> str:
|
|
72
|
+
"""Get configured update frequency.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Update frequency: "on_transition", "on_completion", or "daily"
|
|
76
|
+
Default: "on_transition"
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
auto_updates_config = self.config.get("auto_project_updates", {})
|
|
80
|
+
return auto_updates_config.get("update_frequency", "on_transition")
|
|
81
|
+
|
|
82
|
+
def get_health_tracking_enabled(self) -> bool:
|
|
83
|
+
"""Check if health tracking is enabled.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if auto_project_updates.health_tracking is True
|
|
87
|
+
Default: True
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
auto_updates_config = self.config.get("auto_project_updates", {})
|
|
91
|
+
return auto_updates_config.get("health_tracking", True)
|
|
92
|
+
|
|
93
|
+
async def create_transition_update(
|
|
94
|
+
self,
|
|
95
|
+
ticket_id: str,
|
|
96
|
+
ticket_title: str,
|
|
97
|
+
old_state: str,
|
|
98
|
+
new_state: str,
|
|
99
|
+
parent_epic: str,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
"""Create and post a project update for a ticket transition.
|
|
102
|
+
|
|
103
|
+
This is the main entry point called from ticket_transition.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
ticket_id: ID of the transitioned ticket
|
|
107
|
+
ticket_title: Title of the transitioned ticket
|
|
108
|
+
old_state: Previous state of the ticket
|
|
109
|
+
new_state: New state of the ticket
|
|
110
|
+
parent_epic: ID of the parent epic/project
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dictionary containing:
|
|
114
|
+
- status: "completed" or "error"
|
|
115
|
+
- update_id: ID of created update (if successful)
|
|
116
|
+
- error: Error message (if failed)
|
|
117
|
+
|
|
118
|
+
Error Handling:
|
|
119
|
+
- Failures don't propagate to caller (ticket transition continues)
|
|
120
|
+
- Errors are logged for debugging
|
|
121
|
+
- Returns error status for observability
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
# Check if adapter supports project updates
|
|
126
|
+
if not hasattr(self.adapter, "create_project_update"):
|
|
127
|
+
logger.debug(
|
|
128
|
+
f"Adapter '{self.adapter.adapter_type}' does not support "
|
|
129
|
+
f"project updates, skipping auto-update"
|
|
130
|
+
)
|
|
131
|
+
return {
|
|
132
|
+
"status": "skipped",
|
|
133
|
+
"reason": "adapter_unsupported",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Fetch epic/project data
|
|
137
|
+
epic_data = await self._fetch_epic_data(parent_epic)
|
|
138
|
+
if not epic_data:
|
|
139
|
+
logger.warning(
|
|
140
|
+
f"Could not fetch epic data for {parent_epic}, "
|
|
141
|
+
f"skipping auto-update"
|
|
142
|
+
)
|
|
143
|
+
return {
|
|
144
|
+
"status": "error",
|
|
145
|
+
"error": f"Epic {parent_epic} not found",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Fetch all tickets in the epic
|
|
149
|
+
tickets = await self._fetch_epic_tickets(parent_epic)
|
|
150
|
+
if not tickets:
|
|
151
|
+
logger.debug(
|
|
152
|
+
f"No tickets found for epic {parent_epic}, "
|
|
153
|
+
f"creating minimal update"
|
|
154
|
+
)
|
|
155
|
+
# Still create update with just the transition info
|
|
156
|
+
tickets = []
|
|
157
|
+
|
|
158
|
+
# Perform analysis using StatusAnalyzer
|
|
159
|
+
project_name = epic_data.get("name", parent_epic)
|
|
160
|
+
analysis = self.analyzer.analyze(parent_epic, project_name, tickets)
|
|
161
|
+
|
|
162
|
+
# Format as markdown summary
|
|
163
|
+
summary = self._format_markdown_summary(
|
|
164
|
+
analysis=analysis,
|
|
165
|
+
ticket_id=ticket_id,
|
|
166
|
+
ticket_title=ticket_title,
|
|
167
|
+
old_state=old_state,
|
|
168
|
+
new_state=new_state,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Determine health status
|
|
172
|
+
health = None
|
|
173
|
+
if self.get_health_tracking_enabled():
|
|
174
|
+
health_value = analysis.health
|
|
175
|
+
# Map health string to ProjectUpdateHealth enum
|
|
176
|
+
if health_value:
|
|
177
|
+
try:
|
|
178
|
+
health = ProjectUpdateHealth(health_value.lower())
|
|
179
|
+
except ValueError:
|
|
180
|
+
logger.warning(
|
|
181
|
+
f"Invalid health value '{health_value}', "
|
|
182
|
+
f"defaulting to None"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Post update using project_update_create via adapter
|
|
186
|
+
update = await self.adapter.create_project_update(
|
|
187
|
+
project_id=parent_epic,
|
|
188
|
+
body=summary,
|
|
189
|
+
health=health,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
logger.info(
|
|
193
|
+
f"Created automatic project update for {parent_epic} "
|
|
194
|
+
f"after {ticket_id} transition"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
"status": "completed",
|
|
199
|
+
"update_id": update.id if hasattr(update, "id") else None,
|
|
200
|
+
"project_id": parent_epic,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
# Log error but don't propagate (transition should still succeed)
|
|
205
|
+
logger.error(
|
|
206
|
+
f"Failed to create automatic project update: {e}",
|
|
207
|
+
exc_info=True,
|
|
208
|
+
)
|
|
209
|
+
return {
|
|
210
|
+
"status": "error",
|
|
211
|
+
"error": str(e),
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async def _fetch_epic_data(self, epic_id: str) -> dict[str, Any] | None:
|
|
215
|
+
"""Fetch epic/project data from adapter.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
epic_id: Epic/project ID
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Epic data dictionary or None if not found
|
|
222
|
+
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
if hasattr(self.adapter, "get_epic"):
|
|
226
|
+
epic = await self.adapter.get_epic(epic_id)
|
|
227
|
+
if epic:
|
|
228
|
+
# Convert to dict for processing
|
|
229
|
+
if hasattr(epic, "model_dump"):
|
|
230
|
+
return epic.model_dump()
|
|
231
|
+
elif hasattr(epic, "dict"):
|
|
232
|
+
return epic.dict()
|
|
233
|
+
else:
|
|
234
|
+
return {"id": epic_id, "name": str(epic)}
|
|
235
|
+
return None
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.debug(f"Error fetching epic {epic_id}: {e}")
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
async def _fetch_epic_tickets(self, epic_id: str) -> list[Task]:
|
|
241
|
+
"""Fetch all tickets in an epic.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
epic_id: Epic/project ID
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
List of Task objects in the epic
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
if hasattr(self.adapter, "list_issues_by_epic"):
|
|
252
|
+
tickets = await self.adapter.list_issues_by_epic(epic_id)
|
|
253
|
+
return tickets
|
|
254
|
+
elif hasattr(self.adapter, "list"):
|
|
255
|
+
# Fallback to generic list with parent filter
|
|
256
|
+
tickets = await self.adapter.list(
|
|
257
|
+
limit=100,
|
|
258
|
+
offset=0,
|
|
259
|
+
filters={"parent_epic": epic_id},
|
|
260
|
+
)
|
|
261
|
+
return tickets
|
|
262
|
+
return []
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.debug(f"Error fetching tickets for epic {epic_id}: {e}")
|
|
265
|
+
return []
|
|
266
|
+
|
|
267
|
+
def _format_markdown_summary(
|
|
268
|
+
self,
|
|
269
|
+
analysis: Any,
|
|
270
|
+
ticket_id: str,
|
|
271
|
+
ticket_title: str,
|
|
272
|
+
old_state: str,
|
|
273
|
+
new_state: str,
|
|
274
|
+
) -> str:
|
|
275
|
+
"""Format analysis as markdown summary.
|
|
276
|
+
|
|
277
|
+
This creates a concise, readable summary optimized for Linear's
|
|
278
|
+
project update interface.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
analysis: ProjectStatusResult from StatusAnalyzer
|
|
282
|
+
ticket_id: ID of transitioned ticket
|
|
283
|
+
ticket_title: Title of transitioned ticket
|
|
284
|
+
old_state: Previous state
|
|
285
|
+
new_state: New state
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Formatted markdown summary
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
# Header: Transition trigger
|
|
292
|
+
lines = [
|
|
293
|
+
"## Progress Update (Automated)",
|
|
294
|
+
"",
|
|
295
|
+
f'**Ticket Transitioned**: {ticket_id} "{ticket_title}" '
|
|
296
|
+
f"→ **{new_state.upper()}**",
|
|
297
|
+
"",
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
# Epic status summary
|
|
301
|
+
summary = analysis.summary
|
|
302
|
+
total = summary.get("total", 0)
|
|
303
|
+
done_count = summary.get("done", 0) + summary.get("closed", 0)
|
|
304
|
+
in_progress = summary.get("in_progress", 0)
|
|
305
|
+
blocked = summary.get("blocked", 0)
|
|
306
|
+
|
|
307
|
+
completion_pct = int(analysis.health_metrics.completion_rate * 100)
|
|
308
|
+
|
|
309
|
+
lines.extend(
|
|
310
|
+
[
|
|
311
|
+
f"**Epic Status** ({analysis.project_name}):",
|
|
312
|
+
f"- Completed: {done_count}/{total} tickets ({completion_pct}%)",
|
|
313
|
+
f"- In Progress: {in_progress} ticket{'s' if in_progress != 1 else ''}",
|
|
314
|
+
f"- Blocked: {blocked} ticket{'s' if blocked != 1 else ''}",
|
|
315
|
+
"",
|
|
316
|
+
]
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Recent completions (if any)
|
|
320
|
+
if done_count > 0:
|
|
321
|
+
lines.append("**Recent Completions**:")
|
|
322
|
+
# Show up to 3 most recent completions
|
|
323
|
+
completed_tickets = [
|
|
324
|
+
rec for rec in analysis.recommended_next if hasattr(rec, "ticket_id")
|
|
325
|
+
][:3]
|
|
326
|
+
if completed_tickets:
|
|
327
|
+
for ticket in completed_tickets:
|
|
328
|
+
lines.append(f"- {ticket.ticket_id}: {ticket.title}")
|
|
329
|
+
else:
|
|
330
|
+
lines.append("- (Details not available)")
|
|
331
|
+
lines.append("")
|
|
332
|
+
|
|
333
|
+
# Next up: Recommendations
|
|
334
|
+
if analysis.recommended_next:
|
|
335
|
+
lines.append("**Next Up**:")
|
|
336
|
+
for rec in analysis.recommended_next[:3]:
|
|
337
|
+
priority_label = rec.priority.upper()
|
|
338
|
+
lines.append(
|
|
339
|
+
f"- {rec.ticket_id}: {rec.title} " f"(Priority: {priority_label})"
|
|
340
|
+
)
|
|
341
|
+
lines.append("")
|
|
342
|
+
|
|
343
|
+
# Health status
|
|
344
|
+
if self.get_health_tracking_enabled():
|
|
345
|
+
health_emoji = {
|
|
346
|
+
"on_track": "✅",
|
|
347
|
+
"at_risk": "⚠️",
|
|
348
|
+
"off_track": "🚨",
|
|
349
|
+
}
|
|
350
|
+
emoji = health_emoji.get(analysis.health, "ℹ️")
|
|
351
|
+
lines.append(
|
|
352
|
+
f"**Health**: {emoji} {analysis.health.replace('_', ' ').title()}"
|
|
353
|
+
)
|
|
354
|
+
lines.append("")
|
|
355
|
+
|
|
356
|
+
# Blockers (if any)
|
|
357
|
+
if analysis.blockers:
|
|
358
|
+
lines.append("**Blockers**:")
|
|
359
|
+
for blocker in analysis.blockers[:3]:
|
|
360
|
+
lines.append(
|
|
361
|
+
f"- {blocker['ticket_id']}: {blocker['title']} "
|
|
362
|
+
f"(blocks {blocker['blocks_count']} ticket{'s' if blocker['blocks_count'] > 1 else ''})"
|
|
363
|
+
)
|
|
364
|
+
lines.append("")
|
|
365
|
+
else:
|
|
366
|
+
lines.append("**Blockers**: None")
|
|
367
|
+
lines.append("")
|
|
368
|
+
|
|
369
|
+
# Footer
|
|
370
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
|
371
|
+
lines.extend(
|
|
372
|
+
[
|
|
373
|
+
"---",
|
|
374
|
+
f"*Auto-generated by mcp-ticketer on {timestamp}*",
|
|
375
|
+
]
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return "\n".join(lines)
|
|
@@ -14,6 +14,7 @@ def diagnose_adapter_configuration(console: Console) -> None:
|
|
|
14
14
|
"""Diagnose adapter configuration and provide recommendations.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
|
+
----
|
|
17
18
|
console: Rich console for output
|
|
18
19
|
|
|
19
20
|
"""
|
|
@@ -354,10 +355,11 @@ def get_adapter_status() -> dict[str, Any]:
|
|
|
354
355
|
"""Get current adapter status for programmatic use.
|
|
355
356
|
|
|
356
357
|
Returns:
|
|
358
|
+
-------
|
|
357
359
|
Dictionary with adapter status information
|
|
358
360
|
|
|
359
361
|
"""
|
|
360
|
-
status = {
|
|
362
|
+
status: dict[str, Any] = {
|
|
361
363
|
"adapter_type": None,
|
|
362
364
|
"configuration_source": None,
|
|
363
365
|
"credentials_valid": False,
|
|
@@ -76,6 +76,9 @@ def create_auggie_server_config(
|
|
|
76
76
|
) -> dict[str, Any]:
|
|
77
77
|
"""Create Auggie MCP server configuration for mcp-ticketer.
|
|
78
78
|
|
|
79
|
+
Uses the CLI command (mcp-ticketer mcp) which implements proper
|
|
80
|
+
Content-Length framing via FastMCP SDK, required for modern MCP clients.
|
|
81
|
+
|
|
79
82
|
Args:
|
|
80
83
|
python_path: Path to Python executable in mcp-ticketer venv
|
|
81
84
|
project_config: Project configuration from .mcp-ticketer/config.json
|
|
@@ -85,7 +88,9 @@ def create_auggie_server_config(
|
|
|
85
88
|
Auggie MCP server configuration dict
|
|
86
89
|
|
|
87
90
|
"""
|
|
88
|
-
# Use Python module invocation
|
|
91
|
+
# IMPORTANT: Use CLI command, NOT Python module invocation
|
|
92
|
+
# The CLI uses FastMCP SDK which implements proper Content-Length framing
|
|
93
|
+
# Legacy python -m mcp_ticketer.mcp.server uses line-delimited JSON (incompatible)
|
|
89
94
|
from pathlib import Path
|
|
90
95
|
|
|
91
96
|
# Get adapter configuration
|
|
@@ -137,14 +142,21 @@ def create_auggie_server_config(
|
|
|
137
142
|
if "project_key" in adapter_config:
|
|
138
143
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
139
144
|
|
|
140
|
-
#
|
|
141
|
-
|
|
145
|
+
# Get mcp-ticketer CLI path from Python path
|
|
146
|
+
# If python_path is /path/to/venv/bin/python, CLI is /path/to/venv/bin/mcp-ticketer
|
|
147
|
+
python_dir = Path(python_path).parent
|
|
148
|
+
cli_path = str(python_dir / "mcp-ticketer")
|
|
149
|
+
|
|
150
|
+
# Build CLI arguments
|
|
151
|
+
args = ["mcp"]
|
|
142
152
|
if project_path:
|
|
143
|
-
args.
|
|
153
|
+
args.extend(["--path", project_path])
|
|
144
154
|
|
|
145
155
|
# Create server configuration (simpler than Gemini - no timeout/trust)
|
|
156
|
+
# NOTE: Environment variables below are optional fallbacks
|
|
157
|
+
# The CLI loads config from .mcp-ticketer/config.json
|
|
146
158
|
config = {
|
|
147
|
-
"command":
|
|
159
|
+
"command": cli_path,
|
|
148
160
|
"args": args,
|
|
149
161
|
"env": env_vars,
|
|
150
162
|
}
|