mcp-ticketer 0.4.4__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.

@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.4.5"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -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())}"
@@ -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())}"
@@ -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 find_mcp_ticketer_binary() -> str:
15
- """Find the mcp-ticketer binary path.
15
+ def load_env_file(env_path: Path) -> dict[str, str]:
16
+ """Load environment variables from .env file.
16
17
 
17
- Returns:
18
- Path to mcp-ticketer binary (prefers simple 'mcp-ticketer' if in PATH)
18
+ Args:
19
+ env_path: Path to .env file
19
20
 
20
- Raises:
21
- FileNotFoundError: If binary not found
21
+ Returns:
22
+ Dict of environment variable key-value pairs
22
23
 
23
24
  """
24
- # PRIORITY 1: Check PATH first (like kuzu-memory)
25
- # This allows the system to resolve the binary location
26
- which_result = shutil.which("mcp-ticketer")
27
- if which_result:
28
- # Return just "mcp-ticketer" for PATH-based installations
29
- # This is more portable and matches kuzu-memory's approach
30
- return "mcp-ticketer"
31
-
32
- # FALLBACK: Check development environment
33
- import mcp_ticketer
34
-
35
- package_path = Path(mcp_ticketer.__file__).parent.parent.parent
36
-
37
- # Check for virtual environment bin
38
- possible_paths = [
39
- # Development paths
40
- package_path / "venv" / "bin" / "mcp-ticketer",
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() / ".mcp" / "config.json"
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
- binary_path: str, project_config: dict, cwd: str | None = None
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
- binary_path: Path to mcp-ticketer binary
156
+ python_path: Path to Python executable in mcp-ticketer venv
179
157
  project_config: Project configuration from .mcp-ticketer/config.json
180
- cwd: Working directory for server (optional)
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
- "command": binary_path,
188
- "args": ["mcp", "serve"], # Use 'mcp serve' command to start MCP server
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 adapter-specific environment variables
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
- env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
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
- env_vars["GITHUB_TOKEN"] = adapter_config["token"]
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 binary or project config not found
308
+ FileNotFoundError: If Python executable or project config not found
288
309
  ValueError: If configuration is invalid
289
310
 
290
311
  """
291
- # Step 1: Find mcp-ticketer binary
292
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
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
- binary_path = find_mcp_ticketer_binary()
295
- console.print(f"[green]✓[/green] Found: {binary_path}")
296
- except FileNotFoundError as e:
297
- console.print(f"[red]✗[/red] {e}")
298
- raise
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
- cwd = str(Path.cwd()) if not global_config else None
364
+ project_path = str(Path.cwd()) if not global_config else None
331
365
  server_config = create_mcp_server_config(
332
- binary_path=binary_path, project_config=project_config, cwd=cwd
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" Binary: {binary_path}")
352
- if cwd:
353
- console.print(f" Working directory: {cwd}")
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
@@ -1,5 +1,33 @@
1
1
  """MCP server implementation for ticket management."""
2
2
 
3
- from .server import MCPTicketServer
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .server import MCPTicketServer
4
7
 
5
8
  __all__ = ["MCPTicketServer"]
9
+
10
+
11
+ def __dir__():
12
+ """Return list of available names for dir().
13
+
14
+ This ensures that MCPTicketServer appears in dir() results
15
+ even though it's lazily imported.
16
+ """
17
+ return __all__
18
+
19
+
20
+ def __getattr__(name: str):
21
+ """Lazy import to avoid premature module loading.
22
+
23
+ This prevents the RuntimeWarning when running:
24
+ python -m mcp_ticketer.mcp.server
25
+
26
+ The warning occurred because __init__.py imported server.py before
27
+ runpy could execute it as __main__.
28
+ """
29
+ if name == "MCPTicketServer":
30
+ from .server import MCPTicketServer
31
+
32
+ return MCPTicketServer
33
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,60 @@
1
+ """Main entry point for MCP server module invocation.
2
+
3
+ This module enables running the MCP server via:
4
+ python -m mcp_ticketer.mcp.server [project_path]
5
+
6
+ This is the preferred invocation method for MCP configurations as it:
7
+ - Works reliably across installation methods (pipx, pip, uv)
8
+ - Doesn't depend on binary path detection
9
+ - Follows the proven mcp-vector-search pattern
10
+ """
11
+
12
+ import asyncio
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ from .server import main
17
+
18
+
19
+ def run_server() -> None:
20
+ """Run the MCP server with optional project path argument.
21
+
22
+ Usage:
23
+ python -m mcp_ticketer.mcp.server
24
+ python -m mcp_ticketer.mcp.server /path/to/project
25
+
26
+ Arguments:
27
+ project_path (optional): Path to project directory for context
28
+
29
+ """
30
+ # Check for project path argument
31
+ if len(sys.argv) > 1:
32
+ project_path = Path(sys.argv[1])
33
+
34
+ # Validate project path exists
35
+ if not project_path.exists():
36
+ sys.stderr.write(f"Error: Project path does not exist: {project_path}\n")
37
+ sys.exit(1)
38
+
39
+ # Change to project directory for context
40
+ try:
41
+ import os
42
+
43
+ os.chdir(project_path)
44
+ sys.stderr.write(f"[MCP Server] Working directory: {project_path}\n")
45
+ except OSError as e:
46
+ sys.stderr.write(f"Warning: Could not change to project directory: {e}\n")
47
+
48
+ # Run the async main function
49
+ try:
50
+ asyncio.run(main())
51
+ except KeyboardInterrupt:
52
+ sys.stderr.write("\n[MCP Server] Interrupted by user\n")
53
+ sys.exit(0)
54
+ except Exception as e:
55
+ sys.stderr.write(f"[MCP Server] Fatal error: {e}\n")
56
+ sys.exit(1)
57
+
58
+
59
+ if __name__ == "__main__":
60
+ run_server()
@@ -1,7 +1,9 @@
1
1
  """Async queue system for mcp-ticketer."""
