mcp-ticketer 0.4.4__py3-none-any.whl → 0.4.8__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 (45) hide show
  1. mcp_ticketer/__init__.py +3 -12
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +2 -9
  4. mcp_ticketer/adapters/github.py +2 -1
  5. mcp_ticketer/adapters/jira.py +2 -1
  6. mcp_ticketer/adapters/linear/adapter.py +28 -25
  7. mcp_ticketer/adapters/linear/client.py +2 -1
  8. mcp_ticketer/adapters/linear/mappers.py +2 -1
  9. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  10. mcp_ticketer/cli/auggie_configure.py +35 -15
  11. mcp_ticketer/cli/codex_configure.py +38 -31
  12. mcp_ticketer/cli/configure.py +3 -9
  13. mcp_ticketer/cli/discover.py +2 -6
  14. mcp_ticketer/cli/gemini_configure.py +38 -25
  15. mcp_ticketer/cli/main.py +4 -3
  16. mcp_ticketer/cli/mcp_configure.py +115 -78
  17. mcp_ticketer/cli/python_detection.py +126 -0
  18. mcp_ticketer/core/__init__.py +2 -1
  19. mcp_ticketer/mcp/__init__.py +29 -1
  20. mcp_ticketer/mcp/__main__.py +60 -0
  21. mcp_ticketer/mcp/server/__init__.py +21 -0
  22. mcp_ticketer/mcp/server/__main__.py +60 -0
  23. mcp_ticketer/mcp/{server.py → server/main.py} +15 -35
  24. mcp_ticketer/mcp/{tools → server/tools}/__init__.py +7 -9
  25. mcp_ticketer/queue/__init__.py +1 -0
  26. mcp_ticketer/queue/manager.py +10 -46
  27. mcp_ticketer/queue/ticket_registry.py +5 -5
  28. {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/METADATA +13 -4
  29. mcp_ticketer-0.4.8.dist-info/RECORD +77 -0
  30. mcp_ticketer-0.4.4.dist-info/RECORD +0 -73
  31. /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
  32. /mcp_ticketer/mcp/{dto.py → server/dto.py} +0 -0
  33. /mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +0 -0
  34. /mcp_ticketer/mcp/{server_sdk.py → server/server_sdk.py} +0 -0
  35. /mcp_ticketer/mcp/{tools → server/tools}/attachment_tools.py +0 -0
  36. /mcp_ticketer/mcp/{tools → server/tools}/bulk_tools.py +0 -0
  37. /mcp_ticketer/mcp/{tools → server/tools}/comment_tools.py +0 -0
  38. /mcp_ticketer/mcp/{tools → server/tools}/hierarchy_tools.py +0 -0
  39. /mcp_ticketer/mcp/{tools → server/tools}/pr_tools.py +0 -0
  40. /mcp_ticketer/mcp/{tools → server/tools}/search_tools.py +0 -0
  41. /mcp_ticketer/mcp/{tools → server/tools}/ticket_tools.py +0 -0
  42. {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/WHEEL +0 -0
  43. {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/entry_points.txt +0 -0
  44. {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/licenses/LICENSE +0 -0
  45. {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/top_level.txt +0 -0
mcp_ticketer/__init__.py CHANGED
@@ -1,17 +1,8 @@
1
1
  """MCP Ticketer - Universal ticket management interface."""
2
2
 
3
- from .__version__ import (
4
- __author__,
5
- __author_email__,
6
- __copyright__,
7
- __description__,
8
- __license__,
9
- __title__,
10
- __version__,
11
- __version_info__,
12
- get_user_agent,
13
- get_version,
14
- )
3
+ from .__version__ import (__author__, __author_email__, __copyright__,
4
+ __description__, __license__, __title__, __version__,
5
+ __version_info__, get_user_agent, get_version)
15
6
 
16
7
  __all__ = [
17
8
  "__version__",
@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.4.8"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -10,15 +10,8 @@ from typing import Any
10
10
  from ..core.adapter import BaseAdapter
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
- from ..core.models import (
14
- Attachment,
15
- Comment,
16
- Epic,
17
- Priority,
18
- SearchQuery,
19
- Task,
20
- TicketState,
21
- )
13
+ from ..core.models import (Attachment, Comment, Epic, Priority, SearchQuery,
14
+ Task, TicketState)
22
15
  from ..core.registry import AdapterRegistry
23
16
 
24
17
  # Import ai-trackdown-pytools when available
@@ -9,7 +9,8 @@ import httpx
9
9
 
10
10
  from ..core.adapter import BaseAdapter
11
11
  from ..core.env_loader import load_adapter_config, validate_adapter_config
12
- from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
12
+ from ..core.models import (Comment, Epic, Priority, SearchQuery, Task,
13
+ TicketState)
13
14
  from ..core.registry import AdapterRegistry
14
15
 
15
16
 
@@ -13,7 +13,8 @@ from httpx import AsyncClient, HTTPStatusError, TimeoutException
13
13
 
14
14
  from ..core.adapter import BaseAdapter
15
15
  from ..core.env_loader import load_adapter_config, validate_adapter_config
16
- from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
16
+ from ..core.models import (Comment, Epic, Priority, SearchQuery, Task,
17
+ TicketState)
17
18
  from ..core.registry import AdapterRegistry
18
19
 
19
20
  logger = logging.getLogger(__name__)
@@ -20,27 +20,15 @@ from ...core.adapter import BaseAdapter
20
20
  from ...core.models import Comment, Epic, SearchQuery, Task, TicketState
21
21
  from ...core.registry import AdapterRegistry
22
22
  from .client import LinearGraphQLClient
23
- from .mappers import (
24
- build_linear_issue_input,
25
- build_linear_issue_update_input,
26
- map_linear_comment_to_comment,
27
- map_linear_issue_to_task,
28
- map_linear_project_to_epic,
29
- )
30
- from .queries import (
31
- ALL_FRAGMENTS,
32
- CREATE_ISSUE_MUTATION,
33
- LIST_ISSUES_QUERY,
34
- SEARCH_ISSUES_QUERY,
35
- UPDATE_ISSUE_MUTATION,
36
- WORKFLOW_STATES_QUERY,
37
- )
38
- from .types import (
39
- LinearStateMapping,
40
- build_issue_filter,
41
- get_linear_priority,
42
- get_linear_state_type,
43
- )
23
+ from .mappers import (build_linear_issue_input,
24
+ build_linear_issue_update_input,
25
+ map_linear_comment_to_comment, map_linear_issue_to_task,
26
+ map_linear_project_to_epic)
27
+ from .queries import (ALL_FRAGMENTS, CREATE_ISSUE_MUTATION, LIST_ISSUES_QUERY,
28
+ SEARCH_ISSUES_QUERY, UPDATE_ISSUE_MUTATION,
29
+ WORKFLOW_STATES_QUERY)
30
+ from .types import (LinearStateMapping, build_issue_filter,
31
+ get_linear_priority, get_linear_state_type)
44
32
 
45
33
 
46
34
  class LinearAdapter(BaseAdapter[Task]):
@@ -94,10 +82,25 @@ class LinearAdapter(BaseAdapter[Task]):
94
82
  "Linear API key is required (api_key or LINEAR_API_KEY env var)"
95
83
  )
96
84
 
97
- # Clean API key - remove Bearer prefix if accidentally included in config
98
- # (The client will add it back when making requests)
99
- if self.api_key.startswith("Bearer "):
100
- self.api_key = self.api_key.replace("Bearer ", "")
85
+ # Clean API key - remove common prefixes if accidentally included in config
86
+ # (The client will add Bearer back when making requests)
87
+ if isinstance(self.api_key, str):
88
+ # Remove Bearer prefix
89
+ if self.api_key.startswith("Bearer "):
90
+ self.api_key = self.api_key.replace("Bearer ", "")
91
+ # Remove environment variable name prefix (e.g., "LINEAR_API_KEY=")
92
+ if "=" in self.api_key:
93
+ parts = self.api_key.split("=", 1)
94
+ if len(parts) == 2 and parts[0].upper() in ("LINEAR_API_KEY", "API_KEY"):
95
+ self.api_key = parts[1]
96
+
97
+ # Validate API key format (Linear keys start with "lin_api_")
98
+ if not self.api_key.startswith("lin_api_"):
99
+ raise ValueError(
100
+ f"Invalid Linear API key format. Expected key starting with 'lin_api_', "
101
+ f"got: {self.api_key[:15]}... "
102
+ f"Please check your configuration and ensure the API key is correct."
103
+ )
101
104
 
102
105
  self.workspace = config.get("workspace", "")
103
106
  self.team_key = config.get("team_key")
@@ -16,7 +16,8 @@ except ImportError:
16
16
  HTTPXAsyncTransport = None
17
17
  TransportError = Exception
18
18
 
19
- from ...core.exceptions import AdapterError, AuthenticationError, RateLimitError
19
+ from ...core.exceptions import (AdapterError, AuthenticationError,
20
+ RateLimitError)
20
21
 
21
22
 
22
23
  class LinearGraphQLClient:
@@ -6,7 +6,8 @@ from datetime import datetime
6
6
  from typing import Any
7
7
 
8
8
  from ...core.models import Comment, Epic, Priority, Task, TicketState
9
- from .types import extract_linear_metadata, get_universal_priority, get_universal_state
9
+ from .types import (extract_linear_metadata, get_universal_priority,
10
+ get_universal_state)
10
11
 
11
12
 
12
13
  def map_linear_issue_to_task(issue_data: dict[str, Any]) -> Task:
@@ -197,7 +197,8 @@ def _test_adapter_instantiation(console: Console) -> None:
197
197
  if primary:
198
198
  adapter_type = primary.adapter_type
199
199
  # Build config from discovery
200
- from ..mcp.server import _build_adapter_config_from_env_vars
200
+ from ..mcp.server import \
201
+ _build_adapter_config_from_env_vars
201
202
 
202
203
  config = _build_adapter_config_from_env_vars(adapter_type, {})
203
204
  else:
@@ -384,7 +385,8 @@ def get_adapter_status() -> dict[str, Any]:
384
385
  adapter_type = primary.adapter_type
385
386
  status["configuration_source"] = primary.found_in
386
387
  # Build basic config
387
- from ..mcp.server import _build_adapter_config_from_env_vars
388
+ from ..mcp.server import \
389
+ _build_adapter_config_from_env_vars
388
390
 
389
391
  config = _build_adapter_config_from_env_vars(adapter_type, {})
390
392
  else:
@@ -10,7 +10,8 @@ from typing import Any
10
10
 
11
11
  from rich.console import Console
12
12
 
13
- from .mcp_configure import find_mcp_ticketer_binary, load_project_config
13
+ from .mcp_configure import load_project_config
14
+ from .python_detection import get_mcp_ticketer_python
14
15
 
15
16
  console = Console()
16
17
 
@@ -71,13 +72,14 @@ def save_auggie_config(config_path: Path, config: dict[str, Any]) -> None:
71
72
 
72
73
 
73
74
  def create_auggie_server_config(
74
- binary_path: str, project_config: dict[str, Any]
75
+ python_path: str, project_config: dict[str, Any], project_path: str | None = None
75
76
  ) -> dict[str, Any]:
76
77
  """Create Auggie MCP server configuration for mcp-ticketer.
77
78
 
78
79
  Args:
79
- binary_path: Path to mcp-ticketer binary
80
+ python_path: Path to Python executable in mcp-ticketer venv
80
81
  project_config: Project configuration from .mcp-ticketer/config.json
82
+ project_path: Project directory path (optional)
81
83
 
82
84
  Returns:
83
85
  Auggie MCP server configuration dict
@@ -91,6 +93,10 @@ def create_auggie_server_config(
91
93
  # Build environment variables
92
94
  env_vars = {}
93
95
 
96
+ # Add PYTHONPATH for project context
97
+ if project_path:
98
+ env_vars["PYTHONPATH"] = project_path
99
+
94
100
  # Add adapter type
95
101
  env_vars["MCP_TICKETER_ADAPTER"] = adapter
96
102
 
@@ -128,10 +134,15 @@ def create_auggie_server_config(
128
134
  if "project_key" in adapter_config:
129
135
  env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
130
136
 
137
+ # Use module invocation pattern: python -m mcp_ticketer.mcp.server
138
+ args = ["-m", "mcp_ticketer.mcp.server"]
139
+ if project_path:
140
+ args.append(project_path)
141
+
131
142
  # Create server configuration (simpler than Gemini - no timeout/trust)
132
143
  config = {
133
- "command": binary_path,
134
- "args": ["serve"],
144
+ "command": python_path,
145
+ "args": args,
135
146
  "env": env_vars,
136
147
  }
137
148
 
@@ -214,18 +225,22 @@ def configure_auggie_mcp(force: bool = False) -> None:
214
225
  force: Overwrite existing configuration
215
226
 
216
227
  Raises:
217
- FileNotFoundError: If binary or project config not found
228
+ FileNotFoundError: If Python executable or project config not found
218
229
  ValueError: If configuration is invalid
219
230
 
220
231
  """
221
- # Step 1: Find mcp-ticketer binary
222
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
232
+ # Step 1: Find Python executable
233
+ console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
223
234
  try:
224
- binary_path = find_mcp_ticketer_binary()
225
- console.print(f"[green]✓[/green] Found: {binary_path}")
226
- except FileNotFoundError as e:
227
- console.print(f"[red]✗[/red] {e}")
228
- raise
235
+ python_path = get_mcp_ticketer_python()
236
+ console.print(f"[green]✓[/green] Found: {python_path}")
237
+ except Exception as e:
238
+ console.print(f"[red]✗[/red] Could not find Python executable: {e}")
239
+ raise FileNotFoundError(
240
+ "Could not find mcp-ticketer Python executable. "
241
+ "Please ensure mcp-ticketer is installed.\n"
242
+ "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
243
+ )
229
244
 
230
245
  # Step 2: Load project configuration
231
246
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -259,8 +274,11 @@ def configure_auggie_mcp(force: bool = False) -> None:
259
274
  console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
260
275
 
261
276
  # Step 6: Create mcp-ticketer server config
277
+ project_path = str(Path.cwd())
262
278
  server_config = create_auggie_server_config(
263
- binary_path=binary_path, project_config=project_config
279
+ python_path=python_path,
280
+ project_config=project_config,
281
+ project_path=project_path,
264
282
  )
265
283
 
266
284
  # Step 7: Update Auggie configuration
@@ -279,8 +297,10 @@ def configure_auggie_mcp(force: bool = False) -> None:
279
297
  console.print("\n[bold]Configuration Details:[/bold]")
280
298
  console.print(" Server name: mcp-ticketer")
281
299
  console.print(f" Adapter: {adapter}")
282
- console.print(f" Binary: {binary_path}")
300
+ console.print(f" Python: {python_path}")
301
+ console.print(" Command: python -m mcp_ticketer.mcp.server")
283
302
  console.print(" Scope: Global (affects all projects)")
303
+ console.print(f" Project path: {project_path}")
284
304
  if "env" in server_config:
285
305
  console.print(
286
306
  f" Environment variables: {list(server_config['env'].keys())}"
@@ -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
7
+ import tomllib
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
11
- if sys.version_info >= (3, 11):
12
- import tomllib
13
- else:
14
- import tomli as tomllib
15
-
16
11
  import tomli_w
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
 
@@ -78,14 +74,14 @@ def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
78
74
 
79
75
 
80
76
  def create_codex_server_config(
81
- binary_path: str, project_config: dict, cwd: str | None = None
77
+ python_path: str, project_config: dict, project_path: str | None = None
82
78
  ) -> dict[str, Any]:
83
79
  """Create Codex MCP server configuration for mcp-ticketer.
84
80
 
85
81
  Args:
86
- binary_path: Path to mcp-ticketer binary
82
+ python_path: Path to Python executable in mcp-ticketer venv
87
83
  project_config: Project configuration from .mcp-ticketer/config.json
88
- cwd: Working directory for server (optional, not used for global config)
84
+ project_path: Project directory path (optional, not used for global config)
89
85
 
90
86
  Returns:
91
87
  Codex MCP server configuration dict
@@ -99,9 +95,9 @@ def create_codex_server_config(
99
95
  # Build environment variables
100
96
  env_vars: dict[str, str] = {}
101
97
 
102
- # Add PYTHONPATH if running from development environment
103
- if cwd:
104
- env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
98
+ # Add PYTHONPATH for project context
99
+ if project_path:
100
+ env_vars["PYTHONPATH"] = project_path
105
101
 
106
102
  # Add adapter type
107
103
  env_vars["MCP_TICKETER_ADAPTER"] = adapter
@@ -110,9 +106,9 @@ def create_codex_server_config(
110
106
  if adapter == "aitrackdown":
111
107
  # Set base path for local adapter
112
108
  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)
109
+ if project_path:
110
+ # Use absolute path if project_path is provided
111
+ env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(project_path) / base_path)
116
112
  else:
117
113
  env_vars["MCP_TICKETER_BASE_PATH"] = base_path
118
114
 
@@ -140,11 +136,15 @@ def create_codex_server_config(
140
136
  if "project_key" in adapter_config:
141
137
  env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
142
138
 
139
+ # Use module invocation pattern: python -m mcp_ticketer.mcp.server
140
+ args = ["-m", "mcp_ticketer.mcp.server"]
141
+ if project_path:
142
+ args.append(project_path)
143
+
143
144
  # Create server configuration with Codex-specific structure
144
- # NOTE: Codex uses nested dict structure for env vars
145
145
  config: dict[str, Any] = {
146
- "command": binary_path,
147
- "args": ["serve"],
146
+ "command": python_path,
147
+ "args": args,
148
148
  "env": env_vars,
149
149
  }
150
150
 
@@ -231,18 +231,22 @@ def configure_codex_mcp(force: bool = False) -> None:
231
231
  force: Overwrite existing configuration
232
232
 
233
233
  Raises:
234
- FileNotFoundError: If binary or project config not found
234
+ FileNotFoundError: If Python executable or project config not found
235
235
  ValueError: If configuration is invalid
236
236
 
237
237
  """
238
- # Step 1: Find mcp-ticketer binary
239
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
238
+ # Step 1: Find Python executable
239
+ console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
240
240
  try:
241
- binary_path = find_mcp_ticketer_binary()
242
- console.print(f"[green]✓[/green] Found: {binary_path}")
243
- except FileNotFoundError as e:
244
- console.print(f"[red]✗[/red] {e}")
245
- raise
241
+ python_path = get_mcp_ticketer_python()
242
+ console.print(f"[green]✓[/green] Found: {python_path}")
243
+ except Exception as e:
244
+ console.print(f"[red]✗[/red] Could not find Python executable: {e}")
245
+ raise FileNotFoundError(
246
+ "Could not find mcp-ticketer Python executable. "
247
+ "Please ensure mcp-ticketer is installed.\n"
248
+ "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
249
+ )
246
250
 
247
251
  # Step 2: Load project configuration
248
252
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -279,9 +283,11 @@ def configure_codex_mcp(force: bool = False) -> None:
279
283
 
280
284
  # Step 6: Create mcp-ticketer server config
281
285
  # For global config, include current working directory for context
282
- cwd = str(Path.cwd())
286
+ project_path = str(Path.cwd())
283
287
  server_config = create_codex_server_config(
284
- binary_path=binary_path, project_config=project_config, cwd=cwd
288
+ python_path=python_path,
289
+ project_config=project_config,
290
+ project_path=project_path,
285
291
  )
286
292
 
287
293
  # Step 7: Update Codex configuration
@@ -300,9 +306,10 @@ def configure_codex_mcp(force: bool = False) -> None:
300
306
  console.print("\n[bold]Configuration Details:[/bold]")
301
307
  console.print(" Server name: mcp-ticketer")
302
308
  console.print(f" Adapter: {adapter}")
303
- console.print(f" Binary: {binary_path}")
309
+ console.print(f" Python: {python_path}")
310
+ console.print(" Command: python -m mcp_ticketer.mcp.server")
304
311
  console.print(" Scope: global (Codex only supports global config)")
305
- console.print(f" Working directory: {cwd}")
312
+ console.print(f" Project path: {project_path}")
306
313
  if "env" in server_config:
307
314
  console.print(
308
315
  f" Environment variables: {list(server_config['env'].keys())}"
@@ -8,15 +8,9 @@ from rich.panel import Panel
8
8
  from rich.prompt import Confirm, Prompt
9
9
  from rich.table import Table
10
10
 
11
- from ..core.project_config import (
12
- AdapterConfig,
13
- AdapterType,
14
- ConfigResolver,
15
- ConfigValidator,
16
- HybridConfig,
17
- SyncStrategy,
18
- TicketerConfig,
19
- )
11
+ from ..core.project_config import (AdapterConfig, AdapterType, ConfigResolver,
12
+ ConfigValidator, HybridConfig, SyncStrategy,
13
+ TicketerConfig)
20
14
 
21
15
  console = Console()
22
16
 
@@ -6,12 +6,8 @@ import typer
6
6
  from rich.console import Console
7
7
 
8
8
  from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
9
- from ..core.project_config import (
10
- AdapterConfig,
11
- ConfigResolver,
12
- ConfigValidator,
13
- TicketerConfig,
14
- )
9
+ from ..core.project_config import (AdapterConfig, ConfigResolver,
10
+ ConfigValidator, TicketerConfig)
15
11
 
16
12
  console = Console()
17
13
  app = typer.Typer(help="Auto-discover configuration from .env files")
@@ -6,7 +6,8 @@ from typing import Literal
6
6
 
7
7
  from rich.console import Console
8
8
 
9
- from .mcp_configure import find_mcp_ticketer_binary, load_project_config
9
+ from .mcp_configure import load_project_config
10
+ from .python_detection import get_mcp_ticketer_python
10
11
 
11
12
  console = Console()
12
13
 
@@ -73,14 +74,14 @@ def save_gemini_config(config_path: Path, config: dict) -> None:
73
74
 
74
75
 
75
76
  def create_gemini_server_config(
76
- binary_path: str, project_config: dict, cwd: str | None = None
77
+ python_path: str, project_config: dict, project_path: str | None = None
77
78
  ) -> dict:
78
79
  """Create Gemini MCP server configuration for mcp-ticketer.
79
80
 
80
81
  Args:
81
- binary_path: Path to mcp-ticketer binary
82
+ python_path: Path to Python executable in mcp-ticketer venv
82
83
  project_config: Project configuration from .mcp-ticketer/config.json
83
- cwd: Working directory for server (optional)
84
+ project_path: Project directory path (optional)
84
85
 
85
86
  Returns:
86
87
  Gemini MCP server configuration dict
@@ -94,9 +95,9 @@ def create_gemini_server_config(
94
95
  # Build environment variables
95
96
  env_vars = {}
96
97
 
97
- # Add PYTHONPATH if running from development environment
98
- if cwd:
99
- env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
98
+ # Add PYTHONPATH for project context
99
+ if project_path:
100
+ env_vars["PYTHONPATH"] = project_path
100
101
 
101
102
  # Add adapter type
102
103
  env_vars["MCP_TICKETER_ADAPTER"] = adapter
@@ -105,9 +106,9 @@ def create_gemini_server_config(
105
106
  if adapter == "aitrackdown":
106
107
  # Set base path for local adapter
107
108
  base_path = adapter_config.get("base_path", ".aitrackdown")
108
- if cwd:
109
- # Use absolute path if cwd is provided
110
- env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_path)
109
+ if project_path:
110
+ # Use absolute path if project_path is provided
111
+ env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(project_path) / base_path)
111
112
  else:
112
113
  env_vars["MCP_TICKETER_BASE_PATH"] = base_path
113
114
 
@@ -135,10 +136,15 @@ def create_gemini_server_config(
135
136
  if "project_key" in adapter_config:
136
137
  env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
137
138
 
139
+ # Use module invocation pattern: python -m mcp_ticketer.mcp.server
140
+ args = ["-m", "mcp_ticketer.mcp.server"]
141
+ if project_path:
142
+ args.append(project_path)
143
+
138
144
  # Create server configuration with Gemini-specific options
139
145
  config = {
140
- "command": binary_path,
141
- "args": ["serve"],
146
+ "command": python_path,
147
+ "args": args,
142
148
  "env": env_vars,
143
149
  "timeout": 15000, # 15 seconds timeout
144
150
  "trust": False, # Don't trust by default (security)
@@ -223,18 +229,22 @@ def configure_gemini_mcp(
223
229
  force: Overwrite existing configuration
224
230
 
225
231
  Raises:
226
- FileNotFoundError: If binary or project config not found
232
+ FileNotFoundError: If Python executable or project config not found
227
233
  ValueError: If configuration is invalid
228
234
 
229
235
  """
230
- # Step 1: Find mcp-ticketer binary
231
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
236
+ # Step 1: Find Python executable
237
+ console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
232
238
  try:
233
- binary_path = find_mcp_ticketer_binary()
234
- console.print(f"[green]✓[/green] Found: {binary_path}")
235
- except FileNotFoundError as e:
236
- console.print(f"[red]✗[/red] {e}")
237
- raise
239
+ python_path = get_mcp_ticketer_python()
240
+ console.print(f"[green]✓[/green] Found: {python_path}")
241
+ except Exception as e:
242
+ console.print(f"[red]✗[/red] Could not find Python executable: {e}")
243
+ raise FileNotFoundError(
244
+ "Could not find mcp-ticketer Python executable. "
245
+ "Please ensure mcp-ticketer is installed.\n"
246
+ "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
247
+ )
238
248
 
239
249
  # Step 2: Load project configuration
240
250
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -266,9 +276,11 @@ def configure_gemini_mcp(
266
276
  console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
267
277
 
268
278
  # Step 6: Create mcp-ticketer server config
269
- cwd = str(Path.cwd()) if scope == "project" else None
279
+ project_path = str(Path.cwd()) if scope == "project" else None
270
280
  server_config = create_gemini_server_config(
271
- binary_path=binary_path, project_config=project_config, cwd=cwd
281
+ python_path=python_path,
282
+ project_config=project_config,
283
+ project_path=project_path,
272
284
  )
273
285
 
274
286
  # Step 7: Update Gemini configuration
@@ -287,11 +299,12 @@ def configure_gemini_mcp(
287
299
  console.print("\n[bold]Configuration Details:[/bold]")
288
300
  console.print(" Server name: mcp-ticketer")
289
301
  console.print(f" Adapter: {adapter}")
290
- console.print(f" Binary: {binary_path}")
302
+ console.print(f" Python: {python_path}")
303
+ console.print(" Command: python -m mcp_ticketer.mcp.server")
291
304
  console.print(f" Timeout: {server_config['timeout']}ms")
292
305
  console.print(f" Trust: {server_config['trust']}")
293
- if cwd:
294
- console.print(f" Working directory: {cwd}")
306
+ if project_path:
307
+ console.print(f" Project path: {project_path}")
295
308
  if "env" in server_config:
296
309
  console.print(
297
310
  f" Environment variables: {list(server_config['env'].keys())}"
mcp_ticketer/cli/main.py CHANGED
@@ -20,7 +20,8 @@ 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 configure_wizard, set_adapter_config, show_current_config
23
+ from .configure import (configure_wizard, set_adapter_config,
24
+ show_current_config)
24
25
  from .diagnostics import run_diagnostics
25
26
  from .discover import app as discover_app
26
27
  from .migrate_config import migrate_config_command
@@ -575,7 +576,7 @@ def init(
575
576
  )
576
577
 
577
578
  # First try our improved .env configuration loader
578
- from ..mcp.server import _load_env_configuration
579
+ from ..mcp.server.main import _load_env_configuration
579
580
 
580
581
  env_config = _load_env_configuration()
581
582
 
@@ -2225,7 +2226,7 @@ def mcp_serve(
2225
2226
  adapter_config = adapters_config.get(adapter_type, {})
2226
2227
  else:
2227
2228
  # Priority 2: .env files
2228
- from ..mcp.server import _load_env_configuration
2229
+ from ..mcp.server.main import _load_env_configuration
2229
2230
 
2230
2231
  env_config = _load_env_configuration()
2231
2232
  if env_config: