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,451 @@
1
+ """Platform detection logic for all supported AI coding tools.
2
+
3
+ This module implements comprehensive platform detection with confidence scoring
4
+ and multi-layered validation.
5
+
6
+ Detection Strategy:
7
+ 1. Check for config file existence (+0.4 confidence)
8
+ 2. Validate config format (JSON/TOML parsing) (+0.3 confidence)
9
+ 3. Check CLI availability (+0.2 confidence)
10
+ 4. Check environment variables (+0.1 confidence)
11
+
12
+ Supported Platforms:
13
+ - Claude Code (claude_code)
14
+ - Claude Desktop (claude_desktop)
15
+ - Cursor (cursor)
16
+ - Auggie (auggie)
17
+ - Codex (codex)
18
+ - Gemini CLI (gemini_cli)
19
+ - Windsurf (windsurf)
20
+ - Antigravity (antigravity) - TBD
21
+ """
22
+
23
+ import os
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ from .exceptions import PlatformDetectionError
28
+ from .types import Platform, PlatformInfo, Scope
29
+ from .utils import parse_json_safe, parse_toml_safe, resolve_command_path
30
+
31
+
32
+ class PlatformDetector:
33
+ """Detect which AI coding tool platform is currently running.
34
+
35
+ This class provides methods to detect all supported platforms with
36
+ confidence scoring, allowing intelligent selection in multi-platform
37
+ environments.
38
+
39
+ Example:
40
+ >>> detector = PlatformDetector()
41
+ >>> info = detector.detect()
42
+ >>> print(f"{info.platform}: {info.confidence}")
43
+ Platform.CLAUDE_CODE: 1.0
44
+ """
45
+
46
+ def __init__(self) -> None:
47
+ """Initialize platform detector."""
48
+ pass
49
+
50
+ def detect(self) -> PlatformInfo:
51
+ """Auto-detect current platform with highest confidence.
52
+
53
+ Runs all platform-specific detectors and returns the one with
54
+ highest confidence score.
55
+
56
+ Returns:
57
+ PlatformInfo for detected platform
58
+
59
+ Raises:
60
+ PlatformDetectionError: If no platforms detected
61
+
62
+ Example:
63
+ >>> detector = PlatformDetector()
64
+ >>> info = detector.detect()
65
+ >>> if info.confidence > 0.8:
66
+ ... print(f"Detected {info.platform} with high confidence")
67
+ """
68
+ # Run all detectors
69
+ detectors = [
70
+ self.detect_claude_code,
71
+ self.detect_claude_desktop,
72
+ self.detect_cursor,
73
+ self.detect_auggie,
74
+ self.detect_codex,
75
+ self.detect_gemini_cli,
76
+ self.detect_windsurf,
77
+ self.detect_antigravity,
78
+ ]
79
+
80
+ results: list[tuple[float, Path | None]] = []
81
+ for detector_func in detectors:
82
+ confidence, config_path = detector_func()
83
+ results.append((confidence, config_path))
84
+
85
+ # Find platform with highest confidence
86
+ max_confidence = max(r[0] for r in results)
87
+
88
+ if max_confidence == 0.0:
89
+ raise PlatformDetectionError("No supported platforms detected")
90
+
91
+ # Map results back to platforms
92
+ platform_results = list(
93
+ zip(
94
+ [
95
+ Platform.CLAUDE_CODE,
96
+ Platform.CLAUDE_DESKTOP,
97
+ Platform.CURSOR,
98
+ Platform.AUGGIE,
99
+ Platform.CODEX,
100
+ Platform.GEMINI_CLI,
101
+ Platform.WINDSURF,
102
+ Platform.ANTIGRAVITY,
103
+ ],
104
+ results,
105
+ )
106
+ )
107
+
108
+ # Get platform with max confidence
109
+ for platform, (confidence, config_path) in platform_results:
110
+ if confidence == max_confidence:
111
+ # Determine CLI availability
112
+ cli_available = False
113
+ if platform in (Platform.CLAUDE_CODE, Platform.CLAUDE_DESKTOP):
114
+ cli_available = resolve_command_path("claude") is not None
115
+ elif platform == Platform.CURSOR:
116
+ cli_available = resolve_command_path("cursor") is not None
117
+
118
+ return PlatformInfo(
119
+ platform=platform,
120
+ confidence=confidence,
121
+ config_path=config_path,
122
+ cli_available=cli_available,
123
+ scope_support=Scope.BOTH,
124
+ )
125
+
126
+ # Should never reach here, but fallback to unknown
127
+ return PlatformInfo(
128
+ platform=Platform.UNKNOWN,
129
+ confidence=0.0,
130
+ config_path=None,
131
+ cli_available=False,
132
+ scope_support=Scope.BOTH,
133
+ )
134
+
135
+ # ========================================================================
136
+ # Platform-Specific Detectors
137
+ # ========================================================================
138
+
139
+ def detect_claude_code(self) -> tuple[float, Path | None]:
140
+ """Detect Claude Code installation.
141
+
142
+ Config locations (in priority order):
143
+ 1. ~/.config/claude/mcp.json (new location)
144
+ 2. .claude.json (legacy project-level)
145
+ 3. ~/.claude.json (legacy global)
146
+
147
+ Returns:
148
+ Tuple of (confidence, config_path)
149
+ - confidence: 0.0-1.0
150
+ - config_path: Path if found, None otherwise
151
+
152
+ Example:
153
+ >>> detector = PlatformDetector()
154
+ >>> confidence, path = detector.detect_claude_code()
155
+ >>> if confidence > 0.5:
156
+ ... print(f"Found Claude Code config at {path}")
157
+ """
158
+ confidence = 0.0
159
+ config_path: Path | None = None
160
+
161
+ # Priority 1: New location (~/.config/claude/mcp.json)
162
+ new_config = Path.home() / ".config" / "claude" / "mcp.json"
163
+ if new_config.exists():
164
+ config_path = new_config
165
+ confidence += 0.4
166
+
167
+ # Validate JSON
168
+ try:
169
+ parse_json_safe(new_config)
170
+ confidence += 0.3
171
+ except Exception:
172
+ pass # Invalid JSON reduces confidence
173
+
174
+ # Priority 2: Legacy project location (.claude.json)
175
+ elif Path(".claude.json").exists():
176
+ config_path = Path(".claude.json")
177
+ confidence += 0.4
178
+
179
+ try:
180
+ parse_json_safe(config_path)
181
+ confidence += 0.3
182
+ except Exception:
183
+ pass
184
+
185
+ # Priority 3: Legacy global location (~/.claude.json)
186
+ elif (Path.home() / ".claude.json").exists():
187
+ config_path = Path.home() / ".claude.json"
188
+ confidence += 0.4
189
+
190
+ try:
191
+ parse_json_safe(config_path)
192
+ confidence += 0.3
193
+ except Exception:
194
+ pass
195
+
196
+ # Check CLI availability
197
+ if resolve_command_path("claude") is not None:
198
+ confidence += 0.2
199
+
200
+ # Check environment variables
201
+ if os.getenv("CLAUDE_CODE_ENV"):
202
+ confidence += 0.1
203
+
204
+ return (min(confidence, 1.0), config_path)
205
+
206
+ def detect_claude_desktop(self) -> tuple[float, Path | None]:
207
+ """Detect Claude Desktop installation.
208
+
209
+ Config locations (platform-specific):
210
+ - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
211
+ - Linux: ~/.config/Claude/claude_desktop_config.json
212
+ - Windows: %APPDATA%/Claude/claude_desktop_config.json
213
+
214
+ Returns:
215
+ Tuple of (confidence, config_path)
216
+ """
217
+ confidence = 0.0
218
+ config_path: Path | None = None
219
+
220
+ # Determine platform-specific config path
221
+ if sys.platform == "darwin":
222
+ # macOS
223
+ config_path = (
224
+ Path.home()
225
+ / "Library"
226
+ / "Application Support"
227
+ / "Claude"
228
+ / "claude_desktop_config.json"
229
+ )
230
+ elif sys.platform == "win32":
231
+ # Windows
232
+ appdata = os.environ.get("APPDATA", "")
233
+ if appdata:
234
+ config_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
235
+ else:
236
+ # Linux
237
+ config_path = (
238
+ Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
239
+ )
240
+
241
+ # Check if config exists
242
+ if config_path and config_path.exists():
243
+ confidence += 0.4
244
+
245
+ # Validate JSON
246
+ try:
247
+ parse_json_safe(config_path)
248
+ confidence += 0.3
249
+ except Exception:
250
+ pass
251
+
252
+ # Check CLI availability (Claude Desktop uses same CLI as Claude Code)
253
+ if resolve_command_path("claude") is not None:
254
+ confidence += 0.2
255
+
256
+ # Check for Claude Desktop process
257
+ if sys.platform == "darwin":
258
+ # Check if Claude.app exists
259
+ claude_app = Path("/Applications/Claude.app")
260
+ if claude_app.exists():
261
+ confidence += 0.1
262
+
263
+ return (min(confidence, 1.0), config_path)
264
+
265
+ def detect_cursor(self) -> tuple[float, Path | None]:
266
+ """Detect Cursor installation.
267
+
268
+ Config location: ~/.cursor/mcp.json
269
+
270
+ Returns:
271
+ Tuple of (confidence, config_path)
272
+ """
273
+ confidence = 0.0
274
+ config_path = Path.home() / ".cursor" / "mcp.json"
275
+
276
+ # Check if config exists
277
+ if config_path.exists():
278
+ confidence += 0.4
279
+
280
+ # Validate JSON
281
+ try:
282
+ parse_json_safe(config_path)
283
+ confidence += 0.3
284
+ except Exception:
285
+ pass
286
+
287
+ # Check CLI availability
288
+ if resolve_command_path("cursor") is not None:
289
+ confidence += 0.2
290
+
291
+ # Check for Cursor directory
292
+ if (Path.home() / ".cursor").exists():
293
+ confidence += 0.1
294
+
295
+ return (min(confidence, 1.0), config_path if config_path.exists() else None)
296
+
297
+ def detect_auggie(self) -> tuple[float, Path | None]:
298
+ """Detect Auggie installation.
299
+
300
+ Config location: ~/.augment/settings.json
301
+
302
+ Returns:
303
+ Tuple of (confidence, config_path)
304
+ """
305
+ confidence = 0.0
306
+ config_path = Path.home() / ".augment" / "settings.json"
307
+
308
+ # Check if config exists
309
+ if config_path.exists():
310
+ confidence += 0.4
311
+
312
+ # Validate JSON
313
+ try:
314
+ parse_json_safe(config_path)
315
+ confidence += 0.3
316
+ except Exception:
317
+ pass
318
+
319
+ # Check for Auggie directory
320
+ if (Path.home() / ".augment").exists():
321
+ confidence += 0.2
322
+
323
+ # Check environment
324
+ if os.getenv("AUGGIE_HOME"):
325
+ confidence += 0.1
326
+
327
+ return (min(confidence, 1.0), config_path if config_path.exists() else None)
328
+
329
+ def detect_codex(self) -> tuple[float, Path | None]:
330
+ """Detect Codex installation.
331
+
332
+ Config location: ~/.codex/config.toml
333
+
334
+ Returns:
335
+ Tuple of (confidence, config_path)
336
+ """
337
+ confidence = 0.0
338
+ config_path = Path.home() / ".codex" / "config.toml"
339
+
340
+ # Check if config exists
341
+ if config_path.exists():
342
+ confidence += 0.4
343
+
344
+ # Validate TOML
345
+ try:
346
+ parse_toml_safe(config_path)
347
+ confidence += 0.3
348
+ except Exception:
349
+ pass
350
+
351
+ # Check for Codex directory
352
+ if (Path.home() / ".codex").exists():
353
+ confidence += 0.2
354
+
355
+ # Check CLI
356
+ if resolve_command_path("codex") is not None:
357
+ confidence += 0.1
358
+
359
+ return (min(confidence, 1.0), config_path if config_path.exists() else None)
360
+
361
+ def detect_gemini_cli(self) -> tuple[float, Path | None]:
362
+ """Detect Gemini CLI installation.
363
+
364
+ Config locations (in priority order):
365
+ 1. .gemini/settings.json (project-level)
366
+ 2. ~/.gemini/settings.json (user-level)
367
+
368
+ Returns:
369
+ Tuple of (confidence, config_path)
370
+ """
371
+ confidence = 0.0
372
+ config_path: Path | None = None
373
+
374
+ # Priority 1: Project-level config
375
+ project_config = Path(".gemini") / "settings.json"
376
+ if project_config.exists():
377
+ config_path = project_config
378
+ confidence += 0.4
379
+
380
+ try:
381
+ parse_json_safe(config_path)
382
+ confidence += 0.3
383
+ except Exception:
384
+ pass
385
+
386
+ # Priority 2: User-level config
387
+ elif (Path.home() / ".gemini" / "settings.json").exists():
388
+ config_path = Path.home() / ".gemini" / "settings.json"
389
+ confidence += 0.4
390
+
391
+ try:
392
+ parse_json_safe(config_path)
393
+ confidence += 0.3
394
+ except Exception:
395
+ pass
396
+
397
+ # Check for Gemini directory
398
+ if (Path.home() / ".gemini").exists() or Path(".gemini").exists():
399
+ confidence += 0.2
400
+
401
+ # Check CLI
402
+ if resolve_command_path("gemini") is not None:
403
+ confidence += 0.1
404
+
405
+ return (min(confidence, 1.0), config_path)
406
+
407
+ def detect_windsurf(self) -> tuple[float, Path | None]:
408
+ """Detect Windsurf installation.
409
+
410
+ Config location: ~/.codeium/windsurf/mcp_config.json
411
+
412
+ Returns:
413
+ Tuple of (confidence, config_path)
414
+ """
415
+ confidence = 0.0
416
+ config_path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
417
+
418
+ # Check if config exists
419
+ if config_path.exists():
420
+ confidence += 0.4
421
+
422
+ # Validate JSON
423
+ try:
424
+ parse_json_safe(config_path)
425
+ confidence += 0.3
426
+ except Exception:
427
+ pass
428
+
429
+ # Check for Windsurf directory
430
+ if (Path.home() / ".codeium" / "windsurf").exists():
431
+ confidence += 0.2
432
+
433
+ # Check for Windsurf app (macOS)
434
+ if sys.platform == "darwin":
435
+ windsurf_app = Path("/Applications/Windsurf.app")
436
+ if windsurf_app.exists():
437
+ confidence += 0.1
438
+
439
+ return (min(confidence, 1.0), config_path if config_path.exists() else None)
440
+
441
+ def detect_antigravity(self) -> tuple[float, Path | None]:
442
+ """Detect Antigravity installation.
443
+
444
+ Note: Config location not yet documented. This is a placeholder
445
+ implementation that always returns 0.0 confidence.
446
+
447
+ Returns:
448
+ Tuple of (confidence=0.0, config_path=None)
449
+ """
450
+ # TODO: Update when Antigravity config location is documented
451
+ return (0.0, None)
@@ -0,0 +1,26 @@
1
+ """Platform-specific implementations for MCP installation.
2
+
3
+ This module provides platform-specific strategies and configurations
4
+ for different AI coding tools.
5
+
6
+ Supported platforms:
7
+ - Claude Code (claude_code.py)
8
+ - Cursor (cursor.py)
9
+ - Codex (codex.py)
10
+
11
+ Each platform module provides:
12
+ - Configuration path detection
13
+ - Installation strategy selection
14
+ - Platform-specific validation
15
+ - Command building utilities
16
+ """
17
+
18
+ from .claude_code import ClaudeCodeStrategy
19
+ from .codex import CodexStrategy
20
+ from .cursor import CursorStrategy
21
+
22
+ __all__ = [
23
+ "ClaudeCodeStrategy",
24
+ "CursorStrategy",
25
+ "CodexStrategy",
26
+ ]
@@ -0,0 +1,225 @@
1
+ """Claude Code platform implementation.
2
+
3
+ This module provides platform-specific logic for Claude Code, including
4
+ configuration paths, installation strategies, and validation.
5
+
6
+ Claude Code supports:
7
+ - Project-level config: .claude.json or ~/.config/claude/mcp.json
8
+ - Global config: ~/.config/claude/mcp.json
9
+ - Native CLI: claude mcp add/remove
10
+ - JSON manipulation fallback
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ from ..command_builder import CommandBuilder
16
+ from ..installation_strategy import (
17
+ InstallationStrategy,
18
+ JSONManipulationStrategy,
19
+ NativeCLIStrategy,
20
+ )
21
+ from ..types import InstallMethod, MCPServerConfig, Platform, Scope
22
+ from ..utils import resolve_command_path
23
+
24
+
25
+ class ClaudeCodeStrategy:
26
+ """Claude Code platform implementation.
27
+
28
+ Provides configuration paths and installation strategies for Claude Code.
29
+
30
+ Example:
31
+ >>> strategy = ClaudeCodeStrategy()
32
+ >>> config_path = strategy.get_config_path(Scope.PROJECT)
33
+ >>> installer = strategy.get_strategy(Scope.PROJECT)
34
+ >>> result = installer.install(server, Scope.PROJECT)
35
+ """
36
+
37
+ def __init__(self) -> None:
38
+ """Initialize Claude Code strategy."""
39
+ self.platform = Platform.CLAUDE_CODE
40
+ self.config_format = "json"
41
+
42
+ def get_config_path(self, scope: Scope) -> Path:
43
+ """Get configuration path for scope.
44
+
45
+ Priority order:
46
+ 1. Project scope: .claude.json (legacy) or ~/.config/claude/mcp.json (new)
47
+ 2. Global scope: ~/.config/claude/mcp.json
48
+
49
+ Args:
50
+ scope: Installation scope (PROJECT or GLOBAL)
51
+
52
+ Returns:
53
+ Path to configuration file
54
+
55
+ Example:
56
+ >>> strategy = ClaudeCodeStrategy()
57
+ >>> path = strategy.get_config_path(Scope.PROJECT)
58
+ >>> print(path)
59
+ /home/user/.config/claude/mcp.json
60
+ """
61
+ if scope == Scope.PROJECT:
62
+ # Check for new location first
63
+ new_config = Path.home() / ".config" / "claude" / "mcp.json"
64
+ if new_config.exists():
65
+ return new_config
66
+
67
+ # Fallback to legacy project-level config
68
+ legacy_project = Path(".claude.json")
69
+ if legacy_project.exists():
70
+ return legacy_project
71
+
72
+ # Default to new location if creating new config
73
+ return new_config
74
+
75
+ else: # Scope.GLOBAL
76
+ # Global config always in new location
77
+ return Path.home() / ".config" / "claude" / "mcp.json"
78
+
79
+ def get_strategy(self, scope: Scope) -> InstallationStrategy:
80
+ """Get appropriate installation strategy for scope.
81
+
82
+ Prefers native CLI if available, falls back to JSON manipulation.
83
+
84
+ Args:
85
+ scope: Installation scope
86
+
87
+ Returns:
88
+ Installation strategy instance
89
+
90
+ Example:
91
+ >>> strategy = ClaudeCodeStrategy()
92
+ >>> installer = strategy.get_strategy(Scope.PROJECT)
93
+ >>> if installer.validate():
94
+ ... result = installer.install(server, Scope.PROJECT)
95
+ """
96
+ # Prefer native CLI if available
97
+ if resolve_command_path("claude"):
98
+ return NativeCLIStrategy(self.platform, "claude")
99
+
100
+ # Fallback to JSON manipulation
101
+ config_path = self.get_config_path(scope)
102
+ return JSONManipulationStrategy(self.platform, config_path)
103
+
104
+ def get_strategy_with_fallback(
105
+ self, scope: Scope
106
+ ) -> tuple[InstallationStrategy, InstallationStrategy | None]:
107
+ """Get primary strategy and fallback strategy.
108
+
109
+ Returns both native CLI and JSON strategies for graceful fallback.
110
+
111
+ Args:
112
+ scope: Installation scope
113
+
114
+ Returns:
115
+ Tuple of (primary_strategy, fallback_strategy)
116
+
117
+ Example:
118
+ >>> strategy = ClaudeCodeStrategy()
119
+ >>> primary, fallback = strategy.get_strategy_with_fallback(Scope.PROJECT)
120
+ >>> try:
121
+ ... result = primary.install(server, Scope.PROJECT)
122
+ ... except InstallationError:
123
+ ... if fallback:
124
+ ... result = fallback.install(server, Scope.PROJECT)
125
+ """
126
+ config_path = self.get_config_path(scope)
127
+
128
+ # Primary: Native CLI if available
129
+ primary: InstallationStrategy | None = None
130
+ if resolve_command_path("claude"):
131
+ primary = NativeCLIStrategy(self.platform, "claude")
132
+
133
+ # Fallback: Always JSON
134
+ fallback = JSONManipulationStrategy(self.platform, config_path)
135
+
136
+ # If no CLI, use JSON as primary
137
+ if primary is None:
138
+ return (fallback, None)
139
+
140
+ return (primary, fallback)
141
+
142
+ def validate_installation(self) -> bool:
143
+ """Validate Claude Code is available.
144
+
145
+ Checks for config file existence or CLI availability.
146
+
147
+ Returns:
148
+ True if Claude Code appears to be installed
149
+
150
+ Example:
151
+ >>> strategy = ClaudeCodeStrategy()
152
+ >>> if strategy.validate_installation():
153
+ ... print("Claude Code is available")
154
+ """
155
+ # Check for any config file
156
+ global_config = Path.home() / ".config" / "claude" / "mcp.json"
157
+ legacy_config = Path.home() / ".claude.json"
158
+ project_config = Path(".claude.json")
159
+
160
+ has_config = (
161
+ global_config.exists() or legacy_config.exists() or project_config.exists()
162
+ )
163
+
164
+ # Check for CLI
165
+ has_cli = resolve_command_path("claude") is not None
166
+
167
+ return has_config or has_cli
168
+
169
+ def build_server_config(
170
+ self,
171
+ package: str,
172
+ install_method: InstallMethod | None = None,
173
+ env: dict[str, str] | None = None,
174
+ description: str = "",
175
+ ) -> MCPServerConfig:
176
+ """Build server configuration for Claude Code.
177
+
178
+ Uses CommandBuilder to auto-detect best installation method.
179
+
180
+ Args:
181
+ package: Package name (e.g., "mcp-ticketer")
182
+ install_method: Installation method (auto-detected if None)
183
+ env: Environment variables
184
+ description: Server description
185
+
186
+ Returns:
187
+ Complete server configuration
188
+
189
+ Example:
190
+ >>> strategy = ClaudeCodeStrategy()
191
+ >>> config = strategy.build_server_config(
192
+ ... "mcp-ticketer",
193
+ ... env={"LINEAR_API_KEY": "..."}
194
+ ... )
195
+ >>> print(f"{config.command} {' '.join(config.args)}")
196
+ uv run mcp-ticketer mcp
197
+ """
198
+ builder = CommandBuilder(self.platform)
199
+ return builder.to_server_config(
200
+ package=package,
201
+ install_method=install_method,
202
+ env=env,
203
+ description=description,
204
+ )
205
+
206
+ def get_platform_info(self) -> dict[str, str]:
207
+ """Get platform information.
208
+
209
+ Returns:
210
+ Dict with platform details
211
+
212
+ Example:
213
+ >>> strategy = ClaudeCodeStrategy()
214
+ >>> info = strategy.get_platform_info()
215
+ >>> print(info["name"])
216
+ Claude Code
217
+ """
218
+ return {
219
+ "name": "Claude Code",
220
+ "platform": self.platform.value,
221
+ "config_format": "json",
222
+ "scope_support": "both",
223
+ "cli_available": str(resolve_command_path("claude") is not None),
224
+ "config_key": "mcpServers",
225
+ }