mcp-ticketer 0.4.11__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.

Files changed (111) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/adapters/__init__.py +2 -0
  4. mcp_ticketer/adapters/aitrackdown.py +394 -9
  5. mcp_ticketer/adapters/asana/__init__.py +15 -0
  6. mcp_ticketer/adapters/asana/adapter.py +1416 -0
  7. mcp_ticketer/adapters/asana/client.py +292 -0
  8. mcp_ticketer/adapters/asana/mappers.py +348 -0
  9. mcp_ticketer/adapters/asana/types.py +146 -0
  10. mcp_ticketer/adapters/github.py +836 -105
  11. mcp_ticketer/adapters/hybrid.py +47 -5
  12. mcp_ticketer/adapters/jira.py +772 -1
  13. mcp_ticketer/adapters/linear/adapter.py +2293 -108
  14. mcp_ticketer/adapters/linear/client.py +146 -12
  15. mcp_ticketer/adapters/linear/mappers.py +105 -11
  16. mcp_ticketer/adapters/linear/queries.py +168 -1
  17. mcp_ticketer/adapters/linear/types.py +80 -4
  18. mcp_ticketer/analysis/__init__.py +56 -0
  19. mcp_ticketer/analysis/dependency_graph.py +255 -0
  20. mcp_ticketer/analysis/health_assessment.py +304 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/project_status.py +594 -0
  23. mcp_ticketer/analysis/similarity.py +224 -0
  24. mcp_ticketer/analysis/staleness.py +266 -0
  25. mcp_ticketer/automation/__init__.py +11 -0
  26. mcp_ticketer/automation/project_updates.py +378 -0
  27. mcp_ticketer/cache/memory.py +3 -3
  28. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  29. mcp_ticketer/cli/auggie_configure.py +18 -6
  30. mcp_ticketer/cli/codex_configure.py +175 -60
  31. mcp_ticketer/cli/configure.py +884 -146
  32. mcp_ticketer/cli/cursor_configure.py +314 -0
  33. mcp_ticketer/cli/diagnostics.py +31 -28
  34. mcp_ticketer/cli/discover.py +293 -21
  35. mcp_ticketer/cli/gemini_configure.py +18 -6
  36. mcp_ticketer/cli/init_command.py +880 -0
  37. mcp_ticketer/cli/instruction_commands.py +435 -0
  38. mcp_ticketer/cli/linear_commands.py +99 -15
  39. mcp_ticketer/cli/main.py +109 -2055
  40. mcp_ticketer/cli/mcp_configure.py +673 -99
  41. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  42. mcp_ticketer/cli/migrate_config.py +12 -8
  43. mcp_ticketer/cli/platform_commands.py +6 -6
  44. mcp_ticketer/cli/platform_detection.py +477 -0
  45. mcp_ticketer/cli/platform_installer.py +536 -0
  46. mcp_ticketer/cli/project_update_commands.py +350 -0
  47. mcp_ticketer/cli/queue_commands.py +15 -15
  48. mcp_ticketer/cli/setup_command.py +639 -0
  49. mcp_ticketer/cli/simple_health.py +13 -11
  50. mcp_ticketer/cli/ticket_commands.py +277 -36
  51. mcp_ticketer/cli/update_checker.py +313 -0
  52. mcp_ticketer/cli/utils.py +45 -41
  53. mcp_ticketer/core/__init__.py +35 -1
  54. mcp_ticketer/core/adapter.py +170 -5
  55. mcp_ticketer/core/config.py +38 -31
  56. mcp_ticketer/core/env_discovery.py +33 -3
  57. mcp_ticketer/core/env_loader.py +7 -6
  58. mcp_ticketer/core/exceptions.py +10 -4
  59. mcp_ticketer/core/http_client.py +10 -10
  60. mcp_ticketer/core/instructions.py +405 -0
  61. mcp_ticketer/core/label_manager.py +732 -0
  62. mcp_ticketer/core/mappers.py +32 -20
  63. mcp_ticketer/core/models.py +136 -1
  64. mcp_ticketer/core/onepassword_secrets.py +379 -0
  65. mcp_ticketer/core/priority_matcher.py +463 -0
  66. mcp_ticketer/core/project_config.py +148 -14
  67. mcp_ticketer/core/registry.py +1 -1
  68. mcp_ticketer/core/session_state.py +171 -0
  69. mcp_ticketer/core/state_matcher.py +592 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  73. mcp_ticketer/mcp/__init__.py +2 -2
  74. mcp_ticketer/mcp/server/__init__.py +2 -2
  75. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  76. mcp_ticketer/mcp/server/main.py +187 -93
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +37 -9
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1429 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  90. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  91. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  92. mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
  93. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  94. mcp_ticketer/mcp/server/tools/ticket_tools.py +1182 -82
  95. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  96. mcp_ticketer/queue/health_monitor.py +1 -0
  97. mcp_ticketer/queue/manager.py +4 -4
  98. mcp_ticketer/queue/queue.py +3 -3
  99. mcp_ticketer/queue/run_worker.py +1 -1
  100. mcp_ticketer/queue/ticket_registry.py +2 -2
  101. mcp_ticketer/queue/worker.py +15 -13
  102. mcp_ticketer/utils/__init__.py +5 -0
  103. mcp_ticketer/utils/token_utils.py +246 -0
  104. mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
  105. mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
  106. mcp_ticketer-0.4.11.dist-info/METADATA +0 -496
  107. mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
  108. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
  109. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
  110. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
  111. {mcp_ticketer-0.4.11.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)
