mcp-ticketer 0.3.2__py3-none-any.whl → 0.3.3__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/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +152 -21
- mcp_ticketer/adapters/github.py +4 -4
- mcp_ticketer/adapters/jira.py +6 -6
- mcp_ticketer/adapters/linear/adapter.py +18 -16
- mcp_ticketer/adapters/linear/client.py +7 -7
- mcp_ticketer/adapters/linear/mappers.py +7 -7
- mcp_ticketer/adapters/linear/types.py +10 -10
- mcp_ticketer/cli/adapter_diagnostics.py +2 -2
- mcp_ticketer/cli/codex_configure.py +6 -6
- mcp_ticketer/cli/diagnostics.py +17 -18
- mcp_ticketer/cli/simple_health.py +5 -10
- mcp_ticketer/core/env_loader.py +13 -13
- mcp_ticketer/core/exceptions.py +5 -5
- mcp_ticketer/mcp/constants.py +58 -0
- mcp_ticketer/mcp/dto.py +195 -0
- mcp_ticketer/mcp/response_builder.py +206 -0
- mcp_ticketer/mcp/server.py +311 -1287
- mcp_ticketer/queue/health_monitor.py +14 -14
- mcp_ticketer/queue/manager.py +59 -15
- mcp_ticketer/queue/queue.py +9 -2
- mcp_ticketer/queue/ticket_registry.py +15 -15
- mcp_ticketer/queue/worker.py +25 -18
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/METADATA +1 -1
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/RECORD +29 -26
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.2.dist-info → mcp_ticketer-0.3.3.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from mcp_ticketer.core.models import Priority, TicketState
|
|
9
9
|
|
|
@@ -12,14 +12,14 @@ class LinearPriorityMapping:
|
|
|
12
12
|
"""Mapping between universal Priority and Linear priority values."""
|
|
13
13
|
|
|
14
14
|
# Linear uses numeric priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
|
|
15
|
-
TO_LINEAR:
|
|
15
|
+
TO_LINEAR: dict[Priority, int] = {
|
|
16
16
|
Priority.CRITICAL: 1, # Urgent
|
|
17
17
|
Priority.HIGH: 2, # High
|
|
18
18
|
Priority.MEDIUM: 3, # Medium
|
|
19
19
|
Priority.LOW: 4, # Low
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
FROM_LINEAR:
|
|
22
|
+
FROM_LINEAR: dict[int, Priority] = {
|
|
23
23
|
0: Priority.LOW, # No priority -> Low
|
|
24
24
|
1: Priority.CRITICAL, # Urgent -> Critical
|
|
25
25
|
2: Priority.HIGH, # High -> High
|
|
@@ -32,7 +32,7 @@ class LinearStateMapping:
|
|
|
32
32
|
"""Mapping between universal TicketState and Linear workflow state types."""
|
|
33
33
|
|
|
34
34
|
# Linear workflow state types
|
|
35
|
-
TO_LINEAR:
|
|
35
|
+
TO_LINEAR: dict[TicketState, str] = {
|
|
36
36
|
TicketState.OPEN: "unstarted",
|
|
37
37
|
TicketState.IN_PROGRESS: "started",
|
|
38
38
|
TicketState.READY: "unstarted", # No direct equivalent, use unstarted
|
|
@@ -43,7 +43,7 @@ class LinearStateMapping:
|
|
|
43
43
|
TicketState.BLOCKED: "unstarted",
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
FROM_LINEAR:
|
|
46
|
+
FROM_LINEAR: dict[str, TicketState] = {
|
|
47
47
|
"backlog": TicketState.OPEN,
|
|
48
48
|
"unstarted": TicketState.OPEN,
|
|
49
49
|
"started": TicketState.IN_PROGRESS,
|
|
@@ -152,7 +152,7 @@ def build_issue_filter(
|
|
|
152
152
|
updated_after: str | None = None,
|
|
153
153
|
due_before: str | None = None,
|
|
154
154
|
include_archived: bool = False,
|
|
155
|
-
) ->
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
156
|
"""Build a Linear issue filter from parameters.
|
|
157
157
|
|
|
158
158
|
Args:
|
|
@@ -171,7 +171,7 @@ def build_issue_filter(
|
|
|
171
171
|
Linear GraphQL filter object
|
|
172
172
|
|
|
173
173
|
"""
|
|
174
|
-
issue_filter:
|
|
174
|
+
issue_filter: dict[str, Any] = {}
|
|
175
175
|
|
|
176
176
|
# Team filter (required for most operations)
|
|
177
177
|
if team_id:
|
|
@@ -218,7 +218,7 @@ def build_project_filter(
|
|
|
218
218
|
state: str | None = None,
|
|
219
219
|
team_id: str | None = None,
|
|
220
220
|
include_completed: bool = True,
|
|
221
|
-
) ->
|
|
221
|
+
) -> dict[str, Any]:
|
|
222
222
|
"""Build a Linear project filter from parameters.
|
|
223
223
|
|
|
224
224
|
Args:
|
|
@@ -230,7 +230,7 @@ def build_project_filter(
|
|
|
230
230
|
Linear GraphQL filter object
|
|
231
231
|
|
|
232
232
|
"""
|
|
233
|
-
project_filter:
|
|
233
|
+
project_filter: dict[str, Any] = {}
|
|
234
234
|
|
|
235
235
|
# Team filter
|
|
236
236
|
if team_id:
|
|
@@ -246,7 +246,7 @@ def build_project_filter(
|
|
|
246
246
|
return project_filter
|
|
247
247
|
|
|
248
248
|
|
|
249
|
-
def extract_linear_metadata(issue_data:
|
|
249
|
+
def extract_linear_metadata(issue_data: dict[str, Any]) -> dict[str, Any]:
|
|
250
250
|
"""Extract Linear-specific metadata from issue data.
|
|
251
251
|
|
|
252
252
|
Args:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Adapter diagnostics and configuration validation."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
@@ -350,7 +350,7 @@ def _provide_recommendations(console: Console) -> None:
|
|
|
350
350
|
console.print("• List tickets: [cyan]mcp-ticketer list[/cyan]")
|
|
351
351
|
|
|
352
352
|
|
|
353
|
-
def get_adapter_status() ->
|
|
353
|
+
def get_adapter_status() -> dict[str, Any]:
|
|
354
354
|
"""Get current adapter status for programmatic use.
|
|
355
355
|
|
|
356
356
|
Returns:
|
|
@@ -6,7 +6,7 @@ Unlike Claude Code and Gemini CLI, there is no project-level configuration suppo
|
|
|
6
6
|
|
|
7
7
|
import sys
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Optional
|
|
10
10
|
|
|
11
11
|
if sys.version_info >= (3, 11):
|
|
12
12
|
import tomllib
|
|
@@ -36,7 +36,7 @@ def find_codex_config() -> Path:
|
|
|
36
36
|
return config_path
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def load_codex_config(config_path: Path) ->
|
|
39
|
+
def load_codex_config(config_path: Path) -> dict[str, Any]:
|
|
40
40
|
"""Load existing Codex configuration or return empty structure.
|
|
41
41
|
|
|
42
42
|
Args:
|
|
@@ -61,7 +61,7 @@ def load_codex_config(config_path: Path) -> Dict[str, Any]:
|
|
|
61
61
|
return {"mcp_servers": {}}
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def save_codex_config(config_path: Path, config:
|
|
64
|
+
def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
|
|
65
65
|
"""Save Codex configuration to TOML file.
|
|
66
66
|
|
|
67
67
|
Args:
|
|
@@ -79,7 +79,7 @@ def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
|
|
|
79
79
|
|
|
80
80
|
def create_codex_server_config(
|
|
81
81
|
binary_path: str, project_config: dict, cwd: Optional[str] = None
|
|
82
|
-
) ->
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
83
|
"""Create Codex MCP server configuration for mcp-ticketer.
|
|
84
84
|
|
|
85
85
|
Args:
|
|
@@ -97,7 +97,7 @@ def create_codex_server_config(
|
|
|
97
97
|
adapter_config = adapters_config.get(adapter, {})
|
|
98
98
|
|
|
99
99
|
# Build environment variables
|
|
100
|
-
env_vars:
|
|
100
|
+
env_vars: dict[str, str] = {}
|
|
101
101
|
|
|
102
102
|
# Add PYTHONPATH if running from development environment
|
|
103
103
|
if cwd:
|
|
@@ -142,7 +142,7 @@ def create_codex_server_config(
|
|
|
142
142
|
|
|
143
143
|
# Create server configuration with Codex-specific structure
|
|
144
144
|
# NOTE: Codex uses nested dict structure for env vars
|
|
145
|
-
config:
|
|
145
|
+
config: dict[str, Any] = {
|
|
146
146
|
"command": binary_path,
|
|
147
147
|
"args": ["serve"],
|
|
148
148
|
"env": env_vars,
|
mcp_ticketer/cli/diagnostics.py
CHANGED
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
@@ -108,7 +108,7 @@ class SystemDiagnostics:
|
|
|
108
108
|
self.queue_available = False
|
|
109
109
|
console.print(f"⚠️ Could not initialize worker manager: {e}")
|
|
110
110
|
|
|
111
|
-
async def run_full_diagnosis(self) ->
|
|
111
|
+
async def run_full_diagnosis(self) -> dict[str, Any]:
|
|
112
112
|
"""Run complete system diagnosis and return detailed report."""
|
|
113
113
|
console.print("\n🔍 [bold blue]MCP Ticketer System Diagnosis[/bold blue]")
|
|
114
114
|
console.print("=" * 60)
|
|
@@ -137,7 +137,7 @@ class SystemDiagnostics:
|
|
|
137
137
|
except ImportError:
|
|
138
138
|
return "unknown"
|
|
139
139
|
|
|
140
|
-
def _get_system_info(self) ->
|
|
140
|
+
def _get_system_info(self) -> dict[str, Any]:
|
|
141
141
|
"""Gather system information."""
|
|
142
142
|
return {
|
|
143
143
|
"python_version": sys.version,
|
|
@@ -150,7 +150,7 @@ class SystemDiagnostics:
|
|
|
150
150
|
),
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
async def _diagnose_configuration(self) ->
|
|
153
|
+
async def _diagnose_configuration(self) -> dict[str, Any]:
|
|
154
154
|
"""Diagnose configuration issues."""
|
|
155
155
|
console.print("\n📋 [yellow]Configuration Analysis[/yellow]")
|
|
156
156
|
|
|
@@ -220,7 +220,7 @@ class SystemDiagnostics:
|
|
|
220
220
|
console.print(f"✅ {len(adapters_config)} adapter(s) configured")
|
|
221
221
|
|
|
222
222
|
# Check each adapter configuration
|
|
223
|
-
for name,
|
|
223
|
+
for name, _adapter_config in adapters_config.items():
|
|
224
224
|
try:
|
|
225
225
|
# Use the same adapter creation approach as working commands
|
|
226
226
|
adapter = CommonPatterns.get_adapter(override_adapter=name)
|
|
@@ -256,7 +256,7 @@ class SystemDiagnostics:
|
|
|
256
256
|
|
|
257
257
|
return config_status
|
|
258
258
|
|
|
259
|
-
async def _diagnose_adapters(self) ->
|
|
259
|
+
async def _diagnose_adapters(self) -> dict[str, Any]:
|
|
260
260
|
"""Diagnose adapter functionality."""
|
|
261
261
|
console.print("\n🔌 [yellow]Adapter Diagnosis[/yellow]")
|
|
262
262
|
|
|
@@ -277,7 +277,6 @@ class SystemDiagnostics:
|
|
|
277
277
|
|
|
278
278
|
for name, adapter_config in adapters_config.items():
|
|
279
279
|
adapter_type = adapter_config.get("type", name)
|
|
280
|
-
config_dict = adapter_config
|
|
281
280
|
|
|
282
281
|
details = {
|
|
283
282
|
"type": adapter_type,
|
|
@@ -327,7 +326,7 @@ class SystemDiagnostics:
|
|
|
327
326
|
|
|
328
327
|
return adapter_status
|
|
329
328
|
|
|
330
|
-
async def _diagnose_queue_system(self) ->
|
|
329
|
+
async def _diagnose_queue_system(self) -> dict[str, Any]:
|
|
331
330
|
"""Diagnose queue system health with active testing."""
|
|
332
331
|
console.print("\n⚡ [yellow]Queue System Diagnosis[/yellow]")
|
|
333
332
|
|
|
@@ -454,7 +453,7 @@ class SystemDiagnostics:
|
|
|
454
453
|
|
|
455
454
|
return queue_status
|
|
456
455
|
|
|
457
|
-
async def _test_worker_startup(self) ->
|
|
456
|
+
async def _test_worker_startup(self) -> dict[str, Any]:
|
|
458
457
|
"""Test starting a queue worker."""
|
|
459
458
|
test_result = {
|
|
460
459
|
"attempted": True,
|
|
@@ -496,7 +495,7 @@ class SystemDiagnostics:
|
|
|
496
495
|
|
|
497
496
|
return test_result
|
|
498
497
|
|
|
499
|
-
async def _test_queue_operations(self) ->
|
|
498
|
+
async def _test_queue_operations(self) -> dict[str, Any]:
|
|
500
499
|
"""Test basic queue operations."""
|
|
501
500
|
test_result = {
|
|
502
501
|
"attempted": True,
|
|
@@ -531,7 +530,7 @@ class SystemDiagnostics:
|
|
|
531
530
|
|
|
532
531
|
return test_result
|
|
533
532
|
|
|
534
|
-
async def _test_basic_queue_functionality(self) ->
|
|
533
|
+
async def _test_basic_queue_functionality(self) -> dict[str, Any]:
|
|
535
534
|
"""Test basic queue functionality in fallback mode."""
|
|
536
535
|
test_result = {
|
|
537
536
|
"attempted": True,
|
|
@@ -572,7 +571,7 @@ class SystemDiagnostics:
|
|
|
572
571
|
|
|
573
572
|
return test_result
|
|
574
573
|
|
|
575
|
-
async def _analyze_recent_logs(self) ->
|
|
574
|
+
async def _analyze_recent_logs(self) -> dict[str, Any]:
|
|
576
575
|
"""Analyze recent log entries for issues."""
|
|
577
576
|
console.print("\n📝 [yellow]Recent Log Analysis[/yellow]")
|
|
578
577
|
|
|
@@ -611,7 +610,7 @@ class SystemDiagnostics:
|
|
|
611
610
|
return log_analysis
|
|
612
611
|
|
|
613
612
|
async def _analyze_log_directory(
|
|
614
|
-
self, log_path: Path, log_analysis:
|
|
613
|
+
self, log_path: Path, log_analysis: dict[str, Any]
|
|
615
614
|
):
|
|
616
615
|
"""Analyze logs in a specific directory."""
|
|
617
616
|
try:
|
|
@@ -624,7 +623,7 @@ class SystemDiagnostics:
|
|
|
624
623
|
except Exception as e:
|
|
625
624
|
self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
|
|
626
625
|
|
|
627
|
-
async def _parse_log_file(self, log_file: Path, log_analysis:
|
|
626
|
+
async def _parse_log_file(self, log_file: Path, log_analysis: dict[str, Any]):
|
|
628
627
|
"""Parse individual log file for issues."""
|
|
629
628
|
try:
|
|
630
629
|
with open(log_file) as f:
|
|
@@ -639,7 +638,7 @@ class SystemDiagnostics:
|
|
|
639
638
|
except Exception as e:
|
|
640
639
|
self.warnings.append(f"Could not parse {log_file}: {str(e)}")
|
|
641
640
|
|
|
642
|
-
async def _analyze_performance(self) ->
|
|
641
|
+
async def _analyze_performance(self) -> dict[str, Any]:
|
|
643
642
|
"""Analyze system performance metrics."""
|
|
644
643
|
console.print("\n⚡ [yellow]Performance Analysis[/yellow]")
|
|
645
644
|
|
|
@@ -651,7 +650,7 @@ class SystemDiagnostics:
|
|
|
651
650
|
|
|
652
651
|
try:
|
|
653
652
|
# Test basic operations performance
|
|
654
|
-
|
|
653
|
+
datetime.now()
|
|
655
654
|
|
|
656
655
|
# Test configuration loading
|
|
657
656
|
config_start = datetime.now()
|
|
@@ -671,7 +670,7 @@ class SystemDiagnostics:
|
|
|
671
670
|
|
|
672
671
|
return performance
|
|
673
672
|
|
|
674
|
-
def _generate_recommendations(self) ->
|
|
673
|
+
def _generate_recommendations(self) -> list[str]:
|
|
675
674
|
"""Generate actionable recommendations based on diagnosis."""
|
|
676
675
|
recommendations = []
|
|
677
676
|
|
|
@@ -706,7 +705,7 @@ class SystemDiagnostics:
|
|
|
706
705
|
|
|
707
706
|
return recommendations
|
|
708
707
|
|
|
709
|
-
def _display_diagnosis_summary(self, report:
|
|
708
|
+
def _display_diagnosis_summary(self, report: dict[str, Any]):
|
|
710
709
|
"""Display a comprehensive diagnosis summary."""
|
|
711
710
|
console.print("\n" + "=" * 60)
|
|
712
711
|
console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
@@ -149,7 +149,7 @@ def simple_health_check() -> int:
|
|
|
149
149
|
return 1
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
def simple_diagnose() ->
|
|
152
|
+
def simple_diagnose() -> dict[str, Any]:
|
|
153
153
|
"""Simple diagnosis that works without full config system."""
|
|
154
154
|
console.print("\n🔍 [bold blue]MCP Ticketer Simple Diagnosis[/bold blue]")
|
|
155
155
|
console.print("=" * 60)
|
|
@@ -168,14 +168,9 @@ def simple_diagnose() -> Dict[str, Any]:
|
|
|
168
168
|
console.print("\n📋 [yellow]Basic System Check[/yellow]")
|
|
169
169
|
|
|
170
170
|
# Python version
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
console.print(f"❌ {issue}")
|
|
175
|
-
else:
|
|
176
|
-
console.print(
|
|
177
|
-
f"✅ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
178
|
-
)
|
|
171
|
+
console.print(
|
|
172
|
+
f"✅ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
173
|
+
)
|
|
179
174
|
|
|
180
175
|
# Installation check
|
|
181
176
|
try:
|
mcp_ticketer/core/env_loader.py
CHANGED
|
@@ -12,7 +12,7 @@ import logging
|
|
|
12
12
|
import os
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Any,
|
|
15
|
+
from typing import Any, Optional
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ class EnvKeyConfig:
|
|
|
22
22
|
"""Configuration for environment variable key aliases."""
|
|
23
23
|
|
|
24
24
|
primary_key: str
|
|
25
|
-
aliases:
|
|
25
|
+
aliases: list[str]
|
|
26
26
|
description: str
|
|
27
27
|
required: bool = False
|
|
28
28
|
default: Optional[str] = None
|
|
@@ -113,7 +113,7 @@ class UnifiedEnvLoader:
|
|
|
113
113
|
|
|
114
114
|
"""
|
|
115
115
|
self.project_root = project_root or self._find_project_root()
|
|
116
|
-
self._env_cache:
|
|
116
|
+
self._env_cache: dict[str, str] = {}
|
|
117
117
|
self._load_env_files()
|
|
118
118
|
|
|
119
119
|
def _find_project_root(self) -> Path:
|
|
@@ -148,7 +148,7 @@ class UnifiedEnvLoader:
|
|
|
148
148
|
"""Load variables from a single .env file."""
|
|
149
149
|
try:
|
|
150
150
|
with open(env_file) as f:
|
|
151
|
-
for
|
|
151
|
+
for _line_num, line in enumerate(f, 1):
|
|
152
152
|
line = line.strip()
|
|
153
153
|
|
|
154
154
|
# Skip empty lines and comments
|
|
@@ -177,7 +177,7 @@ class UnifiedEnvLoader:
|
|
|
177
177
|
logger.warning(f"Failed to load {env_file}: {e}")
|
|
178
178
|
|
|
179
179
|
def get_value(
|
|
180
|
-
self, config_key: str, config: Optional[
|
|
180
|
+
self, config_key: str, config: Optional[dict[str, Any]] = None
|
|
181
181
|
) -> Optional[str]:
|
|
182
182
|
"""Get a configuration value using the key alias system.
|
|
183
183
|
|
|
@@ -230,8 +230,8 @@ class UnifiedEnvLoader:
|
|
|
230
230
|
return None
|
|
231
231
|
|
|
232
232
|
def get_adapter_config(
|
|
233
|
-
self, adapter_name: str, base_config: Optional[
|
|
234
|
-
) ->
|
|
233
|
+
self, adapter_name: str, base_config: Optional[dict[str, Any]] = None
|
|
234
|
+
) -> dict[str, Any]:
|
|
235
235
|
"""Get complete configuration for an adapter with environment variable resolution.
|
|
236
236
|
|
|
237
237
|
Args:
|
|
@@ -264,8 +264,8 @@ class UnifiedEnvLoader:
|
|
|
264
264
|
return config
|
|
265
265
|
|
|
266
266
|
def validate_adapter_config(
|
|
267
|
-
self, adapter_name: str, config:
|
|
268
|
-
) ->
|
|
267
|
+
self, adapter_name: str, config: dict[str, Any]
|
|
268
|
+
) -> list[str]:
|
|
269
269
|
"""Validate that all required configuration is present for an adapter.
|
|
270
270
|
|
|
271
271
|
Args:
|
|
@@ -292,7 +292,7 @@ class UnifiedEnvLoader:
|
|
|
292
292
|
|
|
293
293
|
return missing_keys
|
|
294
294
|
|
|
295
|
-
def get_debug_info(self) ->
|
|
295
|
+
def get_debug_info(self) -> dict[str, Any]:
|
|
296
296
|
"""Get debug information about environment loading."""
|
|
297
297
|
return {
|
|
298
298
|
"project_root": str(self.project_root),
|
|
@@ -319,8 +319,8 @@ def get_env_loader() -> UnifiedEnvLoader:
|
|
|
319
319
|
|
|
320
320
|
|
|
321
321
|
def load_adapter_config(
|
|
322
|
-
adapter_name: str, base_config: Optional[
|
|
323
|
-
) ->
|
|
322
|
+
adapter_name: str, base_config: Optional[dict[str, Any]] = None
|
|
323
|
+
) -> dict[str, Any]:
|
|
324
324
|
"""Convenience function to load adapter configuration with environment variables.
|
|
325
325
|
|
|
326
326
|
Args:
|
|
@@ -334,7 +334,7 @@ def load_adapter_config(
|
|
|
334
334
|
return get_env_loader().get_adapter_config(adapter_name, base_config)
|
|
335
335
|
|
|
336
336
|
|
|
337
|
-
def validate_adapter_config(adapter_name: str, config:
|
|
337
|
+
def validate_adapter_config(adapter_name: str, config: dict[str, Any]) -> list[str]:
|
|
338
338
|
"""Convenience function to validate adapter configuration.
|
|
339
339
|
|
|
340
340
|
Args:
|
mcp_ticketer/core/exceptions.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from .models import TicketState
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ class AdapterError(MCPTicketerError):
|
|
|
20
20
|
self,
|
|
21
21
|
message: str,
|
|
22
22
|
adapter_name: str,
|
|
23
|
-
original_error:
|
|
23
|
+
original_error: Exception | None = None,
|
|
24
24
|
):
|
|
25
25
|
"""Initialize adapter error.
|
|
26
26
|
|
|
@@ -55,8 +55,8 @@ class RateLimitError(AdapterError):
|
|
|
55
55
|
self,
|
|
56
56
|
message: str,
|
|
57
57
|
adapter_name: str,
|
|
58
|
-
retry_after:
|
|
59
|
-
original_error:
|
|
58
|
+
retry_after: int | None = None,
|
|
59
|
+
original_error: Exception | None = None,
|
|
60
60
|
):
|
|
61
61
|
"""Initialize rate limit error.
|
|
62
62
|
|
|
@@ -74,7 +74,7 @@ class RateLimitError(AdapterError):
|
|
|
74
74
|
class ValidationError(MCPTicketerError):
|
|
75
75
|
"""Data validation error."""
|
|
76
76
|
|
|
77
|
-
def __init__(self, message: str, field:
|
|
77
|
+
def __init__(self, message: str, field: str | None = None, value: Any = None):
|
|
78
78
|
"""Initialize validation error.
|
|
79
79
|
|
|
80
80
|
Args:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""MCP server constants and configuration."""
|
|
2
|
+
|
|
3
|
+
# JSON-RPC Protocol
|
|
4
|
+
JSONRPC_VERSION = "2.0"
|
|
5
|
+
MCP_PROTOCOL_VERSION = "2024-11-05"
|
|
6
|
+
|
|
7
|
+
# Server Info
|
|
8
|
+
SERVER_NAME = "mcp-ticketer"
|
|
9
|
+
SERVER_VERSION = "0.3.2"
|
|
10
|
+
|
|
11
|
+
# Status Values
|
|
12
|
+
STATUS_COMPLETED = "completed"
|
|
13
|
+
STATUS_ERROR = "error"
|
|
14
|
+
STATUS_NOT_IMPLEMENTED = "not_implemented"
|
|
15
|
+
|
|
16
|
+
# Error Codes
|
|
17
|
+
ERROR_PARSE = -32700
|
|
18
|
+
ERROR_INVALID_REQUEST = -32600
|
|
19
|
+
ERROR_METHOD_NOT_FOUND = -32601
|
|
20
|
+
ERROR_INVALID_PARAMS = -32602
|
|
21
|
+
ERROR_INTERNAL = -32603
|
|
22
|
+
|
|
23
|
+
# Default Values
|
|
24
|
+
DEFAULT_LIMIT = 10
|
|
25
|
+
DEFAULT_OFFSET = 0
|
|
26
|
+
DEFAULT_PRIORITY = "medium"
|
|
27
|
+
DEFAULT_MAX_DEPTH = 3
|
|
28
|
+
DEFAULT_BASE_PATH = ".aitrackdown"
|
|
29
|
+
|
|
30
|
+
# Response Messages
|
|
31
|
+
MSG_TICKET_NOT_FOUND = "Ticket {ticket_id} not found"
|
|
32
|
+
MSG_UPDATE_FAILED = "Ticket {ticket_id} not found or update failed"
|
|
33
|
+
MSG_TRANSITION_FAILED = "Ticket {ticket_id} not found or transition failed"
|
|
34
|
+
MSG_EPIC_NOT_FOUND = "Epic {epic_id} not found"
|
|
35
|
+
MSG_MISSING_PARENT_ID = "Tasks must have a parent_id (issue identifier)"
|
|
36
|
+
MSG_UNKNOWN_OPERATION = "Unknown comment operation: {operation}"
|
|
37
|
+
MSG_UNKNOWN_METHOD = "Method not found: {method}"
|
|
38
|
+
MSG_INTERNAL_ERROR = "Internal error: {error}"
|
|
39
|
+
MSG_NO_TICKETS_PROVIDED = "No tickets provided for bulk creation"
|
|
40
|
+
MSG_NO_UPDATES_PROVIDED = "No updates provided for bulk operation"
|
|
41
|
+
MSG_MISSING_TITLE = "Ticket {index} missing required 'title' field"
|
|
42
|
+
MSG_MISSING_TICKET_ID = "Update {index} missing required 'ticket_id' field"
|
|
43
|
+
MSG_TICKET_ID_REQUIRED = "ticket_id is required"
|
|
44
|
+
MSG_PR_URL_REQUIRED = "pr_url is required"
|
|
45
|
+
MSG_ATTACHMENT_NOT_IMPLEMENTED = "Attachment functionality not yet implemented"
|
|
46
|
+
MSG_PR_NOT_SUPPORTED = "PR creation not supported for adapter: {adapter}"
|
|
47
|
+
MSG_PR_LINK_NOT_SUPPORTED = "PR linking not supported for adapter: {adapter}"
|
|
48
|
+
MSG_UNKNOWN_TOOL = "Unknown tool: {tool}"
|
|
49
|
+
MSG_GITHUB_CONFIG_REQUIRED = "GitHub owner and repo are required for Linear PR creation"
|
|
50
|
+
|
|
51
|
+
# Attachment Alternative Messages
|
|
52
|
+
ATTACHMENT_ALTERNATIVES = [
|
|
53
|
+
"Add file URLs in comments",
|
|
54
|
+
"Use external file storage",
|
|
55
|
+
]
|
|
56
|
+
ATTACHMENT_NOT_IMPLEMENTED_REASON = (
|
|
57
|
+
"File attachments require adapter-specific implementation"
|
|
58
|
+
)
|