2
2
 
3
- from .manager import WorkerManager
4
3
  from .queue import Queue, QueueItem, QueueStatus
5
4
  from .worker import Worker
6
5
 
6
+ # Import manager last to avoid circular import
7
+ from .manager import WorkerManager
8
+
7
9
  __all__ = ["Queue", "QueueItem", "QueueStatus", "Worker", "WorkerManager"]
@@ -4,14 +4,14 @@ import fcntl
4
4
  import logging
5
5
  import os
6
6
  import subprocess
7
- import sys
8
7
  import time
9
8
  from pathlib import Path
10
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
11
10
 
12
11
  import psutil
13
12
 
14
- from .queue import Queue
13
+ if TYPE_CHECKING:
14
+ from .queue import Queue
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -21,6 +21,9 @@ class WorkerManager:
21
21
 
22
22
  def __init__(self):
23
23
  """Initialize worker manager."""
24
+ # Lazy import to avoid circular dependency
25
+ from .queue import Queue
26
+
24
27
  self.lock_file = Path.home() / ".mcp-ticketer" / "worker.lock"
25
28
  self.pid_file = Path.home() / ".mcp-ticketer" / "worker.pid"
26
29
  self.lock_file.parent.mkdir(parents=True, exist_ok=True)
@@ -123,7 +126,10 @@ class WorkerManager:
123
126
  try:
124
127
  # Start worker in subprocess using the same Python executable as the CLI
125
128
  # This ensures the worker can import mcp_ticketer modules
126
- python_executable = self._get_python_executable()
129
+ # Lazy import to avoid circular dependency
130
+ from ..cli.python_detection import get_mcp_ticketer_python
131
+
132
+ python_executable = get_mcp_ticketer_python()
127
133
  cmd = [python_executable, "-m", "mcp_ticketer.queue.run_worker"]
128
134
 
129
135
  # Prepare environment for subprocess
@@ -320,48 +326,6 @@ class WorkerManager:
320
326
  except (OSError, ValueError):
321
327
  return None
322
328
 
323
- def _get_python_executable(self) -> str:
324
- """Get the correct Python executable for the worker subprocess.
325
-
326
- This ensures the worker uses the same Python environment as the CLI,
327
- which is critical for module imports to work correctly.
328
-
329
- Returns:
330
- Path to Python executable
331
-
332
- """
333
- # First, try to detect if we're running in a pipx environment
334
- # by checking if the current executable is in a pipx venv
335
- current_executable = sys.executable
336
-
337
- # Check if we're in a pipx venv (path contains /pipx/venvs/)
338
- if "/pipx/venvs/" in current_executable:
339
- logger.debug(f"Using pipx Python executable: {current_executable}")
340
- return current_executable
341
-
342
- # Check if we can find the mcp-ticketer executable and extract its Python
343
- import shutil
344
-
345
- mcp_ticketer_path = shutil.which("mcp-ticketer")
346
- if mcp_ticketer_path:
347
- try:
348
- # Read the shebang line to get the Python executable
349
- with open(mcp_ticketer_path) as f:
350
- first_line = f.readline().strip()
351
- if first_line.startswith("#!") and "python" in first_line:
352
- python_path = first_line[2:].strip()
353
- if os.path.exists(python_path):
354
- logger.debug(
355
- f"Using Python from mcp-ticketer shebang: {python_path}"
356
- )
357
- return python_path
358
- except OSError:
359
- pass
360
-
361
- # Fallback to sys.executable
362
- logger.debug(f"Using sys.executable as fallback: {current_executable}")
363
- return current_executable
364
-
365
329
  def _cleanup(self):
