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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +394 -9
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1416 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +836 -105
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +772 -1
- mcp_ticketer/adapters/linear/adapter.py +2293 -108
- mcp_ticketer/adapters/linear/client.py +146 -12
- mcp_ticketer/adapters/linear/mappers.py +105 -11
- 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/cache/memory.py +3 -3
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +18 -6
- mcp_ticketer/cli/codex_configure.py +175 -60
- mcp_ticketer/cli/configure.py +884 -146
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +31 -28
- mcp_ticketer/cli/discover.py +293 -21
- mcp_ticketer/cli/gemini_configure.py +18 -6
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +99 -15
- mcp_ticketer/cli/main.py +109 -2055
- mcp_ticketer/cli/mcp_configure.py +673 -99
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +6 -6
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +13 -11
- mcp_ticketer/cli/ticket_commands.py +277 -36
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +45 -41
- mcp_ticketer/core/__init__.py +35 -1
- mcp_ticketer/core/adapter.py +170 -5
- mcp_ticketer/core/config.py +38 -31
- mcp_ticketer/core/env_discovery.py +33 -3
- mcp_ticketer/core/env_loader.py +7 -6
- mcp_ticketer/core/exceptions.py +10 -4
- mcp_ticketer/core/http_client.py +10 -10
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +32 -20
- mcp_ticketer/core/models.py +136 -1
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +148 -14
- mcp_ticketer/core/registry.py +1 -1
- 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/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +2 -2
- mcp_ticketer/mcp/server/__init__.py +2 -2
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +187 -93
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +37 -9
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
- 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 +1429 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- 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 +1182 -82
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/health_monitor.py +1 -0
- mcp_ticketer/queue/manager.py +4 -4
- mcp_ticketer/queue/queue.py +3 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +2 -2
- mcp_ticketer/queue/worker.py +15 -13
- 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.4.11.dist-info/METADATA +0 -496
- mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/discover.py
CHANGED
|
@@ -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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
|
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
|
-
#
|
|
142
|
-
|
|
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.
|
|
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":
|
|
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]")
|