mcp-ticketer 0.12.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +385 -6
- mcp_ticketer/adapters/asana/adapter.py +108 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github.py +525 -11
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +521 -0
- mcp_ticketer/adapters/linear/adapter.py +1784 -101
- mcp_ticketer/adapters/linear/client.py +85 -3
- mcp_ticketer/adapters/linear/mappers.py +96 -8
- mcp_ticketer/adapters/linear/queries.py +168 -1
- mcp_ticketer/adapters/linear/types.py +80 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +851 -103
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +233 -3151
- mcp_ticketer/cli/mcp_configure.py +672 -98
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +264 -24
- mcp_ticketer/core/__init__.py +28 -6
- mcp_ticketer/core/adapter.py +166 -1
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/models.py +135 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/session_state.py +171 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +31 -12
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +6 -8
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1184 -136
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1070 -123
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""MCP server management commands for mcp-ticketer."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
# Create MCP configuration command group
|
|
13
|
+
mcp_app = typer.Typer(
|
|
14
|
+
name="mcp",
|
|
15
|
+
help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
|
|
16
|
+
add_completion=False,
|
|
17
|
+
invoke_without_command=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@mcp_app.callback()
|
|
22
|
+
def mcp_callback(
|
|
23
|
+
ctx: typer.Context,
|
|
24
|
+
project_path: str | None = typer.Option(
|
|
25
|
+
None, "--path", "-p", help="Project directory path (default: current directory)"
|
|
26
|
+
),
|
|
27
|
+
) -> None:
|
|
28
|
+
"""MCP command group - runs MCP server if no subcommand provided.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
--------
|
|
32
|
+
mcp-ticketer mcp # Start server in current directory
|
|
33
|
+
mcp-ticketer mcp --path /dir # Start server in specific directory
|
|
34
|
+
mcp-ticketer mcp -p /dir # Start server (short form)
|
|
35
|
+
mcp-ticketer mcp status # Check MCP status
|
|
36
|
+
mcp-ticketer mcp serve # Explicitly start server
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
if ctx.invoked_subcommand is None:
|
|
40
|
+
# No subcommand provided, run the serve command
|
|
41
|
+
# Change to project directory if provided
|
|
42
|
+
if project_path:
|
|
43
|
+
import os
|
|
44
|
+
|
|
45
|
+
os.chdir(project_path)
|
|
46
|
+
# Invoke the serve command through context
|
|
47
|
+
ctx.invoke(mcp_serve, adapter=None, base_path=None)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@mcp_app.command(name="serve")
|
|
51
|
+
def mcp_serve(
|
|
52
|
+
adapter: str | None = typer.Option(
|
|
53
|
+
None, "--adapter", "-a", help="Override default adapter type"
|
|
54
|
+
),
|
|
55
|
+
base_path: str | None = typer.Option(
|
|
56
|
+
None, "--base-path", help="Base path for AITrackdown adapter"
|
|
57
|
+
),
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Start MCP server for JSON-RPC communication over stdio.
|
|
60
|
+
|
|
61
|
+
This command is used by Claude Code/Desktop when connecting to the MCP server.
|
|
62
|
+
You typically don't need to run this manually - use 'mcp-ticketer install add' to configure.
|
|
63
|
+
|
|
64
|
+
Configuration Resolution:
|
|
65
|
+
- When MCP server starts, it uses the current working directory (cwd)
|
|
66
|
+
- The cwd is set by Claude Code/Desktop from the 'cwd' field in .mcp/config.json
|
|
67
|
+
- Configuration is loaded with this priority:
|
|
68
|
+
1. Project-specific: .mcp-ticketer/config.json in cwd
|
|
69
|
+
2. Global: ~/.mcp-ticketer/config.json
|
|
70
|
+
3. Default: aitrackdown adapter with .aitrackdown base path
|
|
71
|
+
"""
|
|
72
|
+
# Local imports to avoid circular dependency
|
|
73
|
+
from ..mcp.server.server_sdk import configure_adapter
|
|
74
|
+
from ..mcp.server.server_sdk import main as sdk_main
|
|
75
|
+
|
|
76
|
+
# Import load_config locally to avoid circular import
|
|
77
|
+
# (main.py imports this module, so we can't import from main at module level)
|
|
78
|
+
from .ticket_commands import load_config
|
|
79
|
+
|
|
80
|
+
# Load configuration (respects project-specific config in cwd)
|
|
81
|
+
config = load_config()
|
|
82
|
+
|
|
83
|
+
# Determine adapter type with priority: CLI arg > config > .env files > default
|
|
84
|
+
if adapter:
|
|
85
|
+
# Priority 1: Command line argument
|
|
86
|
+
adapter_type = adapter
|
|
87
|
+
# Get base config from config file
|
|
88
|
+
adapters_config = config.get("adapters", {})
|
|
89
|
+
adapter_config = adapters_config.get(adapter_type, {})
|
|
90
|
+
else:
|
|
91
|
+
# Priority 2: Configuration file (project-specific)
|
|
92
|
+
adapter_type = config.get("default_adapter")
|
|
93
|
+
if adapter_type:
|
|
94
|
+
adapters_config = config.get("adapters", {})
|
|
95
|
+
adapter_config = adapters_config.get(adapter_type, {})
|
|
96
|
+
else:
|
|
97
|
+
# Priority 3: .env files (auto-detection fallback)
|
|
98
|
+
from ..mcp.server.main import _load_env_configuration
|
|
99
|
+
|
|
100
|
+
env_config = _load_env_configuration()
|
|
101
|
+
if env_config:
|
|
102
|
+
adapter_type = env_config["adapter_type"]
|
|
103
|
+
adapter_config = env_config["adapter_config"]
|
|
104
|
+
else:
|
|
105
|
+
# Priority 4: Default fallback
|
|
106
|
+
adapter_type = "aitrackdown"
|
|
107
|
+
adapters_config = config.get("adapters", {})
|
|
108
|
+
adapter_config = adapters_config.get(adapter_type, {})
|
|
109
|
+
|
|
110
|
+
# Override with command line options if provided (highest priority)
|
|
111
|
+
if base_path and adapter_type == "aitrackdown":
|
|
112
|
+
adapter_config["base_path"] = base_path
|
|
113
|
+
|
|
114
|
+
# Fallback to legacy config format
|
|
115
|
+
if not adapter_config and "config" in config:
|
|
116
|
+
adapter_config = config["config"]
|
|
117
|
+
|
|
118
|
+
# MCP server uses stdio for JSON-RPC, so we can't print to stdout
|
|
119
|
+
# Only print to stderr to avoid interfering with the protocol
|
|
120
|
+
if sys.stderr.isatty():
|
|
121
|
+
# Only print if stderr is a terminal (not redirected)
|
|
122
|
+
console.file = sys.stderr
|
|
123
|
+
console.print(
|
|
124
|
+
f"[green]Starting MCP SDK server[/green] with {adapter_type} adapter"
|
|
125
|
+
)
|
|
126
|
+
console.print(
|
|
127
|
+
"[dim]Server running on stdio. Send JSON-RPC requests via stdin.[/dim]"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Configure adapter and run SDK server
|
|
131
|
+
try:
|
|
132
|
+
configure_adapter(adapter_type, adapter_config)
|
|
133
|
+
sdk_main()
|
|
134
|
+
except KeyboardInterrupt:
|
|
135
|
+
# Send this to stderr
|
|
136
|
+
if sys.stderr.isatty():
|
|
137
|
+
console.print("\n[yellow]Server stopped by user[/yellow]")
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
# Log error to stderr
|
|
141
|
+
sys.stderr.write(f"MCP server error: {e}\n")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@mcp_app.command(name="claude")
|
|
146
|
+
def mcp_claude(
|
|
147
|
+
global_config: bool = typer.Option(
|
|
148
|
+
False,
|
|
149
|
+
"--global",
|
|
150
|
+
"-g",
|
|
151
|
+
help="Configure Claude Desktop instead of project-level",
|
|
152
|
+
),
|
|
153
|
+
force: bool = typer.Option(
|
|
154
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
155
|
+
),
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Configure Claude Code to use mcp-ticketer MCP server.
|
|
158
|
+
|
|
159
|
+
Reads configuration from .mcp-ticketer/config.json and updates
|
|
160
|
+
Claude Code's MCP settings accordingly.
|
|
161
|
+
|
|
162
|
+
By default, configures project-level (.mcp/config.json).
|
|
163
|
+
Use --global to configure Claude Desktop instead.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
--------
|
|
167
|
+
# Configure for current project (default)
|
|
168
|
+
mcp-ticketer mcp claude
|
|
169
|
+
|
|
170
|
+
# Configure Claude Desktop globally
|
|
171
|
+
mcp-ticketer mcp claude --global
|
|
172
|
+
|
|
173
|
+
# Force overwrite existing configuration
|
|
174
|
+
mcp-ticketer mcp claude --force
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
from ..cli.mcp_configure import configure_claude_mcp
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
configure_claude_mcp(global_config=global_config, force=force)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
183
|
+
raise typer.Exit(1) from e
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@mcp_app.command(name="gemini")
|
|
187
|
+
def mcp_gemini(
|
|
188
|
+
scope: str = typer.Option(
|
|
189
|
+
"project",
|
|
190
|
+
"--scope",
|
|
191
|
+
"-s",
|
|
192
|
+
help="Configuration scope: 'project' (default) or 'user'",
|
|
193
|
+
),
|
|
194
|
+
force: bool = typer.Option(
|
|
195
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
196
|
+
),
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Configure Gemini CLI to use mcp-ticketer MCP server.
|
|
199
|
+
|
|
200
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
201
|
+
Gemini CLI settings file with mcp-ticketer configuration.
|
|
202
|
+
|
|
203
|
+
By default, configures project-level (.gemini/settings.json).
|
|
204
|
+
Use --scope user to configure user-level (~/.gemini/settings.json).
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
--------
|
|
208
|
+
# Configure for current project (default)
|
|
209
|
+
mcp-ticketer mcp gemini
|
|
210
|
+
|
|
211
|
+
# Configure at user level
|
|
212
|
+
mcp-ticketer mcp gemini --scope user
|
|
213
|
+
|
|
214
|
+
# Force overwrite existing configuration
|
|
215
|
+
mcp-ticketer mcp gemini --force
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
from ..cli.gemini_configure import configure_gemini_mcp
|
|
219
|
+
|
|
220
|
+
# Validate scope parameter
|
|
221
|
+
if scope not in ["project", "user"]:
|
|
222
|
+
console.print(
|
|
223
|
+
f"[red]✗ Invalid scope:[/red] '{scope}'. Must be 'project' or 'user'"
|
|
224
|
+
)
|
|
225
|
+
raise typer.Exit(1) from None
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
configure_gemini_mcp(scope=scope, force=force) # type: ignore
|
|
229
|
+
except Exception as e:
|
|
230
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
231
|
+
raise typer.Exit(1) from e
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@mcp_app.command(name="codex")
|
|
235
|
+
def mcp_codex(
|
|
236
|
+
force: bool = typer.Option(
|
|
237
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
238
|
+
),
|
|
239
|
+
) -> None:
|
|
240
|
+
"""Configure Codex CLI to use mcp-ticketer MCP server.
|
|
241
|
+
|
|
242
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
243
|
+
Codex CLI config.toml with mcp-ticketer configuration.
|
|
244
|
+
|
|
245
|
+
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
246
|
+
There is no project-level configuration support. After configuration,
|
|
247
|
+
you must restart Codex CLI for changes to take effect.
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
--------
|
|
251
|
+
# Configure Codex CLI globally
|
|
252
|
+
mcp-ticketer mcp codex
|
|
253
|
+
|
|
254
|
+
# Force overwrite existing configuration
|
|
255
|
+
mcp-ticketer mcp codex --force
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
from ..cli.codex_configure import configure_codex_mcp
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
configure_codex_mcp(force=force)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
264
|
+
raise typer.Exit(1) from e
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@mcp_app.command(name="auggie")
|
|
268
|
+
def mcp_auggie(
|
|
269
|
+
force: bool = typer.Option(
|
|
270
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
271
|
+
),
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Configure Auggie CLI to use mcp-ticketer MCP server.
|
|
274
|
+
|
|
275
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
276
|
+
Auggie CLI settings.json with mcp-ticketer configuration.
|
|
277
|
+
|
|
278
|
+
IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
|
|
279
|
+
There is no project-level configuration support. After configuration,
|
|
280
|
+
you must restart Auggie CLI for changes to take effect.
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
--------
|
|
284
|
+
# Configure Auggie CLI globally
|
|
285
|
+
mcp-ticketer mcp auggie
|
|
286
|
+
|
|
287
|
+
# Force overwrite existing configuration
|
|
288
|
+
mcp-ticketer mcp auggie --force
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
from ..cli.auggie_configure import configure_auggie_mcp
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
configure_auggie_mcp(force=force)
|
|
295
|
+
except Exception as e:
|
|
296
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
297
|
+
raise typer.Exit(1) from e
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@mcp_app.command(name="status")
|
|
301
|
+
def mcp_status() -> None:
|
|
302
|
+
"""Check MCP server status.
|
|
303
|
+
|
|
304
|
+
Shows whether the MCP server is configured and running for various platforms.
|
|
305
|
+
|
|
306
|
+
Examples:
|
|
307
|
+
--------
|
|
308
|
+
mcp-ticketer mcp status
|
|
309
|
+
|
|
310
|
+
"""
|
|
311
|
+
console.print("[bold]MCP Server Status[/bold]\n")
|
|
312
|
+
|
|
313
|
+
# Check project-level configuration
|
|
314
|
+
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
315
|
+
if project_config.exists():
|
|
316
|
+
console.print(f"[green]✓[/green] Project config found: {project_config}")
|
|
317
|
+
try:
|
|
318
|
+
with open(project_config) as f:
|
|
319
|
+
config = json.load(f)
|
|
320
|
+
adapter = config.get("default_adapter", "aitrackdown")
|
|
321
|
+
console.print(f" Default adapter: [cyan]{adapter}[/cyan]")
|
|
322
|
+
except Exception as e:
|
|
323
|
+
console.print(f" [yellow]Warning: Could not read config: {e}[/yellow]")
|
|
324
|
+
else:
|
|
325
|
+
console.print("[yellow]○[/yellow] No project config found")
|
|
326
|
+
|
|
327
|
+
# Check Claude Code configuration
|
|
328
|
+
claude_code_config = Path.cwd() / ".mcp" / "config.json"
|
|
329
|
+
if claude_code_config.exists():
|
|
330
|
+
console.print(
|
|
331
|
+
f"\n[green]✓[/green] Claude Code configured: {claude_code_config}"
|
|
332
|
+
)
|
|
333
|
+
else:
|
|
334
|
+
console.print("\n[yellow]○[/yellow] Claude Code not configured")
|
|
335
|
+
|
|
336
|
+
# Check Claude Desktop configuration
|
|
337
|
+
claude_desktop_config = (
|
|
338
|
+
Path.home()
|
|
339
|
+
/ "Library"
|
|
340
|
+
/ "Application Support"
|
|
341
|
+
/ "Claude"
|
|
342
|
+
/ "claude_desktop_config.json"
|
|
343
|
+
)
|
|
344
|
+
if claude_desktop_config.exists():
|
|
345
|
+
try:
|
|
346
|
+
with open(claude_desktop_config) as f:
|
|
347
|
+
config = json.load(f)
|
|
348
|
+
if "mcpServers" in config and "mcp-ticketer" in config["mcpServers"]:
|
|
349
|
+
console.print(
|
|
350
|
+
f"[green]✓[/green] Claude Desktop configured: {claude_desktop_config}"
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
console.print(
|
|
354
|
+
"[yellow]○[/yellow] Claude Desktop config exists but mcp-ticketer not found"
|
|
355
|
+
)
|
|
356
|
+
except Exception:
|
|
357
|
+
console.print(
|
|
358
|
+
"[yellow]○[/yellow] Claude Desktop config exists but could not be read"
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
console.print("[yellow]○[/yellow] Claude Desktop not configured")
|
|
362
|
+
|
|
363
|
+
# Check Gemini configuration
|
|
364
|
+
gemini_project_config = Path.cwd() / ".gemini" / "settings.json"
|
|
365
|
+
gemini_user_config = Path.home() / ".gemini" / "settings.json"
|
|
366
|
+
if gemini_project_config.exists():
|
|
367
|
+
console.print(
|
|
368
|
+
f"\n[green]✓[/green] Gemini (project) configured: {gemini_project_config}"
|
|
369
|
+
)
|
|
370
|
+
elif gemini_user_config.exists():
|
|
371
|
+
console.print(
|
|
372
|
+
f"\n[green]✓[/green] Gemini (user) configured: {gemini_user_config}"
|
|
373
|
+
)
|
|
374
|
+
else:
|
|
375
|
+
console.print("\n[yellow]○[/yellow] Gemini not configured")
|
|
376
|
+
|
|
377
|
+
# Check Codex configuration
|
|
378
|
+
codex_config = Path.home() / ".codex" / "config.toml"
|
|
379
|
+
if codex_config.exists():
|
|
380
|
+
console.print(f"[green]✓[/green] Codex configured: {codex_config}")
|
|
381
|
+
else:
|
|
382
|
+
console.print("[yellow]○[/yellow] Codex not configured")
|
|
383
|
+
|
|
384
|
+
# Check Auggie configuration
|
|
385
|
+
auggie_config = Path.home() / ".augment" / "settings.json"
|
|
386
|
+
if auggie_config.exists():
|
|
387
|
+
console.print(f"[green]✓[/green] Auggie configured: {auggie_config}")
|
|
388
|
+
else:
|
|
389
|
+
console.print("[yellow]○[/yellow] Auggie not configured")
|
|
390
|
+
|
|
391
|
+
console.print(
|
|
392
|
+
"\n[dim]Run 'mcp-ticketer install <platform>' to configure a platform[/dim]"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@mcp_app.command(name="stop")
|
|
397
|
+
def mcp_stop() -> None:
|
|
398
|
+
"""Stop MCP server (placeholder - MCP runs on-demand via stdio).
|
|
399
|
+
|
|
400
|
+
Note: The MCP server runs on-demand when AI clients connect via stdio.
|
|
401
|
+
It doesn't run as a persistent background service, so there's nothing to stop.
|
|
402
|
+
This command is provided for consistency but has no effect.
|
|
403
|
+
|
|
404
|
+
Examples:
|
|
405
|
+
--------
|
|
406
|
+
mcp-ticketer mcp stop
|
|
407
|
+
|
|
408
|
+
"""
|
|
409
|
+
console.print(
|
|
410
|
+
"[yellow]ℹ[/yellow] MCP server runs on-demand via stdio (not as a background service)"
|
|
411
|
+
)
|
|
412
|
+
console.print("There is no persistent server process to stop.")
|
|
413
|
+
console.print(
|
|
414
|
+
"\n[dim]The server starts automatically when AI clients connect and stops when they disconnect.[/dim]"
|
|
415
|
+
)
|
|
@@ -49,14 +49,20 @@ class PlatformDetector:
|
|
|
49
49
|
def detect_claude_code() -> DetectedPlatform | None:
|
|
50
50
|
"""Detect Claude Code installation.
|
|
51
51
|
|
|
52
|
-
Claude Code uses project-level configuration stored in
|
|
53
|
-
|
|
52
|
+
Claude Code uses project-level configuration stored in either:
|
|
53
|
+
- ~/.config/claude/mcp.json (new global location with flat structure)
|
|
54
|
+
- ~/.claude.json (legacy location with projects structure)
|
|
54
55
|
|
|
55
56
|
Returns:
|
|
56
57
|
DetectedPlatform if Claude Code config exists, None otherwise
|
|
57
58
|
|
|
58
59
|
"""
|
|
59
|
-
|
|
60
|
+
# Check new global location first
|
|
61
|
+
new_config_path = Path.home() / ".config" / "claude" / "mcp.json"
|
|
62
|
+
old_config_path = Path.home() / ".claude.json"
|
|
63
|
+
|
|
64
|
+
# Priority: Use new location if it exists
|
|
65
|
+
config_path = new_config_path if new_config_path.exists() else old_config_path
|
|
60
66
|
|
|
61
67
|
# Check if config file exists
|
|
62
68
|
if not config_path.exists():
|
|
@@ -194,6 +200,51 @@ class PlatformDetector:
|
|
|
194
200
|
executable_path=executable_path,
|
|
195
201
|
)
|
|
196
202
|
|
|
203
|
+
@staticmethod
|
|
204
|
+
def detect_cursor() -> DetectedPlatform | None:
|
|
205
|
+
"""Detect Cursor code editor installation.
|
|
206
|
+
|
|
207
|
+
Cursor uses project-level MCP configuration stored in:
|
|
208
|
+
- ~/.cursor/mcp.json (global location with flat structure)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
DetectedPlatform if Cursor config exists, None otherwise
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
# Check global configuration location
|
|
215
|
+
config_path = Path.home() / ".cursor" / "mcp.json"
|
|
216
|
+
|
|
217
|
+
# Check if config file exists
|
|
218
|
+
if not config_path.exists():
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
# Validate it's valid JSON
|
|
222
|
+
try:
|
|
223
|
+
with config_path.open() as f:
|
|
224
|
+
content = f.read().strip()
|
|
225
|
+
if content: # Only validate if not empty
|
|
226
|
+
json.loads(content)
|
|
227
|
+
|
|
228
|
+
return DetectedPlatform(
|
|
229
|
+
name="cursor",
|
|
230
|
+
display_name="Cursor",
|
|
231
|
+
config_path=config_path,
|
|
232
|
+
is_installed=True,
|
|
233
|
+
scope="project",
|
|
234
|
+
executable_path=None, # Cursor doesn't have a CLI
|
|
235
|
+
)
|
|
236
|
+
except (json.JSONDecodeError, OSError):
|
|
237
|
+
# Config exists but is corrupted - still consider it "detected"
|
|
238
|
+
# but mark as not installed/usable
|
|
239
|
+
return DetectedPlatform(
|
|
240
|
+
name="cursor",
|
|
241
|
+
display_name="Cursor",
|
|
242
|
+
config_path=config_path,
|
|
243
|
+
is_installed=False,
|
|
244
|
+
scope="project",
|
|
245
|
+
executable_path=None,
|
|
246
|
+
)
|
|
247
|
+
|
|
197
248
|
@staticmethod
|
|
198
249
|
def detect_codex() -> DetectedPlatform | None:
|
|
199
250
|
"""Detect Codex installation.
|
|
@@ -304,11 +355,14 @@ class PlatformDetector:
|
|
|
304
355
|
)
|
|
305
356
|
|
|
306
357
|
@classmethod
|
|
307
|
-
def detect_all(
|
|
358
|
+
def detect_all(
|
|
359
|
+
cls, project_path: Path | None = None, exclude_desktop: bool = False
|
|
360
|
+
) -> list[DetectedPlatform]:
|
|
308
361
|
"""Detect all installed AI client platforms.
|
|
309
362
|
|
|
310
363
|
Args:
|
|
311
364
|
project_path: Optional project directory for project-level detection
|
|
365
|
+
exclude_desktop: If True, exclude desktop AI assistants (Claude Desktop)
|
|
312
366
|
|
|
313
367
|
Returns:
|
|
314
368
|
List of detected platforms (empty if none found)
|
|
@@ -326,30 +380,40 @@ class PlatformDetector:
|
|
|
326
380
|
>>> gemini = next(p for p in platforms if p.name == "gemini")
|
|
327
381
|
>>> print(gemini.scope) # "project" or "global" or "both"
|
|
328
382
|
|
|
383
|
+
>>> # Exclude desktop AI assistants (code editors only)
|
|
384
|
+
>>> platforms = detector.detect_all(exclude_desktop=True)
|
|
385
|
+
>>> # Returns: Claude Code, Cursor, Auggie, Codex, Gemini (NOT Claude Desktop)
|
|
386
|
+
|
|
329
387
|
"""
|
|
330
388
|
detected = []
|
|
331
389
|
|
|
332
|
-
# Detect Claude Code
|
|
390
|
+
# Detect Claude Code (project-level code editor)
|
|
333
391
|
claude_code = cls.detect_claude_code()
|
|
334
392
|
if claude_code:
|
|
335
393
|
detected.append(claude_code)
|
|
336
394
|
|
|
337
|
-
# Detect Claude Desktop
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
395
|
+
# Detect Claude Desktop (desktop AI assistant - optional)
|
|
396
|
+
if not exclude_desktop:
|
|
397
|
+
claude_desktop = cls.detect_claude_desktop()
|
|
398
|
+
if claude_desktop:
|
|
399
|
+
detected.append(claude_desktop)
|
|
400
|
+
|
|
401
|
+
# Detect Cursor (code editor)
|
|
402
|
+
cursor = cls.detect_cursor()
|
|
403
|
+
if cursor:
|
|
404
|
+
detected.append(cursor)
|
|
341
405
|
|
|
342
|
-
# Detect Auggie
|
|
406
|
+
# Detect Auggie (code assistant)
|
|
343
407
|
auggie = cls.detect_auggie()
|
|
344
408
|
if auggie:
|
|
345
409
|
detected.append(auggie)
|
|
346
410
|
|
|
347
|
-
# Detect Codex
|
|
411
|
+
# Detect Codex (code assistant)
|
|
348
412
|
codex = cls.detect_codex()
|
|
349
413
|
if codex:
|
|
350
414
|
detected.append(codex)
|
|
351
415
|
|
|
352
|
-
# Detect Gemini (with project path support)
|
|
416
|
+
# Detect Gemini (code assistant with project path support)
|
|
353
417
|
gemini = cls.detect_gemini(project_path=project_path)
|
|
354
418
|
if gemini:
|
|
355
419
|
detected.append(gemini)
|
|
@@ -381,6 +445,7 @@ def get_platform_by_name(
|
|
|
381
445
|
detection_map = {
|
|
382
446
|
"claude-code": detector.detect_claude_code,
|
|
383
447
|
"claude-desktop": detector.detect_claude_desktop,
|
|
448
|
+
"cursor": detector.detect_cursor,
|
|
384
449
|
"auggie": detector.detect_auggie,
|
|
385
450
|
"codex": detector.detect_codex,
|
|
386
451
|
"gemini": lambda: detector.detect_gemini(project_path),
|