366
330
  """Clean up lock and PID files."""
367
331
  self._release_lock()
@@ -202,7 +202,7 @@ class TicketRegistry:
202
202
  return None
203
203
 
204
204
  columns = [desc[0] for desc in cursor.description]
205
- ticket_info = dict(zip(columns, row))
205
+ ticket_info = dict(zip(columns, row, strict=False))
206
206
 
207
207
  # Parse JSON fields
208
208
  if ticket_info.get("ticket_data"):
@@ -236,7 +236,7 @@ class TicketRegistry:
236
236
  columns = [desc[0] for desc in cursor.description]
237
237
 
238
238
  for row in cursor.fetchall():
239
- ticket_info = dict(zip(columns, row))
239
+ ticket_info = dict(zip(columns, row, strict=False))
240
240
 
241
241
  # Parse JSON fields
242
242
  if ticket_info.get("ticket_data"):
@@ -273,7 +273,7 @@ class TicketRegistry:
273
273
  columns = [desc[0] for desc in cursor.description]
274
274
 
275
275
  for row in cursor.fetchall():
276
- ticket_info = dict(zip(columns, row))
276
+ ticket_info = dict(zip(columns, row, strict=False))
277
277
 
278
278
  # Parse JSON fields
279
279
  if ticket_info.get("ticket_data"):
@@ -306,7 +306,7 @@ class TicketRegistry:
306
306
  columns = [desc[0] for desc in cursor.description]
307
307
 
308
308
  for row in cursor.fetchall():
309
- ticket_info = dict(zip(columns, row))
309
+ ticket_info = dict(zip(columns, row, strict=False))
310
310
 
311
311
  # Parse JSON fields
312
312
  if ticket_info.get("ticket_data"):
@@ -436,7 +436,7 @@ class TicketRegistry:
436
436
  columns = [desc[0] for desc in cursor.description]
437
437
 
438
438
  for row in cursor.fetchall():
439
- recovery_info = dict(zip(columns, row))
439
+ recovery_info = dict(zip(columns, row, strict=False))
440
440
  if recovery_info.get("recovery_data"):