@@ -115,7 +115,7 @@ class MemoryCache:
115
115
  return len(self._cache)
116
116
 
117
117
  @staticmethod
118
- def generate_key(*args, **kwargs) -> str:
118
+ def generate_key(*args: Any, **kwargs: Any) -> str:
119
119
  """Generate cache key from arguments.
120
120
 
121
121
  Args:
@@ -139,7 +139,7 @@ def cache_decorator(
139
139
  key_prefix: str = "",
140
140
  cache_instance: MemoryCache | None = None,
141
141
  ) -> Callable:
142
- """Decorator for caching async function results.
142
+ """Decorate async function to cache its results.
143
143
 
144
144
  Args:
145
145
  ttl: TTL for cached results
@@ -155,7 +155,7 @@ def cache_decorator(
155
155
 
156
156
  def decorator(func: Callable) -> Callable:
157
157
  @wraps(func)
158
- async def wrapper(*args, **kwargs):
158
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
159
159
  # Generate cache key
160
160
  base_key = MemoryCache.generate_key(*args, **kwargs)
161
161
  cache_key = f"{key_prefix}:{func.__name__}:{base_key}"
@@ -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
  """
@@ -343,7 +344,7 @@ def _provide_recommendations(console: Console) -> None:
343
344
  console.print("• For local files: [cyan]mcp-ticketer init aitrackdown[/cyan]")
344
345
 
345
346
  console.print("\n[bold]Test Configuration:[/bold]")
346
- console.print("• Run diagnostics: [cyan]mcp-ticketer diagnose[/cyan]")
347
+ console.print("• Run diagnostics: [cyan]mcp-ticketer doctor[/cyan]")
347
348
  console.print(
348
349
  "• Test ticket creation: [cyan]mcp-ticketer create 'Test ticket'[/cyan]"
349
350
  )
@@ -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 pattern (works regardless of where package is installed)
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
- # Use Python module invocation pattern
141
- args = ["-m", "mcp_ticketer.mcp.server"]
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.append(project_path)
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": python_path,
159
+ "command": cli_path,
148
160
  "args": args,
149
161
  "env": env_vars,
150
162
  }
@@ -243,7 +255,7 @@ def configure_auggie_mcp(force: bool = False) -> None:
243
255
  "Could not find mcp-ticketer Python executable. "
244
256
  "Please ensure mcp-ticketer is installed.\n"
245
257
  "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
246
- )
258
+ ) from e
247
259
 
248
260
  # Step 2: Load project configuration
249
261
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")