mcp-ticketer 0.4.3__py3-none-any.whl → 0.4.5__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 +12 -3
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +16 -5
- mcp_ticketer/adapters/github.py +1 -2
- mcp_ticketer/adapters/jira.py +1 -2
- mcp_ticketer/adapters/linear/adapter.py +21 -9
- mcp_ticketer/adapters/linear/client.py +1 -2
- mcp_ticketer/adapters/linear/mappers.py +1 -2
- mcp_ticketer/cli/adapter_diagnostics.py +2 -4
- mcp_ticketer/cli/auggie_configure.py +35 -15
- mcp_ticketer/cli/codex_configure.py +38 -31
- mcp_ticketer/cli/configure.py +9 -3
- mcp_ticketer/cli/discover.py +6 -2
- mcp_ticketer/cli/gemini_configure.py +38 -25
- mcp_ticketer/cli/main.py +147 -32
- mcp_ticketer/cli/mcp_configure.py +115 -78
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/core/__init__.py +1 -2
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server.py +34 -14
- mcp_ticketer/mcp/tools/__init__.py +9 -7
- mcp_ticketer/queue/__init__.py +3 -1
- mcp_ticketer/queue/manager.py +10 -46
- mcp_ticketer/queue/ticket_registry.py +5 -5
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/METADATA +13 -4
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/RECORD +31 -29
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py
CHANGED
|
@@ -20,8 +20,7 @@ from ..core.models import Comment, SearchQuery
|
|
|
20
20
|
from ..queue import Queue, QueueStatus, WorkerManager
|
|
21
21
|
from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
|
|
22
22
|
from ..queue.ticket_registry import TicketRegistry
|
|
23
|
-
from .configure import
|
|
24
|
-
show_current_config)
|
|
23
|
+
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
25
24
|
from .diagnostics import run_diagnostics
|
|
26
25
|
from .discover import app as discover_app
|
|
27
26
|
from .migrate_config import migrate_config_command
|
|
@@ -679,7 +678,9 @@ def init(
|
|
|
679
678
|
if not linear_team_key and not linear_team_id and not discovered:
|
|
680
679
|
console.print("\n[bold]Linear Team Configuration[/bold]")
|
|
681
680
|
console.print("Enter your team key (e.g., 'ENG', 'DESIGN', 'PRODUCT')")
|
|
682
|
-
console.print(
|
|
681
|
+
console.print(
|
|
682
|
+
"[dim]Find it in: Linear Settings → Teams → Your Team → Key field[/dim]\n"
|
|
683
|
+
)
|
|
683
684
|
|
|
684
685
|
linear_team_key = typer.prompt("Team key")
|
|
685
686
|
|
|
@@ -893,10 +894,11 @@ def _show_next_steps(
|
|
|
893
894
|
console.print("\n3. [cyan]Check local ticket storage:[/cyan]")
|
|
894
895
|
console.print(" ls .aitrackdown/")
|
|
895
896
|
|
|
896
|
-
console.print("\n4. [cyan]
|
|
897
|
-
console.print(" mcp-ticketer
|
|
898
|
-
console.print(" mcp-ticketer
|
|
899
|
-
console.print(" mcp-ticketer
|
|
897
|
+
console.print("\n4. [cyan]Install MCP for AI clients (optional):[/cyan]")
|
|
898
|
+
console.print(" mcp-ticketer install claude-code # For Claude Code")
|
|
899
|
+
console.print(" mcp-ticketer install claude-desktop # For Claude Desktop")
|
|
900
|
+
console.print(" mcp-ticketer install auggie # For Auggie")
|
|
901
|
+
console.print(" mcp-ticketer install gemini # For Gemini CLI")
|
|
900
902
|
|
|
901
903
|
console.print(f"\n[dim]Configuration saved to: {config_file_path}[/dim]")
|
|
902
904
|
console.print("[dim]Run 'mcp-ticketer --help' for more commands[/dim]")
|
|
@@ -1871,6 +1873,10 @@ mcp_app = typer.Typer(
|
|
|
1871
1873
|
|
|
1872
1874
|
@app.command()
|
|
1873
1875
|
def install(
|
|
1876
|
+
platform: str | None = typer.Argument(
|
|
1877
|
+
None,
|
|
1878
|
+
help="Platform to install (claude-code, claude-desktop, gemini, codex, auggie)",
|
|
1879
|
+
),
|
|
1874
1880
|
adapter: str | None = typer.Option(
|
|
1875
1881
|
None,
|
|
1876
1882
|
"--adapter",
|
|
@@ -1918,41 +1924,31 @@ def install(
|
|
|
1918
1924
|
github_token: str | None = typer.Option(
|
|
1919
1925
|
None, "--github-token", help="GitHub Personal Access Token"
|
|
1920
1926
|
),
|
|
1921
|
-
platform: str | None = typer.Option(
|
|
1922
|
-
None,
|
|
1923
|
-
"--platform",
|
|
1924
|
-
help="Platform to configure MCP for (claude-code, claude-desktop, auggie, gemini, codex)",
|
|
1925
|
-
),
|
|
1926
1927
|
dry_run: bool = typer.Option(
|
|
1927
1928
|
False,
|
|
1928
1929
|
"--dry-run",
|
|
1929
1930
|
help="Show what would be done without making changes (for platform installation)",
|
|
1930
1931
|
),
|
|
1931
1932
|
) -> None:
|
|
1932
|
-
"""Install
|
|
1933
|
+
"""Install MCP for AI platforms OR initialize adapter setup.
|
|
1933
1934
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1935
|
+
With platform argument (new syntax): Install MCP configuration for AI platforms
|
|
1936
|
+
Without platform argument (legacy): Run adapter setup wizard (same as 'init' and 'setup')
|
|
1936
1937
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
mcp-ticketer install
|
|
1938
|
+
New Command Structure:
|
|
1939
|
+
# Install MCP for AI platforms
|
|
1940
|
+
mcp-ticketer install claude-code # Claude Code (project-level)
|
|
1941
|
+
mcp-ticketer install claude-desktop # Claude Desktop (global)
|
|
1942
|
+
mcp-ticketer install gemini # Gemini CLI
|
|
1943
|
+
mcp-ticketer install codex # Codex
|
|
1944
|
+
mcp-ticketer install auggie # Auggie
|
|
1944
1945
|
|
|
1945
|
-
|
|
1946
|
+
Legacy Adapter Setup (still supported):
|
|
1947
|
+
mcp-ticketer install # Interactive setup wizard
|
|
1946
1948
|
mcp-ticketer install --adapter linear
|
|
1947
1949
|
|
|
1948
|
-
# Install MCP for Claude Code
|
|
1949
|
-
mcp-ticketer install --platform claude-code
|
|
1950
|
-
|
|
1951
|
-
# Install MCP for Claude Desktop
|
|
1952
|
-
mcp-ticketer install --platform claude-desktop
|
|
1953
|
-
|
|
1954
1950
|
"""
|
|
1955
|
-
# If platform
|
|
1951
|
+
# If platform argument is provided, handle MCP platform installation (NEW SYNTAX)
|
|
1956
1952
|
if platform is not None:
|
|
1957
1953
|
# Import configuration functions
|
|
1958
1954
|
from .auggie_configure import configure_auggie_mcp
|
|
@@ -2004,8 +2000,8 @@ def install(
|
|
|
2004
2000
|
raise typer.Exit(1)
|
|
2005
2001
|
return
|
|
2006
2002
|
|
|
2007
|
-
# Otherwise, delegate to init for adapter initialization
|
|
2008
|
-
# This makes 'install' and 'init' synonymous when called without
|
|
2003
|
+
# Otherwise, delegate to init for adapter initialization (LEGACY BEHAVIOR)
|
|
2004
|
+
# This makes 'install' and 'init' synonymous when called without platform argument
|
|
2009
2005
|
init(
|
|
2010
2006
|
adapter=adapter,
|
|
2011
2007
|
project_path=project_path,
|
|
@@ -2429,6 +2425,125 @@ def mcp_auggie(
|
|
|
2429
2425
|
raise typer.Exit(1)
|
|
2430
2426
|
|
|
2431
2427
|
|
|
2428
|
+
@mcp_app.command(name="status")
|
|
2429
|
+
def mcp_status():
|
|
2430
|
+
"""Check MCP server status.
|
|
2431
|
+
|
|
2432
|
+
Shows whether the MCP server is configured and running for various platforms.
|
|
2433
|
+
|
|
2434
|
+
Examples:
|
|
2435
|
+
mcp-ticketer mcp status
|
|
2436
|
+
|
|
2437
|
+
"""
|
|
2438
|
+
import json
|
|
2439
|
+
from pathlib import Path
|
|
2440
|
+
|
|
2441
|
+
console.print("[bold]MCP Server Status[/bold]\n")
|
|
2442
|
+
|
|
2443
|
+
# Check project-level configuration
|
|
2444
|
+
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
2445
|
+
if project_config.exists():
|
|
2446
|
+
console.print(f"[green]✓[/green] Project config found: {project_config}")
|
|
2447
|
+
try:
|
|
2448
|
+
with open(project_config) as f:
|
|
2449
|
+
config = json.load(f)
|
|
2450
|
+
adapter = config.get("default_adapter", "aitrackdown")
|
|
2451
|
+
console.print(f" Default adapter: [cyan]{adapter}[/cyan]")
|
|
2452
|
+
except Exception as e:
|
|
2453
|
+
console.print(f" [yellow]Warning: Could not read config: {e}[/yellow]")
|
|
2454
|
+
else:
|
|
2455
|
+
console.print("[yellow]○[/yellow] No project config found")
|
|
2456
|
+
|
|
2457
|
+
# Check Claude Code configuration
|
|
2458
|
+
claude_code_config = Path.cwd() / ".mcp" / "config.json"
|
|
2459
|
+
if claude_code_config.exists():
|
|
2460
|
+
console.print(
|
|
2461
|
+
f"\n[green]✓[/green] Claude Code configured: {claude_code_config}"
|
|
2462
|
+
)
|
|
2463
|
+
else:
|
|
2464
|
+
console.print("\n[yellow]○[/yellow] Claude Code not configured")
|
|
2465
|
+
|
|
2466
|
+
# Check Claude Desktop configuration
|
|
2467
|
+
claude_desktop_config = (
|
|
2468
|
+
Path.home()
|
|
2469
|
+
/ "Library"
|
|
2470
|
+
/ "Application Support"
|
|
2471
|
+
/ "Claude"
|
|
2472
|
+
/ "claude_desktop_config.json"
|
|
2473
|
+
)
|
|
2474
|
+
if claude_desktop_config.exists():
|
|
2475
|
+
try:
|
|
2476
|
+
with open(claude_desktop_config) as f:
|
|
2477
|
+
config = json.load(f)
|
|
2478
|
+
if "mcpServers" in config and "mcp-ticketer" in config["mcpServers"]:
|
|
2479
|
+
console.print(
|
|
2480
|
+
f"[green]✓[/green] Claude Desktop configured: {claude_desktop_config}"
|
|
2481
|
+
)
|
|
2482
|
+
else:
|
|
2483
|
+
console.print(
|
|
2484
|
+
"[yellow]○[/yellow] Claude Desktop config exists but mcp-ticketer not found"
|
|
2485
|
+
)
|
|
2486
|
+
except Exception:
|
|
2487
|
+
console.print(
|
|
2488
|
+
"[yellow]○[/yellow] Claude Desktop config exists but could not be read"
|
|
2489
|
+
)
|
|
2490
|
+
else:
|
|
2491
|
+
console.print("[yellow]○[/yellow] Claude Desktop not configured")
|
|
2492
|
+
|
|
2493
|
+
# Check Gemini configuration
|
|
2494
|
+
gemini_project_config = Path.cwd() / ".gemini" / "settings.json"
|
|
2495
|
+
gemini_user_config = Path.home() / ".gemini" / "settings.json"
|
|
2496
|
+
if gemini_project_config.exists():
|
|
2497
|
+
console.print(
|
|
2498
|
+
f"\n[green]✓[/green] Gemini (project) configured: {gemini_project_config}"
|
|
2499
|
+
)
|
|
2500
|
+
elif gemini_user_config.exists():
|
|
2501
|
+
console.print(
|
|
2502
|
+
f"\n[green]✓[/green] Gemini (user) configured: {gemini_user_config}"
|
|
2503
|
+
)
|
|
2504
|
+
else:
|
|
2505
|
+
console.print("\n[yellow]○[/yellow] Gemini not configured")
|
|
2506
|
+
|
|
2507
|
+
# Check Codex configuration
|
|
2508
|
+
codex_config = Path.home() / ".codex" / "config.toml"
|
|
2509
|
+
if codex_config.exists():
|
|
2510
|
+
console.print(f"[green]✓[/green] Codex configured: {codex_config}")
|
|
2511
|
+
else:
|
|
2512
|
+
console.print("[yellow]○[/yellow] Codex not configured")
|
|
2513
|
+
|
|
2514
|
+
# Check Auggie configuration
|
|
2515
|
+
auggie_config = Path.home() / ".augment" / "settings.json"
|
|
2516
|
+
if auggie_config.exists():
|
|
2517
|
+
console.print(f"[green]✓[/green] Auggie configured: {auggie_config}")
|
|
2518
|
+
else:
|
|
2519
|
+
console.print("[yellow]○[/yellow] Auggie not configured")
|
|
2520
|
+
|
|
2521
|
+
console.print(
|
|
2522
|
+
"\n[dim]Run 'mcp-ticketer install <platform>' to configure a platform[/dim]"
|
|
2523
|
+
)
|
|
2524
|
+
|
|
2525
|
+
|
|
2526
|
+
@mcp_app.command(name="stop")
|
|
2527
|
+
def mcp_stop():
|
|
2528
|
+
"""Stop MCP server (placeholder - MCP runs on-demand via stdio).
|
|
2529
|
+
|
|
2530
|
+
Note: The MCP server runs on-demand when AI clients connect via stdio.
|
|
2531
|
+
It doesn't run as a persistent background service, so there's nothing to stop.
|
|
2532
|
+
This command is provided for consistency but has no effect.
|
|
2533
|
+
|
|
2534
|
+
Examples:
|
|
2535
|
+
mcp-ticketer mcp stop
|
|
2536
|
+
|
|
2537
|
+
"""
|
|
2538
|
+
console.print(
|
|
2539
|
+
"[yellow]ℹ[/yellow] MCP server runs on-demand via stdio (not as a background service)"
|
|
2540
|
+
)
|
|
2541
|
+
console.print("There is no persistent server process to stop.")
|
|
2542
|
+
console.print(
|
|
2543
|
+
"\n[dim]The server starts automatically when AI clients connect and stops when they disconnect.[/dim]"
|
|
2544
|
+
)
|
|
2545
|
+
|
|
2546
|
+
|
|
2432
2547
|
# Add command groups to main app (must be after all subcommands are defined)
|
|
2433
2548
|
app.add_typer(mcp_app, name="mcp")
|
|
2434
2549
|
|
|
@@ -2,65 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
import shutil
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
10
|
+
from .python_detection import get_mcp_ticketer_python
|
|
11
|
+
|
|
11
12
|
console = Console()
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
-
"""
|
|
15
|
+
def load_env_file(env_path: Path) -> dict[str, str]:
|
|
16
|
+
"""Load environment variables from .env file.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
Path to
|
|
18
|
+
Args:
|
|
19
|
+
env_path: Path to .env file
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
Returns:
|
|
22
|
+
Dict of environment variable key-value pairs
|
|
22
23
|
|
|
23
24
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
package_path / ".venv" / "bin" / "mcp-ticketer",
|
|
42
|
-
package_path / "test_venv" / "bin" / "mcp-ticketer",
|
|
43
|
-
# System installation
|
|
44
|
-
Path.home() / ".local" / "bin" / "mcp-ticketer",
|
|
45
|
-
# pipx installation
|
|
46
|
-
Path.home()
|
|
47
|
-
/ ".local"
|
|
48
|
-
/ "pipx"
|
|
49
|
-
/ "venvs"
|
|
50
|
-
/ "mcp-ticketer"
|
|
51
|
-
/ "bin"
|
|
52
|
-
/ "mcp-ticketer",
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
# Check possible paths
|
|
56
|
-
for path in possible_paths:
|
|
57
|
-
if path.exists():
|
|
58
|
-
return str(path.resolve())
|
|
59
|
-
|
|
60
|
-
raise FileNotFoundError(
|
|
61
|
-
"Could not find mcp-ticketer binary. Please ensure mcp-ticketer is installed.\n"
|
|
62
|
-
"Install with: pip install mcp-ticketer"
|
|
63
|
-
)
|
|
25
|
+
env_vars = {}
|
|
26
|
+
if not env_path.exists():
|
|
27
|
+
return env_vars
|
|
28
|
+
|
|
29
|
+
with open(env_path) as f:
|
|
30
|
+
for line in f:
|
|
31
|
+
line = line.strip()
|
|
32
|
+
# Skip comments and empty lines
|
|
33
|
+
if not line or line.startswith("#"):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# Parse KEY=VALUE format
|
|
37
|
+
if "=" in line:
|
|
38
|
+
key, value = line.split("=", 1)
|
|
39
|
+
env_vars[key.strip()] = value.strip()
|
|
40
|
+
|
|
41
|
+
return env_vars
|
|
64
42
|
|
|
65
43
|
|
|
66
44
|
def load_project_config() -> dict:
|
|
@@ -129,8 +107,8 @@ def find_claude_mcp_config(global_config: bool = False) -> Path:
|
|
|
129
107
|
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
130
108
|
)
|
|
131
109
|
else:
|
|
132
|
-
# Project-level configuration
|
|
133
|
-
config_path = Path.cwd() / ".
|
|
110
|
+
# Project-level configuration for Claude Code
|
|
111
|
+
config_path = Path.cwd() / ".claude" / "settings.local.json"
|
|
134
112
|
|
|
135
113
|
return config_path
|
|
136
114
|
|
|
@@ -149,7 +127,7 @@ def load_claude_mcp_config(config_path: Path) -> dict:
|
|
|
149
127
|
with open(config_path) as f:
|
|
150
128
|
return json.load(f)
|
|
151
129
|
|
|
152
|
-
# Return empty structure
|
|
130
|
+
# Return empty structure (Claude Code uses mcpServers key)
|
|
153
131
|
return {"mcpServers": {}}
|
|
154
132
|
|
|
155
133
|
|
|
@@ -170,28 +148,37 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
|
|
|
170
148
|
|
|
171
149
|
|
|
172
150
|
def create_mcp_server_config(
|
|
173
|
-
|
|
151
|
+
python_path: str, project_config: dict, project_path: str | None = None
|
|
174
152
|
) -> dict:
|
|
175
153
|
"""Create MCP server configuration for mcp-ticketer.
|
|
176
154
|
|
|
177
155
|
Args:
|
|
178
|
-
|
|
156
|
+
python_path: Path to Python executable in mcp-ticketer venv
|
|
179
157
|
project_config: Project configuration from .mcp-ticketer/config.json
|
|
180
|
-
|
|
158
|
+
project_path: Project directory path (optional)
|
|
181
159
|
|
|
182
160
|
Returns:
|
|
183
|
-
MCP server configuration dict
|
|
161
|
+
MCP server configuration dict matching Claude Code stdio pattern
|
|
184
162
|
|
|
185
163
|
"""
|
|
164
|
+
# Ensure python3 is used (not python)
|
|
165
|
+
if python_path.endswith("/python"):
|
|
166
|
+
python_path = python_path.replace("/python", "/python3")
|
|
167
|
+
|
|
168
|
+
# Use module invocation pattern: python -m mcp_ticketer.mcp.server
|
|
169
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
170
|
+
|
|
171
|
+
# Add project path if provided
|
|
172
|
+
if project_path:
|
|
173
|
+
args.append(project_path)
|
|
174
|
+
|
|
175
|
+
# REQUIRED: Add "type": "stdio" for Claude Code compatibility
|
|
186
176
|
config = {
|
|
187
|
-
"
|
|
188
|
-
"
|
|
177
|
+
"type": "stdio",
|
|
178
|
+
"command": python_path,
|
|
179
|
+
"args": args,
|
|
189
180
|
}
|
|
190
181
|
|
|
191
|
-
# Add working directory if provided
|
|
192
|
-
if cwd:
|
|
193
|
-
config["cwd"] = cwd
|
|
194
|
-
|
|
195
182
|
# Add environment variables based on adapter
|
|
196
183
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
197
184
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -199,15 +186,49 @@ def create_mcp_server_config(
|
|
|
199
186
|
|
|
200
187
|
env_vars = {}
|
|
201
188
|
|
|
202
|
-
# Add
|
|
189
|
+
# Add PYTHONPATH for project context
|
|
190
|
+
if project_path:
|
|
191
|
+
env_vars["PYTHONPATH"] = project_path
|
|
192
|
+
|
|
193
|
+
# Add MCP_TICKETER_ADAPTER to identify which adapter to use
|
|
194
|
+
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
195
|
+
|
|
196
|
+
# Load environment variables from .env.local if it exists
|
|
197
|
+
if project_path:
|
|
198
|
+
env_file_path = Path(project_path) / ".env.local"
|
|
199
|
+
env_file_vars = load_env_file(env_file_path)
|
|
200
|
+
|
|
201
|
+
# Add relevant adapter-specific vars from .env.local
|
|
202
|
+
adapter_env_keys = {
|
|
203
|
+
"linear": ["LINEAR_API_KEY", "LINEAR_TEAM_ID", "LINEAR_TEAM_KEY"],
|
|
204
|
+
"github": ["GITHUB_TOKEN", "GITHUB_OWNER", "GITHUB_REPO"],
|
|
205
|
+
"jira": [
|
|
206
|
+
"JIRA_ACCESS_USER",
|
|
207
|
+
"JIRA_ACCESS_TOKEN",
|
|
208
|
+
"JIRA_ORGANIZATION_ID",
|
|
209
|
+
"JIRA_URL",
|
|
210
|
+
"JIRA_EMAIL",
|
|
211
|
+
"JIRA_API_TOKEN",
|
|
212
|
+
],
|
|
213
|
+
"aitrackdown": [], # No specific env vars needed
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Include adapter-specific env vars from .env.local
|
|
217
|
+
for key in adapter_env_keys.get(adapter, []):
|
|
218
|
+
if key in env_file_vars:
|
|
219
|
+
env_vars[key] = env_file_vars[key]
|
|
220
|
+
|
|
221
|
+
# Fallback: Add adapter-specific environment variables from project config
|
|
203
222
|
if adapter == "linear" and "api_key" in adapter_config:
|
|
204
|
-
|
|
223
|
+
if "LINEAR_API_KEY" not in env_vars:
|
|
224
|
+
env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
|
|
205
225
|
elif adapter == "github" and "token" in adapter_config:
|
|
206
|
-
|
|
226
|
+
if "GITHUB_TOKEN" not in env_vars:
|
|
227
|
+
env_vars["GITHUB_TOKEN"] = adapter_config["token"]
|
|
207
228
|
elif adapter == "jira":
|
|
208
|
-
if "api_token" in adapter_config:
|
|
229
|
+
if "api_token" in adapter_config and "JIRA_API_TOKEN" not in env_vars:
|
|
209
230
|
env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
|
|
210
|
-
if "email" in adapter_config:
|
|
231
|
+
if "email" in adapter_config and "JIRA_EMAIL" not in env_vars:
|
|
211
232
|
env_vars["JIRA_EMAIL"] = adapter_config["email"]
|
|
212
233
|
|
|
213
234
|
if env_vars:
|
|
@@ -284,18 +305,31 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
284
305
|
force: Overwrite existing configuration
|
|
285
306
|
|
|
286
307
|
Raises:
|
|
287
|
-
FileNotFoundError: If
|
|
308
|
+
FileNotFoundError: If Python executable or project config not found
|
|
288
309
|
ValueError: If configuration is invalid
|
|
289
310
|
|
|
290
311
|
"""
|
|
291
|
-
#
|
|
292
|
-
|
|
312
|
+
# Determine project path for venv detection
|
|
313
|
+
project_path = Path.cwd() if not global_config else None
|
|
314
|
+
|
|
315
|
+
# Step 1: Find Python executable (project-specific if available)
|
|
316
|
+
console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
|
|
293
317
|
try:
|
|
294
|
-
|
|
295
|
-
console.print(f"[green]✓[/green] Found: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
318
|
+
python_path = get_mcp_ticketer_python(project_path=project_path)
|
|
319
|
+
console.print(f"[green]✓[/green] Found: {python_path}")
|
|
320
|
+
|
|
321
|
+
# Show if using project venv or fallback
|
|
322
|
+
if project_path and str(project_path / ".venv") in python_path:
|
|
323
|
+
console.print("[dim]Using project-specific venv[/dim]")
|
|
324
|
+
else:
|
|
325
|
+
console.print("[dim]Using pipx/system Python[/dim]")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
console.print(f"[red]✗[/red] Could not find Python executable: {e}")
|
|
328
|
+
raise FileNotFoundError(
|
|
329
|
+
"Could not find mcp-ticketer Python executable. "
|
|
330
|
+
"Please ensure mcp-ticketer is installed.\n"
|
|
331
|
+
"Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
|
|
332
|
+
)
|
|
299
333
|
|
|
300
334
|
# Step 2: Load project configuration
|
|
301
335
|
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
@@ -327,9 +361,11 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
327
361
|
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
328
362
|
|
|
329
363
|
# Step 6: Create mcp-ticketer server config
|
|
330
|
-
|
|
364
|
+
project_path = str(Path.cwd()) if not global_config else None
|
|
331
365
|
server_config = create_mcp_server_config(
|
|
332
|
-
|
|
366
|
+
python_path=python_path,
|
|
367
|
+
project_config=project_config,
|
|
368
|
+
project_path=project_path,
|
|
333
369
|
)
|
|
334
370
|
|
|
335
371
|
# Step 7: Update MCP configuration
|
|
@@ -348,9 +384,10 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
348
384
|
console.print("\n[bold]Configuration Details:[/bold]")
|
|
349
385
|
console.print(" Server name: mcp-ticketer")
|
|
350
386
|
console.print(f" Adapter: {adapter}")
|
|
351
|
-
console.print(f"
|
|
352
|
-
|
|
353
|
-
|
|
387
|
+
console.print(f" Python: {python_path}")
|
|
388
|
+
console.print(" Command: python -m mcp_ticketer.mcp.server")
|
|
389
|
+
if project_path:
|
|
390
|
+
console.print(f" Project path: {project_path}")
|
|
354
391
|
if "env" in server_config:
|
|
355
392
|
console.print(
|
|
356
393
|
f" Environment variables: {list(server_config['env'].keys())}"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Reliable Python executable detection for mcp-ticketer.
|
|
2
|
+
|
|
3
|
+
This module provides reliable detection of the Python executable for mcp-ticketer
|
|
4
|
+
across different installation methods (pipx, pip, uv, direct venv).
|
|
5
|
+
|
|
6
|
+
The module follows the proven pattern from mcp-vector-search:
|
|
7
|
+
- Detect venv Python path reliably
|
|
8
|
+
- Use `python -m mcp_ticketer.mcp.server` instead of binary paths
|
|
9
|
+
- Support multiple installation methods transparently
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_mcp_ticketer_python(project_path: Path | None = None) -> str:
|
|
19
|
+
"""Get the correct Python executable for mcp-ticketer MCP server.
|
|
20
|
+
|
|
21
|
+
This function follows the mcp-vector-search pattern of using project-specific
|
|
22
|
+
venv Python for proper project isolation and dependency management.
|
|
23
|
+
|
|
24
|
+
Detection priority:
|
|
25
|
+
1. Project-local venv (.venv/bin/python) if project_path provided
|
|
26
|
+
2. Current Python executable if in pipx venv
|
|
27
|
+
3. Python from mcp-ticketer binary shebang
|
|
28
|
+
4. Current Python executable (fallback)
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
project_path: Optional project directory path to check for local venv
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to Python executable
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> # With project venv
|
|
38
|
+
>>> python_path = get_mcp_ticketer_python(Path("/home/user/my-project"))
|
|
39
|
+
>>> # Returns: "/home/user/my-project/.venv/bin/python"
|
|
40
|
+
|
|
41
|
+
>>> # Without project path (fallback to pipx)
|
|
42
|
+
>>> python_path = get_mcp_ticketer_python()
|
|
43
|
+
>>> # Returns: "/Users/user/.local/pipx/venvs/mcp-ticketer/bin/python"
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
# Priority 1: Check for project-local venv
|
|
47
|
+
if project_path:
|
|
48
|
+
project_venv_python = project_path / ".venv" / "bin" / "python"
|
|
49
|
+
if project_venv_python.exists():
|
|
50
|
+
return str(project_venv_python)
|
|
51
|
+
|
|
52
|
+
current_executable = sys.executable
|
|
53
|
+
|
|
54
|
+
# Priority 2: Check if we're in a pipx venv
|
|
55
|
+
if "/pipx/venvs/" in current_executable:
|
|
56
|
+
return current_executable
|
|
57
|
+
|
|
58
|
+
# Priority 3: Check mcp-ticketer binary shebang
|
|
59
|
+
mcp_ticketer_path = shutil.which("mcp-ticketer")
|
|
60
|
+
if mcp_ticketer_path:
|
|
61
|
+
try:
|
|
62
|
+
with open(mcp_ticketer_path) as f:
|
|
63
|
+
first_line = f.readline().strip()
|
|
64
|
+
if first_line.startswith("#!") and "python" in first_line:
|
|
65
|
+
python_path = first_line[2:].strip()
|
|
66
|
+
if os.path.exists(python_path):
|
|
67
|
+
return python_path
|
|
68
|
+
except OSError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# Priority 4: Fallback to current Python
|
|
72
|
+
return current_executable
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_mcp_server_command(project_path: str | None = None) -> tuple[str, list[str]]:
|
|
76
|
+
"""Get the complete command to run the MCP server.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
project_path: Optional project path to pass as argument and check for venv
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Tuple of (python_executable, args_list)
|
|
83
|
+
Example: ("/path/to/python", ["-m", "mcp_ticketer.mcp.server", "/project/path"])
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
>>> python, args = get_mcp_server_command("/home/user/project")
|
|
87
|
+
>>> # python: "/home/user/project/.venv/bin/python" (if .venv exists)
|
|
88
|
+
>>> # args: ["-m", "mcp_ticketer.mcp.server", "/home/user/project"]
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
# Convert project_path to Path object for venv detection
|
|
92
|
+
project_path_obj = Path(project_path) if project_path else None
|
|
93
|
+
python_path = get_mcp_ticketer_python(project_path=project_path_obj)
|
|
94
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
95
|
+
|
|
96
|
+
if project_path:
|
|
97
|
+
args.append(str(project_path))
|
|
98
|
+
|
|
99
|
+
return python_path, args
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def validate_python_executable(python_path: str) -> bool:
|
|
103
|
+
"""Validate that a Python executable can import mcp_ticketer.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
python_path: Path to Python executable to validate
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if Python can import mcp_ticketer, False otherwise
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> is_valid = validate_python_executable("/usr/bin/python3")
|
|
113
|
+
>>> # Returns: False (system Python doesn't have mcp_ticketer)
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
import subprocess
|
|
118
|
+
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
[python_path, "-c", "import mcp_ticketer.mcp.server"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
timeout=5,
|
|
123
|
+
)
|
|
124
|
+
return result.returncode == 0
|
|
125
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
126
|
+
return False
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Core models and abstractions for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
from .adapter import BaseAdapter
|
|
4
|
-
from .models import
|
|
5
|
-
TicketType)
|
|
4
|
+
from .models import Attachment, Comment, Epic, Priority, Task, TicketState, TicketType
|
|
6
5
|
from .registry import AdapterRegistry
|
|
7
6
|
|
|
8
7
|
__all__ = [
|