mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__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 (129) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/aitrackdown.py +507 -6
  5. mcp_ticketer/adapters/asana/adapter.py +229 -0
  6. mcp_ticketer/adapters/asana/mappers.py +14 -0
  7. mcp_ticketer/adapters/github/__init__.py +26 -0
  8. mcp_ticketer/adapters/github/adapter.py +3229 -0
  9. mcp_ticketer/adapters/github/client.py +335 -0
  10. mcp_ticketer/adapters/github/mappers.py +797 -0
  11. mcp_ticketer/adapters/github/queries.py +692 -0
  12. mcp_ticketer/adapters/github/types.py +460 -0
  13. mcp_ticketer/adapters/hybrid.py +47 -5
  14. mcp_ticketer/adapters/jira/__init__.py +35 -0
  15. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  16. mcp_ticketer/adapters/jira/client.py +271 -0
  17. mcp_ticketer/adapters/jira/mappers.py +246 -0
  18. mcp_ticketer/adapters/jira/queries.py +216 -0
  19. mcp_ticketer/adapters/jira/types.py +304 -0
  20. mcp_ticketer/adapters/linear/adapter.py +2730 -139
  21. mcp_ticketer/adapters/linear/client.py +175 -3
  22. mcp_ticketer/adapters/linear/mappers.py +203 -8
  23. mcp_ticketer/adapters/linear/queries.py +280 -3
  24. mcp_ticketer/adapters/linear/types.py +120 -4
  25. mcp_ticketer/analysis/__init__.py +56 -0
  26. mcp_ticketer/analysis/dependency_graph.py +255 -0
  27. mcp_ticketer/analysis/health_assessment.py +304 -0
  28. mcp_ticketer/analysis/orphaned.py +218 -0
  29. mcp_ticketer/analysis/project_status.py +594 -0
  30. mcp_ticketer/analysis/similarity.py +224 -0
  31. mcp_ticketer/analysis/staleness.py +266 -0
  32. mcp_ticketer/automation/__init__.py +11 -0
  33. mcp_ticketer/automation/project_updates.py +378 -0
  34. mcp_ticketer/cli/adapter_diagnostics.py +3 -1
  35. mcp_ticketer/cli/auggie_configure.py +17 -5
  36. mcp_ticketer/cli/codex_configure.py +97 -61
  37. mcp_ticketer/cli/configure.py +1288 -105
  38. mcp_ticketer/cli/cursor_configure.py +314 -0
  39. mcp_ticketer/cli/diagnostics.py +13 -12
  40. mcp_ticketer/cli/discover.py +5 -0
  41. mcp_ticketer/cli/gemini_configure.py +17 -5
  42. mcp_ticketer/cli/init_command.py +880 -0
  43. mcp_ticketer/cli/install_mcp_server.py +418 -0
  44. mcp_ticketer/cli/instruction_commands.py +6 -0
  45. mcp_ticketer/cli/main.py +267 -3175
  46. mcp_ticketer/cli/mcp_configure.py +821 -119
  47. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  48. mcp_ticketer/cli/platform_detection.py +77 -12
  49. mcp_ticketer/cli/platform_installer.py +545 -0
  50. mcp_ticketer/cli/project_update_commands.py +350 -0
  51. mcp_ticketer/cli/setup_command.py +795 -0
  52. mcp_ticketer/cli/simple_health.py +12 -10
  53. mcp_ticketer/cli/ticket_commands.py +705 -103
  54. mcp_ticketer/cli/utils.py +113 -0
  55. mcp_ticketer/core/__init__.py +56 -6
  56. mcp_ticketer/core/adapter.py +533 -2
  57. mcp_ticketer/core/config.py +21 -21
  58. mcp_ticketer/core/exceptions.py +7 -1
  59. mcp_ticketer/core/label_manager.py +732 -0
  60. mcp_ticketer/core/mappers.py +31 -19
  61. mcp_ticketer/core/milestone_manager.py +252 -0
  62. mcp_ticketer/core/models.py +480 -0
  63. mcp_ticketer/core/onepassword_secrets.py +1 -1
  64. mcp_ticketer/core/priority_matcher.py +463 -0
  65. mcp_ticketer/core/project_config.py +132 -14
  66. mcp_ticketer/core/project_utils.py +281 -0
  67. mcp_ticketer/core/project_validator.py +376 -0
  68. mcp_ticketer/core/session_state.py +176 -0
  69. mcp_ticketer/core/state_matcher.py +625 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/mcp/server/__main__.py +2 -1
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/main.py +106 -25
  75. mcp_ticketer/mcp/server/routing.py +723 -0
  76. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  77. mcp_ticketer/mcp/server/tools/__init__.py +33 -11
  78. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  79. mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
  80. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  81. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  82. mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
  83. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  84. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
  85. mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
  86. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  87. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  88. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  89. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  90. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  91. mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
  92. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  93. mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
  94. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
  95. mcp_ticketer/queue/queue.py +68 -0
  96. mcp_ticketer/queue/worker.py +1 -1
  97. mcp_ticketer/utils/__init__.py +5 -0
  98. mcp_ticketer/utils/token_utils.py +246 -0
  99. mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
  100. mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
  101. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  102. py_mcp_installer/examples/phase3_demo.py +178 -0
  103. py_mcp_installer/scripts/manage_version.py +54 -0
  104. py_mcp_installer/setup.py +6 -0
  105. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  106. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  107. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  108. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  109. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  110. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  111. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  112. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  113. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  114. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  115. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  116. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  117. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  118. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  119. py_mcp_installer/tests/__init__.py +0 -0
  120. py_mcp_installer/tests/platforms/__init__.py +0 -0
  121. py_mcp_installer/tests/test_platform_detector.py +17 -0
  122. mcp_ticketer/adapters/github.py +0 -1574
  123. mcp_ticketer/adapters/jira.py +0 -1258
  124. mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
  125. mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
  126. mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
  127. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  128. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  129. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,656 @@
