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.
- mcp_ticketer/__init__.py +3 -12
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +2 -9
- mcp_ticketer/adapters/github.py +2 -1
- mcp_ticketer/adapters/jira.py +2 -1
- mcp_ticketer/adapters/linear/adapter.py +28 -25
- mcp_ticketer/adapters/linear/client.py +2 -1
- mcp_ticketer/adapters/linear/mappers.py +2 -1
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +35 -15
- mcp_ticketer/cli/codex_configure.py +38 -31
- mcp_ticketer/cli/configure.py +3 -9
- mcp_ticketer/cli/discover.py +2 -6
- mcp_ticketer/cli/gemini_configure.py +38 -25
- mcp_ticketer/cli/main.py +4 -3
- mcp_ticketer/cli/mcp_configure.py +115 -78
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/core/__init__.py +2 -1
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +21 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/{server.py → server/main.py} +15 -35
- mcp_ticketer/mcp/{tools → server/tools}/__init__.py +7 -9
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/manager.py +10 -46
- mcp_ticketer/queue/ticket_registry.py +5 -5
- {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/METADATA +13 -4
- mcp_ticketer-0.4.8.dist-info/RECORD +77 -0
- mcp_ticketer-0.4.4.dist-info/RECORD +0 -73
- /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
- /mcp_ticketer/mcp/{dto.py → server/dto.py} +0 -0
- /mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +0 -0
- /mcp_ticketer/mcp/{server_sdk.py → server/server_sdk.py} +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/attachment_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/bulk_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/comment_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/hierarchy_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/pr_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/search_tools.py +0 -0
- /mcp_ticketer/mcp/{tools → server/tools}/ticket_tools.py +0 -0
- {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.4.dist-info → mcp_ticketer-0.4.8.dist-info}/top_level.txt +0 -0
|
@@ -2,65 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
import shutil
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
10
|
+
from .python_detection import get_mcp_ticketer_python
|
|
11
|
+
|
|
11
12
|
console = Console()
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
-
"""
|
|
15
|
+
def load_env_file(env_path: Path) -> dict[str, str]:
|
|
16
|
+
"""Load environment variables from .env file.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
Path to
|
|
18
|
+
Args:
|
|
19
|
+
env_path: Path to .env file
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
Returns:
|
|
22
|
+
Dict of environment variable key-value pairs
|
|
22
23
|
|
|
23
24
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
package_path / ".venv" / "bin" / "mcp-ticketer",
|
|
42
|
-
package_path / "test_venv" / "bin" / "mcp-ticketer",
|
|
43
|
-
# System installation
|
|
44
|
-
Path.home() / ".local" / "bin" / "mcp-ticketer",
|
|
45
|
-
# pipx installation
|
|
46
|
-
Path.home()
|
|
47
|
-
/ ".local"
|
|
48
|
-
/ "pipx"
|
|
49
|
-
/ "venvs"
|
|
50
|
-
/ "mcp-ticketer"
|
|
51
|
-
/ "bin"
|
|
52
|
-
/ "mcp-ticketer",
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
# Check possible paths
|
|
56
|
-
for path in possible_paths:
|
|
57
|
-
if path.exists():
|
|
58
|
-
return str(path.resolve())
|
|
59
|
-
|
|
60
|
-
raise FileNotFoundError(
|
|
61
|
-
"Could not find mcp-ticketer binary. Please ensure mcp-ticketer is installed.\n"
|
|
62
|
-
"Install with: pip install mcp-ticketer"
|
|
63
|
-
)
|
|
25
|
+
env_vars = {}
|
|
26
|
+
if not env_path.exists():
|
|
27
|
+
return env_vars
|
|
28
|
+
|
|
29
|
+
with open(env_path) as f:
|
|
30
|
+
for line in f:
|
|
31
|
+
line = line.strip()
|
|
32
|
+
# Skip comments and empty lines
|
|
33
|
+
if not line or line.startswith("#"):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# Parse KEY=VALUE format
|
|
37
|
+
if "=" in line:
|
|
38
|
+
key, value = line.split("=", 1)
|
|
39
|
+
env_vars[key.strip()] = value.strip()
|
|
40
|
+
|
|
41
|
+
return env_vars
|
|
64
42
|
|
|
65
43
|
|
|
66
44
|
def load_project_config() -> dict:
|
|
@@ -129,8 +107,8 @@ def find_claude_mcp_config(global_config: bool = False) -> Path:
|
|
|
129
107
|
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
130
108
|
)
|
|
131
109
|
else:
|
|
132
|
-
# Project-level configuration
|
|
133
|
-
config_path = Path.cwd() / ".
|
|
110
|
+
# Project-level configuration for Claude Code
|
|
111
|
+
config_path = Path.cwd() / ".claude" / "settings.local.json"
|
|
134
112
|
|
|
135
113
|
return config_path
|
|
136
114
|
|
|
@@ -149,7 +127,7 @@ def load_claude_mcp_config(config_path: Path) -> dict:
|
|
|
149
127
|
with open(config_path) as f:
|
|
150
128
|
return json.load(f)
|
|
151
129
|
|
|
152
|
-
# Return empty structure
|
|
130
|
+
# Return empty structure (Claude Code uses mcpServers key)
|
|
153
131
|
return {"mcpServers": {}}
|
|
154
132
|
|
|
155
133
|
|
|
@@ -170,28 +148,37 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
|
|
|
170
148
|
|
|
171
149
|
|
|
172
150
|
def create_mcp_server_config(
|
|
173
|
-
|
|
151
|
+
python_path: str, project_config: dict, project_path: str | None = None
|
|
174
152
|
) -> dict:
|
|
175
153
|
"""Create MCP server configuration for mcp-ticketer.
|
|
176
154
|
|
|
177
155
|
Args:
|
|
178
|
-
|
|
156
|
+
python_path: Path to Python executable in mcp-ticketer venv
|
|
179
157
|
project_config: Project configuration from .mcp-ticketer/config.json
|
|
180
|
-
|
|
158
|
+
project_path: Project directory path (optional)
|
|
181
159
|
|
|
182
160
|
Returns:
|
|
183
|
-
MCP server configuration dict
|
|
161
|
+
MCP server configuration dict matching Claude Code stdio pattern
|
|
184
162
|
|
|
185
163
|
"""
|
|
164
|
+
# Ensure python3 is used (not python)
|
|
165
|
+
if python_path.endswith("/python"):
|
|
166
|
+
python_path = python_path.replace("/python", "/python3")
|
|
167
|
+
|
|
168
|
+
# Use module invocation pattern: python -m mcp_ticketer.mcp.server
|
|
169
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
170
|
+
|
|
171
|
+
# Add project path if provided
|
|
172
|
+
if project_path:
|
|
173
|
+
args.append(project_path)
|
|
174
|
+
|
|
175
|
+
# REQUIRED: Add "type": "stdio" for Claude Code compatibility
|
|
186
176
|
config = {
|
|
187
|
-
"
|
|
188
|
-
"
|
|
177
|
+
"type": "stdio",
|
|
178
|
+
"command": python_path,
|
|
179
|
+
"args": args,
|
|
189
180
|
}
|
|
190
181
|
|
|
191
|
-
# Add working directory if provided
|
|
192
|
-
if cwd:
|
|
193
|
-
config["cwd"] = cwd
|
|
194
|
-
|
|
195
182
|
# Add environment variables based on adapter
|
|
196
183
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
197
184
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -199,15 +186,49 @@ def create_mcp_server_config(
|
|
|
199
186
|
|
|
200
187
|
env_vars = {}
|
|
201
188
|
|
|
202
|
-
# Add
|
|
189
|
+
# Add PYTHONPATH for project context
|
|
190
|
+
if project_path:
|
|
191
|
+
env_vars["PYTHONPATH"] = project_path
|
|
192
|
+
|
|
193
|
+
# Add MCP_TICKETER_ADAPTER to identify which adapter to use
|
|
194
|
+
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
195
|
+
|
|
196
|
+
# Load environment variables from .env.local if it exists
|
|
197
|
+
if project_path:
|
|
198
|
+
env_file_path = Path(project_path) / ".env.local"
|
|
199
|
+
env_file_vars = load_env_file(env_file_path)
|
|
200
|
+
|
|
201
|
+
# Add relevant adapter-specific vars from .env.local
|
|
202
|
+
adapter_env_keys = {
|
|
203
|
+
"linear": ["LINEAR_API_KEY", "LINEAR_TEAM_ID", "LINEAR_TEAM_KEY"],
|
|
204
|
+
"github": ["GITHUB_TOKEN", "GITHUB_OWNER", "GITHUB_REPO"],
|
|
205
|
+
"jira": [
|
|
206
|
+
"JIRA_ACCESS_USER",
|
|
207
|
+
"JIRA_ACCESS_TOKEN",
|
|
208
|
+
"JIRA_ORGANIZATION_ID",
|
|
209
|
+
"JIRA_URL",
|
|
210
|
+
"JIRA_EMAIL",
|
|
211
|
+
"JIRA_API_TOKEN",
|
|
212
|
+
],
|
|
213
|
+
"aitrackdown": [], # No specific env vars needed
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Include adapter-specific env vars from .env.local
|
|
217
|
+
for key in adapter_env_keys.get(adapter, []):
|
|
218
|
+
if key in env_file_vars:
|
|
219
|
+
env_vars[key] = env_file_vars[key]
|
|
220
|
+
|
|
221
|
+
# Fallback: Add adapter-specific environment variables from project config
|
|
203
222
|
if adapter == "linear" and "api_key" in adapter_config:
|
|
204
|
-
|
|
223
|
+
if "LINEAR_API_KEY" not in env_vars:
|
|
224
|
+
env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
|
|
205
225
|
elif adapter == "github" and "token" in adapter_config:
|
|
206
|
-
|
|
226
|
+
if "GITHUB_TOKEN" not in env_vars:
|
|
227
|
+
env_vars["GITHUB_TOKEN"] = adapter_config["token"]
|
|
207
228
|
elif adapter == "jira":
|
|
208
|
-
if "api_token" in adapter_config:
|
|
229
|
+
if "api_token" in adapter_config and "JIRA_API_TOKEN" not in env_vars:
|
|
209
230
|
env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
|
|
210
|
-
if "email" in adapter_config:
|
|
231
|
+
if "email" in adapter_config and "JIRA_EMAIL" not in env_vars:
|
|
211
232
|
env_vars["JIRA_EMAIL"] = adapter_config["email"]
|
|
212
233
|
|
|
213
234
|
if env_vars:
|
|
@@ -284,18 +305,31 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
284
305
|
force: Overwrite existing configuration
|
|
285
306
|
|
|
286
307
|
Raises:
|
|
287
|
-
FileNotFoundError: If
|
|
308
|
+
FileNotFoundError: If Python executable or project config not found
|
|
288
309
|
ValueError: If configuration is invalid
|
|
289
310
|
|
|
290
311
|
"""
|
|
291
|
-
#
|
|
292
|
-
|
|
312
|
+
# Determine project path for venv detection
|
|
313
|
+
project_path = Path.cwd() if not global_config else None
|
|
314
|
+
|
|
315
|
+
# Step 1: Find Python executable (project-specific if available)
|
|
316
|
+
console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
|
|
293
317
|
try:
|
|
294
|
-
|
|
295
|
-
console.print(f"[green]✓[/green] Found: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
318
|
+
python_path = get_mcp_ticketer_python(project_path=project_path)
|
|
319
|
+
console.print(f"[green]✓[/green] Found: {python_path}")
|
|
320
|
+
|
|
321
|
+
# Show if using project venv or fallback
|
|
322
|
+
if project_path and str(project_path / ".venv") in python_path:
|
|
323
|
+
console.print("[dim]Using project-specific venv[/dim]")
|
|
324
|
+
else:
|
|
325
|
+
console.print("[dim]Using pipx/system Python[/dim]")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
console.print(f"[red]✗[/red] Could not find Python executable: {e}")
|
|
328
|
+
raise FileNotFoundError(
|
|
329
|
+
"Could not find mcp-ticketer Python executable. "
|
|
330
|
+
"Please ensure mcp-ticketer is installed.\n"
|
|
331
|
+
"Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
|
|
332
|
+
)
|
|
299
333
|
|
|
300
334
|
# Step 2: Load project configuration
|
|
301
335
|
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
@@ -327,9 +361,11 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
327
361
|
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
328
362
|
|
|
329
363
|
# Step 6: Create mcp-ticketer server config
|
|
330
|
-
|
|
364
|
+
project_path = str(Path.cwd()) if not global_config else None
|
|
331
365
|
server_config = create_mcp_server_config(
|
|
332
|
-
|
|
366
|
+
python_path=python_path,
|
|
367
|
+
project_config=project_config,
|
|
368
|
+
project_path=project_path,
|
|
333
369
|
)
|
|
334
370
|
|
|
335
371
|
# Step 7: Update MCP configuration
|
|
@@ -348,9 +384,10 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
348
384
|
console.print("\n[bold]Configuration Details:[/bold]")
|
|
349
385
|
console.print(" Server name: mcp-ticketer")
|
|
350
386
|
console.print(f" Adapter: {adapter}")
|
|
351
|
-
console.print(f"
|
|
352
|
-
|
|
353
|
-
|
|
387
|
+
console.print(f" Python: {python_path}")
|
|
388
|
+
console.print(" Command: python -m mcp_ticketer.mcp.server")
|
|
389
|
+
if project_path:
|
|
390
|
+
console.print(f" Project path: {project_path}")
|
|
354
391
|
if "env" in server_config:
|
|
355
392
|
console.print(
|
|
356
393
|
f" Environment variables: {list(server_config['env'].keys())}"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Reliable Python executable detection for mcp-ticketer.
|
|
2
|
+
|
|
3
|
+
This module provides reliable detection of the Python executable for mcp-ticketer
|
|
4
|
+
across different installation methods (pipx, pip, uv, direct venv).
|
|
5
|
+
|
|
6
|
+
The module follows the proven pattern from mcp-vector-search:
|
|
7
|
+
- Detect venv Python path reliably
|
|
8
|
+
- Use `python -m mcp_ticketer.mcp.server` instead of binary paths
|
|
9
|
+
- Support multiple installation methods transparently
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_mcp_ticketer_python(project_path: Path | None = None) -> str:
|
|
19
|
+
"""Get the correct Python executable for mcp-ticketer MCP server.
|
|
20
|
+
|
|
21
|
+
This function follows the mcp-vector-search pattern of using project-specific
|
|
22
|
+
venv Python for proper project isolation and dependency management.
|
|
23
|
+
|
|
24
|
+
Detection priority:
|
|
25
|
+
1. Project-local venv (.venv/bin/python) if project_path provided
|
|
26
|
+
2. Current Python executable if in pipx venv
|
|
27
|
+
3. Python from mcp-ticketer binary shebang
|
|
28
|
+
4. Current Python executable (fallback)
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
project_path: Optional project directory path to check for local venv
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to Python executable
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> # With project venv
|
|
38
|
+
>>> python_path = get_mcp_ticketer_python(Path("/home/user/my-project"))
|
|
39
|
+
>>> # Returns: "/home/user/my-project/.venv/bin/python"
|
|
40
|
+
|
|
41
|
+
>>> # Without project path (fallback to pipx)
|
|
42
|
+
>>> python_path = get_mcp_ticketer_python()
|
|
43
|
+
>>> # Returns: "/Users/user/.local/pipx/venvs/mcp-ticketer/bin/python"
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
# Priority 1: Check for project-local venv
|
|
47
|
+
if project_path:
|
|
48
|
+
project_venv_python = project_path / ".venv" / "bin" / "python"
|
|
49
|
+
if project_venv_python.exists():
|
|
50
|
+
return str(project_venv_python)
|
|
51
|
+
|
|
52
|
+
current_executable = sys.executable
|
|
53
|
+
|
|
54
|
+
# Priority 2: Check if we're in a pipx venv
|
|
55
|
+
if "/pipx/venvs/" in current_executable:
|
|
56
|
+
return current_executable
|
|
57
|
+
|
|
58
|
+
# Priority 3: Check mcp-ticketer binary shebang
|
|
59
|
+
mcp_ticketer_path = shutil.which("mcp-ticketer")
|
|
60
|
+
if mcp_ticketer_path:
|
|
61
|
+
try:
|
|
62
|
+
with open(mcp_ticketer_path) as f:
|
|
63
|
+
first_line = f.readline().strip()
|
|
64
|
+
if first_line.startswith("#!") and "python" in first_line:
|
|
65
|
+
python_path = first_line[2:].strip()
|
|
66
|
+
if os.path.exists(python_path):
|
|
67
|
+
return python_path
|
|
68
|
+
except OSError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# Priority 4: Fallback to current Python
|
|
72
|
+
return current_executable
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_mcp_server_command(project_path: str | None = None) -> tuple[str, list[str]]:
|
|
76
|
+
"""Get the complete command to run the MCP server.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
project_path: Optional project path to pass as argument and check for venv
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Tuple of (python_executable, args_list)
|
|
83
|
+
Example: ("/path/to/python", ["-m", "mcp_ticketer.mcp.server", "/project/path"])
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
>>> python, args = get_mcp_server_command("/home/user/project")
|
|
87
|
+
>>> # python: "/home/user/project/.venv/bin/python" (if .venv exists)
|
|
88
|
+
>>> # args: ["-m", "mcp_ticketer.mcp.server", "/home/user/project"]
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
# Convert project_path to Path object for venv detection
|
|
92
|
+
project_path_obj = Path(project_path) if project_path else None
|
|
93
|
+
python_path = get_mcp_ticketer_python(project_path=project_path_obj)
|
|
94
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
95
|
+
|
|
96
|
+
if project_path:
|
|
97
|
+
args.append(str(project_path))
|
|
98
|
+
|
|
99
|
+
return python_path, args
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def validate_python_executable(python_path: str) -> bool:
|
|
103
|
+
"""Validate that a Python executable can import mcp_ticketer.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
python_path: Path to Python executable to validate
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if Python can import mcp_ticketer, False otherwise
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> is_valid = validate_python_executable("/usr/bin/python3")
|
|
113
|
+
>>> # Returns: False (system Python doesn't have mcp_ticketer)
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
import subprocess
|
|
118
|
+
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
[python_path, "-c", "import mcp_ticketer.mcp.server"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
timeout=5,
|
|
123
|
+
)
|
|
124
|
+
return result.returncode == 0
|
|
125
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
126
|
+
return False
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Core models and abstractions for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
from .adapter import BaseAdapter
|
|
4
|
-
from .models import Attachment, Comment, Epic, Priority, Task, TicketState,
|
|
4
|
+
from .models import (Attachment, Comment, Epic, Priority, Task, TicketState,
|
|
5
|
+
TicketType)
|
|
5
6
|
from .registry import AdapterRegistry
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
mcp_ticketer/mcp/__init__.py
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
"""MCP server implementation for ticket management."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .server.main 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 before
|
|
27
|
+
runpy could execute it as __main__.
|
|
28
|
+
"""
|
|
29
|
+
if name == "MCPTicketServer":
|
|
30
|
+
from .server.main 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()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""MCP Server package for mcp-ticketer.
|
|
2
|
+
|
|
3
|
+
This package provides the FastMCP server implementation for ticket management
|
|
4
|
+
operations via the Model Context Protocol (MCP).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .main import main
|
|
11
|
+
|
|
12
|
+
__all__ = ["main"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name: str):
|
|
16
|
+
"""Lazy import to avoid premature module loading."""
|
|
17
|
+
if name == "main":
|
|
18
|
+
from .main import main
|
|
19
|
+
|
|
20
|
+
return main
|
|
21
|
+
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 .main 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()
|
|
@@ -11,41 +11,21 @@ from dotenv import load_dotenv
|
|
|
11
11
|
# Import adapters module to trigger registration
|
|
12
12
|
import mcp_ticketer.adapters # noqa: F401
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
MSG_MISSING_TITLE,
|
|
30
|
-
MSG_NO_TICKETS_PROVIDED,
|
|
31
|
-
MSG_NO_UPDATES_PROVIDED,
|
|
32
|
-
MSG_TICKET_NOT_FOUND,
|
|
33
|
-
MSG_TRANSITION_FAILED,
|
|
34
|
-
MSG_UNKNOWN_METHOD,
|
|
35
|
-
MSG_UNKNOWN_OPERATION,
|
|
36
|
-
MSG_UPDATE_FAILED,
|
|
37
|
-
SERVER_NAME,
|
|
38
|
-
SERVER_VERSION,
|
|
39
|
-
STATUS_COMPLETED,
|
|
40
|
-
STATUS_ERROR,
|
|
41
|
-
)
|
|
42
|
-
from .dto import (
|
|
43
|
-
CreateEpicRequest,
|
|
44
|
-
CreateIssueRequest,
|
|
45
|
-
CreateTaskRequest,
|
|
46
|
-
CreateTicketRequest,
|
|
47
|
-
ReadTicketRequest,
|
|
48
|
-
)
|
|
14
|
+
from ...core import AdapterRegistry
|
|
15
|
+
from ...core.models import (Comment, Epic, Priority, SearchQuery, Task,
|
|
16
|
+
TicketState)
|
|
17
|
+
from .constants import (DEFAULT_BASE_PATH, DEFAULT_LIMIT, DEFAULT_MAX_DEPTH,
|
|
18
|
+
DEFAULT_OFFSET, ERROR_INTERNAL, ERROR_METHOD_NOT_FOUND,
|
|
19
|
+
ERROR_PARSE, JSONRPC_VERSION, MCP_PROTOCOL_VERSION,
|
|
20
|
+
MSG_EPIC_NOT_FOUND, MSG_INTERNAL_ERROR,
|
|
21
|
+
MSG_MISSING_TICKET_ID, MSG_MISSING_TITLE,
|
|
22
|
+
MSG_NO_TICKETS_PROVIDED, MSG_NO_UPDATES_PROVIDED,
|
|
23
|
+
MSG_TICKET_NOT_FOUND, MSG_TRANSITION_FAILED,
|
|
24
|
+
MSG_UNKNOWN_METHOD, MSG_UNKNOWN_OPERATION,
|
|
25
|
+
MSG_UPDATE_FAILED, SERVER_NAME, SERVER_VERSION,
|
|
26
|
+
STATUS_COMPLETED, STATUS_ERROR)
|
|
27
|
+
from .dto import (CreateEpicRequest, CreateIssueRequest, CreateTaskRequest,
|
|
28
|
+
CreateTicketRequest, ReadTicketRequest)
|
|
49
29
|
from .response_builder import ResponseBuilder
|
|
50
30
|
|
|
51
31
|
# Load environment variables early (prioritize .env.local)
|
|
@@ -17,15 +17,13 @@ Modules:
|
|
|
17
17
|
|
|
18
18
|
# Import all tool modules to register them with FastMCP
|
|
19
19
|
# Order matters - import core functionality first
|
|
20
|
-
from . import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ticket_tools, # noqa: F401
|
|
28
|
-
)
|
|
20
|
+
from . import attachment_tools # noqa: F401
|
|
21
|
+
from . import bulk_tools # noqa: F401
|
|
22
|
+
from . import comment_tools # noqa: F401
|
|
23
|
+
from . import hierarchy_tools # noqa: F401
|
|
24
|
+
from . import pr_tools # noqa: F401
|
|
25
|
+
from . import search_tools # noqa: F401
|
|
26
|
+
from . import ticket_tools # noqa: F401
|
|
29
27
|
|
|
30
28
|
__all__ = [
|
|
31
29
|
"ticket_tools",
|