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.

Files changed (31) hide show
  1. mcp_ticketer/__init__.py +12 -3
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +16 -5
  4. mcp_ticketer/adapters/github.py +1 -2
  5. mcp_ticketer/adapters/jira.py +1 -2
  6. mcp_ticketer/adapters/linear/adapter.py +21 -9
  7. mcp_ticketer/adapters/linear/client.py +1 -2
  8. mcp_ticketer/adapters/linear/mappers.py +1 -2
  9. mcp_ticketer/cli/adapter_diagnostics.py +2 -4
  10. mcp_ticketer/cli/auggie_configure.py +35 -15
  11. mcp_ticketer/cli/codex_configure.py +38 -31
  12. mcp_ticketer/cli/configure.py +9 -3
  13. mcp_ticketer/cli/discover.py +6 -2
  14. mcp_ticketer/cli/gemini_configure.py +38 -25
  15. mcp_ticketer/cli/main.py +147 -32
  16. mcp_ticketer/cli/mcp_configure.py +115 -78
  17. mcp_ticketer/cli/python_detection.py +126 -0
  18. mcp_ticketer/core/__init__.py +1 -2
  19. mcp_ticketer/mcp/__init__.py +29 -1
  20. mcp_ticketer/mcp/__main__.py +60 -0
  21. mcp_ticketer/mcp/server.py +34 -14
  22. mcp_ticketer/mcp/tools/__init__.py +9 -7
  23. mcp_ticketer/queue/__init__.py +3 -1
  24. mcp_ticketer/queue/manager.py +10 -46
  25. mcp_ticketer/queue/ticket_registry.py +5 -5
  26. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/METADATA +13 -4
  27. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/RECORD +31 -29
  28. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/WHEEL +0 -0
  29. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/entry_points.txt +0 -0
  30. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/licenses/LICENSE +0 -0
  31. {mcp_ticketer-0.4.3.dist-info → mcp_ticketer-0.4.5.dist-info}/top_level.txt +0 -0
mcp_ticketer/__init__.py CHANGED
@@ -1,8 +1,17 @@
1
1
  """MCP Ticketer - Universal ticket management interface."""
2
2
 
3
- from .__version__ import (__author__, __author_email__, __copyright__,
4
- __description__, __license__, __title__, __version__,
5
- __version_info__, get_user_agent, get_version)
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
+ )
6
15
 
7
16
  __all__ = [
8
17
  "__version__",
@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.4.5"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -10,8 +10,15 @@ from typing import Any
10
10
  from ..core.adapter import BaseAdapter
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
- from ..core.models import (Attachment, Comment, Epic, Priority, SearchQuery,
14
- Task, TicketState)
13
+ from ..core.models import (
14
+ Attachment,
15
+ Comment,
16
+ Epic,
17
+ Priority,
18
+ SearchQuery,
19
+ Task,
20
+ TicketState,
21
+ )
15
22
  from ..core.registry import AdapterRegistry
16
23
 
17
24
  # Import ai-trackdown-pytools when available
@@ -766,7 +773,7 @@ class AITrackdownAdapter(BaseAdapter[Task]):
766
773
  # CRITICAL SECURITY CHECK: Ensure ticket directory is within base attachments
767
774
  base_attachments = (self.base_path / "attachments").resolve()
768
775
  if not str(attachments_dir).startswith(str(base_attachments)):
769
- raise ValueError(f"Invalid ticket_id: path traversal detected")
776
+ raise ValueError("Invalid ticket_id: path traversal detected")
770
777
 
771
778
  if not attachments_dir.exists():
772
779
  return []
@@ -828,9 +835,13 @@ class AITrackdownAdapter(BaseAdapter[Task]):
828
835
  # CRITICAL SECURITY CHECK: Ensure paths are within attachments_dir
829
836
  base_resolved = attachments_dir.resolve()
830
837
  if not str(attachment_file).startswith(str(base_resolved)):
831
- raise ValueError(f"Invalid attachment path: path traversal detected in attachment_id")
838
+ raise ValueError(
839
+ "Invalid attachment path: path traversal detected in attachment_id"
840
+ )
832
841
  if not str(metadata_file).startswith(str(base_resolved)):
833
- raise ValueError(f"Invalid attachment path: path traversal detected in attachment_id")
842
+ raise ValueError(
843
+ "Invalid attachment path: path traversal detected in attachment_id"
844
+ )
834
845
 
835
846
  # Delete files if they exist
836
847
  deleted = False
@@ -9,8 +9,7 @@ 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,
13
- TicketState)
12
+ from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
14
13
  from ..core.registry import AdapterRegistry
15
14
 
16
15
 
@@ -13,8 +13,7 @@ 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,
17
- TicketState)
16
+ from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
18
17
  from ..core.registry import AdapterRegistry
19
18
 
20
19
  logger = logging.getLogger(__name__)
@@ -20,15 +20,27 @@ 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 (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)
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
+ )
32
44
 
33
45
 
34
46
  class LinearAdapter(BaseAdapter[Task]):
@@ -16,8 +16,7 @@ except ImportError:
16
16
  HTTPXAsyncTransport = None
17
17
  TransportError = Exception
18
18
 
19
- from ...core.exceptions import (AdapterError, AuthenticationError,
20
- RateLimitError)
19
+ from ...core.exceptions import AdapterError, AuthenticationError, RateLimitError
21
20
 
22
21
 
23
22
  class LinearGraphQLClient:
@@ -6,8 +6,7 @@ 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,
10
- get_universal_state)
9
+ from .types import extract_linear_metadata, get_universal_priority, get_universal_state
11
10
 
12
11
 
13
12
  def map_linear_issue_to_task(issue_data: dict[str, Any]) -> Task:
@@ -197,8 +197,7 @@ 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 \
201
- _build_adapter_config_from_env_vars
200
+ from ..mcp.server import _build_adapter_config_from_env_vars
202
201
 
203
202
  config = _build_adapter_config_from_env_vars(adapter_type, {})
204
203
  else:
@@ -385,8 +384,7 @@ def get_adapter_status() -> dict[str, Any]:
385
384
  adapter_type = primary.adapter_type
386
385
  status["configuration_source"] = primary.found_in
387
386
  # Build basic config
388
- from ..mcp.server import \
389
- _build_adapter_config_from_env_vars
387
+ from ..mcp.server import _build_adapter_config_from_env_vars
390
388
 
391
389
  config = _build_adapter_config_from_env_vars(adapter_type, {})
392
390
  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,9 +8,15 @@ 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 (AdapterConfig, AdapterType, ConfigResolver,
12
- ConfigValidator, HybridConfig, SyncStrategy,
13
- TicketerConfig)
11
+ from ..core.project_config import (
12
+ AdapterConfig,
13
+ AdapterType,
14
+ ConfigResolver,
15
+ ConfigValidator,
16
+ HybridConfig,
17
+ SyncStrategy,
18
+ TicketerConfig,
19
+ )
14
20
 
15
21
  console = Console()
16
22
 
@@ -6,8 +6,12 @@ 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 (AdapterConfig, ConfigResolver,
10
- ConfigValidator, TicketerConfig)
9
+ from ..core.project_config import (
10
+ AdapterConfig,
11
+ ConfigResolver,
12
+ ConfigValidator,
13
+ TicketerConfig,
14
+ )
11
15
 
12
16
  console = Console()
13
17
  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())}"