1
+ """Main installer orchestrator for MCP servers.
2
+
3
+ This module provides the primary API facade for installing, managing, and
4
+ inspecting MCP server configurations across all supported platforms.
5
+
6
+ Design Philosophy:
7
+ - Simple API with smart defaults (auto-detection)
8
+ - Atomic operations with backup/restore
9
+ - Comprehensive validation and inspection
10
+ - Dry-run support for safe testing
11
+ - Platform-agnostic interface
12
+
13
+ Example:
14
+ >>> from py_mcp_installer import MCPInstaller
15
+ >>> installer = MCPInstaller.auto_detect()
16
+ >>> result = installer.install_server(
17
+ ... name="mcp-ticketer",
18
+ ... command="uv",
19
+ ... args=["run", "mcp-ticketer", "mcp"],
20
+ ... description="Ticket management MCP server"
21
+ ... )
22
+ >>> print(result.message)
23
+ Successfully installed mcp-ticketer
24
+ """
25
+
26
+ import logging
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ from .command_builder import CommandBuilder
31
+ from .exceptions import (
32
+ ConfigurationError,
33
+ InstallationError,
34
+ PlatformDetectionError,
35
+ PlatformNotSupportedError,
36
+ ValidationError,
37
+ )
38
+ from .installation_strategy import (
39
+ InstallationStrategy as BaseStrategy,
40
+ )
41
+ from .installation_strategy import (
42
+ JSONManipulationStrategy,
43
+ )
44
+ from .mcp_inspector import InspectionReport, MCPInspector, ValidationIssue
45
+ from .platform_detector import PlatformDetector
46
+ from .platforms import ClaudeCodeStrategy, CodexStrategy, CursorStrategy
47
+ from .types import (
48
+ InstallationResult,
49
+ InstallMethod,
50
+ MCPServerConfig,
51
+ Platform,
52
+ PlatformInfo,
53
+ Scope,
54
+ )
55
+ from .utils import detect_install_method, resolve_command_path
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+
60
+ # ============================================================================
61
+ # Main Installer API
62
+ # ============================================================================
63
+
64
+
65
+ class MCPInstaller:
66
+ """Main API facade for MCP server installation.
67
+
68
+ Provides a unified interface for installing, managing, and inspecting
69
+ MCP servers across all supported platforms. Automatically detects platform
70
+ and selects best installation method.
71
+
72
+ Attributes:
73
+ platform_info: Detected platform information
74
+ config_path: Path to configuration file
75
+ dry_run: If True, preview changes without applying
76
+ verbose: If True, enable verbose logging
77
+
78
+ Example:
79
+ >>> # Auto-detect platform and install
80
+ >>> installer = MCPInstaller.auto_detect()
81
+ >>> result = installer.install_server(
82
+ ... name="mcp-ticketer",
83
+ ... command="uv",
84
+ ... args=["run", "mcp-ticketer", "mcp"]
85
+ ... )
86
+ >>> if result.success:
87
+ ... print(f"Installed to {result.config_path}")
88
+
89
+ >>> # Inspect existing installation
90
+ >>> report = installer.inspect_installation()
91
+ >>> print(report.summary())
92
+
93
+ >>> # List all servers
94
+ >>> servers = installer.list_servers()
95
+ >>> for server in servers:
96
+ ... print(f"- {server.name}: {server.command}")
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ platform: Platform | None = None,
102
+ dry_run: bool = False,
103
+ verbose: bool = False,
104
+ ) -> None:
105
+ """Initialize installer.
106
+
107
+ Args:
108
+ platform: Force specific platform (None = auto-detect)
109
+ dry_run: Preview changes without applying
110
+ verbose: Enable verbose logging
111
+
112
+ Raises:
113
+ PlatformDetectionError: If platform cannot be detected
114
+ PlatformNotSupportedError: If platform is not supported
115
+
116
+ Example:
117
+ >>> # Auto-detect platform
118
+ >>> installer = MCPInstaller()
119
+
120
+ >>> # Force specific platform
121
+ >>> installer = MCPInstaller(platform=Platform.CLAUDE_CODE)
122
+
123
+ >>> # Dry-run mode (safe testing)
124
+ >>> installer = MCPInstaller(dry_run=True, verbose=True)
125
+ """
126
+ self.dry_run = dry_run
127
+ self.verbose = verbose
128
+
129
+ # Configure logging
130
+ if verbose:
131
+ logging.basicConfig(level=logging.DEBUG)
132
+ logger.setLevel(logging.DEBUG)
133
+ else:
134
+ logger.setLevel(logging.INFO)
135
+
136
+ # Detect or use provided platform
137
+ if platform:
138
+ # For forced platform, we still need to detect but validate it matches
139
+ detector = PlatformDetector()
140
+ detected_info = detector.detect()
141
+ if detected_info.platform != platform:
142
+ raise PlatformNotSupportedError(
143
+ platform.value,
144
+ [p.value for p in Platform if p != Platform.UNKNOWN],
145
+ )
146
+ self._platform_info = detected_info
147
+ else:
148
+ self._platform_info = self._detect_platform()
149
+
150
+ logger.info(
151
+ f"Initialized for {self._platform_info.platform.value} "
152
+ f"(confidence: {self._platform_info.confidence:.2f})"
153
+ )
154
+
155
+ # Initialize components
156
+ self._command_builder = CommandBuilder(self._platform_info.platform)
157
+ self._inspector = MCPInspector(self._platform_info)
158
+
159
+ # Select installation strategy
160
+ self._strategy = self._select_strategy()
161
+
162
+ @classmethod
163
+ def auto_detect(cls, **kwargs: Any) -> "MCPInstaller":
164
+ """Create installer with auto-detected platform.
165
+
166
+ This is the recommended way to create an installer instance.
167
+
168
+ Args:
169
+ **kwargs: Additional arguments passed to __init__
170
+
171
+ Returns:
172
+ Configured MCPInstaller instance
173
+
174
+ Example:
175
+ >>> installer = MCPInstaller.auto_detect()
176
+ >>> print(f"Detected: {installer.platform_info.platform.value}")
177
+ """
178
+ return cls(platform=None, **kwargs)
179
+
180
+ def install_server(
181
+ self,
182
+ name: str,
183
+ command: str,
184
+ args: list[str] | None = None,
185
+ env: dict[str, str] | None = None,
186
+ description: str = "",
187
+ scope: Scope = Scope.PROJECT,
188
+ method: InstallMethod | None = None,
189
+ ) -> InstallationResult:
190
+ """Install MCP server.
191
+
192
+ Auto-detects best installation method if not specified. Creates backup
193
+ of existing config before making changes.
194
+
195
+ Args:
196
+ name: Unique server identifier (e.g., "mcp-ticketer")
197
+ command: Executable command (e.g., "uv", "/usr/bin/python")
198
+ args: Command arguments (e.g., ["run", "mcp-ticketer", "mcp"])
199
+ env: Environment variables (e.g., {"API_KEY": "..."})
200
+ description: Human-readable description
201
+ scope: Installation scope (PROJECT or GLOBAL)
202
+ method: Installation method (auto-detect if None)
203
+
204
+ Returns:
205
+ InstallationResult with success status and details
206
+
207
+ Raises:
208
+ ValidationError: If server configuration is invalid
209
+ InstallationError: If installation fails
210
+
211
+ Example:
212
+ >>> # Simple installation with auto-detection
213
+ >>> result = installer.install_server(
214
+ ... name="mcp-ticketer",
215
+ ... command="uv",
216
+ ... args=["run", "mcp-ticketer", "mcp"],
217
+ ... description="Ticket management"
218
+ ... )
219
+
220
+ >>> # Install with environment variables
221
+ >>> result = installer.install_server(
222
+ ... name="github-mcp",
223
+ ... command="npx",
224
+ ... args=["-y", "@modelcontextprotocol/server-github"],
225
+ ... env={"GITHUB_TOKEN": "ghp_..."}
226
+ ... )
227
+
228
+ >>> # Force specific method
229
+ >>> result = installer.install_server(
230
+ ... name="custom-server",
231
+ ... command="python",
232
+ ... args=["-m", "my_server"],
233
+ ... method=InstallMethod.PYTHON_MODULE
234
+ ... )
235
+ """
236
+ logger.info(f"Installing server: {name}")
237
+
238
+ # Validate inputs
239
+ if not name:
240
+ raise ValidationError(
241
+ "Server name is required", "Provide a unique server name"
242
+ )
243
+
244
+ if not command and not method:
245
+ raise ValidationError(
246
+ "Either command or method must be provided",
247
+ "Provide command parameter or specify installation method",
248
+ )
249
+
250
+ # Auto-detect method if not provided
251
+ if method is None:
252
+ # Default to UV_RUN as recommended method
253
+ if command == "uv":
254
+ method = InstallMethod.UV_RUN
255
+ elif resolve_command_path(name):
256
+ # Check if installed method
257
+ install_check = detect_install_method(name)
258
+ if install_check == "pipx":
259
+ method = InstallMethod.PIPX
260
+ else:
261
+ method = InstallMethod.DIRECT
262
+ else:
263
+ method = InstallMethod.PYTHON_MODULE
264
+ logger.debug(f"Auto-detected method: {method.value}")
265
+
266
+ # Build complete command if needed
267
+ if not command:
268
+ command = self._command_builder.build_command(
269
+ MCPServerConfig(name=name, command="", args=args or []),
270
+ method,
271
+ )
272
+
273
+ # Create server config
274
+ server = MCPServerConfig(
275
+ name=name,
276
+ command=command,
277
+ args=args or [],
278
+ env=env or {},
279
+ description=description,
280
+ )
281
+
282
+ # Validate server config
283
+ issues = self._inspector.validate_server(server)
284
+ errors = [i for i in issues if i.severity == "error"]
285
+ if errors:
286
+ error_msg = "\n".join(f"- {e.message}" for e in errors)
287
+ raise ValidationError(
288
+ f"Server configuration invalid:\n{error_msg}",
289
+ "Fix validation errors before installing",
290
+ )
291
+
292
+ # Log warnings
293
+ warnings = [i for i in issues if i.severity == "warning"]
294
+ for warning in warnings:
295
+ logger.warning(f"{warning.message} - {warning.fix_suggestion}")
296
+
297
+ # Install using strategy
298
+ if self.dry_run:
299
+ logger.info(
300
+ f"[DRY RUN] Would install {name} to {self._platform_info.config_path}"
301
+ )
302
+ return InstallationResult(
303
+ success=True,
304
+ platform=self._platform_info.platform,
305
+ server_name=name,
306
+ method=method,
307
+ message=f"[DRY RUN] Would install {name}",
308
+ config_path=self._platform_info.config_path,
309
+ )
310
+
311
+ try:
312
+ result = self._strategy.install(server, scope)
313
+ logger.info(f"Successfully installed {name}")
314
+ return result
315
+ except Exception as e:
316
+ logger.error(f"Installation failed: {e}", exc_info=True)
317
+ raise InstallationError(
318
+ f"Failed to install {name}: {e}",
319
+ "Check logs for details and verify permissions",
320
+ ) from e
321
+
322
+ def uninstall_server(
323
+ self, name: str, scope: Scope = Scope.PROJECT
324
+ ) -> InstallationResult:
325
+ """Remove MCP server from configuration.
326
+
327
+ Creates backup before removing. Server data/packages are not removed,
328
+ only the configuration entry.
329
+
330
+ Args:
331
+ name: Server name to uninstall
332
+ scope: Installation scope (PROJECT or GLOBAL)
333
+
334
+ Returns:
335
+ InstallationResult with success status
336
+
337
+ Raises:
338
+ InstallationError: If uninstallation fails
339
+
340
+ Example:
341
+ >>> result = installer.uninstall_server("old-server")
342
+ >>> if result.success:
343
+ ... print(f"Removed {result.server_name}")
344
+ """
345
+ logger.info(f"Uninstalling server: {name}")
346
+
347
+ if self.dry_run:
348
+ logger.info(f"[DRY RUN] Would uninstall {name}")
349
+ return InstallationResult(
350
+ success=True,
351
+ platform=self._platform_info.platform,
352
+ server_name=name,
353
+ method=InstallMethod.DIRECT, # Not relevant for uninstall
354
+ message=f"[DRY RUN] Would uninstall {name}",
355
+ config_path=self._platform_info.config_path,
356
+ )
357
+
358
+ try:
359
+ result = self._strategy.uninstall(name, scope)
360
+ logger.info(f"Successfully uninstalled {name}")
361
+ return result
362
+ except Exception as e:
363
+ logger.error(f"Uninstallation failed: {e}", exc_info=True)
364
+ raise InstallationError(
365
+ f"Failed to uninstall {name}: {e}",
366
+ "Check logs for details and verify permissions",
367
+ ) from e
368
+
369
+ def list_servers(self, scope: Scope = Scope.PROJECT) -> list[MCPServerConfig]:
370
+ """List all installed MCP servers.
371
+
372
+ Args:
373
+ scope: Installation scope (PROJECT or GLOBAL)
374
+
375
+ Returns:
376
+ List of installed server configurations (empty if none)
377
+
378
+ Example:
379
+ >>> servers = installer.list_servers()
380
+ >>> for server in servers:
381
+ ... print(f"{server.name}: {server.command} {' '.join(server.args)}")
382
+ mcp-ticketer: uv run mcp-ticketer mcp
383
+ github-mcp: npx -y @modelcontextprotocol/server-github
384
+ """
385
+ try:
386
+ return self._strategy.list_servers(scope)
387
+ except Exception as e:
388
+ logger.error(f"Failed to list servers: {e}", exc_info=True)
389
+ return []
390
+
391
+ def get_server(
392
+ self, name: str, scope: Scope = Scope.PROJECT
393
+ ) -> MCPServerConfig | None:
394
+ """Get specific server configuration.
395
+
396
+ Args:
397
+ name: Server name to retrieve
398
+ scope: Installation scope (PROJECT or GLOBAL)
399
+
400
+ Returns:
401
+ Server configuration or None if not found
402
+
403
+ Example:
404
+ >>> server = installer.get_server("mcp-ticketer")
405
+ >>> if server:
406
+ ... print(f"Command: {server.command}")
407
+ ... print(f"Args: {server.args}")
408
+ """
409
+ servers = self.list_servers(scope)
410
+ for server in servers:
411
+ if server.name == name:
412
+ return server
413
+ return None
414
+
415
+ def inspect_installation(self) -> InspectionReport:
416
+ """Run comprehensive inspection.
417
+
418
+ Validates all servers, checks for legacy format, detects duplicates,
419
+ and provides recommendations for improvements.
420
+
421
+ Returns:
422
+ Complete inspection report
423
+
424
+ Example:
425
+ >>> report = installer.inspect_installation()
426
+ >>> print(report.summary())
427
+ Inspection PASS: 5/5 servers valid
428
+ Errors: 0, Warnings: 2, Info: 1
429
+
430
+ >>> # Fix issues
431
+ >>> if report.has_warnings():
432
+ ... for issue in report.issues:
433
+ ... if issue.auto_fixable:
434
+ ... inspector.auto_fix(issue)
435
+ """
436
+ logger.info("Running installation inspection...")
437
+ report = self._inspector.inspect()
438
+
439
+ if self.verbose:
440
+ print("\n" + "=" * 60)
441
+ print(report.summary())
442
+ print("=" * 60)
443
+
444
+ if report.issues:
445
+ print("\nIssues:")
446
+ for issue in report.issues:
447
+ print(f"\n[{issue.severity.upper()}] {issue.message}")
448
+ if issue.server_name:
449
+ print(f" Server: {issue.server_name}")
450
+ print(f" Fix: {issue.fix_suggestion}")
451
+ if issue.auto_fixable:
452
+ print(" (Auto-fixable)")
453
+
454
+ if report.recommendations:
455
+ print("\nRecommendations:")
456
+ for rec in report.recommendations:
457
+ print(f" - {rec}")
458
+ print()
459
+
460
+ return report
461
+
462
+ def fix_issues(self, auto_fix: bool = True) -> list[str]:
463
+ """Fix detected issues.
464
+
465
+ Runs inspection and attempts to auto-fix all fixable issues.
466
+ Non-fixable issues are logged as warnings.
467
+
468
+ Args:
469
+ auto_fix: If True, actually apply fixes (False = dry run)
470
+
471
+ Returns:
472
+ List of fixes applied
473
+
474
+ Example:
475
+ >>> fixes = installer.fix_issues()
476
+ >>> for fix in fixes:
477
+ ... print(f"Fixed: {fix}")
478
+ Fixed: Created default config file
479
+ Fixed: Migrated legacy format to modern format
480
+ Fixed: Removed deprecated args from server 'old-server'
481
+ """
482
+ logger.info("Checking for fixable issues...")
483
+ report = self.inspect_installation()
484
+
485
+ fixes: list[str] = []
486
+ auto_fixable = [i for i in report.issues if i.auto_fixable]
487
+
488
+ if not auto_fixable:
489
+ logger.info("No auto-fixable issues found")
490
+ return fixes
491
+
492
+ logger.info(f"Found {len(auto_fixable)} auto-fixable issues")
493
+
494
+ for issue in auto_fixable:
495
+ if self.dry_run or not auto_fix:
496
+ fixes.append(f"[DRY RUN] Would fix: {issue.message}")
497
+ logger.info(f"[DRY RUN] Would fix: {issue.message}")
498
+ else:
499
+ try:
500
+ if self._inspector.auto_fix(issue):
501
+ fixes.append(issue.message)
502
+ logger.info(f"Fixed: {issue.message}")
503
+ else:
504
+ logger.warning(f"Could not auto-fix: {issue.message}")
505
+ except Exception as e:
506
+ logger.error(f"Fix failed for '{issue.message}': {e}")
507
+
508
+ return fixes
509
+
510
+ def migrate_legacy(self) -> bool:
511
+ """Migrate from legacy line-delimited JSON format.
512
+
513
+ Converts old line-delimited JSON format to modern FastMCP SDK format.
514
+ Creates backup before migration.
515
+
516
+ Returns:
517
+ True if migration succeeded (or not needed)
518
+
519
+ Example:
520
+ >>> if installer.migrate_legacy():
521
+ ... print("Migration successful")
522
+ """
523
+ logger.info("Checking for legacy format...")
524
+
525
+ if not self._inspector.check_legacy_format():
526
+ logger.info("No legacy format detected")
527
+ return True
528
+
529
+ if self.dry_run:
530
+ logger.info("[DRY RUN] Would migrate legacy format")
531
+ return True
532
+
533
+ logger.info("Migrating from legacy format...")
534
+
535
+ try:
536
+ # Auto-fix will handle the migration
537
+ issue = ValidationIssue(
538
+ severity="warning",
539
+ message="Legacy format detected",
540
+ server_name=None,
541
+ fix_suggestion="Migrate to modern format",
542
+ auto_fixable=True,
543
+ )
544
+
545
+ success = self._inspector.auto_fix(issue)
546
+ if success:
547
+ logger.info("Migration successful")
548
+ else:
549
+ logger.error("Migration failed")
550
+ return success
551
+
552
+ except Exception as e:
553
+ logger.error(f"Migration error: {e}", exc_info=True)
554
+ return False
555
+
556
+ @property
557
+ def platform_info(self) -> PlatformInfo:
558
+ """Get detected platform information.
559
+
560
+ Returns:
561
+ Platform information including confidence and paths
562
+
563
+ Example:
564
+ >>> info = installer.platform_info
565
+ >>> print(f"Platform: {info.platform.value}")
566
+ >>> print(f"Config: {info.config_path}")
567
+ >>> print(f"Confidence: {info.confidence}")
568
+ """
569
+ return self._platform_info
570
+
571
+ @property
572
+ def config_path(self) -> Path:
573
+ """Get configuration file path.
574
+
575
+ Returns:
576
+ Path to platform's config file
577
+
578
+ Example:
579
+ >>> print(installer.config_path)
580
+ /home/user/.config/claude/mcp.json
581
+ """
582
+ return self._platform_info.config_path or Path()
583
+
584
+ # ========================================================================
585
+ # Private Helper Methods
586
+ # ========================================================================
587
+
588
+ def _detect_platform(self) -> PlatformInfo:
589
+ """Detect platform automatically.
590
+
591
+ Returns:
592
+ Detected platform info
593
+
594
+ Raises:
595
+ PlatformDetectionError: If no platform detected
596
+ """
597
+ detector = PlatformDetector()
598
+ info = detector.detect()
599
+
600
+ if info.platform == Platform.UNKNOWN or info.confidence == 0.0:
601
+ raise PlatformDetectionError()
602
+
603
+ return info
604
+
605
+ def _select_strategy(self) -> BaseStrategy:
606
+ """Select best installation strategy for platform.
607
+
608
+ Returns:
609
+ Installation strategy instance
610
+
611
+ Raises:
612
+ PlatformNotSupportedError: If platform has no strategy
613
+ """
614
+ platform = self._platform_info.platform
615
+
616
+ # Platform-specific strategies (with fallbacks)
617
+ if platform == Platform.CLAUDE_CODE:
618
+ claude_strategy = ClaudeCodeStrategy()
619
+ # Use the strategy's get_strategy method to get actual installer
620
+ return claude_strategy.get_strategy(Scope.PROJECT)
621
+
622
+ elif platform == Platform.CLAUDE_DESKTOP:
623
+ # Use same strategy as Claude Code
624
+ desktop_strategy = ClaudeCodeStrategy()
625
+ return desktop_strategy.get_strategy(Scope.GLOBAL)
626
+
627
+ elif platform == Platform.CURSOR:
628
+ cursor_strategy = CursorStrategy()
629
+ return cursor_strategy.get_strategy(Scope.PROJECT)
630
+
631
+ elif platform == Platform.CODEX:
632
+ codex_strategy = CodexStrategy()
633
+ return codex_strategy.get_strategy(Scope.GLOBAL)
634
+
635
+ elif platform in [Platform.AUGGIE, Platform.WINDSURF, Platform.GEMINI_CLI]:
636
+ # Generic JSON manipulation for these platforms
637
+ if not self._platform_info.config_path:
638
+ raise ConfigurationError(
639
+ f"No config path for {platform.value}",
640
+ "Ensure platform is installed correctly",
641
+ )
642
+
643
+ return JSONManipulationStrategy(
644
+ platform=platform,
645
+ config_path=self._platform_info.config_path,
646
+ )
647
+
648
+ else:
649
+ raise PlatformNotSupportedError(
650
+ platform.value,
651
+ [
652
+ p.value
653
+ for p in Platform
654
+ if p not in [Platform.UNKNOWN, Platform.ANTIGRAVITY]
655
+ ],
656
+ )