441
441
  recovery_info["recovery_data"] = json.loads(
442
442
  recovery_info["recovery_data"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Universal ticket management interface for AI agents with MCP support
5
5
  Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
6
6
  Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
@@ -282,16 +282,25 @@ mcp-ticketer uninstall auggie # Alias for remove
282
282
  {
283
283
  "mcpServers": {
284
284
  "mcp-ticketer": {
285
- "command": "/path/to/mcp-ticketer",
286
- "args": ["serve"],
285
+ "command": "/path/to/venv/bin/python",
286
+ "args": ["-m", "mcp_ticketer.mcp.server", "/absolute/path/to/project"],
287
287
  "env": {
288
- "MCP_TICKETER_ADAPTER": "aitrackdown"
288
+ "MCP_TICKETER_ADAPTER": "aitrackdown",
289
+ "PYTHONPATH": "/absolute/path/to/project"
289
290
  }
290
291
  }
291
292
  }
292
293
  }
293
294
  ```
294
295
 
296
+ **Why this pattern?**
297
+ - **More Reliable**: Uses venv Python directly instead of binary wrapper
298
+ - **Consistent**: Matches proven mcp-vector-search pattern
299
+ - **Universal**: Works across pipx, pip, and uv installations
300
+ - **Better Errors**: Python module invocation provides clearer error messages
301
+
302
+ **Automatic Detection**: The `mcp-ticketer install` commands automatically detect your venv Python and generate the correct configuration.
303
+
295
304
  **See [AI Client Integration Guide](docs/AI_CLIENT_INTEGRATION.md) for client-specific details.**
296
305
 
297
306
  ## ⚙️ Configuration
@@ -1,5 +1,5 @@
1
1
  mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
2
- mcp_ticketer/__version__.py,sha256=Mb9Xgbq06svVZk98uwuhxlw-J1Tb25SzmoU6bCUjQyU,1117
2
+ mcp_ticketer/__version__.py,sha256=vioHFC2eLXv3RlmwMaPO8QGtnhB8gou02LbH7LA6A-U,1117
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
5
5
  mcp_ticketer/adapters/aitrackdown.py,sha256=qCR53JpOVlOHiR_pdjGF2RYOemgkKxIwDTOlpcYF18E,30320
@@ -17,17 +17,18 @@ mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d
17
17
  mcp_ticketer/cache/memory.py,sha256=rWphWZy7XTbHezC7HMRQN9ISUhYo0Pc2OTgLG30vHnI,5047
18
18
  mcp_ticketer/cli/__init__.py,sha256=l9Q8iKmfGkTu0cssHBVqNZTsL4eAtFzOB25AED_0G6g,89
19
19
  mcp_ticketer/cli/adapter_diagnostics.py,sha256=pQDdtDgBwSW04wdFEPVzwbul3KgfB9g6ZMS85qpYulY,14988
20
- mcp_ticketer/cli/auggie_configure.py,sha256=Kd28n16s-X3_KtNfi2fXKij6iK-SnZRHrpgDns3Kq7g,10808
21
- mcp_ticketer/cli/codex_configure.py,sha256=9BoO8E_OypW7fs7UTRu76FMnU68E_HrbbkD696cgH58,11764
20
+ mcp_ticketer/cli/auggie_configure.py,sha256=BA31HvOXljPqi3QMKt5eI5jYUWCnnH00sDqwR3y2nSY,11701
21
+ mcp_ticketer/cli/codex_configure.py,sha256=LLGzsFjCNO3irtabSazCpsZ5eUmG6eAjCNn6B5M4aHQ,12249
22
22
  mcp_ticketer/cli/configure.py,sha256=T4LczvZIc2FZM-joqICL8NpjCeThAUknEhJkHsmpdc8,16158
23
23
  mcp_ticketer/cli/diagnostics.py,sha256=s7P4vDzPpthgiBJfAOXpunlhZ62buHg_BA5W7ghQBqg,29952
24
24
  mcp_ticketer/cli/discover.py,sha256=paG8zElIFEK6lgVD-tHWoFDTuWQ185LirFp0fVlYgB0,13148
25
- mcp_ticketer/cli/gemini_configure.py,sha256=72lXywYJKdGqAhtJpC6fPVYY1T_w7_ND07AfmhumGUc,12010
25
+ mcp_ticketer/cli/gemini_configure.py,sha256=GXbQkfZGsmx_2cJKxH_5z3JdcwpMopF0duI_TzbKip8,12644
26
26
  mcp_ticketer/cli/linear_commands.py,sha256=YnmqRacAfTdF0CLr4BXOxFs3uwm_hVifbGdTe6t2CfA,17273
27
27
  mcp_ticketer/cli/main.py,sha256=DV05n5-JGIjIX1t4-oR7j5tuE4c2Z0of6KVahUzGiJY,89254
28
- mcp_ticketer/cli/mcp_configure.py,sha256=lozgV_VZmOyZfPesg0oan8p_f93kGWyTDhE1byCbv4k,12324
28
+ mcp_ticketer/cli/mcp_configure.py,sha256=dgApMLBApLxU0fvFOyDBkX69GOGVNTXhAKM-hZGw5v8,14142
29
29
  mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
30
30
  mcp_ticketer/cli/platform_commands.py,sha256=pTLRT2wot8dAmy1-roJWWOT0Cxu7j-06BlWDnZ9a4jY,3624
31
+ mcp_ticketer/cli/python_detection.py,sha256=qmhi0CIDKH_AUVGkJ9jyY1zBpx1cwiQNv0vnEvMYDFQ,4272
31
32
  mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
32
33
  mcp_ticketer/cli/simple_health.py,sha256=GlOLRRFoifCna995NoHuKpb3xmFkLi2b3Ke1hyeDvq4,7950
33
34
  mcp_ticketer/cli/ticket_commands.py,sha256=zmtePGhZzhw_r-0xWQMzXSJMo684PlzEN5LYfcR3_dw,26589
@@ -43,7 +44,8 @@ mcp_ticketer/core/mappers.py,sha256=okte6EV_OuPvnM1KXHUcfrpPd7TWnKh44X3_W3HxwiI,
43
44
  mcp_ticketer/core/models.py,sha256=ABKdyAkEkKtMF_d6D8_qRL-2ujz1DshemHSyqTPUthA,14448
44
45
  mcp_ticketer/core/project_config.py,sha256=DmLekuMuOgNtzg-olOU4Utv00DdCH1-CXuoooA-adMs,23609
45
46
  mcp_ticketer/core/registry.py,sha256=gBeXcZ3grHl9gYFbyRp-C4IM7SD_KGTeXT_1jG8XrCc,3470
46
- mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,123
47
+ mcp_ticketer/mcp/__init__.py,sha256=fWcCqL7MxKcsnq8vUqDV-rsxykOo3J5_98bba1oT1dw,857
48
+ mcp_ticketer/mcp/__main__.py,sha256=Fo_5KJOFako2gi1Z1kk5zEt2sGJW6BX6oXlYp7twYTs,1713
47
49
  mcp_ticketer/mcp/constants.py,sha256=EBGsJtBPaTCvAm5rOMknckrXActrNIls7lRklnh1L4s,2072
48
50
  mcp_ticketer/mcp/dto.py,sha256=FR_OBtaxrno8AsHynPwUUW715iAoaBkrr7Ud8HZTQW8,7233
49
51
  mcp_ticketer/mcp/response_builder.py,sha256=DUfe1e0CcXPlepLq-cGH6b_THqoZEynYfVKkZEeLe0M,4933
@@ -57,17 +59,17 @@ mcp_ticketer/mcp/tools/hierarchy_tools.py,sha256=08KxOawUKGZK3kYPZ5w9mRk-lVaSFxQ
57
59
  mcp_ticketer/mcp/tools/pr_tools.py,sha256=SnKJRsvkmmr2eh1qaIqxC0PHD6rVXKg6mkk9Skdpc0Y,4539
58
60
  mcp_ticketer/mcp/tools/search_tools.py,sha256=6qwSBRkGx-zO0rxebeQqMcBj006ZovAgfyLGfw3kxig,6951
59
61
  mcp_ticketer/mcp/tools/ticket_tools.py,sha256=KYMl2h-nf7hZq-kOi1z4H0TEj7MzMklOsRIl4HaP8WQ,8121
60
- mcp_ticketer/queue/__init__.py,sha256=1YIaCpZpFqPcqvDEQXiEvDLiw94DXRdCJkBaVIFQrms,231
62
+ mcp_ticketer/queue/__init__.py,sha256=yQtdFNHhk1G_BHTuYk-Vlu6ZgEQ6Ik994zMH1IGHwbc,279
61
63
  mcp_ticketer/queue/__main__.py,sha256=gc_tE9NUdK07OJfTZuD4t6KeBD_vxFQIhknGTQUG_jk,109
62
64
  mcp_ticketer/queue/health_monitor.py,sha256=TDmPnYuZJb3yHNJlGFvE9UU-LfsKTrC4Vapyvdb3fso,12226
63
- mcp_ticketer/queue/manager.py,sha256=9CO15ORLeedXkUtnU3pn2JQqZDS3flE7Rq1OdkJFl6g,12233
65
+ mcp_ticketer/queue/manager.py,sha256=UHkI0OSDyspY0EM2UVf5E1PjUTmZIcOcgGNovxtuVYs,10732
64
66
  mcp_ticketer/queue/queue.py,sha256=q9HDXgnlwspamMJIeu9og7qONttXHmFZHPSaMtJDPlw,17923
65
67
  mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
66
- mcp_ticketer/queue/ticket_registry.py,sha256=8pHAx1H83CzjcaIzi_Tq3DtbDBDcDJIPUS9ofusTuWA,15421
68
+ mcp_ticketer/queue/ticket_registry.py,sha256=xVg3i7Eb5rtQY-4bbw3zYY1h-C6jF1t1NZEGhObzD8g,15491
67
69
  mcp_ticketer/queue/worker.py,sha256=AJHtpJZEhGoPuCDPXSMsn9DeODelo5f__0C__3zoN08,20970
68
- mcp_ticketer-0.4.4.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
69
- mcp_ticketer-0.4.4.dist-info/METADATA,sha256=mHtcKrZBEaJaUSJOkfYV34WE2D9y3dVbDDJ1vseNf_4,15478
70
- mcp_ticketer-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
71
- mcp_ticketer-0.4.4.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
72
- mcp_ticketer-0.4.4.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
73
- mcp_ticketer-0.4.4.dist-info/RECORD,,
70
+ mcp_ticketer-0.4.5.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
71
+ mcp_ticketer-0.4.5.dist-info/METADATA,sha256=l1eDYLelVRQ1aJldM9nXa0_vvelSgQ6o78smdDlCYjo,16020
72
+ mcp_ticketer-0.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ mcp_ticketer-0.4.5.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
74
+ mcp_ticketer-0.4.5.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
75
+ mcp_ticketer-0.4.5.dist-info/RECORD,,