mcp-ticketer 0.1.22__py3-none-any.whl → 0.1.24__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 +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +15 -14
- mcp_ticketer/adapters/github.py +21 -20
- mcp_ticketer/adapters/hybrid.py +13 -12
- mcp_ticketer/adapters/jira.py +32 -27
- mcp_ticketer/adapters/linear.py +29 -26
- mcp_ticketer/cache/memory.py +2 -2
- mcp_ticketer/cli/auggie_configure.py +237 -0
- mcp_ticketer/cli/codex_configure.py +257 -0
- mcp_ticketer/cli/gemini_configure.py +261 -0
- mcp_ticketer/cli/main.py +171 -10
- mcp_ticketer/cli/migrate_config.py +3 -7
- mcp_ticketer/cli/utils.py +8 -8
- mcp_ticketer/core/adapter.py +12 -11
- mcp_ticketer/core/config.py +17 -17
- mcp_ticketer/core/env_discovery.py +24 -24
- mcp_ticketer/core/http_client.py +13 -13
- mcp_ticketer/core/mappers.py +25 -25
- mcp_ticketer/core/models.py +10 -10
- mcp_ticketer/core/project_config.py +25 -22
- mcp_ticketer/core/registry.py +7 -7
- mcp_ticketer/mcp/server.py +18 -18
- mcp_ticketer/queue/manager.py +2 -2
- mcp_ticketer/queue/queue.py +7 -7
- mcp_ticketer/queue/worker.py +8 -8
- {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/METADATA +58 -8
- mcp_ticketer-0.1.24.dist-info/RECORD +45 -0
- mcp_ticketer-0.1.22.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py
CHANGED
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Optional
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from dotenv import load_dotenv
|
|
@@ -59,8 +59,7 @@ def main_callback(
|
|
|
59
59
|
help="Show version and exit",
|
|
60
60
|
),
|
|
61
61
|
):
|
|
62
|
-
"""MCP Ticketer - Universal ticket management interface.
|
|
63
|
-
"""
|
|
62
|
+
"""MCP Ticketer - Universal ticket management interface."""
|
|
64
63
|
pass
|
|
65
64
|
|
|
66
65
|
|
|
@@ -801,7 +800,7 @@ def create(
|
|
|
801
800
|
priority: Priority = typer.Option(
|
|
802
801
|
Priority.MEDIUM, "--priority", "-p", help="Priority level"
|
|
803
802
|
),
|
|
804
|
-
tags: Optional[
|
|
803
|
+
tags: Optional[list[str]] = typer.Option(
|
|
805
804
|
None, "--tag", "-t", help="Tags (can be specified multiple times)"
|
|
806
805
|
),
|
|
807
806
|
assignee: Optional[str] = typer.Option(
|
|
@@ -1008,12 +1007,39 @@ def update(
|
|
|
1008
1007
|
@app.command()
|
|
1009
1008
|
def transition(
|
|
1010
1009
|
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1011
|
-
|
|
1010
|
+
state_positional: Optional[TicketState] = typer.Argument(
|
|
1011
|
+
None, help="Target state (positional - deprecated, use --state instead)"
|
|
1012
|
+
),
|
|
1013
|
+
state: Optional[TicketState] = typer.Option(
|
|
1014
|
+
None, "--state", "-s", help="Target state (recommended)"
|
|
1015
|
+
),
|
|
1012
1016
|
adapter: Optional[AdapterType] = typer.Option(
|
|
1013
1017
|
None, "--adapter", help="Override default adapter"
|
|
1014
1018
|
),
|
|
1015
1019
|
) -> None:
|
|
1016
|
-
"""Change ticket state with validation.
|
|
1020
|
+
"""Change ticket state with validation.
|
|
1021
|
+
|
|
1022
|
+
Examples:
|
|
1023
|
+
# Recommended syntax with flag:
|
|
1024
|
+
mcp-ticketer transition BTA-215 --state done
|
|
1025
|
+
mcp-ticketer transition BTA-215 -s in_progress
|
|
1026
|
+
|
|
1027
|
+
# Legacy positional syntax (still supported):
|
|
1028
|
+
mcp-ticketer transition BTA-215 done
|
|
1029
|
+
|
|
1030
|
+
"""
|
|
1031
|
+
# Determine which state to use (prefer flag over positional)
|
|
1032
|
+
target_state = state if state is not None else state_positional
|
|
1033
|
+
|
|
1034
|
+
if target_state is None:
|
|
1035
|
+
console.print("[red]Error: State is required[/red]")
|
|
1036
|
+
console.print(
|
|
1037
|
+
"Use either:\n"
|
|
1038
|
+
" - Flag syntax (recommended): mcp-ticketer transition TICKET-ID --state STATE\n"
|
|
1039
|
+
" - Positional syntax: mcp-ticketer transition TICKET-ID STATE"
|
|
1040
|
+
)
|
|
1041
|
+
raise typer.Exit(1)
|
|
1042
|
+
|
|
1017
1043
|
# Get the adapter name
|
|
1018
1044
|
config = load_config()
|
|
1019
1045
|
adapter_name = (
|
|
@@ -1025,14 +1051,16 @@ def transition(
|
|
|
1025
1051
|
queue_id = queue.add(
|
|
1026
1052
|
ticket_data={
|
|
1027
1053
|
"ticket_id": ticket_id,
|
|
1028
|
-
"state":
|
|
1054
|
+
"state": (
|
|
1055
|
+
target_state.value if hasattr(target_state, "value") else target_state
|
|
1056
|
+
),
|
|
1029
1057
|
},
|
|
1030
1058
|
adapter=adapter_name,
|
|
1031
1059
|
operation="transition",
|
|
1032
1060
|
)
|
|
1033
1061
|
|
|
1034
1062
|
console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
|
|
1035
|
-
console.print(f" Ticket: {ticket_id} → {
|
|
1063
|
+
console.print(f" Ticket: {ticket_id} → {target_state}")
|
|
1036
1064
|
console.print("[dim]Use 'mcp-ticketer status {queue_id}' to check progress[/dim]")
|
|
1037
1065
|
|
|
1038
1066
|
# Start worker if needed
|
|
@@ -1090,6 +1118,13 @@ app.add_typer(queue_app, name="queue")
|
|
|
1090
1118
|
# Add discover command to main app
|
|
1091
1119
|
app.add_typer(discover_app, name="discover")
|
|
1092
1120
|
|
|
1121
|
+
# Create MCP configuration command group
|
|
1122
|
+
mcp_app = typer.Typer(
|
|
1123
|
+
name="mcp",
|
|
1124
|
+
help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
|
|
1125
|
+
add_completion=False,
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1093
1128
|
|
|
1094
1129
|
@app.command()
|
|
1095
1130
|
def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
|
|
@@ -1205,8 +1240,8 @@ def serve(
|
|
|
1205
1240
|
sys.exit(1)
|
|
1206
1241
|
|
|
1207
1242
|
|
|
1208
|
-
@
|
|
1209
|
-
def
|
|
1243
|
+
@mcp_app.command(name="claude")
|
|
1244
|
+
def mcp_claude(
|
|
1210
1245
|
global_config: bool = typer.Option(
|
|
1211
1246
|
False,
|
|
1212
1247
|
"--global",
|
|
@@ -1224,6 +1259,17 @@ def mcp(
|
|
|
1224
1259
|
|
|
1225
1260
|
By default, configures project-level (.mcp/config.json).
|
|
1226
1261
|
Use --global to configure Claude Desktop instead.
|
|
1262
|
+
|
|
1263
|
+
Examples:
|
|
1264
|
+
# Configure for current project (default)
|
|
1265
|
+
mcp-ticketer mcp claude
|
|
1266
|
+
|
|
1267
|
+
# Configure Claude Desktop globally
|
|
1268
|
+
mcp-ticketer mcp claude --global
|
|
1269
|
+
|
|
1270
|
+
# Force overwrite existing configuration
|
|
1271
|
+
mcp-ticketer mcp claude --force
|
|
1272
|
+
|
|
1227
1273
|
"""
|
|
1228
1274
|
from ..cli.mcp_configure import configure_claude_mcp
|
|
1229
1275
|
|
|
@@ -1234,6 +1280,121 @@ def mcp(
|
|
|
1234
1280
|
raise typer.Exit(1)
|
|
1235
1281
|
|
|
1236
1282
|
|
|
1283
|
+
@mcp_app.command(name="gemini")
|
|
1284
|
+
def mcp_gemini(
|
|
1285
|
+
scope: str = typer.Option(
|
|
1286
|
+
"project",
|
|
1287
|
+
"--scope",
|
|
1288
|
+
"-s",
|
|
1289
|
+
help="Configuration scope: 'project' (default) or 'user'",
|
|
1290
|
+
),
|
|
1291
|
+
force: bool = typer.Option(
|
|
1292
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
1293
|
+
),
|
|
1294
|
+
):
|
|
1295
|
+
"""Configure Gemini CLI to use mcp-ticketer MCP server.
|
|
1296
|
+
|
|
1297
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
1298
|
+
Gemini CLI settings file with mcp-ticketer configuration.
|
|
1299
|
+
|
|
1300
|
+
By default, configures project-level (.gemini/settings.json).
|
|
1301
|
+
Use --scope user to configure user-level (~/.gemini/settings.json).
|
|
1302
|
+
|
|
1303
|
+
Examples:
|
|
1304
|
+
# Configure for current project (default)
|
|
1305
|
+
mcp-ticketer mcp gemini
|
|
1306
|
+
|
|
1307
|
+
# Configure at user level
|
|
1308
|
+
mcp-ticketer mcp gemini --scope user
|
|
1309
|
+
|
|
1310
|
+
# Force overwrite existing configuration
|
|
1311
|
+
mcp-ticketer mcp gemini --force
|
|
1312
|
+
|
|
1313
|
+
"""
|
|
1314
|
+
from ..cli.gemini_configure import configure_gemini_mcp
|
|
1315
|
+
|
|
1316
|
+
# Validate scope parameter
|
|
1317
|
+
if scope not in ["project", "user"]:
|
|
1318
|
+
console.print(
|
|
1319
|
+
f"[red]✗ Invalid scope:[/red] '{scope}'. Must be 'project' or 'user'"
|
|
1320
|
+
)
|
|
1321
|
+
raise typer.Exit(1)
|
|
1322
|
+
|
|
1323
|
+
try:
|
|
1324
|
+
configure_gemini_mcp(scope=scope, force=force) # type: ignore
|
|
1325
|
+
except Exception as e:
|
|
1326
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
1327
|
+
raise typer.Exit(1)
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
@mcp_app.command(name="codex")
|
|
1331
|
+
def mcp_codex(
|
|
1332
|
+
force: bool = typer.Option(
|
|
1333
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
1334
|
+
),
|
|
1335
|
+
):
|
|
1336
|
+
"""Configure Codex CLI to use mcp-ticketer MCP server.
|
|
1337
|
+
|
|
1338
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
1339
|
+
Codex CLI config.toml with mcp-ticketer configuration.
|
|
1340
|
+
|
|
1341
|
+
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
1342
|
+
There is no project-level configuration support. After configuration,
|
|
1343
|
+
you must restart Codex CLI for changes to take effect.
|
|
1344
|
+
|
|
1345
|
+
Examples:
|
|
1346
|
+
# Configure Codex CLI globally
|
|
1347
|
+
mcp-ticketer mcp codex
|
|
1348
|
+
|
|
1349
|
+
# Force overwrite existing configuration
|
|
1350
|
+
mcp-ticketer mcp codex --force
|
|
1351
|
+
|
|
1352
|
+
"""
|
|
1353
|
+
from ..cli.codex_configure import configure_codex_mcp
|
|
1354
|
+
|
|
1355
|
+
try:
|
|
1356
|
+
configure_codex_mcp(force=force)
|
|
1357
|
+
except Exception as e:
|
|
1358
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
1359
|
+
raise typer.Exit(1)
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
@mcp_app.command(name="auggie")
|
|
1363
|
+
def mcp_auggie(
|
|
1364
|
+
force: bool = typer.Option(
|
|
1365
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
1366
|
+
),
|
|
1367
|
+
):
|
|
1368
|
+
"""Configure Auggie CLI to use mcp-ticketer MCP server.
|
|
1369
|
+
|
|
1370
|
+
Reads configuration from .mcp-ticketer/config.json and creates
|
|
1371
|
+
Auggie CLI settings.json with mcp-ticketer configuration.
|
|
1372
|
+
|
|
1373
|
+
IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
|
|
1374
|
+
There is no project-level configuration support. After configuration,
|
|
1375
|
+
you must restart Auggie CLI for changes to take effect.
|
|
1376
|
+
|
|
1377
|
+
Examples:
|
|
1378
|
+
# Configure Auggie CLI globally
|
|
1379
|
+
mcp-ticketer mcp auggie
|
|
1380
|
+
|
|
1381
|
+
# Force overwrite existing configuration
|
|
1382
|
+
mcp-ticketer mcp auggie --force
|
|
1383
|
+
|
|
1384
|
+
"""
|
|
1385
|
+
from ..cli.auggie_configure import configure_auggie_mcp
|
|
1386
|
+
|
|
1387
|
+
try:
|
|
1388
|
+
configure_auggie_mcp(force=force)
|
|
1389
|
+
except Exception as e:
|
|
1390
|
+
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
1391
|
+
raise typer.Exit(1)
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
# Add MCP command group to main app (must be after all subcommands are defined)
|
|
1395
|
+
app.add_typer(mcp_app, name="mcp")
|
|
1396
|
+
|
|
1397
|
+
|
|
1237
1398
|
def main():
|
|
1238
1399
|
"""Main entry point."""
|
|
1239
1400
|
app()
|
|
@@ -3,16 +3,12 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import shutil
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.prompt import Confirm
|
|
10
10
|
|
|
11
|
-
from ..core.project_config import
|
|
12
|
-
AdapterConfig,
|
|
13
|
-
ConfigResolver,
|
|
14
|
-
TicketerConfig,
|
|
15
|
-
)
|
|
11
|
+
from ..core.project_config import AdapterConfig, ConfigResolver, TicketerConfig
|
|
16
12
|
|
|
17
13
|
console = Console()
|
|
18
14
|
|
|
@@ -96,7 +92,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
|
|
|
96
92
|
console.print(f"[yellow]Old config backed up at: {backup_path}[/yellow]")
|
|
97
93
|
|
|
98
94
|
|
|
99
|
-
def _migrate_old_to_new(old_config:
|
|
95
|
+
def _migrate_old_to_new(old_config: dict[str, Any]) -> TicketerConfig:
|
|
100
96
|
"""Migrate old configuration format to new format.
|
|
101
97
|
|
|
102
98
|
Old format examples:
|
mcp_ticketer/cli/utils.py
CHANGED
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import os
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Callable,
|
|
9
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
10
10
|
|
|
11
11
|
import typer
|
|
12
12
|
from rich.console import Console
|
|
@@ -166,7 +166,7 @@ class CommonPatterns:
|
|
|
166
166
|
|
|
167
167
|
@staticmethod
|
|
168
168
|
def queue_operation(
|
|
169
|
-
ticket_data:
|
|
169
|
+
ticket_data: dict[str, Any],
|
|
170
170
|
operation: str,
|
|
171
171
|
adapter_name: Optional[str] = None,
|
|
172
172
|
show_progress: bool = True,
|
|
@@ -197,7 +197,7 @@ class CommonPatterns:
|
|
|
197
197
|
return queue_id
|
|
198
198
|
|
|
199
199
|
@staticmethod
|
|
200
|
-
def display_ticket_table(tickets:
|
|
200
|
+
def display_ticket_table(tickets: list[Task], title: str = "Tickets") -> None:
|
|
201
201
|
"""Display tickets in a formatted table."""
|
|
202
202
|
if not tickets:
|
|
203
203
|
console.print("[yellow]No tickets found[/yellow]")
|
|
@@ -222,7 +222,7 @@ class CommonPatterns:
|
|
|
222
222
|
console.print(table)
|
|
223
223
|
|
|
224
224
|
@staticmethod
|
|
225
|
-
def display_ticket_details(ticket: Task, comments: Optional[
|
|
225
|
+
def display_ticket_details(ticket: Task, comments: Optional[list] = None) -> None:
|
|
226
226
|
"""Display detailed ticket information."""
|
|
227
227
|
console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
|
|
228
228
|
console.print(f"Title: {ticket.title}")
|
|
@@ -367,7 +367,7 @@ class ConfigValidator:
|
|
|
367
367
|
"""Configuration validation utilities."""
|
|
368
368
|
|
|
369
369
|
@staticmethod
|
|
370
|
-
def validate_adapter_config(adapter_type: str, config: dict) ->
|
|
370
|
+
def validate_adapter_config(adapter_type: str, config: dict) -> list[str]:
|
|
371
371
|
"""Validate adapter configuration and return list of issues."""
|
|
372
372
|
issues = []
|
|
373
373
|
|
|
@@ -483,7 +483,7 @@ def create_standard_ticket_command(operation: str):
|
|
|
483
483
|
priority: Optional[Priority] = None,
|
|
484
484
|
state: Optional[TicketState] = None,
|
|
485
485
|
assignee: Optional[str] = None,
|
|
486
|
-
tags: Optional[
|
|
486
|
+
tags: Optional[list[str]] = None,
|
|
487
487
|
adapter: Optional[str] = None,
|
|
488
488
|
):
|
|
489
489
|
"""Template for ticket commands."""
|
|
@@ -562,7 +562,7 @@ class TicketCommands:
|
|
|
562
562
|
title: str,
|
|
563
563
|
description: Optional[str] = None,
|
|
564
564
|
priority: Priority = Priority.MEDIUM,
|
|
565
|
-
tags: Optional[
|
|
565
|
+
tags: Optional[list[str]] = None,
|
|
566
566
|
assignee: Optional[str] = None,
|
|
567
567
|
adapter: Optional[str] = None,
|
|
568
568
|
) -> str:
|
|
@@ -579,7 +579,7 @@ class TicketCommands:
|
|
|
579
579
|
|
|
580
580
|
@staticmethod
|
|
581
581
|
def update_ticket(
|
|
582
|
-
ticket_id: str, updates:
|
|
582
|
+
ticket_id: str, updates: dict[str, Any], adapter: Optional[str] = None
|
|
583
583
|
) -> str:
|
|
584
584
|
"""Update a ticket."""
|
|
585
585
|
if not updates:
|
mcp_ticketer/core/adapter.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Base adapter abstract class for ticket systems."""
|
|
2
2
|
|
|
3
|
+
import builtins
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
5
6
|
|
|
6
7
|
from .models import Comment, Epic, SearchQuery, Task, TicketState, TicketType
|
|
7
8
|
|
|
@@ -12,7 +13,7 @@ T = TypeVar("T", Epic, Task)
|
|
|
12
13
|
class BaseAdapter(ABC, Generic[T]):
|
|
13
14
|
"""Abstract base class for all ticket system adapters."""
|
|
14
15
|
|
|
15
|
-
def __init__(self, config:
|
|
16
|
+
def __init__(self, config: dict[str, Any]):
|
|
16
17
|
"""Initialize adapter with configuration.
|
|
17
18
|
|
|
18
19
|
Args:
|
|
@@ -23,7 +24,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
23
24
|
self._state_mapping = self._get_state_mapping()
|
|
24
25
|
|
|
25
26
|
@abstractmethod
|
|
26
|
-
def _get_state_mapping(self) ->
|
|
27
|
+
def _get_state_mapping(self) -> dict[TicketState, str]:
|
|
27
28
|
"""Get mapping from universal states to system-specific states.
|
|
28
29
|
|
|
29
30
|
Returns:
|
|
@@ -69,7 +70,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
69
70
|
pass
|
|
70
71
|
|
|
71
72
|
@abstractmethod
|
|
72
|
-
async def update(self, ticket_id: str, updates:
|
|
73
|
+
async def update(self, ticket_id: str, updates: dict[str, Any]) -> Optional[T]:
|
|
73
74
|
"""Update a ticket.
|
|
74
75
|
|
|
75
76
|
Args:
|
|
@@ -97,8 +98,8 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
97
98
|
|
|
98
99
|
@abstractmethod
|
|
99
100
|
async def list(
|
|
100
|
-
self, limit: int = 10, offset: int = 0, filters: Optional[
|
|
101
|
-
) ->
|
|
101
|
+
self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
|
|
102
|
+
) -> list[T]:
|
|
102
103
|
"""List tickets with pagination and filters.
|
|
103
104
|
|
|
104
105
|
Args:
|
|
@@ -113,7 +114,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
113
114
|
pass
|
|
114
115
|
|
|
115
116
|
@abstractmethod
|
|
116
|
-
async def search(self, query: SearchQuery) ->
|
|
117
|
+
async def search(self, query: SearchQuery) -> builtins.list[T]:
|
|
117
118
|
"""Search tickets using advanced query.
|
|
118
119
|
|
|
119
120
|
Args:
|
|
@@ -157,7 +158,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
157
158
|
@abstractmethod
|
|
158
159
|
async def get_comments(
|
|
159
160
|
self, ticket_id: str, limit: int = 10, offset: int = 0
|
|
160
|
-
) ->
|
|
161
|
+
) -> builtins.list[Comment]:
|
|
161
162
|
"""Get comments for a ticket.
|
|
162
163
|
|
|
163
164
|
Args:
|
|
@@ -264,7 +265,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
264
265
|
return result
|
|
265
266
|
return None
|
|
266
267
|
|
|
267
|
-
async def list_epics(self, **kwargs) ->
|
|
268
|
+
async def list_epics(self, **kwargs) -> builtins.list[Epic]:
|
|
268
269
|
"""List all epics.
|
|
269
270
|
|
|
270
271
|
Args:
|
|
@@ -308,7 +309,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
308
309
|
)
|
|
309
310
|
return await self.create(task)
|
|
310
311
|
|
|
311
|
-
async def list_issues_by_epic(self, epic_id: str) ->
|
|
312
|
+
async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
|
|
312
313
|
"""List all issues in epic.
|
|
313
314
|
|
|
314
315
|
Args:
|
|
@@ -359,7 +360,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
359
360
|
|
|
360
361
|
return await self.create(task)
|
|
361
362
|
|
|
362
|
-
async def list_tasks_by_issue(self, issue_id: str) ->
|
|
363
|
+
async def list_tasks_by_issue(self, issue_id: str) -> builtins.list[Task]:
|
|
363
364
|
"""List all tasks under an issue.
|
|
364
365
|
|
|
365
366
|
Args:
|
mcp_ticketer/core/config.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from functools import lru_cache
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Optional, Union
|
|
10
10
|
|
|
11
11
|
import yaml
|
|
12
12
|
from pydantic import BaseModel, Field, root_validator, validator
|
|
@@ -31,7 +31,7 @@ class BaseAdapterConfig(BaseModel):
|
|
|
31
31
|
enabled: bool = True
|
|
32
32
|
timeout: float = 30.0
|
|
33
33
|
max_retries: int = 3
|
|
34
|
-
rate_limit: Optional[
|
|
34
|
+
rate_limit: Optional[dict[str, Any]] = None
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class GitHubConfig(BaseAdapterConfig):
|
|
@@ -43,10 +43,10 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
43
43
|
repo: Optional[str] = Field(None, env="GITHUB_REPO")
|
|
44
44
|
api_url: str = "https://api.github.com"
|
|
45
45
|
use_projects_v2: bool = False
|
|
46
|
-
custom_priority_scheme: Optional[
|
|
46
|
+
custom_priority_scheme: Optional[dict[str, list[str]]] = None
|
|
47
47
|
|
|
48
48
|
@validator("token", pre=True, always=True)
|
|
49
|
-
def validate_token(
|
|
49
|
+
def validate_token(self, v):
|
|
50
50
|
if not v:
|
|
51
51
|
v = os.getenv("GITHUB_TOKEN")
|
|
52
52
|
if not v:
|
|
@@ -54,7 +54,7 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
54
54
|
return v
|
|
55
55
|
|
|
56
56
|
@validator("owner", pre=True, always=True)
|
|
57
|
-
def validate_owner(
|
|
57
|
+
def validate_owner(self, v):
|
|
58
58
|
if not v:
|
|
59
59
|
v = os.getenv("GITHUB_OWNER")
|
|
60
60
|
if not v:
|
|
@@ -62,7 +62,7 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
62
62
|
return v
|
|
63
63
|
|
|
64
64
|
@validator("repo", pre=True, always=True)
|
|
65
|
-
def validate_repo(
|
|
65
|
+
def validate_repo(self, v):
|
|
66
66
|
if not v:
|
|
67
67
|
v = os.getenv("GITHUB_REPO")
|
|
68
68
|
if not v:
|
|
@@ -82,7 +82,7 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
82
82
|
verify_ssl: bool = True
|
|
83
83
|
|
|
84
84
|
@validator("server", pre=True, always=True)
|
|
85
|
-
def validate_server(
|
|
85
|
+
def validate_server(self, v):
|
|
86
86
|
if not v:
|
|
87
87
|
v = os.getenv("JIRA_SERVER")
|
|
88
88
|
if not v:
|
|
@@ -90,7 +90,7 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
90
90
|
return v.rstrip("/")
|
|
91
91
|
|
|
92
92
|
@validator("email", pre=True, always=True)
|
|
93
|
-
def validate_email(
|
|
93
|
+
def validate_email(self, v):
|
|
94
94
|
if not v:
|
|
95
95
|
v = os.getenv("JIRA_EMAIL")
|
|
96
96
|
if not v:
|
|
@@ -98,7 +98,7 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
98
98
|
return v
|
|
99
99
|
|
|
100
100
|
@validator("api_token", pre=True, always=True)
|
|
101
|
-
def validate_api_token(
|
|
101
|
+
def validate_api_token(self, v):
|
|
102
102
|
if not v:
|
|
103
103
|
v = os.getenv("JIRA_API_TOKEN")
|
|
104
104
|
if not v:
|
|
@@ -116,7 +116,7 @@ class LinearConfig(BaseAdapterConfig):
|
|
|
116
116
|
api_url: str = "https://api.linear.app/graphql"
|
|
117
117
|
|
|
118
118
|
@validator("api_key", pre=True, always=True)
|
|
119
|
-
def validate_api_key(
|
|
119
|
+
def validate_api_key(self, v):
|
|
120
120
|
if not v:
|
|
121
121
|
v = os.getenv("LINEAR_API_KEY")
|
|
122
122
|
if not v:
|
|
@@ -155,7 +155,7 @@ class LoggingConfig(BaseModel):
|
|
|
155
155
|
class AppConfig(BaseModel):
|
|
156
156
|
"""Main application configuration."""
|
|
157
157
|
|
|
158
|
-
adapters:
|
|
158
|
+
adapters: dict[
|
|
159
159
|
str, Union[GitHubConfig, JiraConfig, LinearConfig, AITrackdownConfig]
|
|
160
160
|
] = {}
|
|
161
161
|
queue: QueueConfig = QueueConfig()
|
|
@@ -164,7 +164,7 @@ class AppConfig(BaseModel):
|
|
|
164
164
|
default_adapter: Optional[str] = None
|
|
165
165
|
|
|
166
166
|
@root_validator(skip_on_failure=True)
|
|
167
|
-
def validate_adapters(
|
|
167
|
+
def validate_adapters(self, values):
|
|
168
168
|
"""Validate adapter configurations."""
|
|
169
169
|
adapters = values.get("adapters", {})
|
|
170
170
|
|
|
@@ -185,7 +185,7 @@ class AppConfig(BaseModel):
|
|
|
185
185
|
"""Get configuration for a specific adapter."""
|
|
186
186
|
return self.adapters.get(adapter_name)
|
|
187
187
|
|
|
188
|
-
def get_enabled_adapters(self) ->
|
|
188
|
+
def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
|
|
189
189
|
"""Get all enabled adapters."""
|
|
190
190
|
return {
|
|
191
191
|
name: config for name, config in self.adapters.items() if config.enabled
|
|
@@ -197,7 +197,7 @@ class ConfigurationManager:
|
|
|
197
197
|
|
|
198
198
|
_instance: Optional["ConfigurationManager"] = None
|
|
199
199
|
_config: Optional[AppConfig] = None
|
|
200
|
-
_config_file_paths:
|
|
200
|
+
_config_file_paths: list[Path] = []
|
|
201
201
|
|
|
202
202
|
def __new__(cls) -> "ConfigurationManager":
|
|
203
203
|
"""Singleton pattern for global config access."""
|
|
@@ -209,7 +209,7 @@ class ConfigurationManager:
|
|
|
209
209
|
"""Initialize configuration manager."""
|
|
210
210
|
if not hasattr(self, "_initialized"):
|
|
211
211
|
self._initialized = True
|
|
212
|
-
self._config_cache:
|
|
212
|
+
self._config_cache: dict[str, Any] = {}
|
|
213
213
|
self._find_config_files()
|
|
214
214
|
|
|
215
215
|
def _find_config_files(self) -> None:
|
|
@@ -302,7 +302,7 @@ class ConfigurationManager:
|
|
|
302
302
|
self._config = AppConfig(**config_data)
|
|
303
303
|
return self._config
|
|
304
304
|
|
|
305
|
-
def _load_config_file(self, config_path: Path) ->
|
|
305
|
+
def _load_config_file(self, config_path: Path) -> dict[str, Any]:
|
|
306
306
|
"""Load configuration from YAML or JSON file."""
|
|
307
307
|
try:
|
|
308
308
|
with open(config_path, encoding="utf-8") as file:
|
|
@@ -332,7 +332,7 @@ class ConfigurationManager:
|
|
|
332
332
|
config = self.get_config()
|
|
333
333
|
return config.get_adapter_config(adapter_name)
|
|
334
334
|
|
|
335
|
-
def get_enabled_adapters(self) ->
|
|
335
|
+
def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
|
|
336
336
|
"""Get all enabled adapter configurations."""
|
|
337
337
|
config = self.get_config()
|
|
338
338
|
return config.get_enabled_adapters()
|