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
@@ -4,8 +4,15 @@ from pathlib import Path
4
4
 
5
5
  import typer
6
6
  from rich.console import Console
7
+ from rich.panel import Panel
8
+ from rich.table import Table
7
9
 
8
10
  from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
11
+ from ..core.onepassword_secrets import (
12
+ OnePasswordConfig,
13
+ OnePasswordSecretsLoader,
14
+ check_op_cli_status,
15
+ )
9
16
  from ..core.project_config import (
10
17
  AdapterConfig,
11
18
  ConfigResolver,
@@ -21,10 +28,12 @@ def _mask_sensitive(value: str, key: str) -> str:
21
28
  """Mask sensitive values for display.
22
29
 
23
30
  Args:
31
+ ----
24
32
  value: Value to potentially mask
25
33
  key: Key name to determine if masking needed
26
34
 
27
35
  Returns:
36
+ -------
28
37
  Masked or original value
29
38
 
30
39
  """
@@ -54,6 +63,7 @@ def _display_discovered_adapter(
54
63
  """Display information about a discovered adapter.
55
64
 
56
65
  Args:
66
+ ----
57
67
  adapter: Discovered adapter to display
58
68
  discovery: EnvDiscovery instance for validation
59
69
 
@@ -181,7 +191,7 @@ def save(
181
191
  console.print(
182
192
  "[dim]Make sure your .env file contains adapter credentials[/dim]"
183
193
  )
184
- raise typer.Exit(1)
194
+ raise typer.Exit(1) from None
185
195
 
186
196
  # Determine which adapter to save
187
197
  if adapter:
@@ -191,13 +201,13 @@ def save(
191
201
  console.print(
192
202
  f"[dim]Available: {', '.join(a.adapter_type for a in result.adapters)}[/dim]"
193
203
  )
194
- raise typer.Exit(1)
204
+ raise typer.Exit(1) from None
195
205
  else:
196
206
  # Use recommended adapter
197
207
  discovered_adapter = result.get_primary_adapter()
198
208
  if not discovered_adapter:
199
209
  console.print("[red]Could not determine recommended adapter[/red]")
200
- raise typer.Exit(1)
210
+ raise typer.Exit(1) from None
201
211
 
202
212
  console.print(
203
213
  f"[bold]Using recommended adapter:[/bold] {discovered_adapter.adapter_type}"
@@ -216,7 +226,7 @@ def save(
216
226
  console.print(
217
227
  "[dim]Fix the configuration in your .env file and try again[/dim]"
218
228
  )
219
- raise typer.Exit(1)
229
+ raise typer.Exit(1) from None
220
230
 
221
231
  if dry_run:
222
232
  console.print("\n[yellow]Dry run - no changes made[/yellow]")
@@ -239,14 +249,15 @@ def save(
239
249
  # Add to config
240
250
  config.adapters[discovered_adapter.adapter_type] = adapter_config
241
251
 
242
- # Save
252
+ # Save (always to project config for security)
243
253
  try:
254
+ resolver.save_project_config(config, proj_path)
255
+ config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
256
+
244
257
  if global_config:
245
- resolver.save_global_config(config)
246
- config_location = resolver.GLOBAL_CONFIG_PATH
247
- else:
248
- resolver.save_project_config(config, proj_path)
249
- config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
258
+ console.print(
259
+ "[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
260
+ )
250
261
 
251
262
  console.print(f"\n[green]✅ Configuration saved to:[/green] {config_location}")
252
263
  console.print(
@@ -255,7 +266,7 @@ def save(
255
266
 
256
267
  except Exception as e:
257
268
  console.print(f"\n[red]Failed to save configuration:[/red] {e}")
258
- raise typer.Exit(1)
269
+ raise typer.Exit(1) from None
259
270
 
260
271
 
261
272
  @app.command()
@@ -283,7 +294,7 @@ def interactive(
283
294
  console.print(f" ✅ {env_file}")
284
295
  else:
285
296
  console.print("[red]No .env files found[/red]")
286
- raise typer.Exit(1)
297
+ raise typer.Exit(1) from None
287
298
 
288
299
  # Show discovered adapters
289
300
  if not result.adapters:
@@ -291,7 +302,7 @@ def interactive(
291
302
  console.print(
292
303
  "[dim]Make sure your .env file contains adapter credentials[/dim]"
293
304
  )
294
- raise typer.Exit(1)
305
+ raise typer.Exit(1) from None
295
306
 
296
307
  console.print("\n[bold]Detected adapter configurations:[/bold]")
297
308
  for i, adapter in enumerate(result.adapters, 1):
@@ -331,7 +342,7 @@ def interactive(
331
342
  if choice in [1, 2]:
332
343
  if not primary:
333
344
  console.print("[red]No recommended adapter found[/red]")
334
- raise typer.Exit(1)
345
+ raise typer.Exit(1) from None
335
346
  adapters_to_save = [primary]
336
347
  default_adapter = primary.adapter_type
337
348
  elif choice == 3:
@@ -347,7 +358,7 @@ def interactive(
347
358
  default_adapter = selected.adapter_type
348
359
  else:
349
360
  console.print("[red]Invalid choice[/red]")
350
- raise typer.Exit(1)
361
+ raise typer.Exit(1) from None
351
362
  else: # choice == 4
352
363
  adapters_to_save = result.adapters
353
364
  default_adapter = (
@@ -388,22 +399,283 @@ def interactive(
388
399
 
389
400
  console.print(f" ✅ Added {discovered_adapter.adapter_type}")
390
401
 
391
- # Save
402
+ # Save (always to project config for security)
392
403
  try:
404
+ resolver.save_project_config(config, proj_path)
405
+ config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
406
+
393
407
  if save_global:
394
- resolver.save_global_config(config)
395
- config_location = resolver.GLOBAL_CONFIG_PATH
396
- else:
397
- resolver.save_project_config(config, proj_path)
398
- config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
408
+ console.print(
409
+ "[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
410
+ )
399
411
 
400
412
  console.print(f"\n[green]✅ Configuration saved to:[/green] {config_location}")
401
413
  console.print(f"[green]✅ Default adapter:[/green] {config.default_adapter}")
402
414
 
403
415
  except Exception as e:
404
416
  console.print(f"\n[red]Failed to save configuration:[/red] {e}")
417
+ raise typer.Exit(1) from None
418
+
419
+
420
+ @app.command(name="1password-status")
421
+ def onepassword_status() -> None:
422
+ """Check 1Password CLI installation and authentication status."""
423
+ console.print(
424
+ Panel.fit(
425
+ "[bold cyan]1Password CLI Status[/bold cyan]\n"
426
+ "Checking 1Password integration...",
427
+ border_style="cyan",
428
+ )
429
+ )
430
+
431
+ status = check_op_cli_status()
432
+
433
+ # Create status table
434
+ table = Table(title="1Password CLI Status")
435
+ table.add_column("Component", style="cyan")
436
+ table.add_column("Status", style="white")
437
+
438
+ # CLI installed
439
+ if status["installed"]:
440
+ table.add_row(
441
+ "CLI Installed", f"[green]✓ Yes[/green] (version {status['version']})"
442
+ )
443
+ else:
444
+ table.add_row("CLI Installed", "[red]✗ No[/red]")
445
+ console.print(table)
446
+ console.print(
447
+ "\n[yellow]Install 1Password CLI:[/yellow]\n"
448
+ " macOS: brew install 1password-cli\n"
449
+ " Linux: See https://developer.1password.com/docs/cli/get-started/\n"
450
+ " Windows: See https://developer.1password.com/docs/cli/get-started/"
451
+ )
452
+ return
453
+
454
+ # Authentication
455
+ if status["authenticated"]:
456
+ table.add_row("Authentication", "[green]✓ Signed in[/green]")
457
+
458
+ # Show accounts
459
+ if status["accounts"]:
460
+ for account in status["accounts"]:
461
+ account_url = account.get("url", "N/A")
462
+ account_email = account.get("email", "N/A")
463
+ table.add_row(" Account", f"{account_email} ({account_url})")
464
+ else:
465
+ table.add_row("Authentication", "[yellow]⚠ Not signed in[/yellow]")
466
+
467
+ console.print(table)
468
+
469
+ if not status["authenticated"]:
470
+ console.print("\n[yellow]Sign in to 1Password:[/yellow]\n" " Run: op signin\n")
471
+ else:
472
+ console.print(
473
+ "\n[green]✓ 1Password CLI is ready to use![/green]\n\n"
474
+ "You can now use .env files with op:// secret references.\n"
475
+ "Run 'mcp-ticketer discover 1password-template' to create template files."
476
+ )
477
+
478
+
479
+ @app.command(name="1password-template")
480
+ def onepassword_template(
481
+ adapter: str = typer.Argument(
482
+ ...,
483
+ help="Adapter type (linear, github, jira, aitrackdown)",
484
+ ),
485
+ vault: str = typer.Option(
486
+ "Development",
487
+ "--vault",
488
+ "-v",
489
+ help="1Password vault name for secret references",
490
+ ),
491
+ item: str | None = typer.Option(
492
+ None,
493
+ "--item",
494
+ "-i",
495
+ help="1Password item name (defaults to adapter name)",
496
+ ),
497
+ output: Path | None = typer.Option(
498
+ None,
499
+ "--output",
500
+ "-o",
501
+ help="Output file path (defaults to .env.1password)",
502
+ ),
503
+ ) -> None:
504
+ """Create a .env template file with 1Password secret references.
505
+
506
+ This creates a template file that uses op:// secret references,
507
+ which can be used with: op run --env-file=.env.1password -- <command>
508
+
509
+ Examples:
510
+ --------
511
+ # Create Linear template
512
+ mcp-ticketer discover 1password-template linear
513
+
514
+ # Create GitHub template with custom vault
515
+ mcp-ticketer discover 1password-template github --vault=Production
516
+
517
+ # Create template with custom item name
518
+ mcp-ticketer discover 1password-template jira --item="JIRA API Keys"
519
+
520
+ """
521
+ # Check if op CLI is available
522
+ status = check_op_cli_status()
523
+ if not status["installed"]:
524
+ console.print(
525
+ "[red]1Password CLI not installed.[/red]\n\n"
526
+ "Install it first:\n"
527
+ " macOS: brew install 1password-cli\n"
528
+ " Other: https://developer.1password.com/docs/cli/get-started/"
529
+ )
405
530
  raise typer.Exit(1)
406
531
 
532
+ # Set default output path
533
+ if output is None:
534
+ output = Path(f".env.1password.{adapter.lower()}")
535
+
536
+ # Create loader and generate template
537
+ loader = OnePasswordSecretsLoader(OnePasswordConfig())
538
+ loader.create_template_file(output, adapter, vault, item)
539
+
540
+ console.print(
541
+ Panel.fit(
542
+ f"[bold green]✓ Template created![/bold green]\n\n"
543
+ f"File: {output}\n"
544
+ f"Vault: {vault}\n"
545
+ f"Item: {item or adapter.upper()}\n\n"
546
+ f"[bold]Next steps:[/bold]\n"
547
+ f"1. Create item '{item or adapter.upper()}' in 1Password vault '{vault}'\n"
548
+ f"2. Add the required fields to the item\n"
549
+ f"3. Test with: op run --env-file={output} -- mcp-ticketer discover show\n"
550
+ f"4. Save config: op run --env-file={output} -- mcp-ticketer discover save",
551
+ border_style="green",
552
+ )
553
+ )
554
+
555
+ # Show template contents
556
+ console.print("\n[bold]Template contents:[/bold]\n")
557
+ console.print(Panel(output.read_text(), border_style="dim"))
558
+
559
+
560
+ @app.command(name="1password-test")
561
+ def onepassword_test(
562
+ env_file: Path = typer.Option(
563
+ ".env.1password",
564
+ "--file",
565
+ "-f",
566
+ help="Path to .env file with op:// references",
567
+ ),
568
+ ) -> None:
569
+ """Test 1Password secret resolution from .env file.
570
+
571
+ This command loads secrets from the specified .env file and
572
+ displays the resolved values (with sensitive data masked).
573
+
574
+ Example:
575
+ -------
576
+ mcp-ticketer discover 1password-test --file=.env.1password.linear
577
+
578
+ """
579
+ # Check if file exists
580
+ if not env_file.exists():
581
+ console.print(f"[red]File not found:[/red] {env_file}")
582
+ raise typer.Exit(1)
583
+
584
+ # Check if op CLI is available and authenticated
585
+ status = check_op_cli_status()
586
+ if not status["installed"]:
587
+ console.print("[red]1Password CLI not installed.[/red]")
588
+ raise typer.Exit(1)
589
+
590
+ if not status["authenticated"]:
591
+ console.print(
592
+ "[red]1Password CLI not authenticated.[/red]\n\n" "Run: op signin"
593
+ )
594
+ raise typer.Exit(1)
595
+
596
+ console.print(
597
+ Panel.fit(
598
+ f"[bold cyan]Testing 1Password Secret Resolution[/bold cyan]\n"
599
+ f"File: {env_file}",
600
+ border_style="cyan",
601
+ )
602
+ )
603
+
604
+ # Load secrets
605
+ loader = OnePasswordSecretsLoader(OnePasswordConfig())
606
+
607
+ try:
608
+ secrets = loader.load_secrets_from_env_file(env_file)
609
+
610
+ # Display resolved secrets
611
+ table = Table(title="Resolved Secrets")
612
+ table.add_column("Variable", style="cyan")
613
+ table.add_column("Value", style="green")
614
+
615
+ for key, value in secrets.items():
616
+ # Mask sensitive values
617
+ display_value = _mask_sensitive(value, key)
618
+ table.add_row(key, display_value)
619
+
620
+ console.print(table)
621
+
622
+ console.print(
623
+ f"\n[green]✓ Successfully resolved {len(secrets)} secrets![/green]"
624
+ )
625
+
626
+ # Test discovery with these secrets
627
+ console.print("\n[bold]Testing configuration discovery...[/bold]")
628
+ EnvDiscovery(enable_1password=False) # Already resolved
629
+
630
+ # Temporarily write resolved secrets to test discovery
631
+ import tempfile
632
+
633
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as tmp:
634
+ for key, value in secrets.items():
635
+ tmp.write(f"{key}={value}\n")
636
+ tmp_path = Path(tmp.name)
637
+
638
+ try:
639
+ # Mock the env file loading by directly providing secrets
640
+ from ..core.env_discovery import DiscoveryResult
641
+
642
+ DiscoveryResult()
643
+
644
+ # Try to detect adapters from the resolved secrets
645
+ from ..core.env_discovery import EnvDiscovery as ED
646
+
647
+ ed = ED(enable_1password=False)
648
+ ed.project_path = Path.cwd()
649
+
650
+ # Manually detect from secrets dict
651
+ linear_adapter = ed._detect_linear(secrets, str(env_file))
652
+ if linear_adapter:
653
+ console.print("\n[green]✓ Detected Linear configuration[/green]")
654
+ _display_discovered_adapter(linear_adapter, ed)
655
+
656
+ github_adapter = ed._detect_github(secrets, str(env_file))
657
+ if github_adapter:
658
+ console.print("\n[green]✓ Detected GitHub configuration[/green]")
659
+ _display_discovered_adapter(github_adapter, ed)
660
+
661
+ jira_adapter = ed._detect_jira(secrets, str(env_file))
662
+ if jira_adapter:
663
+ console.print("\n[green]✓ Detected JIRA configuration[/green]")
664
+ _display_discovered_adapter(jira_adapter, ed)
665
+ finally:
666
+ tmp_path.unlink()
667
+
668
+ except Exception as e:
669
+ console.print(f"\n[red]Failed to resolve secrets:[/red] {e}")
670
+ console.print(
671
+ "\n[yellow]Troubleshooting:[/yellow]\n"
672
+ "1. Check that the item exists in 1Password\n"
673
+ "2. Verify the vault name is correct\n"
674
+ "3. Ensure all field names match\n"
675
+ f"4. Run: op inject --in-file={env_file} (to see detailed errors)"
676
+ )
677
+ raise typer.Exit(1) from None
678
+
407
679
 
408
680
  if __name__ == "__main__":
409
681
  app()
@@ -78,6 +78,9 @@ def create_gemini_server_config(
78
78
  ) -> dict:
79
79
  """Create Gemini MCP server configuration for mcp-ticketer.
80
80
 
81
+ Uses the CLI command (mcp-ticketer mcp) which implements proper
82
+ Content-Length framing via FastMCP SDK, required for modern MCP clients.
83
+
81
84
  Args:
82
85
  python_path: Path to Python executable in mcp-ticketer venv
83
86
  project_config: Project configuration from .mcp-ticketer/config.json
@@ -87,7 +90,9 @@ def create_gemini_server_config(
87
90
  Gemini MCP server configuration dict
88
91
 
89
92
  """
90
- # Use Python module invocation pattern (works regardless of where package is installed)
93
+ # IMPORTANT: Use CLI command, NOT Python module invocation
94
+ # The CLI uses FastMCP SDK which implements proper Content-Length framing
95
+ # Legacy python -m mcp_ticketer.mcp.server uses line-delimited JSON (incompatible)
91
96
 
92
97
  # Get adapter configuration
93
98
  adapter = project_config.get("default_adapter", "aitrackdown")
@@ -138,14 +143,21 @@ def create_gemini_server_config(
138
143
  if "project_key" in adapter_config:
139
144
  env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
140
145
 
141
- # Use Python module invocation pattern
142
- args = ["-m", "mcp_ticketer.mcp.server"]
146
+ # Get mcp-ticketer CLI path from Python path
147
+ # If python_path is /path/to/venv/bin/python, CLI is /path/to/venv/bin/mcp-ticketer
148
+ python_dir = Path(python_path).parent
149
+ cli_path = str(python_dir / "mcp-ticketer")
150
+
151
+ # Build CLI arguments
152
+ args = ["mcp"]
143
153
  if project_path:
144
- args.append(project_path)
154
+ args.extend(["--path", project_path])
145
155
 
146
156
  # Create server configuration with Gemini-specific options
157
+ # NOTE: Environment variables below are optional fallbacks
158
+ # The CLI loads config from .mcp-ticketer/config.json
147
159
  config = {
148
- "command": python_path,
160
+ "command": cli_path,
149
161
  "args": args,
150
162
  "env": env_vars,
151
163
  "timeout": 15000, # 15 seconds timeout
@@ -246,7 +258,7 @@ def configure_gemini_mcp(
246
258
  "Could not find mcp-ticketer Python executable. "
247
259
  "Please ensure mcp-ticketer is installed.\n"
248
260
  "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
249
- )
261
+ ) from e
250
262
 
251
263
  # Step 2: Load project configuration
252
264
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")