mcp-ticketer 0.2.0__py3-none-any.whl → 2.2.9__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.
Files changed (160) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/__init__.py +2 -0
  5. mcp_ticketer/adapters/aitrackdown.py +930 -52
  6. mcp_ticketer/adapters/asana/__init__.py +15 -0
  7. mcp_ticketer/adapters/asana/adapter.py +1537 -0
  8. mcp_ticketer/adapters/asana/client.py +292 -0
  9. mcp_ticketer/adapters/asana/mappers.py +348 -0
  10. mcp_ticketer/adapters/asana/types.py +146 -0
  11. mcp_ticketer/adapters/github/__init__.py +26 -0
  12. mcp_ticketer/adapters/github/adapter.py +3229 -0
  13. mcp_ticketer/adapters/github/client.py +335 -0
  14. mcp_ticketer/adapters/github/mappers.py +797 -0
  15. mcp_ticketer/adapters/github/queries.py +692 -0
  16. mcp_ticketer/adapters/github/types.py +460 -0
  17. mcp_ticketer/adapters/hybrid.py +58 -16
  18. mcp_ticketer/adapters/jira/__init__.py +35 -0
  19. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  20. mcp_ticketer/adapters/jira/client.py +271 -0
  21. mcp_ticketer/adapters/jira/mappers.py +246 -0
  22. mcp_ticketer/adapters/jira/queries.py +216 -0
  23. mcp_ticketer/adapters/jira/types.py +304 -0
  24. mcp_ticketer/adapters/linear/__init__.py +1 -1
  25. mcp_ticketer/adapters/linear/adapter.py +3810 -462
  26. mcp_ticketer/adapters/linear/client.py +312 -69
  27. mcp_ticketer/adapters/linear/mappers.py +305 -85
  28. mcp_ticketer/adapters/linear/queries.py +317 -17
  29. mcp_ticketer/adapters/linear/types.py +187 -64
  30. mcp_ticketer/adapters/linear.py +2 -2
  31. mcp_ticketer/analysis/__init__.py +56 -0
  32. mcp_ticketer/analysis/dependency_graph.py +255 -0
  33. mcp_ticketer/analysis/health_assessment.py +304 -0
  34. mcp_ticketer/analysis/orphaned.py +218 -0
  35. mcp_ticketer/analysis/project_status.py +594 -0
  36. mcp_ticketer/analysis/similarity.py +224 -0
  37. mcp_ticketer/analysis/staleness.py +266 -0
  38. mcp_ticketer/automation/__init__.py +11 -0
  39. mcp_ticketer/automation/project_updates.py +378 -0
  40. mcp_ticketer/cache/memory.py +9 -8
  41. mcp_ticketer/cli/adapter_diagnostics.py +421 -0
  42. mcp_ticketer/cli/auggie_configure.py +116 -15
  43. mcp_ticketer/cli/codex_configure.py +274 -82
  44. mcp_ticketer/cli/configure.py +1323 -151
  45. mcp_ticketer/cli/cursor_configure.py +314 -0
  46. mcp_ticketer/cli/diagnostics.py +209 -114
  47. mcp_ticketer/cli/discover.py +297 -26
  48. mcp_ticketer/cli/gemini_configure.py +119 -26
  49. mcp_ticketer/cli/init_command.py +880 -0
  50. mcp_ticketer/cli/install_mcp_server.py +418 -0
  51. mcp_ticketer/cli/instruction_commands.py +435 -0
  52. mcp_ticketer/cli/linear_commands.py +256 -130
  53. mcp_ticketer/cli/main.py +140 -1284
  54. mcp_ticketer/cli/mcp_configure.py +1013 -100
  55. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  56. mcp_ticketer/cli/migrate_config.py +12 -8
  57. mcp_ticketer/cli/platform_commands.py +123 -0
  58. mcp_ticketer/cli/platform_detection.py +477 -0
  59. mcp_ticketer/cli/platform_installer.py +545 -0
  60. mcp_ticketer/cli/project_update_commands.py +350 -0
  61. mcp_ticketer/cli/python_detection.py +126 -0
  62. mcp_ticketer/cli/queue_commands.py +15 -15
  63. mcp_ticketer/cli/setup_command.py +794 -0
  64. mcp_ticketer/cli/simple_health.py +84 -59
  65. mcp_ticketer/cli/ticket_commands.py +1375 -0
  66. mcp_ticketer/cli/update_checker.py +313 -0
  67. mcp_ticketer/cli/utils.py +195 -72
  68. mcp_ticketer/core/__init__.py +64 -1
  69. mcp_ticketer/core/adapter.py +618 -18
  70. mcp_ticketer/core/config.py +77 -68
  71. mcp_ticketer/core/env_discovery.py +75 -16
  72. mcp_ticketer/core/env_loader.py +121 -97
  73. mcp_ticketer/core/exceptions.py +32 -24
  74. mcp_ticketer/core/http_client.py +26 -26
  75. mcp_ticketer/core/instructions.py +405 -0
  76. mcp_ticketer/core/label_manager.py +732 -0
  77. mcp_ticketer/core/mappers.py +42 -30
  78. mcp_ticketer/core/milestone_manager.py +252 -0
  79. mcp_ticketer/core/models.py +566 -19
  80. mcp_ticketer/core/onepassword_secrets.py +379 -0
  81. mcp_ticketer/core/priority_matcher.py +463 -0
  82. mcp_ticketer/core/project_config.py +189 -49
  83. mcp_ticketer/core/project_utils.py +281 -0
  84. mcp_ticketer/core/project_validator.py +376 -0
  85. mcp_ticketer/core/registry.py +3 -3
  86. mcp_ticketer/core/session_state.py +176 -0
  87. mcp_ticketer/core/state_matcher.py +592 -0
  88. mcp_ticketer/core/url_parser.py +425 -0
  89. mcp_ticketer/core/validators.py +69 -0
  90. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  91. mcp_ticketer/mcp/__init__.py +29 -1
  92. mcp_ticketer/mcp/__main__.py +60 -0
  93. mcp_ticketer/mcp/server/__init__.py +25 -0
  94. mcp_ticketer/mcp/server/__main__.py +60 -0
  95. mcp_ticketer/mcp/server/constants.py +58 -0
  96. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  97. mcp_ticketer/mcp/server/dto.py +195 -0
  98. mcp_ticketer/mcp/server/main.py +1343 -0
  99. mcp_ticketer/mcp/server/response_builder.py +206 -0
  100. mcp_ticketer/mcp/server/routing.py +723 -0
  101. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  102. mcp_ticketer/mcp/server/tools/__init__.py +69 -0
  103. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  104. mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
  105. mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
  106. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  107. mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
  108. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  109. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
  110. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  111. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  112. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  113. mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
  114. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  115. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  116. mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
  117. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  118. mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
  119. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  120. mcp_ticketer/queue/__init__.py +1 -0
  121. mcp_ticketer/queue/health_monitor.py +168 -136
  122. mcp_ticketer/queue/manager.py +78 -63
  123. mcp_ticketer/queue/queue.py +108 -21
  124. mcp_ticketer/queue/run_worker.py +2 -2
  125. mcp_ticketer/queue/ticket_registry.py +213 -155
  126. mcp_ticketer/queue/worker.py +96 -58
  127. mcp_ticketer/utils/__init__.py +5 -0
  128. mcp_ticketer/utils/token_utils.py +246 -0
  129. mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
  130. mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
  131. mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
  132. py_mcp_installer/examples/phase3_demo.py +178 -0
  133. py_mcp_installer/scripts/manage_version.py +54 -0
  134. py_mcp_installer/setup.py +6 -0
  135. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  136. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  137. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  138. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  139. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  140. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  141. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  142. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  143. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  144. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  145. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  146. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  147. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  148. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  149. py_mcp_installer/tests/__init__.py +0 -0
  150. py_mcp_installer/tests/platforms/__init__.py +0 -0
  151. py_mcp_installer/tests/test_platform_detector.py +17 -0
  152. mcp_ticketer/adapters/github.py +0 -1354
  153. mcp_ticketer/adapters/jira.py +0 -1011
  154. mcp_ticketer/mcp/server.py +0 -1895
  155. mcp_ticketer-0.2.0.dist-info/METADATA +0 -414
  156. mcp_ticketer-0.2.0.dist-info/RECORD +0 -58
  157. mcp_ticketer-0.2.0.dist-info/top_level.txt +0 -1
  158. {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
  159. {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
  160. {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -4,19 +4,15 @@ Codex CLI only supports global configuration at ~/.codex/config.toml.
4
4
  Unlike Claude Code and Gemini CLI, there is no project-level configuration support.
5
5
  """
6
6
 
7
- import sys
8
7
  from pathlib import Path
9
- from typing import Any, Dict, Optional
10
-
11
- if sys.version_info >= (3, 11):
12
- import tomllib
13
- else:
14
- import tomli as tomllib
8
+ from typing import Any
15
9
 
16
10
  import tomli_w
11
+ import tomllib
17
12
  from rich.console import Console
18
13
 
19
- from .mcp_configure import find_mcp_ticketer_binary, load_project_config
14
+ from .mcp_configure import load_project_config
15
+ from .python_detection import get_mcp_ticketer_python
20
16
 
21
17
  console = Console()
22
18
 
@@ -28,6 +24,7 @@ def find_codex_config() -> Path:
28
24
  No project-level or user-scoped configuration is available.
29
25
 
30
26
  Returns:
27
+ -------
31
28
  Path to Codex global config file at ~/.codex/config.toml
32
29
 
33
30
  """
@@ -36,13 +33,15 @@ def find_codex_config() -> Path:
36
33
  return config_path
37
34
 
38
35
 
39
- def load_codex_config(config_path: Path) -> Dict[str, Any]:
36
+ def load_codex_config(config_path: Path) -> dict[str, Any]:
40
37
  """Load existing Codex configuration or return empty structure.
41
38
 
42
39
  Args:
40
+ ----
43
41
  config_path: Path to Codex config.toml file
44
42
 
45
43
  Returns:
44
+ -------
46
45
  Codex configuration dict with mcp_servers section
47
46
 
48
47
  """
@@ -61,10 +60,11 @@ def load_codex_config(config_path: Path) -> Dict[str, Any]:
61
60
  return {"mcp_servers": {}}
62
61
 
63
62
 
64
- def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
63
+ def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
65
64
  """Save Codex configuration to TOML file.
66
65
 
67
66
  Args:
67
+ ----
68
68
  config_path: Path to Codex config.toml file
69
69
  config: Configuration to save
70
70
 
@@ -78,79 +78,219 @@ def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
78
78
 
79
79
 
80
80
  def create_codex_server_config(
81
- binary_path: str, project_config: dict, cwd: Optional[str] = None
82
- ) -> Dict[str, Any]:
81
+ python_path: str, project_config: dict, project_path: str | None = None
82
+ ) -> dict[str, Any]:
83
83
  """Create Codex MCP server configuration for mcp-ticketer.
84
84
 
85
+ Uses the CLI command (mcp-ticketer mcp) which implements proper
86
+ Content-Length framing via FastMCP SDK, required for modern MCP clients.
87
+
85
88
  Args:
86
- binary_path: Path to mcp-ticketer binary
89
+ ----
90
+ python_path: Path to Python executable in mcp-ticketer venv
87
91
  project_config: Project configuration from .mcp-ticketer/config.json
88
- cwd: Working directory for server (optional, not used for global config)
92
+ project_path: Project directory path (optional)
89
93
 
90
94
  Returns:
95
+ -------
91
96
  Codex MCP server configuration dict
92
97
 
93
98
  """
94
- # Get adapter configuration
95
- adapter = project_config.get("default_adapter", "aitrackdown")
96
- adapters_config = project_config.get("adapters", {})
97
- adapter_config = adapters_config.get(adapter, {})
98
-
99
- # Build environment variables
100
- env_vars: Dict[str, str] = {}
101
-
102
- # Add PYTHONPATH if running from development environment
103
- if cwd:
104
- env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
105
-
106
- # Add adapter type
107
- env_vars["MCP_TICKETER_ADAPTER"] = adapter
108
-
109
- # Add adapter-specific environment variables
110
- if adapter == "aitrackdown":
111
- # Set base path for local adapter
112
- base_path = adapter_config.get("base_path", ".aitrackdown")
113
- if cwd:
114
- # Use absolute path if cwd is provided
115
- env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_path)
116
- else:
117
- env_vars["MCP_TICKETER_BASE_PATH"] = base_path
118
-
119
- elif adapter == "linear":
120
- if "api_key" in adapter_config:
121
- env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
122
- if "team_id" in adapter_config:
123
- env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
124
-
125
- elif adapter == "github":
126
- if "token" in adapter_config:
127
- env_vars["GITHUB_TOKEN"] = adapter_config["token"]
128
- if "owner" in adapter_config:
129
- env_vars["GITHUB_OWNER"] = adapter_config["owner"]
130
- if "repo" in adapter_config:
131
- env_vars["GITHUB_REPO"] = adapter_config["repo"]
132
-
133
- elif adapter == "jira":
134
- if "api_token" in adapter_config:
135
- env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
136
- if "email" in adapter_config:
137
- env_vars["JIRA_EMAIL"] = adapter_config["email"]
138
- if "server" in adapter_config:
139
- env_vars["JIRA_SERVER"] = adapter_config["server"]
140
- if "project_key" in adapter_config:
141
- env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
99
+ # IMPORTANT: Use CLI command, NOT Python module invocation
100
+ # The CLI uses FastMCP SDK which implements proper Content-Length framing
101
+ # Legacy python -m mcp_ticketer.mcp.server uses line-delimited JSON (incompatible)
102
+
103
+ # Get mcp-ticketer CLI path from Python path
104
+ # If python_path is /path/to/venv/bin/python, CLI is /path/to/venv/bin/mcp-ticketer
105
+ python_dir = Path(python_path).parent
106
+ cli_path = str(python_dir / "mcp-ticketer")
107
+
108
+ # Build CLI arguments
109
+ args = ["mcp"]
110
+ if project_path:
111
+ args.extend(["--path", project_path])
142
112
 
143
113
  # Create server configuration with Codex-specific structure
144
- # NOTE: Codex uses nested dict structure for env vars
145
- config: Dict[str, Any] = {
146
- "command": binary_path,
147
- "args": ["serve"],
148
- "env": env_vars,
114
+ # No environment variables needed - config loaded from .mcp-ticketer/config.json
115
+ config: dict[str, Any] = {
116
+ "command": cli_path,
117
+ "args": args,
149
118
  }
150
119
 
151
120
  return config
152
121
 
153
122
 
123
+ def _test_configuration(adapter: str, project_config: dict) -> bool:
124
+ """Test the configuration by validating adapter credentials.
125
+
126
+ Args:
127
+ ----
128
+ adapter: Adapter type (linear, github, jira, aitrackdown)
129
+ project_config: Project configuration dict
130
+
131
+ Returns:
132
+ -------
133
+ True if validation passed, False otherwise
134
+
135
+ """
136
+ try:
137
+ from ..core import AdapterRegistry
138
+
139
+ # Get adapter configuration
140
+ adapters_config = project_config.get("adapters", {})
141
+ adapter_config = adapters_config.get(adapter, {})
142
+
143
+ # Test adapter instantiation
144
+ console.print(f" Testing {adapter} adapter...")
145
+
146
+ try:
147
+ adapter_instance = AdapterRegistry.get_adapter(adapter, adapter_config)
148
+ console.print(" [green]✓[/green] Adapter instantiated successfully")
149
+
150
+ # Test credentials if validation method exists
151
+ if hasattr(adapter_instance, "validate_credentials"):
152
+ console.print(f" Validating {adapter} credentials...")
153
+ is_valid, error_msg = adapter_instance.validate_credentials()
154
+
155
+ if is_valid:
156
+ console.print(" [green]✓[/green] Credentials are valid")
157
+ return True
158
+ else:
159
+ console.print(
160
+ f" [red]✗[/red] Credential validation failed: {error_msg}"
161
+ )
162
+ return False
163
+ else:
164
+ # No validation method, assume valid
165
+ console.print(
166
+ f" [yellow]○[/yellow] No credential validation available for {adapter}"
167
+ )
168
+ return True
169
+
170
+ except Exception as e:
171
+ console.print(f" [red]✗[/red] Adapter instantiation failed: {e}")
172
+
173
+ # Provide helpful error messages based on adapter type
174
+ if adapter == "linear":
175
+ console.print(
176
+ "\n [yellow]Linear requires:[/yellow] LINEAR_API_KEY and LINEAR_TEAM_ID"
177
+ )
178
+ elif adapter == "github":
179
+ console.print(
180
+ "\n [yellow]GitHub requires:[/yellow] GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO"
181
+ )
182
+ elif adapter == "jira":
183
+ console.print(
184
+ "\n [yellow]JIRA requires:[/yellow] JIRA_SERVER, JIRA_EMAIL, JIRA_API_TOKEN"
185
+ )
186
+
187
+ return False
188
+
189
+ except Exception as e:
190
+ console.print(f" [red]✗[/red] Configuration test error: {e}")
191
+ return False
192
+
193
+
194
+ def detect_legacy_config(config_path: Path) -> tuple[bool, dict[str, Any] | None]:
195
+ """Detect if existing config uses legacy Python module invocation.
196
+
197
+ Args:
198
+ ----
199
+ config_path: Path to Codex config.toml file
200
+
201
+ Returns:
202
+ -------
203
+ Tuple of (is_legacy, server_config):
204
+ - is_legacy: True if config uses 'python -m mcp_ticketer.mcp.server'
205
+ - server_config: The legacy server config dict, or None if not legacy
206
+
207
+ """
208
+ if not config_path.exists():
209
+ return False, None
210
+
211
+ codex_config = load_codex_config(config_path)
212
+ mcp_servers = codex_config.get("mcp_servers", {})
213
+
214
+ if "mcp-ticketer" in mcp_servers:
215
+ server_config = mcp_servers["mcp-ticketer"]
216
+ args = server_config.get("args", [])
217
+
218
+ # Check for legacy pattern: ["-m", "mcp_ticketer.mcp.server", ...]
219
+ if len(args) >= 2 and args[0] == "-m" and "mcp_ticketer.mcp.server" in args[1]:
220
+ return True, server_config
221
+
222
+ return False, None
223
+
224
+
225
+ def remove_codex_mcp(dry_run: bool = False) -> None:
226
+ """Remove mcp-ticketer from Codex CLI configuration.
227
+
228
+ IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
229
+ This will remove mcp-ticketer from the global configuration.
230
+
231
+ Args:
232
+ ----
233
+ dry_run: Show what would be removed without making changes
234
+
235
+ """
236
+ # Step 1: Find Codex config location (always global)
237
+ console.print("[cyan]🔍 Removing Codex CLI global configuration...[/cyan]")
238
+ console.print(
239
+ "[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
240
+ )
241
+
242
+ codex_config_path = find_codex_config()
243
+ console.print(f"[dim]Config location: {codex_config_path}[/dim]")
244
+
245
+ # Step 2: Check if config file exists
246
+ if not codex_config_path.exists():
247
+ console.print(
248
+ f"[yellow]⚠ No configuration found at {codex_config_path}[/yellow]"
249
+ )
250
+ console.print("[dim]mcp-ticketer is not configured for Codex CLI[/dim]")
251
+ return
252
+
253
+ # Step 3: Load existing Codex configuration
254
+ codex_config = load_codex_config(codex_config_path)
255
+
256
+ # Step 4: Check if mcp-ticketer is configured
257
+ # NOTE: Use underscore mcp_servers, not camelCase
258
+ mcp_servers = codex_config.get("mcp_servers", {})
259
+ if "mcp-ticketer" not in mcp_servers:
260
+ console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
261
+ console.print(f"[dim]No mcp-ticketer entry found in {codex_config_path}[/dim]")
262
+ return
263
+
264
+ # Step 5: Show what would be removed (dry run or actual removal)
265
+ if dry_run:
266
+ console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
267
+ console.print(" Server name: mcp-ticketer")
268
+ console.print(f" From: {codex_config_path}")
269
+ console.print(" Scope: Global (all sessions)")
270
+ return
271
+
272
+ # Step 6: Remove mcp-ticketer from configuration
273
+ del codex_config["mcp_servers"]["mcp-ticketer"]
274
+
275
+ # Step 7: Save updated configuration
276
+ try:
277
+ save_codex_config(codex_config_path, codex_config)
278
+ console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
279
+ console.print(f"[dim]Configuration updated: {codex_config_path}[/dim]")
280
+
281
+ # Next steps
282
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
283
+ console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
284
+ console.print("2. mcp-ticketer will no longer be available via MCP")
285
+ console.print(
286
+ "\n[yellow]⚠ Note: This removes global configuration affecting all Codex sessions[/yellow]"
287
+ )
288
+
289
+ except Exception as e:
290
+ console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
291
+ raise
292
+
293
+
154
294
  def configure_codex_mcp(force: bool = False) -> None:
155
295
  """Configure Codex CLI to use mcp-ticketer.
156
296
 
@@ -160,21 +300,27 @@ def configure_codex_mcp(force: bool = False) -> None:
160
300
  After configuration, you must restart Codex CLI for changes to take effect.
161
301
 
162
302
  Args:
303
+ ----
163
304
  force: Overwrite existing configuration
164
305
 
165
306
  Raises:
166
- FileNotFoundError: If binary or project config not found
307
+ ------
308
+ FileNotFoundError: If Python executable or project config not found
167
309
  ValueError: If configuration is invalid
168
310
 
169
311
  """
170
- # Step 1: Find mcp-ticketer binary
171
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
312
+ # Step 1: Find Python executable
313
+ console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
172
314
  try:
173
- binary_path = find_mcp_ticketer_binary()
174
- console.print(f"[green]✓[/green] Found: {binary_path}")
175
- except FileNotFoundError as e:
176
- console.print(f"[red]✗[/red] {e}")
177
- raise
315
+ python_path = get_mcp_ticketer_python()
316
+ console.print(f"[green]✓[/green] Found: {python_path}")
317
+ except Exception as e:
318
+ console.print(f"[red]✗[/red] Could not find Python executable: {e}")
319
+ raise FileNotFoundError(
320
+ "Could not find mcp-ticketer Python executable. "
321
+ "Please ensure mcp-ticketer is installed.\n"
322
+ "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
323
+ ) from e
178
324
 
179
325
  # Step 2: Load project configuration
180
326
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -195,6 +341,26 @@ def configure_codex_mcp(force: bool = False) -> None:
195
341
  codex_config_path = find_codex_config()
196
342
  console.print(f"[dim]Config location: {codex_config_path}[/dim]")
197
343
 
344
+ # Step 3.5: Check for legacy configuration (DETECTION & MIGRATION)
345
+ is_legacy, legacy_config = detect_legacy_config(codex_config_path)
346
+ if is_legacy:
347
+ console.print("\n[yellow]⚠ LEGACY CONFIGURATION DETECTED[/yellow]")
348
+ console.print(
349
+ "[yellow]Your current configuration uses the legacy line-delimited JSON server:[/yellow]"
350
+ )
351
+ console.print(f"[dim] Command: {legacy_config.get('command')}[/dim]")
352
+ console.print(f"[dim] Args: {legacy_config.get('args')}[/dim]")
353
+ console.print(
354
+ "\n[red]This legacy server is incompatible with modern MCP clients (Codex, Claude Desktop/Code).[/red]"
355
+ )
356
+ console.print(
357
+ "[red]The legacy server uses line-delimited JSON instead of Content-Length framing.[/red]"
358
+ )
359
+ console.print(
360
+ "\n[cyan]✨ Automatically migrating to modern FastMCP-based server...[/cyan]"
361
+ )
362
+ force = True # Auto-enable force mode for migration
363
+
198
364
  # Step 4: Load existing Codex configuration
199
365
  codex_config = load_codex_config(codex_config_path)
200
366
 
@@ -207,13 +373,17 @@ def configure_codex_mcp(force: bool = False) -> None:
207
373
  console.print("[dim]Use --force to overwrite existing configuration[/dim]")
208
374
  return
209
375
  else:
210
- console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
376
+ if not is_legacy:
377
+ console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
378
+ # If is_legacy, we already printed migration message above
211
379
 
212
380
  # Step 6: Create mcp-ticketer server config
213
381
  # For global config, include current working directory for context
214
- cwd = str(Path.cwd())
382
+ project_path = str(Path.cwd())
215
383
  server_config = create_codex_server_config(
216
- binary_path=binary_path, project_config=project_config, cwd=cwd
384
+ python_path=python_path,
385
+ project_config=project_config,
386
+ project_path=project_path,
217
387
  )
218
388
 
219
389
  # Step 7: Update Codex configuration
@@ -232,12 +402,34 @@ def configure_codex_mcp(force: bool = False) -> None:
232
402
  console.print("\n[bold]Configuration Details:[/bold]")
233
403
  console.print(" Server name: mcp-ticketer")
234
404
  console.print(f" Adapter: {adapter}")
235
- console.print(f" Binary: {binary_path}")
405
+ console.print(f" Python: {python_path}")
406
+ console.print(f" Command: {server_config.get('command')}")
407
+ console.print(f" Args: {server_config.get('args')}")
408
+ console.print(" Protocol: Content-Length framing (FastMCP SDK)")
236
409
  console.print(" Scope: global (Codex only supports global config)")
237
- console.print(f" Working directory: {cwd}")
238
- if "env" in server_config:
410
+ console.print(f" Project path: {project_path}")
411
+
412
+ # Step 9: Test configuration
413
+ console.print("\n[cyan]🧪 Testing configuration...[/cyan]")
414
+ test_success = _test_configuration(adapter, project_config)
415
+
416
+ if not test_success:
417
+ console.print(
418
+ "[yellow]⚠ Configuration saved but validation failed. "
419
+ "Please check your credentials and settings.[/yellow]"
420
+ )
421
+
422
+ # Migration success message (if legacy config was detected)
423
+ if is_legacy:
424
+ console.print("\n[green]✅ Migration Complete![/green]")
425
+ console.print(
426
+ "[green]Your configuration has been upgraded from legacy line-delimited JSON[/green]"
427
+ )
428
+ console.print(
429
+ "[green]to modern Content-Length framing (FastMCP SDK).[/green]"
430
+ )
239
431
  console.print(
240
- f" Environment variables: {list(server_config['env'].keys())}"
432
+ "\n[cyan]This fixes MCP connection issues with Codex and other modern clients.[/cyan]"
241
433
  )
242
434
 
243
435
  # Next steps