mcp-ticketer 0.1.39__py3-none-any.whl → 0.3.0__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.
@@ -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