mcp-ticketer 0.2.0__py3-none-any.whl → 0.3.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.

@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.1"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -0,0 +1,384 @@
1
+ """Adapter diagnostics and configuration validation."""
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from ..core import AdapterRegistry
14
+ from ..core.env_discovery import discover_config
15
+
16
+
17
+ def diagnose_adapter_configuration(console: Console) -> None:
18
+ """Diagnose adapter configuration and provide recommendations.
19
+
20
+ Args:
21
+ console: Rich console for output
22
+ """
23
+ console.print("\n[bold blue]🔍 MCP Ticketer Adapter Configuration Diagnostics[/bold blue]\n")
24
+
25
+ # 1. Check .env files
26
+ _check_env_files(console)
27
+
28
+ # 2. Check configuration files
29
+ _check_configuration_files(console)
30
+
31
+ # 3. Check adapter discovery
32
+ _check_adapter_discovery(console)
33
+
34
+ # 4. Test adapter instantiation
35
+ _test_adapter_instantiation(console)
36
+
37
+ # 5. Provide recommendations
38
+ _provide_recommendations(console)
39
+
40
+
41
+ def _check_env_files(console: Console) -> None:
42
+ """Check .env files for configuration."""
43
+ console.print("[bold]1. .env File Configuration[/bold]")
44
+
45
+ # Load .env files
46
+ from ..mcp.server import _load_env_configuration
47
+ env_config = _load_env_configuration()
48
+
49
+ # Check for .env files
50
+ env_files = [".env.local", ".env"]
51
+
52
+ table = Table(show_header=True, header_style="bold magenta")
53
+ table.add_column("File", style="cyan")
54
+ table.add_column("Status", style="green")
55
+ table.add_column("Variables Found", style="yellow")
56
+
57
+ for env_file in env_files:
58
+ env_path = Path.cwd() / env_file
59
+ if env_path.exists():
60
+ try:
61
+ # Count variables in file
62
+ var_count = 0
63
+ with open(env_path, 'r') as f:
64
+ for line in f:
65
+ line = line.strip()
66
+ if line and not line.startswith('#') and '=' in line:
67
+ var_count += 1
68
+
69
+ status = "✅ Found"
70
+ variables = f"{var_count} variables"
71
+ except Exception:
72
+ status = "⚠️ Error reading"
73
+ variables = "Unknown"
74
+ else:
75
+ status = "❌ Missing"
76
+ variables = "N/A"
77
+
78
+ table.add_row(env_file, status, variables)
79
+
80
+ console.print(table)
81
+
82
+ # Show discovered configuration
83
+ if env_config:
84
+ console.print(f"\n[green]✅ Discovered adapter: {env_config['adapter_type']}[/green]")
85
+ config_keys = list(env_config['adapter_config'].keys())
86
+ console.print(f"[dim]Configuration keys: {config_keys}[/dim]")
87
+ else:
88
+ console.print("\n[yellow]⚠️ No adapter configuration found in .env files[/yellow]")
89
+
90
+ console.print()
91
+
92
+
93
+ def _check_configuration_files(console: Console) -> None:
94
+ """Check configuration files."""
95
+ console.print("[bold]2. Configuration Files[/bold]")
96
+
97
+ config_files = [
98
+ (".env.local", "Local environment file (highest priority)"),
99
+ (".env", "Environment file"),
100
+ (".mcp-ticketer/config.json", "Project configuration"),
101
+ (str(Path.home() / ".mcp-ticketer" / "config.json"), "Global configuration"),
102
+ ]
103
+
104
+ table = Table(show_header=True, header_style="bold magenta")
105
+ table.add_column("File", style="cyan")
106
+ table.add_column("Description", style="white")
107
+ table.add_column("Status", style="green")
108
+ table.add_column("Size", style="yellow")
109
+
110
+ for file_path, description in config_files:
111
+ path = Path(file_path)
112
+ if path.exists():
113
+ try:
114
+ size = path.stat().st_size
115
+ status = "✅ Found"
116
+ size_str = f"{size} bytes"
117
+ except Exception:
118
+ status = "⚠️ Error"
119
+ size_str = "Unknown"
120
+ else:
121
+ status = "❌ Missing"
122
+ size_str = "N/A"
123
+
124
+ table.add_row(str(path), description, status, size_str)
125
+
126
+ console.print(table)
127
+ console.print()
128
+
129
+
130
+ def _check_adapter_discovery(console: Console) -> None:
131
+ """Check adapter discovery from configuration."""
132
+ console.print("[bold]3. Adapter Discovery[/bold]")
133
+
134
+ try:
135
+ # Discover configuration
136
+ discovered = discover_config(Path.cwd())
137
+
138
+ if discovered and discovered.adapters:
139
+ primary = discovered.get_primary_adapter()
140
+
141
+ table = Table(show_header=True, header_style="bold magenta")
142
+ table.add_column("Adapter", style="cyan")
143
+ table.add_column("Confidence", style="white")
144
+ table.add_column("Source", style="green")
145
+ table.add_column("Status", style="yellow")
146
+
147
+ for adapter_info in discovered.adapters:
148
+ confidence = f"{adapter_info.confidence:.0%}"
149
+ status = "✅ Primary" if adapter_info == primary else "⚪ Available"
150
+
151
+ table.add_row(
152
+ adapter_info.adapter_type,
153
+ confidence,
154
+ adapter_info.found_in,
155
+ status
156
+ )
157
+
158
+ console.print(table)
159
+
160
+ if primary:
161
+ console.print(f"\n[green]✅ Primary adapter detected: {primary.adapter_type}[/green]")
162
+ console.print(f"[dim]Source: {primary.found_in}[/dim]")
163
+ console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
164
+ else:
165
+ console.print("\n[yellow]⚠️ No primary adapter detected[/yellow]")
166
+ else:
167
+ console.print("[red]❌ No adapters discovered[/red]")
168
+ console.print("[dim]This usually means no credentials are configured[/dim]")
169
+
170
+ except Exception as e:
171
+ console.print(f"[red]❌ Error during discovery: {e}[/red]")
172
+
173
+ console.print()
174
+
175
+
176
+ def _test_adapter_instantiation(console: Console) -> None:
177
+ """Test adapter instantiation."""
178
+ console.print("[bold]4. Adapter Instantiation Test[/bold]")
179
+
180
+ # Determine which adapter to test from .env files
181
+ from ..mcp.server import _load_env_configuration
182
+ env_config = _load_env_configuration()
183
+
184
+ if env_config:
185
+ adapter_type = env_config["adapter_type"]
186
+ config = env_config["adapter_config"]
187
+ else:
188
+ # Try to discover from existing discovery system
189
+ try:
190
+ discovered = discover_config(Path.cwd())
191
+ if discovered and discovered.adapters:
192
+ primary = discovered.get_primary_adapter()
193
+ if primary:
194
+ adapter_type = primary.adapter_type
195
+ # Build config from discovery
196
+ from ..mcp.server import _build_adapter_config_from_env_vars
197
+ config = _build_adapter_config_from_env_vars(adapter_type, {})
198
+ else:
199
+ adapter_type = "aitrackdown"
200
+ config = {"base_path": ".aitrackdown"}
201
+ else:
202
+ adapter_type = "aitrackdown"
203
+ config = {"base_path": ".aitrackdown"}
204
+ except Exception:
205
+ adapter_type = "aitrackdown"
206
+ config = {"base_path": ".aitrackdown"}
207
+
208
+ console.print(f"Testing adapter: [cyan]{adapter_type}[/cyan]")
209
+ console.print(f"Configuration keys: [yellow]{list(config.keys())}[/yellow]")
210
+
211
+ try:
212
+ # Try to instantiate adapter
213
+ adapter = AdapterRegistry.get_adapter(adapter_type, config)
214
+
215
+ console.print(f"[green]✅ Adapter instantiated successfully: {adapter.__class__.__name__}[/green]")
216
+
217
+ # Test basic functionality
218
+ if hasattr(adapter, 'validate_credentials'):
219
+ try:
220
+ is_valid, error_msg = adapter.validate_credentials()
221
+ if is_valid:
222
+ console.print("[green]✅ Credentials validation passed[/green]")
223
+ else:
224
+ console.print(f"[red]❌ Credentials validation failed: {error_msg}[/red]")
225
+ except Exception as e:
226
+ console.print(f"[yellow]⚠️ Credentials validation error: {e}[/yellow]")
227
+
228
+ except Exception as e:
229
+ console.print(f"[red]❌ Adapter instantiation failed: {e}[/red]")
230
+
231
+ # Provide specific guidance based on adapter type
232
+ if adapter_type == "linear":
233
+ console.print("\n[yellow]Linear adapter requires in .env/.env.local:[/yellow]")
234
+ console.print("• LINEAR_API_KEY=your_api_key")
235
+ console.print("• LINEAR_TEAM_ID=your_team_id (or LINEAR_TEAM_KEY=your_team_key)")
236
+ elif adapter_type == "github":
237
+ console.print("\n[yellow]GitHub adapter requires in .env/.env.local:[/yellow]")
238
+ console.print("• GITHUB_TOKEN=your_token")
239
+ console.print("• GITHUB_OWNER=your_username")
240
+ console.print("• GITHUB_REPO=your_repository")
241
+ elif adapter_type == "jira":
242
+ console.print("\n[yellow]JIRA adapter requires in .env/.env.local:[/yellow]")
243
+ console.print("• JIRA_SERVER=your_server_url")
244
+ console.print("• JIRA_EMAIL=your_email")
245
+ console.print("• JIRA_API_TOKEN=your_token")
246
+
247
+ console.print()
248
+
249
+
250
+ def _provide_recommendations(console: Console) -> None:
251
+ """Provide configuration recommendations."""
252
+ console.print("[bold]5. Recommendations[/bold]")
253
+
254
+ # Check .env configuration
255
+ from ..mcp.server import _load_env_configuration
256
+ env_config = _load_env_configuration()
257
+
258
+ recommendations = []
259
+
260
+ if not env_config:
261
+ recommendations.append(
262
+ "Create .env.local or .env file with adapter configuration"
263
+ )
264
+ recommendations.append(
265
+ "Add MCP_TICKETER_ADAPTER=linear (or github, jira) to specify adapter type"
266
+ )
267
+ else:
268
+ adapter_type = env_config["adapter_type"]
269
+ config = env_config["adapter_config"]
270
+
271
+ # Check for incomplete configurations
272
+ if adapter_type == "linear":
273
+ if not config.get("api_key"):
274
+ recommendations.append("Add LINEAR_API_KEY to .env file")
275
+ if not config.get("team_id") and not config.get("team_key"):
276
+ recommendations.append("Add LINEAR_TEAM_ID or LINEAR_TEAM_KEY to .env file")
277
+
278
+ elif adapter_type == "github":
279
+ missing = []
280
+ if not config.get("token"):
281
+ missing.append("GITHUB_TOKEN")
282
+ if not config.get("owner"):
283
+ missing.append("GITHUB_OWNER")
284
+ if not config.get("repo"):
285
+ missing.append("GITHUB_REPO")
286
+ if missing:
287
+ recommendations.append(f"Add missing GitHub variables to .env: {', '.join(missing)}")
288
+
289
+ elif adapter_type == "jira":
290
+ missing = []
291
+ if not config.get("server"):
292
+ missing.append("JIRA_SERVER")
293
+ if not config.get("email"):
294
+ missing.append("JIRA_EMAIL")
295
+ if not config.get("api_token"):
296
+ missing.append("JIRA_API_TOKEN")
297
+ if missing:
298
+ recommendations.append(f"Add missing JIRA variables to .env: {', '.join(missing)}")
299
+
300
+ if recommendations:
301
+ for i, rec in enumerate(recommendations, 1):
302
+ console.print(f"{i}. [yellow]{rec}[/yellow]")
303
+ else:
304
+ console.print("[green]✅ Configuration looks good![/green]")
305
+
306
+ # Show .env file examples
307
+ console.print("\n[bold].env File Examples:[/bold]")
308
+ console.print("• Linear: [cyan]echo 'MCP_TICKETER_ADAPTER=linear\\nLINEAR_API_KEY=your_key\\nLINEAR_TEAM_ID=your_team' > .env.local[/cyan]")
309
+ console.print("• GitHub: [cyan]echo 'MCP_TICKETER_ADAPTER=github\\nGITHUB_TOKEN=your_token\\nGITHUB_OWNER=user\\nGITHUB_REPO=repo' > .env.local[/cyan]")
310
+
311
+ console.print("\n[bold]Quick Setup Commands:[/bold]")
312
+ console.print("• For Linear: [cyan]mcp-ticketer init linear[/cyan]")
313
+ console.print("• For GitHub: [cyan]mcp-ticketer init github[/cyan]")
314
+ console.print("• For JIRA: [cyan]mcp-ticketer init jira[/cyan]")
315
+ console.print("• For local files: [cyan]mcp-ticketer init aitrackdown[/cyan]")
316
+
317
+ console.print("\n[bold]Test Configuration:[/bold]")
318
+ console.print("• Run diagnostics: [cyan]mcp-ticketer diagnose[/cyan]")
319
+ console.print("• Test ticket creation: [cyan]mcp-ticketer create 'Test ticket'[/cyan]")
320
+ console.print("• List tickets: [cyan]mcp-ticketer list[/cyan]")
321
+
322
+
323
+ def get_adapter_status() -> Dict[str, Any]:
324
+ """Get current adapter status for programmatic use.
325
+
326
+ Returns:
327
+ Dictionary with adapter status information
328
+ """
329
+ status = {
330
+ "adapter_type": None,
331
+ "configuration_source": None,
332
+ "credentials_valid": False,
333
+ "error_message": None,
334
+ "recommendations": []
335
+ }
336
+
337
+ try:
338
+ # Check .env files first
339
+ from ..mcp.server import _load_env_configuration
340
+ env_config = _load_env_configuration()
341
+
342
+ if env_config:
343
+ adapter_type = env_config["adapter_type"]
344
+ config = env_config["adapter_config"]
345
+ status["configuration_source"] = ".env files"
346
+ else:
347
+ # Try discovery system
348
+ discovered = discover_config(Path.cwd())
349
+ if discovered and discovered.adapters:
350
+ primary = discovered.get_primary_adapter()
351
+ if primary:
352
+ adapter_type = primary.adapter_type
353
+ status["configuration_source"] = primary.found_in
354
+ # Build basic config
355
+ from ..mcp.server import _build_adapter_config_from_env_vars
356
+ config = _build_adapter_config_from_env_vars(adapter_type, {})
357
+ else:
358
+ adapter_type = "aitrackdown"
359
+ config = {"base_path": ".aitrackdown"}
360
+ status["configuration_source"] = "default"
361
+ else:
362
+ adapter_type = "aitrackdown"
363
+ config = {"base_path": ".aitrackdown"}
364
+ status["configuration_source"] = "default"
365
+
366
+ status["adapter_type"] = adapter_type
367
+
368
+ # Test adapter instantiation
369
+ adapter = AdapterRegistry.get_adapter(adapter_type, config)
370
+
371
+ # Test credentials if possible
372
+ if hasattr(adapter, 'validate_credentials'):
373
+ is_valid, error_msg = adapter.validate_credentials()
374
+ status["credentials_valid"] = is_valid
375
+ if not is_valid:
376
+ status["error_message"] = error_msg
377
+ else:
378
+ status["credentials_valid"] = True # Assume valid if no validation method
379
+
380
+ except Exception as e:
381
+ status["error_message"] = str(e)
382
+ status["recommendations"].append("Check .env file configuration and credentials")
383
+
384
+ return status
mcp_ticketer/cli/main.py CHANGED
@@ -317,13 +317,161 @@ def get_adapter(
317
317
  return AdapterRegistry.get_adapter(adapter_type, adapter_config)
318
318
 
319
319
 
320
+ def _prompt_for_adapter_selection(console: Console) -> str:
321
+ """Interactive prompt for adapter selection.
322
+
323
+ Args:
324
+ console: Rich console for output
325
+
326
+ Returns:
327
+ Selected adapter type
328
+ """
329
+ console.print("\n[bold blue]🚀 MCP Ticketer Setup[/bold blue]")
330
+ console.print("Choose which ticket system you want to connect to:\n")
331
+
332
+ # Define adapter options with descriptions
333
+ adapters = [
334
+ {
335
+ "name": "linear",
336
+ "title": "Linear",
337
+ "description": "Modern project management (linear.app)",
338
+ "requirements": "API key and team ID"
339
+ },
340
+ {
341
+ "name": "github",
342
+ "title": "GitHub Issues",
343
+ "description": "GitHub repository issues",
344
+ "requirements": "Personal access token, owner, and repo"
345
+ },
346
+ {
347
+ "name": "jira",
348
+ "title": "JIRA",
349
+ "description": "Atlassian JIRA project management",
350
+ "requirements": "Server URL, email, and API token"
351
+ },
352
+ {
353
+ "name": "aitrackdown",
354
+ "title": "Local Files (AITrackdown)",
355
+ "description": "Store tickets in local files (no external service)",
356
+ "requirements": "None - works offline"
357
+ }
358
+ ]
359
+
360
+ # Display options
361
+ for i, adapter in enumerate(adapters, 1):
362
+ console.print(f"[cyan]{i}.[/cyan] [bold]{adapter['title']}[/bold]")
363
+ console.print(f" {adapter['description']}")
364
+ console.print(f" [dim]Requirements: {adapter['requirements']}[/dim]\n")
365
+
366
+ # Get user selection
367
+ while True:
368
+ try:
369
+ choice = typer.prompt(
370
+ "Select adapter (1-4)",
371
+ type=int,
372
+ default=1
373
+ )
374
+ if 1 <= choice <= len(adapters):
375
+ selected_adapter = adapters[choice - 1]
376
+ console.print(f"\n[green]✓ Selected: {selected_adapter['title']}[/green]")
377
+ return selected_adapter["name"]
378
+ else:
379
+ console.print(f"[red]Please enter a number between 1 and {len(adapters)}[/red]")
380
+ except (ValueError, typer.Abort):
381
+ console.print("[yellow]Setup cancelled.[/yellow]")
382
+ raise typer.Exit(0)
383
+
384
+
385
+ @app.command()
386
+ def setup(
387
+ adapter: Optional[str] = typer.Option(
388
+ None,
389
+ "--adapter",
390
+ "-a",
391
+ help="Adapter type to use (interactive prompt if not specified)",
392
+ ),
393
+ project_path: Optional[str] = typer.Option(
394
+ None, "--path", help="Project path (default: current directory)"
395
+ ),
396
+ global_config: bool = typer.Option(
397
+ False,
398
+ "--global",
399
+ "-g",
400
+ help="Save to global config instead of project-specific",
401
+ ),
402
+ base_path: Optional[str] = typer.Option(
403
+ None,
404
+ "--base-path",
405
+ "-p",
406
+ help="Base path for ticket storage (AITrackdown only)",
407
+ ),
408
+ api_key: Optional[str] = typer.Option(
409
+ None, "--api-key", help="API key for Linear or API token for JIRA"
410
+ ),
411
+ team_id: Optional[str] = typer.Option(
412
+ None, "--team-id", help="Linear team ID (required for Linear adapter)"
413
+ ),
414
+ jira_server: Optional[str] = typer.Option(
415
+ None,
416
+ "--jira-server",
417
+ help="JIRA server URL (e.g., https://company.atlassian.net)",
418
+ ),
419
+ jira_email: Optional[str] = typer.Option(
420
+ None, "--jira-email", help="JIRA user email for authentication"
421
+ ),
422
+ jira_project: Optional[str] = typer.Option(
423
+ None, "--jira-project", help="Default JIRA project key"
424
+ ),
425
+ github_owner: Optional[str] = typer.Option(
426
+ None, "--github-owner", help="GitHub repository owner"
427
+ ),
428
+ github_repo: Optional[str] = typer.Option(
429
+ None, "--github-repo", help="GitHub repository name"
430
+ ),
431
+ github_token: Optional[str] = typer.Option(
432
+ None, "--github-token", help="GitHub Personal Access Token"
433
+ ),
434
+ ) -> None:
435
+ """Interactive setup wizard for MCP Ticketer (alias for init).
436
+
437
+ This command provides a user-friendly setup experience with prompts
438
+ to guide you through configuring MCP Ticketer for your preferred
439
+ ticket management system. It's identical to 'init' and 'install'.
440
+
441
+ Examples:
442
+ # Run interactive setup
443
+ mcp-ticketer setup
444
+
445
+ # Setup with specific adapter
446
+ mcp-ticketer setup --adapter linear
447
+
448
+ # Setup for different project
449
+ mcp-ticketer setup --path /path/to/project
450
+ """
451
+ # Call init with all parameters
452
+ init(
453
+ adapter=adapter,
454
+ project_path=project_path,
455
+ global_config=global_config,
456
+ base_path=base_path,
457
+ api_key=api_key,
458
+ team_id=team_id,
459
+ jira_server=jira_server,
460
+ jira_email=jira_email,
461
+ jira_project=jira_project,
462
+ github_owner=github_owner,
463
+ github_repo=github_repo,
464
+ github_token=github_token,
465
+ )
466
+
467
+
320
468
  @app.command()
321
469
  def init(
322
470
  adapter: Optional[str] = typer.Option(
323
471
  None,
324
472
  "--adapter",
325
473
  "-a",
326
- help="Adapter type to use (auto-detected from .env if not specified)",
474
+ help="Adapter type to use (interactive prompt if not specified)",
327
475
  ),
328
476
  project_path: Optional[str] = typer.Option(
329
477
  None, "--path", help="Project path (default: current directory)"
@@ -369,11 +517,17 @@ def init(
369
517
  ) -> None:
370
518
  """Initialize mcp-ticketer for the current project.
371
519
 
520
+ This command sets up MCP Ticketer configuration with interactive prompts
521
+ to guide you through the process. It auto-detects adapter configuration
522
+ from .env files or prompts for interactive setup if no configuration is found.
523
+
372
524
  Creates .mcp-ticketer/config.json in the current directory with
373
525
  auto-detected or specified adapter configuration.
374
526
 
527
+ Note: 'setup' and 'install' are synonyms for this command.
528
+
375
529
  Examples:
376
- # Auto-detect from .env.local
530
+ # Interactive setup (same as 'setup' and 'install')
377
531
  mcp-ticketer init
378
532
 
379
533
  # Force specific adapter
@@ -414,32 +568,60 @@ def init(
414
568
  console.print(
415
569
  "[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
416
570
  )
417
- discovered = discover_config(proj_path)
418
-
419
- if discovered and discovered.adapters:
420
- primary = discovered.get_primary_adapter()
421
- if primary:
422
- adapter_type = primary.adapter_type
423
- console.print(
424
- f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
425
- )
426
571
 
427
- # Show what was discovered
428
- console.print(
429
- f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
430
- )
431
- console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
432
- else:
433
- adapter_type = "aitrackdown" # Fallback
434
- console.print(
435
- "[yellow]⚠ No credentials found, defaulting to aitrackdown[/yellow]"
436
- )
437
- else:
438
- adapter_type = "aitrackdown" # Fallback
572
+ # First try our improved .env configuration loader
573
+ from ..mcp.server import _load_env_configuration
574
+ env_config = _load_env_configuration()
575
+
576
+ if env_config:
577
+ adapter_type = env_config["adapter_type"]
439
578
  console.print(
440
- "[yellow] No .env files found, defaulting to aitrackdown[/yellow]"
579
+ f"[green] Detected {adapter_type} adapter from environment files[/green]"
441
580
  )
442
581
 
582
+ # Show what was discovered
583
+ console.print(f"\n[dim]Configuration found in: .env files[/dim]")
584
+ console.print(f"[dim]Confidence: 100%[/dim]")
585
+
586
+ # Ask user to confirm auto-detected adapter
587
+ if not typer.confirm(
588
+ f"Use detected {adapter_type} adapter?",
589
+ default=True,
590
+ ):
591
+ adapter_type = None # Will trigger interactive selection
592
+ else:
593
+ # Fallback to old discovery system for backward compatibility
594
+ discovered = discover_config(proj_path)
595
+
596
+ if discovered and discovered.adapters:
597
+ primary = discovered.get_primary_adapter()
598
+ if primary:
599
+ adapter_type = primary.adapter_type
600
+ console.print(
601
+ f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
602
+ )
603
+
604
+ # Show what was discovered
605
+ console.print(
606
+ f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
607
+ )
608
+ console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
609
+
610
+ # Ask user to confirm auto-detected adapter
611
+ if not typer.confirm(
612
+ f"Use detected {adapter_type} adapter?",
613
+ default=True,
614
+ ):
615
+ adapter_type = None # Will trigger interactive selection
616
+ else:
617
+ adapter_type = None # Will trigger interactive selection
618
+ else:
619
+ adapter_type = None # Will trigger interactive selection
620
+
621
+ # If no adapter determined, show interactive selection
622
+ if not adapter_type:
623
+ adapter_type = _prompt_for_adapter_selection(console)
624
+
443
625
  # 2. Create configuration based on adapter type
444
626
  config = {"default_adapter": adapter_type, "adapters": {}}
445
627
 
@@ -462,59 +644,99 @@ def init(
462
644
  }
463
645
 
464
646
  elif adapter_type == "linear":
465
- # If not auto-discovered, build from CLI params
647
+ # If not auto-discovered, build from CLI params or prompt
466
648
  if adapter_type not in config["adapters"]:
467
649
  linear_config = {}
468
650
 
469
- # Team ID
470
- if team_id:
471
- linear_config["team_id"] = team_id
472
-
473
651
  # API Key
474
652
  linear_api_key = api_key or os.getenv("LINEAR_API_KEY")
653
+ if not linear_api_key and not discovered:
654
+ console.print("\n[bold]Linear Configuration[/bold]")
655
+ console.print("You need a Linear API key to connect to Linear.")
656
+ console.print("[dim]Get your API key at: https://linear.app/settings/api[/dim]\n")
657
+
658
+ linear_api_key = typer.prompt(
659
+ "Enter your Linear API key",
660
+ hide_input=True
661
+ )
662
+
475
663
  if linear_api_key:
476
664
  linear_config["api_key"] = linear_api_key
477
- elif not discovered:
478
- console.print("[yellow]Warning:[/yellow] No Linear API key provided.")
479
- console.print(
480
- "Set LINEAR_API_KEY environment variable or use --api-key option"
481
- )
482
665
 
483
- if linear_config:
484
- linear_config["type"] = "linear"
485
- config["adapters"]["linear"] = linear_config
666
+ # Team ID
667
+ linear_team_id = team_id or os.getenv("LINEAR_TEAM_ID")
668
+ if not linear_team_id and not discovered:
669
+ console.print("\nYou need your Linear team ID.")
670
+ console.print("[dim]Find it in Linear settings or team URL[/dim]\n")
671
+
672
+ linear_team_id = typer.prompt("Enter your Linear team ID")
673
+
674
+ if linear_team_id:
675
+ linear_config["team_id"] = linear_team_id
676
+
677
+ if not linear_config.get("api_key") or not linear_config.get("team_id"):
678
+ console.print("[red]Error:[/red] Linear requires both API key and team ID")
679
+ console.print("Run 'mcp-ticketer init --adapter linear' with proper credentials")
680
+ raise typer.Exit(1)
681
+
682
+ linear_config["type"] = "linear"
683
+ config["adapters"]["linear"] = linear_config
486
684
 
487
685
  elif adapter_type == "jira":
488
- # If not auto-discovered, build from CLI params
686
+ # If not auto-discovered, build from CLI params or prompt
489
687
  if adapter_type not in config["adapters"]:
490
688
  server = jira_server or os.getenv("JIRA_SERVER")
491
689
  email = jira_email or os.getenv("JIRA_EMAIL")
492
690
  token = api_key or os.getenv("JIRA_API_TOKEN")
493
691
  project = jira_project or os.getenv("JIRA_PROJECT_KEY")
494
692
 
693
+ # Interactive prompts for missing values
694
+ if not server and not discovered:
695
+ console.print("\n[bold]JIRA Configuration[/bold]")
696
+ console.print("Enter your JIRA server details.\n")
697
+
698
+ server = typer.prompt(
699
+ "JIRA server URL (e.g., https://company.atlassian.net)"
700
+ )
701
+
702
+ if not email and not discovered:
703
+ email = typer.prompt("Your JIRA email address")
704
+
705
+ if not token and not discovered:
706
+ console.print("\nYou need a JIRA API token.")
707
+ console.print("[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n")
708
+
709
+ token = typer.prompt(
710
+ "Enter your JIRA API token",
711
+ hide_input=True
712
+ )
713
+
714
+ if not project and not discovered:
715
+ project = typer.prompt(
716
+ "Default JIRA project key (optional, press Enter to skip)",
717
+ default="",
718
+ show_default=False
719
+ )
720
+
721
+ # Validate required fields
495
722
  if not server:
496
723
  console.print("[red]Error:[/red] JIRA server URL is required")
497
- console.print(
498
- "Use --jira-server or set JIRA_SERVER environment variable"
499
- )
500
724
  raise typer.Exit(1)
501
725
 
502
726
  if not email:
503
727
  console.print("[red]Error:[/red] JIRA email is required")
504
- console.print("Use --jira-email or set JIRA_EMAIL environment variable")
505
728
  raise typer.Exit(1)
506
729
 
507
730
  if not token:
508
731
  console.print("[red]Error:[/red] JIRA API token is required")
509
- console.print(
510
- "Use --api-key or set JIRA_API_TOKEN environment variable"
511
- )
512
- console.print(
513
- "[dim]Generate token at: https://id.atlassian.com/manage/api-tokens[/dim]"
514
- )
515
732
  raise typer.Exit(1)
516
733
 
517
- jira_config = {"server": server, "email": email, "api_token": token}
734
+ jira_config = {
735
+ "server": server,
736
+ "email": email,
737
+ "api_token": token,
738
+ "type": "jira"
739
+ }
518
740
 
519
741
  if project:
520
742
  jira_config["project_key"] = project
@@ -522,45 +744,50 @@ def init(
522
744
  config["adapters"]["jira"] = jira_config
523
745
 
524
746
  elif adapter_type == "github":
525
- # If not auto-discovered, build from CLI params
747
+ # If not auto-discovered, build from CLI params or prompt
526
748
  if adapter_type not in config["adapters"]:
527
749
  owner = github_owner or os.getenv("GITHUB_OWNER")
528
750
  repo = github_repo or os.getenv("GITHUB_REPO")
529
751
  token = github_token or os.getenv("GITHUB_TOKEN")
530
752
 
753
+ # Interactive prompts for missing values
754
+ if not owner and not discovered:
755
+ console.print("\n[bold]GitHub Configuration[/bold]")
756
+ console.print("Enter your GitHub repository details.\n")
757
+
758
+ owner = typer.prompt("GitHub repository owner (username or organization)")
759
+
760
+ if not repo and not discovered:
761
+ repo = typer.prompt("GitHub repository name")
762
+
763
+ if not token and not discovered:
764
+ console.print("\nYou need a GitHub Personal Access Token.")
765
+ console.print("[dim]Create one at: https://github.com/settings/tokens/new[/dim]")
766
+ console.print("[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]\n")
767
+
768
+ token = typer.prompt(
769
+ "Enter your GitHub Personal Access Token",
770
+ hide_input=True
771
+ )
772
+
773
+ # Validate required fields
531
774
  if not owner:
532
775
  console.print("[red]Error:[/red] GitHub repository owner is required")
533
- console.print(
534
- "Use --github-owner or set GITHUB_OWNER environment variable"
535
- )
536
776
  raise typer.Exit(1)
537
777
 
538
778
  if not repo:
539
779
  console.print("[red]Error:[/red] GitHub repository name is required")
540
- console.print(
541
- "Use --github-repo or set GITHUB_REPO environment variable"
542
- )
543
780
  raise typer.Exit(1)
544
781
 
545
782
  if not token:
546
- console.print(
547
- "[red]Error:[/red] GitHub Personal Access Token is required"
548
- )
549
- console.print(
550
- "Use --github-token or set GITHUB_TOKEN environment variable"
551
- )
552
- console.print(
553
- "[dim]Create token at: https://github.com/settings/tokens/new[/dim]"
554
- )
555
- console.print(
556
- "[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]"
557
- )
783
+ console.print("[red]Error:[/red] GitHub Personal Access Token is required")
558
784
  raise typer.Exit(1)
559
785
 
560
786
  config["adapters"]["github"] = {
561
787
  "owner": owner,
562
788
  "repo": repo,
563
789
  "token": token,
790
+ "type": "github"
564
791
  }
565
792
 
566
793
  # 5. Save to appropriate location
@@ -600,6 +827,48 @@ def init(
600
827
  f.write("# MCP Ticketer\n.mcp-ticketer/\n")
601
828
  console.print("[dim]✓ Created .gitignore with .mcp-ticketer/[/dim]")
602
829
 
830
+ # Show next steps
831
+ _show_next_steps(console, adapter_type, config_file_path)
832
+
833
+
834
+ def _show_next_steps(console: Console, adapter_type: str, config_file_path: Path) -> None:
835
+ """Show helpful next steps after initialization.
836
+
837
+ Args:
838
+ console: Rich console for output
839
+ adapter_type: Type of adapter that was configured
840
+ config_file_path: Path to the configuration file
841
+ """
842
+ console.print("\n[bold green]🎉 Setup Complete![/bold green]")
843
+ console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
844
+
845
+ console.print("[bold]Next Steps:[/bold]")
846
+ console.print("1. [cyan]Test your configuration:[/cyan]")
847
+ console.print(" mcp-ticketer diagnose")
848
+ console.print("\n2. [cyan]Create a test ticket:[/cyan]")
849
+ console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
850
+
851
+ if adapter_type != "aitrackdown":
852
+ console.print(f"\n3. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]")
853
+
854
+ if adapter_type == "linear":
855
+ console.print(" Check your Linear workspace for the new ticket")
856
+ elif adapter_type == "github":
857
+ console.print(" Check your GitHub repository's Issues tab")
858
+ elif adapter_type == "jira":
859
+ console.print(" Check your JIRA project for the new ticket")
860
+ else:
861
+ console.print("\n3. [cyan]Check local ticket storage:[/cyan]")
862
+ console.print(" ls .aitrackdown/")
863
+
864
+ console.print("\n4. [cyan]Configure MCP clients (optional):[/cyan]")
865
+ console.print(" mcp-ticketer mcp claude # For Claude Code")
866
+ console.print(" mcp-ticketer mcp auggie # For Auggie")
867
+ console.print(" mcp-ticketer mcp gemini # For Gemini CLI")
868
+
869
+ console.print(f"\n[dim]Configuration saved to: {config_file_path}[/dim]")
870
+ console.print("[dim]Run 'mcp-ticketer --help' for more commands[/dim]")
871
+
603
872
 
604
873
  @app.command()
605
874
  def install(
@@ -653,12 +922,12 @@ def install(
653
922
  ) -> None:
654
923
  """Initialize mcp-ticketer for the current project (alias for init).
655
924
 
656
- This command is synonymous with 'init' and provides the same functionality.
657
- Creates .mcp-ticketer/config.json in the current directory with
658
- auto-detected or specified adapter configuration.
925
+ This command is synonymous with 'init' and 'setup' - all three provide
926
+ identical functionality with interactive prompts to guide you through
927
+ configuring MCP Ticketer for your preferred ticket management system.
659
928
 
660
929
  Examples:
661
- # Auto-detect from .env.local
930
+ # Interactive setup (same as 'init' and 'setup')
662
931
  mcp-ticketer install
663
932
 
664
933
  # Force specific adapter
@@ -1548,6 +1817,9 @@ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
1548
1817
  console.print(f"\nRetry Count: {item.retry_count}")
1549
1818
 
1550
1819
 
1820
+
1821
+
1822
+
1551
1823
  @app.command()
1552
1824
  def serve(
1553
1825
  adapter: Optional[AdapterType] = typer.Option(
@@ -1575,16 +1847,27 @@ def serve(
1575
1847
  # Load configuration (respects project-specific config in cwd)
1576
1848
  config = load_config()
1577
1849
 
1578
- # Determine adapter type
1579
- adapter_type = (
1580
- adapter.value if adapter else config.get("default_adapter", "aitrackdown")
1581
- )
1582
-
1583
- # Get adapter configuration
1584
- adapters_config = config.get("adapters", {})
1585
- adapter_config = adapters_config.get(adapter_type, {})
1850
+ # Determine adapter type with priority: CLI arg > .env files > config > default
1851
+ if adapter:
1852
+ # Priority 1: Command line argument
1853
+ adapter_type = adapter.value
1854
+ # Get base config from config file
1855
+ adapters_config = config.get("adapters", {})
1856
+ adapter_config = adapters_config.get(adapter_type, {})
1857
+ else:
1858
+ # Priority 2: .env files
1859
+ from ..mcp.server import _load_env_configuration
1860
+ env_config = _load_env_configuration()
1861
+ if env_config:
1862
+ adapter_type = env_config["adapter_type"]
1863
+ adapter_config = env_config["adapter_config"]
1864
+ else:
1865
+ # Priority 3: Configuration file
1866
+ adapter_type = config.get("default_adapter", "aitrackdown")
1867
+ adapters_config = config.get("adapters", {})
1868
+ adapter_config = adapters_config.get(adapter_type, {})
1586
1869
 
1587
- # Override with command line options if provided
1870
+ # Override with command line options if provided (highest priority)
1588
1871
  if base_path and adapter_type == "aitrackdown":
1589
1872
  adapter_config["base_path"] = base_path
1590
1873
 
@@ -454,11 +454,32 @@ class EnvDiscovery:
454
454
  """
455
455
  base_path = self._find_key_value(env_vars, AITRACKDOWN_PATH_PATTERNS)
456
456
 
457
- # Also check if .aitrackdown directory exists
457
+ # Check for explicit MCP_TICKETER_ADAPTER setting
458
+ explicit_adapter = env_vars.get("MCP_TICKETER_ADAPTER")
459
+ if explicit_adapter and explicit_adapter != "aitrackdown":
460
+ # If another adapter is explicitly set, don't detect aitrackdown
461
+ return None
462
+
463
+ # Check if .aitrackdown directory exists
458
464
  aitrackdown_dir = self.project_path / ".aitrackdown"
465
+
466
+ # Only detect aitrackdown if:
467
+ # 1. There's an explicit base_path setting, OR
468
+ # 2. There's a .aitrackdown directory AND no other adapter variables are present
469
+ has_other_adapter_vars = (
470
+ any(key.startswith("LINEAR_") for key in env_vars) or
471
+ any(key.startswith("GITHUB_") for key in env_vars) or
472
+ any(key.startswith("JIRA_") for key in env_vars)
473
+ )
474
+
459
475
  if not base_path and not aitrackdown_dir.exists():
460
476
  return None
461
477
 
478
+ if not base_path and has_other_adapter_vars:
479
+ # Don't detect aitrackdown if other adapter variables are present
480
+ # unless explicitly configured
481
+ return None
482
+
462
483
  config: dict[str, Any] = {
463
484
  "adapter": AdapterType.AITRACKDOWN.value,
464
485
  }
@@ -468,8 +489,15 @@ class EnvDiscovery:
468
489
  else:
469
490
  config["base_path"] = ".aitrackdown"
470
491
 
471
- # AITrackdown has no required external credentials
472
- confidence = 1.0 if aitrackdown_dir.exists() else 0.7
492
+ # Lower confidence when other adapter variables are present
493
+ if has_other_adapter_vars:
494
+ confidence = 0.3 # Low confidence when other adapters are configured
495
+ elif base_path:
496
+ confidence = 1.0 # High confidence when explicitly configured
497
+ elif aitrackdown_dir.exists():
498
+ confidence = 0.8 # Medium confidence when directory exists
499
+ else:
500
+ confidence = 0.5 # Low confidence as fallback
473
501
 
474
502
  return DiscoveredAdapter(
475
503
  adapter_type=AdapterType.AITRACKDOWN.value,
@@ -1652,56 +1652,191 @@ async def main():
1652
1652
  # Load configuration
1653
1653
  import json
1654
1654
  import logging
1655
+ import os
1655
1656
  from pathlib import Path
1656
1657
 
1657
1658
  logger = logging.getLogger(__name__)
1658
1659
 
1659
- # ONLY read from project-local config, never from user home
1660
- config_file = Path.cwd() / ".mcp-ticketer" / "config.json"
1661
- if config_file.exists():
1662
- # Validate config is within project
1663
- try:
1664
- if not config_file.resolve().is_relative_to(Path.cwd().resolve()):
1665
- logger.error(
1666
- f"Security violation: Config file {config_file} "
1667
- "is not within project directory"
1668
- )
1669
- raise ValueError(
1670
- f"Security violation: Config file {config_file} "
1671
- "is not within project directory"
1672
- )
1673
- except (ValueError, RuntimeError):
1674
- # is_relative_to may raise ValueError in some cases
1675
- pass
1660
+ # Initialize defaults
1661
+ adapter_type = "aitrackdown"
1662
+ adapter_config = {"base_path": ".aitrackdown"}
1663
+
1664
+ # Priority 1: Check .env files (highest priority for MCP)
1665
+ env_config = _load_env_configuration()
1666
+ if env_config and env_config.get("adapter_type"):
1667
+ adapter_type = env_config["adapter_type"]
1668
+ adapter_config = env_config["adapter_config"]
1669
+ logger.info(f"Using adapter from .env files: {adapter_type}")
1670
+ logger.info(f"Built adapter config from .env: {list(adapter_config.keys())}")
1671
+ else:
1672
+ # Priority 2: Check project-local config file
1673
+ config_file = Path.cwd() / ".mcp-ticketer" / "config.json"
1674
+ if config_file.exists():
1675
+ # Validate config is within project
1676
+ try:
1677
+ if not config_file.resolve().is_relative_to(Path.cwd().resolve()):
1678
+ logger.error(
1679
+ f"Security violation: Config file {config_file} "
1680
+ "is not within project directory"
1681
+ )
1682
+ raise ValueError(
1683
+ f"Security violation: Config file {config_file} "
1684
+ "is not within project directory"
1685
+ )
1686
+ except (ValueError, RuntimeError):
1687
+ # is_relative_to may raise ValueError in some cases
1688
+ pass
1676
1689
 
1677
- try:
1678
- with open(config_file) as f:
1679
- config = json.load(f)
1680
- adapter_type = config.get("default_adapter", "aitrackdown")
1681
- # Get adapter-specific config
1682
- adapters_config = config.get("adapters", {})
1683
- adapter_config = adapters_config.get(adapter_type, {})
1684
- # Fallback to legacy config format
1685
- if not adapter_config and "config" in config:
1686
- adapter_config = config["config"]
1687
- logger.info(
1688
- f"Loaded MCP configuration from project-local: {config_file}"
1689
- )
1690
- except (OSError, json.JSONDecodeError) as e:
1691
- logger.warning(f"Could not load project config: {e}, using defaults")
1690
+ try:
1691
+ with open(config_file) as f:
1692
+ config = json.load(f)
1693
+ adapter_type = config.get("default_adapter", "aitrackdown")
1694
+ # Get adapter-specific config
1695
+ adapters_config = config.get("adapters", {})
1696
+ adapter_config = adapters_config.get(adapter_type, {})
1697
+ # Fallback to legacy config format
1698
+ if not adapter_config and "config" in config:
1699
+ adapter_config = config["config"]
1700
+ logger.info(
1701
+ f"Loaded MCP configuration from project-local: {config_file}"
1702
+ )
1703
+ except (OSError, json.JSONDecodeError) as e:
1704
+ logger.warning(f"Could not load project config: {e}, using defaults")
1705
+ adapter_type = "aitrackdown"
1706
+ adapter_config = {"base_path": ".aitrackdown"}
1707
+ else:
1708
+ # Priority 3: Default to aitrackdown
1709
+ logger.info("No configuration found, defaulting to aitrackdown adapter")
1692
1710
  adapter_type = "aitrackdown"
1693
1711
  adapter_config = {"base_path": ".aitrackdown"}
1694
- else:
1695
- # Default to aitrackdown with local base path
1696
- logger.info("No project-local config found, defaulting to aitrackdown adapter")
1697
- adapter_type = "aitrackdown"
1698
- adapter_config = {"base_path": ".aitrackdown"}
1712
+
1713
+ # Log final configuration for debugging
1714
+ logger.info(f"Starting MCP server with adapter: {adapter_type}")
1715
+ logger.debug(f"Adapter config keys: {list(adapter_config.keys())}")
1699
1716
 
1700
1717
  # Create and run server
1701
1718
  server = MCPTicketServer(adapter_type, adapter_config)
1702
1719
  await server.run()
1703
1720
 
1704
1721
 
1722
+ def _load_env_configuration() -> Optional[dict[str, Any]]:
1723
+ """Load adapter configuration from .env files.
1724
+
1725
+ Checks .env.local first (highest priority), then .env.
1726
+
1727
+ Returns:
1728
+ Dictionary with 'adapter_type' and 'adapter_config' keys, or None if no config found
1729
+ """
1730
+ from pathlib import Path
1731
+
1732
+ # Check for .env files in order of preference
1733
+ env_files = [".env.local", ".env"]
1734
+ env_vars = {}
1735
+
1736
+ for env_file in env_files:
1737
+ env_path = Path.cwd() / env_file
1738
+ if env_path.exists():
1739
+ try:
1740
+ # Parse .env file manually to avoid external dependencies
1741
+ with open(env_path, 'r') as f:
1742
+ for line in f:
1743
+ line = line.strip()
1744
+ if line and not line.startswith('#') and '=' in line:
1745
+ key, value = line.split('=', 1)
1746
+ key = key.strip()
1747
+ value = value.strip().strip('"').strip("'")
1748
+ if value: # Only add non-empty values
1749
+ env_vars[key] = value
1750
+ except Exception:
1751
+ continue
1752
+
1753
+ if not env_vars:
1754
+ return None
1755
+
1756
+ # Determine adapter type and build config
1757
+ adapter_type = env_vars.get("MCP_TICKETER_ADAPTER")
1758
+ if not adapter_type:
1759
+ # Auto-detect based on available keys
1760
+ if any(key.startswith("LINEAR_") for key in env_vars):
1761
+ adapter_type = "linear"
1762
+ elif any(key.startswith("GITHUB_") for key in env_vars):
1763
+ adapter_type = "github"
1764
+ elif any(key.startswith("JIRA_") for key in env_vars):
1765
+ adapter_type = "jira"
1766
+ else:
1767
+ return None
1768
+
1769
+ # Build adapter-specific configuration
1770
+ adapter_config = _build_adapter_config_from_env_vars(adapter_type, env_vars)
1771
+
1772
+ if not adapter_config:
1773
+ return None
1774
+
1775
+ return {
1776
+ "adapter_type": adapter_type,
1777
+ "adapter_config": adapter_config
1778
+ }
1779
+
1780
+
1781
+ def _build_adapter_config_from_env_vars(adapter_type: str, env_vars: dict[str, str]) -> dict[str, Any]:
1782
+ """Build adapter configuration from parsed environment variables.
1783
+
1784
+ Args:
1785
+ adapter_type: Type of adapter to configure
1786
+ env_vars: Dictionary of environment variables from .env files
1787
+
1788
+ Returns:
1789
+ Dictionary of adapter configuration
1790
+ """
1791
+ config = {}
1792
+
1793
+ if adapter_type == "linear":
1794
+ # Linear adapter configuration
1795
+ if env_vars.get("LINEAR_API_KEY"):
1796
+ config["api_key"] = env_vars["LINEAR_API_KEY"]
1797
+ if env_vars.get("LINEAR_TEAM_ID"):
1798
+ config["team_id"] = env_vars["LINEAR_TEAM_ID"]
1799
+ if env_vars.get("LINEAR_TEAM_KEY"):
1800
+ config["team_key"] = env_vars["LINEAR_TEAM_KEY"]
1801
+ if env_vars.get("LINEAR_API_URL"):
1802
+ config["api_url"] = env_vars["LINEAR_API_URL"]
1803
+
1804
+ elif adapter_type == "github":
1805
+ # GitHub adapter configuration
1806
+ if env_vars.get("GITHUB_TOKEN"):
1807
+ config["token"] = env_vars["GITHUB_TOKEN"]
1808
+ if env_vars.get("GITHUB_OWNER"):
1809
+ config["owner"] = env_vars["GITHUB_OWNER"]
1810
+ if env_vars.get("GITHUB_REPO"):
1811
+ config["repo"] = env_vars["GITHUB_REPO"]
1812
+
1813
+ elif adapter_type == "jira":
1814
+ # JIRA adapter configuration
1815
+ if env_vars.get("JIRA_SERVER"):
1816
+ config["server"] = env_vars["JIRA_SERVER"]
1817
+ if env_vars.get("JIRA_EMAIL"):
1818
+ config["email"] = env_vars["JIRA_EMAIL"]
1819
+ if env_vars.get("JIRA_API_TOKEN"):
1820
+ config["api_token"] = env_vars["JIRA_API_TOKEN"]
1821
+ if env_vars.get("JIRA_PROJECT_KEY"):
1822
+ config["project_key"] = env_vars["JIRA_PROJECT_KEY"]
1823
+
1824
+ elif adapter_type == "aitrackdown":
1825
+ # AITrackdown adapter configuration
1826
+ base_path = env_vars.get("MCP_TICKETER_BASE_PATH", ".aitrackdown")
1827
+ config["base_path"] = base_path
1828
+ config["auto_create_dirs"] = True
1829
+
1830
+ # Add any generic overrides
1831
+ if env_vars.get("MCP_TICKETER_API_KEY"):
1832
+ config["api_key"] = env_vars["MCP_TICKETER_API_KEY"]
1833
+
1834
+ return config
1835
+
1836
+
1837
+
1838
+
1839
+
1705
1840
  # Add diagnostic handler methods to MCPTicketServer class
1706
1841
  async def _handle_system_health(self, arguments: dict[str, Any]) -> dict[str, Any]:
1707
1842
  """Handle system health check."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Universal ticket management interface for AI agents with MCP support
5
5
  Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
6
6
  Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
@@ -1,5 +1,5 @@
1
1
  mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
2
- mcp_ticketer/__version__.py,sha256=9j49fzPH9jSgtpm7Tgb5_MiRjdY95EWDMnBbiisOXqM,1117
2
+ mcp_ticketer/__version__.py,sha256=hTsAyUEYaGHMTOcqUuCMjSc3y7Sywx_V8sgWryJLRsY,1117
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
5
5
  mcp_ticketer/adapters/aitrackdown.py,sha256=stlbge8K6w-EyQkw_vEQNSXQgCOWN5tOlQUgGWZQNMQ,17936
@@ -16,6 +16,7 @@ mcp_ticketer/adapters/linear/types.py,sha256=VuGPu1Z5jGHtbI2zkCyL5YFnwQNukGW-UV7
16
16
  mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d4,138
17
17
  mcp_ticketer/cache/memory.py,sha256=2yBqGi9i0SanlUhJoOC7nijWjoMa3_ntPe-V-AV-LfU,5042
18
18
  mcp_ticketer/cli/__init__.py,sha256=l9Q8iKmfGkTu0cssHBVqNZTsL4eAtFzOB25AED_0G6g,89
19
+ mcp_ticketer/cli/adapter_diagnostics.py,sha256=t7RyesBRSyJBoVvft2scdNRcugJDIWvqYS9NszPV5ok,14820
19
20
  mcp_ticketer/cli/auggie_configure.py,sha256=MXKzLtqe3K_UTQ2GacHAWbvf_B0779KL325smiAKE0Q,8212
20
21
  mcp_ticketer/cli/codex_configure.py,sha256=xDppHouT6_-cYXswyAggoPX5bSlRXMvCoM_x9PQ-42A,9086
21
22
  mcp_ticketer/cli/configure.py,sha256=BsA_pSHQMQS0t1bJO_wMM8LWsd5sWJDASjEPRHvwC18,16198
@@ -23,7 +24,7 @@ mcp_ticketer/cli/diagnostics.py,sha256=AC7cMQHVWdHfrYH2Y1tkhmRezM2-mID5E_Dhfil7F
23
24
  mcp_ticketer/cli/discover.py,sha256=AF_qlQc1Oo0UkWayoF5pmRChS5J3fJjH6f2YZzd_k8w,13188
24
25
  mcp_ticketer/cli/gemini_configure.py,sha256=ZNSA1lBW-itVToza-JxW95Po7daVXKiZAh7lp6pmXMU,9343
25
26
  mcp_ticketer/cli/linear_commands.py,sha256=b5v4c9uBNPwj_vdy314JkLZbyC1fXU6IcY2VoG0z7gI,17193
26
- mcp_ticketer/cli/main.py,sha256=9e83BCxds9AESIYueK1VjeJ96H7AvRbvPiMYwd4EeKs,62258
27
+ mcp_ticketer/cli/main.py,sha256=blViBOtv9HbRUkuAPlb7CstyH3nt90vxgEHIZcdGls8,73130
27
28
  mcp_ticketer/cli/mcp_configure.py,sha256=RzV50UjXgOmvMp-9S0zS39psuvjffVByaMrqrUaAGAM,9594
28
29
  mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
29
30
  mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
@@ -32,7 +33,7 @@ mcp_ticketer/cli/utils.py,sha256=bGoLcqsNsv7YMjVeWc9tlqKQC5zeOmWZp7wEfe0JfzM,229
32
33
  mcp_ticketer/core/__init__.py,sha256=eXovsaJymQRP2AwOBuOy6mFtI3I68D7gGenZ5V-IMqo,349
33
34
  mcp_ticketer/core/adapter.py,sha256=q64LxOInIno7EIbmuxItf8KEsd-g9grCs__Z4uwZHto,10273
34
35
  mcp_ticketer/core/config.py,sha256=hvn8RoN2BSmYzESKqCvcpKleX0PrGiHVQ5v35nX12Mc,19241
35
- mcp_ticketer/core/env_discovery.py,sha256=It62C97UBt96CgVbZCql5NQtONmCHMkX3c8W2kEl-Qk,18716
36
+ mcp_ticketer/core/env_discovery.py,sha256=m2x3K8hrd0_nzmSGZn5EYUnED7MI2qxcWHTp6ns880o,19970
36
37
  mcp_ticketer/core/env_loader.py,sha256=fX8EpHcAJxxhPJ-XY5BD8HlLs-Y9jdlQ24Qtt5ylBNo,12032
37
38
  mcp_ticketer/core/exceptions.py,sha256=78tzV3Muc7E_UhEIByF0NtsPe1I0lFVrbpdDNX56kQg,3737
38
39
  mcp_ticketer/core/http_client.py,sha256=s5ikMiwEJ8TJjNn73wu3gv3OdAtyBEpAqPnSroRMW2k,13971
@@ -41,7 +42,7 @@ mcp_ticketer/core/models.py,sha256=a_2AbL3NlN0pfdZad-hXus_zb4bSi9zISHcsNYl0sng,1
41
42
  mcp_ticketer/core/project_config.py,sha256=yYxlgxjcEPeOwx-b-SXFpe0k9pW9xzBRAK72PsItG-o,23346
42
43
  mcp_ticketer/core/registry.py,sha256=ShYLDPE62KFJpB0kj_zFyQzRxSH3LkQEEuo1jaakb1k,3483
43
44
  mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,123
44
- mcp_ticketer/mcp/server.py,sha256=vs0WR9_KhDaYyhrkbTuTsEThT0bMj6gwuFmAxmzCxwU,76377
45
+ mcp_ticketer/mcp/server.py,sha256=PCNerYqLMq5et3ZYAIdd_q_-4LGLBhDuvPAYTuWxDiQ,81351
45
46
  mcp_ticketer/queue/__init__.py,sha256=1YIaCpZpFqPcqvDEQXiEvDLiw94DXRdCJkBaVIFQrms,231
46
47
  mcp_ticketer/queue/__main__.py,sha256=gc_tE9NUdK07OJfTZuD4t6KeBD_vxFQIhknGTQUG_jk,109
47
48
  mcp_ticketer/queue/health_monitor.py,sha256=aQrlBzfbLWu8-fV2b5CuHs4oqyTqGGcntKIHM3r-dDI,11844
@@ -50,9 +51,9 @@ mcp_ticketer/queue/queue.py,sha256=jSAkYNEIbNH1cbYuF8s6eFuZmXqn8WHXx3mbfMU2Ud8,1
50
51
  mcp_ticketer/queue/run_worker.py,sha256=F7anuhdkgZF9lXZntHuJ7rEzuEkAfAZO1qvGh3R57bw,1033
51
52
  mcp_ticketer/queue/ticket_registry.py,sha256=k8FYg2cFYsI4POb94-o-fTrIVr-ttfi60r0O5YhJYck,15321
52
53
  mcp_ticketer/queue/worker.py,sha256=zXJpyhRJ99be0VLaez3YPtC9OU17vVNu5qhr1dCGaLg,19992
53
- mcp_ticketer-0.2.0.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
54
- mcp_ticketer-0.2.0.dist-info/METADATA,sha256=bvHYuQNE5HYIEgM2BlrpPwCesfgm3jWiax7lwTNGGB0,13219
55
- mcp_ticketer-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
56
- mcp_ticketer-0.2.0.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
57
- mcp_ticketer-0.2.0.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
58
- mcp_ticketer-0.2.0.dist-info/RECORD,,
54
+ mcp_ticketer-0.3.1.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
55
+ mcp_ticketer-0.3.1.dist-info/METADATA,sha256=7MkHCRw1QYtKKRfHVQ2oUMW3syEXGilHV5y-Ooud4xQ,13219
56
+ mcp_ticketer-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ mcp_ticketer-0.3.1.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
58
+ mcp_ticketer-0.3.1.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
59
+ mcp_ticketer-0.3.1.dist-info/RECORD,,