mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.22__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 +7 -7
- mcp_ticketer/__version__.py +4 -2
- mcp_ticketer/adapters/__init__.py +4 -4
- mcp_ticketer/adapters/aitrackdown.py +54 -38
- mcp_ticketer/adapters/github.py +175 -109
- mcp_ticketer/adapters/hybrid.py +90 -45
- mcp_ticketer/adapters/jira.py +139 -130
- mcp_ticketer/adapters/linear.py +374 -225
- mcp_ticketer/cache/__init__.py +1 -1
- mcp_ticketer/cache/memory.py +14 -15
- mcp_ticketer/cli/__init__.py +1 -1
- mcp_ticketer/cli/configure.py +69 -93
- mcp_ticketer/cli/discover.py +43 -35
- mcp_ticketer/cli/main.py +250 -293
- mcp_ticketer/cli/mcp_configure.py +39 -15
- mcp_ticketer/cli/migrate_config.py +10 -12
- mcp_ticketer/cli/queue_commands.py +21 -58
- mcp_ticketer/cli/utils.py +115 -60
- mcp_ticketer/core/__init__.py +2 -2
- mcp_ticketer/core/adapter.py +36 -30
- mcp_ticketer/core/config.py +113 -77
- mcp_ticketer/core/env_discovery.py +51 -19
- mcp_ticketer/core/http_client.py +46 -29
- mcp_ticketer/core/mappers.py +79 -35
- mcp_ticketer/core/models.py +29 -15
- mcp_ticketer/core/project_config.py +131 -66
- mcp_ticketer/core/registry.py +12 -12
- mcp_ticketer/mcp/__init__.py +1 -1
- mcp_ticketer/mcp/server.py +183 -129
- mcp_ticketer/queue/__init__.py +2 -2
- mcp_ticketer/queue/__main__.py +1 -1
- mcp_ticketer/queue/manager.py +29 -25
- mcp_ticketer/queue/queue.py +144 -82
- mcp_ticketer/queue/run_worker.py +2 -3
- mcp_ticketer/queue/worker.py +48 -33
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/METADATA +1 -1
- mcp_ticketer-0.1.22.dist-info/RECORD +42 -0
- mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/top_level.txt +0 -0
|
@@ -20,9 +20,11 @@ def find_mcp_ticketer_binary() -> str:
|
|
|
20
20
|
|
|
21
21
|
Raises:
|
|
22
22
|
FileNotFoundError: If binary not found
|
|
23
|
+
|
|
23
24
|
"""
|
|
24
25
|
# Check if running from development environment
|
|
25
26
|
import mcp_ticketer
|
|
27
|
+
|
|
26
28
|
package_path = Path(mcp_ticketer.__file__).parent.parent.parent
|
|
27
29
|
|
|
28
30
|
# Check for virtual environment bin
|
|
@@ -34,7 +36,13 @@ def find_mcp_ticketer_binary() -> str:
|
|
|
34
36
|
# System installation
|
|
35
37
|
Path.home() / ".local" / "bin" / "mcp-ticketer",
|
|
36
38
|
# pipx installation
|
|
37
|
-
Path.home()
|
|
39
|
+
Path.home()
|
|
40
|
+
/ ".local"
|
|
41
|
+
/ "pipx"
|
|
42
|
+
/ "venvs"
|
|
43
|
+
/ "mcp-ticketer"
|
|
44
|
+
/ "bin"
|
|
45
|
+
/ "mcp-ticketer",
|
|
38
46
|
]
|
|
39
47
|
|
|
40
48
|
# Check PATH
|
|
@@ -62,6 +70,7 @@ def load_project_config() -> dict:
|
|
|
62
70
|
Raises:
|
|
63
71
|
FileNotFoundError: If config not found
|
|
64
72
|
ValueError: If config is invalid
|
|
73
|
+
|
|
65
74
|
"""
|
|
66
75
|
# Check for project-specific config first
|
|
67
76
|
project_config_path = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
@@ -77,7 +86,7 @@ def load_project_config() -> dict:
|
|
|
77
86
|
"Run 'mcp-ticketer init' to create configuration."
|
|
78
87
|
)
|
|
79
88
|
|
|
80
|
-
with open(project_config_path
|
|
89
|
+
with open(project_config_path) as f:
|
|
81
90
|
config = json.load(f)
|
|
82
91
|
|
|
83
92
|
# Validate config
|
|
@@ -95,15 +104,28 @@ def find_claude_mcp_config(global_config: bool = False) -> Path:
|
|
|
95
104
|
|
|
96
105
|
Returns:
|
|
97
106
|
Path to MCP configuration file
|
|
107
|
+
|
|
98
108
|
"""
|
|
99
109
|
if global_config:
|
|
100
110
|
# Claude Desktop configuration
|
|
101
111
|
if sys.platform == "darwin": # macOS
|
|
102
|
-
config_path =
|
|
112
|
+
config_path = (
|
|
113
|
+
Path.home()
|
|
114
|
+
/ "Library"
|
|
115
|
+
/ "Application Support"
|
|
116
|
+
/ "Claude"
|
|
117
|
+
/ "claude_desktop_config.json"
|
|
118
|
+
)
|
|
103
119
|
elif sys.platform == "win32": # Windows
|
|
104
|
-
config_path =
|
|
120
|
+
config_path = (
|
|
121
|
+
Path(os.environ.get("APPDATA", ""))
|
|
122
|
+
/ "Claude"
|
|
123
|
+
/ "claude_desktop_config.json"
|
|
124
|
+
)
|
|
105
125
|
else: # Linux
|
|
106
|
-
config_path =
|
|
126
|
+
config_path = (
|
|
127
|
+
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
128
|
+
)
|
|
107
129
|
else:
|
|
108
130
|
# Project-level configuration
|
|
109
131
|
config_path = Path.cwd() / ".mcp" / "config.json"
|
|
@@ -119,9 +141,10 @@ def load_claude_mcp_config(config_path: Path) -> dict:
|
|
|
119
141
|
|
|
120
142
|
Returns:
|
|
121
143
|
MCP configuration dict
|
|
144
|
+
|
|
122
145
|
"""
|
|
123
146
|
if config_path.exists():
|
|
124
|
-
with open(config_path
|
|
147
|
+
with open(config_path) as f:
|
|
125
148
|
return json.load(f)
|
|
126
149
|
|
|
127
150
|
# Return empty structure
|
|
@@ -134,6 +157,7 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
|
|
|
134
157
|
Args:
|
|
135
158
|
config_path: Path to MCP config file
|
|
136
159
|
config: Configuration to save
|
|
160
|
+
|
|
137
161
|
"""
|
|
138
162
|
# Ensure directory exists
|
|
139
163
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -144,9 +168,7 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
|
|
|
144
168
|
|
|
145
169
|
|
|
146
170
|
def create_mcp_server_config(
|
|
147
|
-
binary_path: str,
|
|
148
|
-
project_config: dict,
|
|
149
|
-
cwd: Optional[str] = None
|
|
171
|
+
binary_path: str, project_config: dict, cwd: Optional[str] = None
|
|
150
172
|
) -> dict:
|
|
151
173
|
"""Create MCP server configuration for mcp-ticketer.
|
|
152
174
|
|
|
@@ -157,6 +179,7 @@ def create_mcp_server_config(
|
|
|
157
179
|
|
|
158
180
|
Returns:
|
|
159
181
|
MCP server configuration dict
|
|
182
|
+
|
|
160
183
|
"""
|
|
161
184
|
config = {
|
|
162
185
|
"command": binary_path,
|
|
@@ -201,6 +224,7 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
201
224
|
Raises:
|
|
202
225
|
FileNotFoundError: If binary or project config not found
|
|
203
226
|
ValueError: If configuration is invalid
|
|
227
|
+
|
|
204
228
|
"""
|
|
205
229
|
# Step 1: Find mcp-ticketer binary
|
|
206
230
|
console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
|
|
@@ -243,9 +267,7 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
243
267
|
# Step 6: Create mcp-ticketer server config
|
|
244
268
|
cwd = str(Path.cwd()) if not global_config else None
|
|
245
269
|
server_config = create_mcp_server_config(
|
|
246
|
-
binary_path=binary_path,
|
|
247
|
-
project_config=project_config,
|
|
248
|
-
cwd=cwd
|
|
270
|
+
binary_path=binary_path, project_config=project_config, cwd=cwd
|
|
249
271
|
)
|
|
250
272
|
|
|
251
273
|
# Step 7: Update MCP configuration
|
|
@@ -257,18 +279,20 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
257
279
|
# Step 8: Save configuration
|
|
258
280
|
try:
|
|
259
281
|
save_claude_mcp_config(mcp_config_path, mcp_config)
|
|
260
|
-
console.print(
|
|
282
|
+
console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
|
|
261
283
|
console.print(f"[dim]Configuration saved to: {mcp_config_path}[/dim]")
|
|
262
284
|
|
|
263
285
|
# Print configuration details
|
|
264
286
|
console.print("\n[bold]Configuration Details:[/bold]")
|
|
265
|
-
console.print(
|
|
287
|
+
console.print(" Server name: mcp-ticketer")
|
|
266
288
|
console.print(f" Adapter: {adapter}")
|
|
267
289
|
console.print(f" Binary: {binary_path}")
|
|
268
290
|
if cwd:
|
|
269
291
|
console.print(f" Working directory: {cwd}")
|
|
270
292
|
if "env" in server_config:
|
|
271
|
-
console.print(
|
|
293
|
+
console.print(
|
|
294
|
+
f" Environment variables: {list(server_config['env'].keys())}"
|
|
295
|
+
)
|
|
272
296
|
|
|
273
297
|
# Next steps
|
|
274
298
|
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import shutil
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Dict, Any
|
|
7
5
|
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict
|
|
8
7
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
from rich.prompt import Confirm
|
|
11
10
|
|
|
12
11
|
from ..core.project_config import (
|
|
12
|
+
AdapterConfig,
|
|
13
13
|
ConfigResolver,
|
|
14
14
|
TicketerConfig,
|
|
15
|
-
AdapterConfig,
|
|
16
|
-
AdapterType
|
|
17
15
|
)
|
|
18
16
|
|
|
19
17
|
console = Console()
|
|
@@ -24,6 +22,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
|
|
|
24
22
|
|
|
25
23
|
Args:
|
|
26
24
|
dry_run: If True, show what would be done without making changes
|
|
25
|
+
|
|
27
26
|
"""
|
|
28
27
|
resolver = ConfigResolver()
|
|
29
28
|
|
|
@@ -34,7 +33,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
|
|
|
34
33
|
|
|
35
34
|
# Load old config
|
|
36
35
|
try:
|
|
37
|
-
with open(resolver.GLOBAL_CONFIG_PATH
|
|
36
|
+
with open(resolver.GLOBAL_CONFIG_PATH) as f:
|
|
38
37
|
old_config = json.load(f)
|
|
39
38
|
except Exception as e:
|
|
40
39
|
console.print(f"[red]Failed to load config: {e}[/red]")
|
|
@@ -76,8 +75,8 @@ def migrate_config_command(dry_run: bool = False) -> None:
|
|
|
76
75
|
return
|
|
77
76
|
|
|
78
77
|
# Backup old config
|
|
79
|
-
backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix(
|
|
80
|
-
timestamp = datetime.now().strftime(
|
|
78
|
+
backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix(".json.bak")
|
|
79
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
81
80
|
backup_path = resolver.GLOBAL_CONFIG_PATH.parent / f"config.{timestamp}.bak"
|
|
82
81
|
|
|
83
82
|
try:
|
|
@@ -90,7 +89,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
|
|
|
90
89
|
# Save new config
|
|
91
90
|
try:
|
|
92
91
|
resolver.save_global_config(new_config)
|
|
93
|
-
console.print(
|
|
92
|
+
console.print("[green]✓[/green] Migration complete!")
|
|
94
93
|
console.print(f"[dim]New config saved to: {resolver.GLOBAL_CONFIG_PATH}[/dim]")
|
|
95
94
|
except Exception as e:
|
|
96
95
|
console.print(f"[red]Failed to save new config: {e}[/red]")
|
|
@@ -132,6 +131,7 @@ def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
|
|
|
132
131
|
|
|
133
132
|
Returns:
|
|
134
133
|
New TicketerConfig object
|
|
134
|
+
|
|
135
135
|
"""
|
|
136
136
|
adapters = {}
|
|
137
137
|
default_adapter = "aitrackdown"
|
|
@@ -164,10 +164,7 @@ def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
|
|
|
164
164
|
default_adapter = old_config.get("default_adapter", "aitrackdown")
|
|
165
165
|
|
|
166
166
|
# Create new config
|
|
167
|
-
new_config = TicketerConfig(
|
|
168
|
-
default_adapter=default_adapter,
|
|
169
|
-
adapters=adapters
|
|
170
|
-
)
|
|
167
|
+
new_config = TicketerConfig(default_adapter=default_adapter, adapters=adapters)
|
|
171
168
|
|
|
172
169
|
return new_config
|
|
173
170
|
|
|
@@ -180,6 +177,7 @@ def validate_migrated_config(config: TicketerConfig) -> bool:
|
|
|
180
177
|
|
|
181
178
|
Returns:
|
|
182
179
|
True if valid, False otherwise
|
|
180
|
+
|
|
183
181
|
"""
|
|
184
182
|
from ..core.project_config import ConfigValidator
|
|
185
183
|
|
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
"""Queue-related CLI commands."""
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
3
5
|
import typer
|
|
4
6
|
from rich.console import Console
|
|
5
7
|
from rich.table import Table
|
|
6
|
-
from rich import print as rprint
|
|
7
|
-
from datetime import datetime
|
|
8
8
|
|
|
9
|
-
from ..queue import Queue, QueueStatus,
|
|
9
|
+
from ..queue import Queue, QueueStatus, Worker, WorkerManager
|
|
10
10
|
|
|
11
|
-
app = typer.Typer(
|
|
12
|
-
name="queue",
|
|
13
|
-
help="Queue management commands"
|
|
14
|
-
)
|
|
11
|
+
app = typer.Typer(name="queue", help="Queue management commands")
|
|
15
12
|
console = Console()
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
@app.command("list")
|
|
19
16
|
def list_queue(
|
|
20
|
-
status: QueueStatus = typer.Option(
|
|
21
|
-
|
|
22
|
-
"--status",
|
|
23
|
-
"-s",
|
|
24
|
-
help="Filter by status"
|
|
25
|
-
),
|
|
26
|
-
limit: int = typer.Option(
|
|
27
|
-
25,
|
|
28
|
-
"--limit",
|
|
29
|
-
"-l",
|
|
30
|
-
help="Maximum items to show"
|
|
31
|
-
)
|
|
17
|
+
status: QueueStatus = typer.Option(None, "--status", "-s", help="Filter by status"),
|
|
18
|
+
limit: int = typer.Option(25, "--limit", "-l", help="Maximum items to show"),
|
|
32
19
|
):
|
|
33
20
|
"""List queue items."""
|
|
34
21
|
queue = Queue()
|
|
@@ -67,7 +54,7 @@ def list_queue(
|
|
|
67
54
|
item.adapter,
|
|
68
55
|
status_str,
|
|
69
56
|
created_str,
|
|
70
|
-
str(item.retry_count)
|
|
57
|
+
str(item.retry_count),
|
|
71
58
|
)
|
|
72
59
|
|
|
73
60
|
console.print(table)
|
|
@@ -82,9 +69,7 @@ def list_queue(
|
|
|
82
69
|
|
|
83
70
|
|
|
84
71
|
@app.command("retry")
|
|
85
|
-
def retry_item(
|
|
86
|
-
queue_id: str = typer.Argument(..., help="Queue ID to retry")
|
|
87
|
-
):
|
|
72
|
+
def retry_item(queue_id: str = typer.Argument(..., help="Queue ID to retry")):
|
|
88
73
|
"""Retry a failed queue item."""
|
|
89
74
|
queue = Queue()
|
|
90
75
|
item = queue.get_item(queue_id)
|
|
@@ -94,7 +79,9 @@ def retry_item(
|
|
|
94
79
|
raise typer.Exit(1)
|
|
95
80
|
|
|
96
81
|
if item.status != QueueStatus.FAILED:
|
|
97
|
-
console.print(
|
|
82
|
+
console.print(
|
|
83
|
+
f"[yellow]Item {queue_id} is not failed (status: {item.status})[/yellow]"
|
|
84
|
+
)
|
|
98
85
|
raise typer.Exit(1)
|
|
99
86
|
|
|
100
87
|
# Reset to pending
|
|
@@ -110,23 +97,12 @@ def retry_item(
|
|
|
110
97
|
@app.command("clear")
|
|
111
98
|
def clear_queue(
|
|
112
99
|
status: QueueStatus = typer.Option(
|
|
113
|
-
None,
|
|
114
|
-
"--status",
|
|
115
|
-
"-s",
|
|
116
|
-
help="Clear only items with this status"
|
|
100
|
+
None, "--status", "-s", help="Clear only items with this status"
|
|
117
101
|
),
|
|
118
102
|
days: int = typer.Option(
|
|
119
|
-
7,
|
|
120
|
-
"--days",
|
|
121
|
-
"-d",
|
|
122
|
-
help="Clear items older than this many days"
|
|
103
|
+
7, "--days", "-d", help="Clear items older than this many days"
|
|
123
104
|
),
|
|
124
|
-
confirm: bool = typer.Option(
|
|
125
|
-
False,
|
|
126
|
-
"--yes",
|
|
127
|
-
"-y",
|
|
128
|
-
help="Skip confirmation"
|
|
129
|
-
)
|
|
105
|
+
confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
130
106
|
):
|
|
131
107
|
"""Clear old queue items."""
|
|
132
108
|
queue = Queue()
|
|
@@ -142,14 +118,11 @@ def clear_queue(
|
|
|
142
118
|
raise typer.Exit(0)
|
|
143
119
|
|
|
144
120
|
queue.cleanup_old(days=days)
|
|
145
|
-
console.print(
|
|
121
|
+
console.print("[green]✓[/green] Cleared old queue items")
|
|
146
122
|
|
|
147
123
|
|
|
148
124
|
# Worker commands
|
|
149
|
-
worker_app = typer.Typer(
|
|
150
|
-
name="worker",
|
|
151
|
-
help="Worker management commands"
|
|
152
|
-
)
|
|
125
|
+
worker_app = typer.Typer(name="worker", help="Worker management commands")
|
|
153
126
|
|
|
154
127
|
|
|
155
128
|
@worker_app.command("start")
|
|
@@ -209,7 +182,7 @@ def worker_status():
|
|
|
209
182
|
status = manager.get_status()
|
|
210
183
|
|
|
211
184
|
if status["running"]:
|
|
212
|
-
console.print(
|
|
185
|
+
console.print("[green]● Worker is running[/green]")
|
|
213
186
|
console.print(f" PID: {status.get('pid')}")
|
|
214
187
|
|
|
215
188
|
if "cpu_percent" in status:
|
|
@@ -237,18 +210,8 @@ def worker_status():
|
|
|
237
210
|
|
|
238
211
|
@worker_app.command("logs")
|
|
239
212
|
def worker_logs(
|
|
240
|
-
lines: int = typer.Option(
|
|
241
|
-
|
|
242
|
-
"--lines",
|
|
243
|
-
"-n",
|
|
244
|
-
help="Number of lines to show"
|
|
245
|
-
),
|
|
246
|
-
follow: bool = typer.Option(
|
|
247
|
-
False,
|
|
248
|
-
"--follow",
|
|
249
|
-
"-f",
|
|
250
|
-
help="Follow log output"
|
|
251
|
-
)
|
|
213
|
+
lines: int = typer.Option(50, "--lines", "-n", help="Number of lines to show"),
|
|
214
|
+
follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
|
|
252
215
|
):
|
|
253
216
|
"""View worker logs."""
|
|
254
217
|
import time
|
|
@@ -264,7 +227,7 @@ def worker_logs(
|
|
|
264
227
|
# Follow mode - like tail -f
|
|
265
228
|
console.print("[dim]Following worker logs (Ctrl+C to stop)...[/dim]\n")
|
|
266
229
|
try:
|
|
267
|
-
with open(log_file
|
|
230
|
+
with open(log_file) as f:
|
|
268
231
|
# Go to end of file
|
|
269
232
|
f.seek(0, 2)
|
|
270
233
|
while True:
|
|
@@ -282,4 +245,4 @@ def worker_logs(
|
|
|
282
245
|
|
|
283
246
|
|
|
284
247
|
# Add worker subcommand to queue app
|
|
285
|
-
app.add_typer(worker_app, name="worker")
|
|
248
|
+
app.add_typer(worker_app, name="worker")
|