mcp-ticketer 0.4.1__py3-none-any.whl → 0.4.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/__init__.py +3 -12
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +243 -11
- mcp_ticketer/adapters/github.py +15 -14
- mcp_ticketer/adapters/hybrid.py +11 -11
- mcp_ticketer/adapters/jira.py +22 -25
- mcp_ticketer/adapters/linear/adapter.py +9 -21
- mcp_ticketer/adapters/linear/client.py +2 -1
- mcp_ticketer/adapters/linear/mappers.py +2 -1
- mcp_ticketer/cache/memory.py +6 -5
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +66 -0
- mcp_ticketer/cli/codex_configure.py +70 -2
- mcp_ticketer/cli/configure.py +7 -14
- mcp_ticketer/cli/diagnostics.py +2 -2
- mcp_ticketer/cli/discover.py +6 -11
- mcp_ticketer/cli/gemini_configure.py +68 -2
- mcp_ticketer/cli/linear_commands.py +6 -7
- mcp_ticketer/cli/main.py +341 -203
- mcp_ticketer/cli/mcp_configure.py +61 -2
- mcp_ticketer/cli/ticket_commands.py +27 -30
- mcp_ticketer/cli/utils.py +23 -22
- mcp_ticketer/core/__init__.py +3 -1
- mcp_ticketer/core/adapter.py +82 -13
- mcp_ticketer/core/config.py +27 -29
- mcp_ticketer/core/env_discovery.py +10 -10
- mcp_ticketer/core/env_loader.py +8 -8
- mcp_ticketer/core/http_client.py +16 -16
- mcp_ticketer/core/mappers.py +10 -10
- mcp_ticketer/core/models.py +50 -20
- mcp_ticketer/core/project_config.py +40 -34
- mcp_ticketer/core/registry.py +2 -2
- mcp_ticketer/mcp/dto.py +32 -32
- mcp_ticketer/mcp/response_builder.py +2 -2
- mcp_ticketer/mcp/server.py +17 -37
- mcp_ticketer/mcp/server_sdk.py +93 -0
- mcp_ticketer/mcp/tools/__init__.py +36 -0
- mcp_ticketer/mcp/tools/attachment_tools.py +179 -0
- mcp_ticketer/mcp/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/tools/comment_tools.py +90 -0
- mcp_ticketer/mcp/tools/hierarchy_tools.py +383 -0
- mcp_ticketer/mcp/tools/pr_tools.py +154 -0
- mcp_ticketer/mcp/tools/search_tools.py +206 -0
- mcp_ticketer/mcp/tools/ticket_tools.py +277 -0
- mcp_ticketer/queue/health_monitor.py +4 -4
- mcp_ticketer/queue/manager.py +2 -2
- mcp_ticketer/queue/queue.py +16 -16
- mcp_ticketer/queue/ticket_registry.py +7 -7
- mcp_ticketer/queue/worker.py +2 -2
- {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/METADATA +90 -17
- mcp_ticketer-0.4.3.dist-info/RECORD +73 -0
- mcp_ticketer-0.4.1.dist-info/RECORD +0 -64
- {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/top_level.txt +0 -0
mcp_ticketer/adapters/jira.py
CHANGED
|
@@ -6,20 +6,21 @@ import logging
|
|
|
6
6
|
import re
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Union
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
from httpx import AsyncClient, HTTPStatusError, TimeoutException
|
|
13
13
|
|
|
14
14
|
from ..core.adapter import BaseAdapter
|
|
15
15
|
from ..core.env_loader import load_adapter_config, validate_adapter_config
|
|
16
|
-
from ..core.models import Comment, Epic, Priority, SearchQuery, Task,
|
|
16
|
+
from ..core.models import (Comment, Epic, Priority, SearchQuery, Task,
|
|
17
|
+
TicketState)
|
|
17
18
|
from ..core.registry import AdapterRegistry
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def parse_jira_datetime(date_str: str) ->
|
|
23
|
+
def parse_jira_datetime(date_str: str) -> datetime | None:
|
|
23
24
|
"""Parse JIRA datetime strings which can be in various formats.
|
|
24
25
|
|
|
25
26
|
JIRA can return dates in formats like:
|
|
@@ -47,7 +48,7 @@ def parse_jira_datetime(date_str: str) -> Optional[datetime]:
|
|
|
47
48
|
return None
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def extract_text_from_adf(adf_content:
|
|
51
|
+
def extract_text_from_adf(adf_content: str | dict[str, Any]) -> str:
|
|
51
52
|
"""Extract plain text from Atlassian Document Format (ADF).
|
|
52
53
|
|
|
53
54
|
Args:
|
|
@@ -221,8 +222,8 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
221
222
|
self,
|
|
222
223
|
method: str,
|
|
223
224
|
endpoint: str,
|
|
224
|
-
data:
|
|
225
|
-
params:
|
|
225
|
+
data: dict[str, Any] | None = None,
|
|
226
|
+
params: dict[str, Any] | None = None,
|
|
226
227
|
retry_count: int = 0,
|
|
227
228
|
) -> dict[str, Any]:
|
|
228
229
|
"""Make HTTP request to JIRA API with retry logic.
|
|
@@ -287,7 +288,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
287
288
|
return self._priority_cache
|
|
288
289
|
|
|
289
290
|
async def _get_issue_types(
|
|
290
|
-
self, project_key:
|
|
291
|
+
self, project_key: str | None = None
|
|
291
292
|
) -> list[dict[str, Any]]:
|
|
292
293
|
"""Get available issue types for a project."""
|
|
293
294
|
key = project_key or self.project_key
|
|
@@ -380,9 +381,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
380
381
|
}
|
|
381
382
|
return mapping.get(priority, JiraPriority.MEDIUM)
|
|
382
383
|
|
|
383
|
-
def _map_priority_from_jira(
|
|
384
|
-
self, jira_priority: Optional[dict[str, Any]]
|
|
385
|
-
) -> Priority:
|
|
384
|
+
def _map_priority_from_jira(self, jira_priority: dict[str, Any] | None) -> Priority:
|
|
386
385
|
"""Map JIRA priority to universal priority."""
|
|
387
386
|
if not jira_priority:
|
|
388
387
|
return Priority.MEDIUM
|
|
@@ -432,7 +431,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
432
431
|
else:
|
|
433
432
|
return TicketState.OPEN
|
|
434
433
|
|
|
435
|
-
def _issue_to_ticket(self, issue: dict[str, Any]) ->
|
|
434
|
+
def _issue_to_ticket(self, issue: dict[str, Any]) -> Epic | Task:
|
|
436
435
|
"""Convert JIRA issue to universal ticket model."""
|
|
437
436
|
fields = issue.get("fields", {})
|
|
438
437
|
|
|
@@ -507,7 +506,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
507
506
|
)
|
|
508
507
|
|
|
509
508
|
def _ticket_to_issue_fields(
|
|
510
|
-
self, ticket:
|
|
509
|
+
self, ticket: Epic | Task, issue_type: str | None = None
|
|
511
510
|
) -> dict[str, Any]:
|
|
512
511
|
"""Convert universal ticket to JIRA issue fields."""
|
|
513
512
|
# Convert description to ADF format for JIRA Cloud
|
|
@@ -556,7 +555,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
556
555
|
|
|
557
556
|
return fields
|
|
558
557
|
|
|
559
|
-
async def create(self, ticket:
|
|
558
|
+
async def create(self, ticket: Epic | Task) -> Epic | Task:
|
|
560
559
|
"""Create a new JIRA issue."""
|
|
561
560
|
# Validate credentials before attempting operation
|
|
562
561
|
is_valid, error_message = self.validate_credentials()
|
|
@@ -576,7 +575,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
576
575
|
created_issue = await self._make_request("GET", f"issue/{ticket.id}")
|
|
577
576
|
return self._issue_to_ticket(created_issue)
|
|
578
577
|
|
|
579
|
-
async def read(self, ticket_id: str) ->
|
|
578
|
+
async def read(self, ticket_id: str) -> Epic | Task | None:
|
|
580
579
|
"""Read a JIRA issue by key."""
|
|
581
580
|
# Validate credentials before attempting operation
|
|
582
581
|
is_valid, error_message = self.validate_credentials()
|
|
@@ -595,7 +594,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
595
594
|
|
|
596
595
|
async def update(
|
|
597
596
|
self, ticket_id: str, updates: dict[str, Any]
|
|
598
|
-
) ->
|
|
597
|
+
) -> Epic | Task | None:
|
|
599
598
|
"""Update a JIRA issue."""
|
|
600
599
|
# Validate credentials before attempting operation
|
|
601
600
|
is_valid, error_message = self.validate_credentials()
|
|
@@ -652,8 +651,8 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
652
651
|
raise
|
|
653
652
|
|
|
654
653
|
async def list(
|
|
655
|
-
self, limit: int = 10, offset: int = 0, filters:
|
|
656
|
-
) -> list[
|
|
654
|
+
self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None
|
|
655
|
+
) -> list[Epic | Task]:
|
|
657
656
|
"""List JIRA issues with pagination."""
|
|
658
657
|
# Build JQL query
|
|
659
658
|
jql_parts = []
|
|
@@ -692,7 +691,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
692
691
|
issues = data.get("issues", [])
|
|
693
692
|
return [self._issue_to_ticket(issue) for issue in issues]
|
|
694
693
|
|
|
695
|
-
async def search(self, query: SearchQuery) -> builtins.list[
|
|
694
|
+
async def search(self, query: SearchQuery) -> builtins.list[Epic | Task]:
|
|
696
695
|
"""Search JIRA issues using JQL."""
|
|
697
696
|
# Build JQL query
|
|
698
697
|
jql_parts = []
|
|
@@ -744,7 +743,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
744
743
|
|
|
745
744
|
async def transition_state(
|
|
746
745
|
self, ticket_id: str, target_state: TicketState
|
|
747
|
-
) ->
|
|
746
|
+
) -> Epic | Task | None:
|
|
748
747
|
"""Transition JIRA issue to a new state."""
|
|
749
748
|
# Get available transitions
|
|
750
749
|
transitions = await self._get_transitions(ticket_id)
|
|
@@ -858,9 +857,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
858
857
|
|
|
859
858
|
return comments
|
|
860
859
|
|
|
861
|
-
async def get_project_info(
|
|
862
|
-
self, project_key: Optional[str] = None
|
|
863
|
-
) -> dict[str, Any]:
|
|
860
|
+
async def get_project_info(self, project_key: str | None = None) -> dict[str, Any]:
|
|
864
861
|
"""Get JIRA project information including workflows and fields."""
|
|
865
862
|
key = project_key or self.project_key
|
|
866
863
|
if not key:
|
|
@@ -882,7 +879,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
882
879
|
|
|
883
880
|
async def execute_jql(
|
|
884
881
|
self, jql: str, limit: int = 50
|
|
885
|
-
) -> builtins.list[
|
|
882
|
+
) -> builtins.list[Epic | Task]:
|
|
886
883
|
"""Execute a raw JQL query.
|
|
887
884
|
|
|
888
885
|
Args:
|
|
@@ -908,7 +905,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
908
905
|
return [self._issue_to_ticket(issue) for issue in issues]
|
|
909
906
|
|
|
910
907
|
async def get_sprints(
|
|
911
|
-
self, board_id:
|
|
908
|
+
self, board_id: int | None = None
|
|
912
909
|
) -> builtins.list[dict[str, Any]]:
|
|
913
910
|
"""Get active sprints for a board (requires JIRA Software).
|
|
914
911
|
|
|
@@ -992,7 +989,7 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
992
989
|
except Exception:
|
|
993
990
|
return []
|
|
994
991
|
|
|
995
|
-
async def get_current_user(self) ->
|
|
992
|
+
async def get_current_user(self) -> dict[str, Any] | None:
|
|
996
993
|
"""Get current authenticated user information."""
|
|
997
994
|
try:
|
|
998
995
|
return await self._make_request("GET", "myself")
|
|
@@ -20,27 +20,15 @@ from ...core.adapter import BaseAdapter
|
|
|
20
20
|
from ...core.models import Comment, Epic, SearchQuery, Task, TicketState
|
|
21
21
|
from ...core.registry import AdapterRegistry
|
|
22
22
|
from .client import LinearGraphQLClient
|
|
23
|
-
from .mappers import (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
from .
|
|
31
|
-
|
|
32
|
-
CREATE_ISSUE_MUTATION,
|
|
33
|
-
LIST_ISSUES_QUERY,
|
|
34
|
-
SEARCH_ISSUES_QUERY,
|
|
35
|
-
UPDATE_ISSUE_MUTATION,
|
|
36
|
-
WORKFLOW_STATES_QUERY,
|
|
37
|
-
)
|
|
38
|
-
from .types import (
|
|
39
|
-
LinearStateMapping,
|
|
40
|
-
build_issue_filter,
|
|
41
|
-
get_linear_priority,
|
|
42
|
-
get_linear_state_type,
|
|
43
|
-
)
|
|
23
|
+
from .mappers import (build_linear_issue_input,
|
|
24
|
+
build_linear_issue_update_input,
|
|
25
|
+
map_linear_comment_to_comment, map_linear_issue_to_task,
|
|
26
|
+
map_linear_project_to_epic)
|
|
27
|
+
from .queries import (ALL_FRAGMENTS, CREATE_ISSUE_MUTATION, LIST_ISSUES_QUERY,
|
|
28
|
+
SEARCH_ISSUES_QUERY, UPDATE_ISSUE_MUTATION,
|
|
29
|
+
WORKFLOW_STATES_QUERY)
|
|
30
|
+
from .types import (LinearStateMapping, build_issue_filter,
|
|
31
|
+
get_linear_priority, get_linear_state_type)
|
|
44
32
|
|
|
45
33
|
|
|
46
34
|
class LinearAdapter(BaseAdapter[Task]):
|
|
@@ -16,7 +16,8 @@ except ImportError:
|
|
|
16
16
|
HTTPXAsyncTransport = None
|
|
17
17
|
TransportError = Exception
|
|
18
18
|
|
|
19
|
-
from ...core.exceptions import AdapterError, AuthenticationError,
|
|
19
|
+
from ...core.exceptions import (AdapterError, AuthenticationError,
|
|
20
|
+
RateLimitError)
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class LinearGraphQLClient:
|
|
@@ -6,7 +6,8 @@ from datetime import datetime
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from ...core.models import Comment, Epic, Priority, Task, TicketState
|
|
9
|
-
from .types import extract_linear_metadata, get_universal_priority,
|
|
9
|
+
from .types import (extract_linear_metadata, get_universal_priority,
|
|
10
|
+
get_universal_state)
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def map_linear_issue_to_task(issue_data: dict[str, Any]) -> Task:
|
mcp_ticketer/cache/memory.py
CHANGED
|
@@ -4,8 +4,9 @@ import asyncio
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import json
|
|
6
6
|
import time
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from functools import wraps
|
|
8
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class CacheEntry:
|
|
@@ -41,7 +42,7 @@ class MemoryCache:
|
|
|
41
42
|
self._default_ttl = default_ttl
|
|
42
43
|
self._lock = asyncio.Lock()
|
|
43
44
|
|
|
44
|
-
async def get(self, key: str) ->
|
|
45
|
+
async def get(self, key: str) -> Any | None:
|
|
45
46
|
"""Get value from cache.
|
|
46
47
|
|
|
47
48
|
Args:
|
|
@@ -60,7 +61,7 @@ class MemoryCache:
|
|
|
60
61
|
del self._cache[key]
|
|
61
62
|
return None
|
|
62
63
|
|
|
63
|
-
async def set(self, key: str, value: Any, ttl:
|
|
64
|
+
async def set(self, key: str, value: Any, ttl: float | None = None) -> None:
|
|
64
65
|
"""Set value in cache.
|
|
65
66
|
|
|
66
67
|
Args:
|
|
@@ -134,9 +135,9 @@ class MemoryCache:
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def cache_decorator(
|
|
137
|
-
ttl:
|
|
138
|
+
ttl: float | None = None,
|
|
138
139
|
key_prefix: str = "",
|
|
139
|
-
cache_instance:
|
|
140
|
+
cache_instance: MemoryCache | None = None,
|
|
140
141
|
) -> Callable:
|
|
141
142
|
"""Decorator for caching async function results.
|
|
142
143
|
|
|
@@ -197,7 +197,8 @@ def _test_adapter_instantiation(console: Console) -> None:
|
|
|
197
197
|
if primary:
|
|
198
198
|
adapter_type = primary.adapter_type
|
|
199
199
|
# Build config from discovery
|
|
200
|
-
from ..mcp.server import
|
|
200
|
+
from ..mcp.server import \
|
|
201
|
+
_build_adapter_config_from_env_vars
|
|
201
202
|
|
|
202
203
|
config = _build_adapter_config_from_env_vars(adapter_type, {})
|
|
203
204
|
else:
|
|
@@ -384,7 +385,8 @@ def get_adapter_status() -> dict[str, Any]:
|
|
|
384
385
|
adapter_type = primary.adapter_type
|
|
385
386
|
status["configuration_source"] = primary.found_in
|
|
386
387
|
# Build basic config
|
|
387
|
-
from ..mcp.server import
|
|
388
|
+
from ..mcp.server import \
|
|
389
|
+
_build_adapter_config_from_env_vars
|
|
388
390
|
|
|
389
391
|
config = _build_adapter_config_from_env_vars(adapter_type, {})
|
|
390
392
|
else:
|
|
@@ -138,6 +138,72 @@ def create_auggie_server_config(
|
|
|
138
138
|
return config
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def remove_auggie_mcp(dry_run: bool = False) -> None:
|
|
142
|
+
"""Remove mcp-ticketer from Auggie CLI configuration.
|
|
143
|
+
|
|
144
|
+
IMPORTANT: Auggie CLI ONLY supports global configuration.
|
|
145
|
+
This will remove mcp-ticketer from ~/.augment/settings.json.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
dry_run: Show what would be removed without making changes
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
# Step 1: Find Auggie config location
|
|
152
|
+
console.print("[cyan]🔍 Removing Auggie CLI global configuration...[/cyan]")
|
|
153
|
+
console.print(
|
|
154
|
+
"[yellow]⚠ NOTE: Auggie only supports global configuration (affects all projects)[/yellow]"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
auggie_config_path = find_auggie_config()
|
|
158
|
+
console.print(f"[dim]Config location: {auggie_config_path}[/dim]")
|
|
159
|
+
|
|
160
|
+
# Step 2: Check if config file exists
|
|
161
|
+
if not auggie_config_path.exists():
|
|
162
|
+
console.print(
|
|
163
|
+
f"[yellow]⚠ No configuration found at {auggie_config_path}[/yellow]"
|
|
164
|
+
)
|
|
165
|
+
console.print("[dim]mcp-ticketer is not configured for Auggie[/dim]")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
# Step 3: Load existing Auggie configuration
|
|
169
|
+
auggie_config = load_auggie_config(auggie_config_path)
|
|
170
|
+
|
|
171
|
+
# Step 4: Check if mcp-ticketer is configured
|
|
172
|
+
if "mcp-ticketer" not in auggie_config.get("mcpServers", {}):
|
|
173
|
+
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
174
|
+
console.print(f"[dim]No mcp-ticketer entry found in {auggie_config_path}[/dim]")
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Step 5: Show what would be removed (dry run or actual removal)
|
|
178
|
+
if dry_run:
|
|
179
|
+
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
180
|
+
console.print(" Server name: mcp-ticketer")
|
|
181
|
+
console.print(f" From: {auggie_config_path}")
|
|
182
|
+
console.print(" Scope: Global (all projects)")
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
# Step 6: Remove mcp-ticketer from configuration
|
|
186
|
+
del auggie_config["mcpServers"]["mcp-ticketer"]
|
|
187
|
+
|
|
188
|
+
# Step 7: Save updated configuration
|
|
189
|
+
try:
|
|
190
|
+
save_auggie_config(auggie_config_path, auggie_config)
|
|
191
|
+
console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
|
|
192
|
+
console.print(f"[dim]Configuration updated: {auggie_config_path}[/dim]")
|
|
193
|
+
|
|
194
|
+
# Next steps
|
|
195
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
196
|
+
console.print("1. Restart Auggie CLI for changes to take effect")
|
|
197
|
+
console.print("2. mcp-ticketer will no longer be available via MCP")
|
|
198
|
+
console.print(
|
|
199
|
+
"\n[yellow]⚠ Note: This removes global configuration affecting all projects[/yellow]"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
|
|
141
207
|
def configure_auggie_mcp(force: bool = False) -> None:
|
|
142
208
|
"""Configure Auggie CLI to use mcp-ticketer.
|
|
143
209
|
|
|
@@ -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
|
|
10
10
|
|
|
11
11
|
if sys.version_info >= (3, 11):
|
|
12
12
|
import tomllib
|
|
@@ -78,7 +78,7 @@ def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def create_codex_server_config(
|
|
81
|
-
binary_path: str, project_config: dict, cwd:
|
|
81
|
+
binary_path: str, project_config: dict, cwd: str | None = None
|
|
82
82
|
) -> dict[str, Any]:
|
|
83
83
|
"""Create Codex MCP server configuration for mcp-ticketer.
|
|
84
84
|
|
|
@@ -151,6 +151,74 @@ def create_codex_server_config(
|
|
|
151
151
|
return config
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def remove_codex_mcp(dry_run: bool = False) -> None:
|
|
155
|
+
"""Remove mcp-ticketer from Codex CLI configuration.
|
|
156
|
+
|
|
157
|
+
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
158
|
+
This will remove mcp-ticketer from the global configuration.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
dry_run: Show what would be removed without making changes
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
# Step 1: Find Codex config location (always global)
|
|
165
|
+
console.print("[cyan]🔍 Removing Codex CLI global configuration...[/cyan]")
|
|
166
|
+
console.print(
|
|
167
|
+
"[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
codex_config_path = find_codex_config()
|
|
171
|
+
console.print(f"[dim]Config location: {codex_config_path}[/dim]")
|
|
172
|
+
|
|
173
|
+
# Step 2: Check if config file exists
|
|
174
|
+
if not codex_config_path.exists():
|
|
175
|
+
console.print(
|
|
176
|
+
f"[yellow]⚠ No configuration found at {codex_config_path}[/yellow]"
|
|
177
|
+
)
|
|
178
|
+
console.print("[dim]mcp-ticketer is not configured for Codex CLI[/dim]")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
# Step 3: Load existing Codex configuration
|
|
182
|
+
codex_config = load_codex_config(codex_config_path)
|
|
183
|
+
|
|
184
|
+
# Step 4: Check if mcp-ticketer is configured
|
|
185
|
+
# NOTE: Use underscore mcp_servers, not camelCase
|
|
186
|
+
mcp_servers = codex_config.get("mcp_servers", {})
|
|
187
|
+
if "mcp-ticketer" not in mcp_servers:
|
|
188
|
+
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
189
|
+
console.print(f"[dim]No mcp-ticketer entry found in {codex_config_path}[/dim]")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Step 5: Show what would be removed (dry run or actual removal)
|
|
193
|
+
if dry_run:
|
|
194
|
+
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
195
|
+
console.print(" Server name: mcp-ticketer")
|
|
196
|
+
console.print(f" From: {codex_config_path}")
|
|
197
|
+
console.print(" Scope: Global (all sessions)")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Step 6: Remove mcp-ticketer from configuration
|
|
201
|
+
del codex_config["mcp_servers"]["mcp-ticketer"]
|
|
202
|
+
|
|
203
|
+
# Step 7: Save updated configuration
|
|
204
|
+
try:
|
|
205
|
+
save_codex_config(codex_config_path, codex_config)
|
|
206
|
+
console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
|
|
207
|
+
console.print(f"[dim]Configuration updated: {codex_config_path}[/dim]")
|
|
208
|
+
|
|
209
|
+
# Next steps
|
|
210
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
211
|
+
console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
|
|
212
|
+
console.print("2. mcp-ticketer will no longer be available via MCP")
|
|
213
|
+
console.print(
|
|
214
|
+
"\n[yellow]⚠ Note: This removes global configuration affecting all Codex sessions[/yellow]"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
except Exception as e:
|
|
218
|
+
console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
|
|
219
|
+
raise
|
|
220
|
+
|
|
221
|
+
|
|
154
222
|
def configure_codex_mcp(force: bool = False) -> None:
|
|
155
223
|
"""Configure Codex CLI to use mcp-ticketer.
|
|
156
224
|
|
mcp_ticketer/cli/configure.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Interactive configuration wizard for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich.console import Console
|
|
@@ -9,15 +8,9 @@ from rich.panel import Panel
|
|
|
9
8
|
from rich.prompt import Confirm, Prompt
|
|
10
9
|
from rich.table import Table
|
|
11
10
|
|
|
12
|
-
from ..core.project_config import (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
ConfigResolver,
|
|
16
|
-
ConfigValidator,
|
|
17
|
-
HybridConfig,
|
|
18
|
-
SyncStrategy,
|
|
19
|
-
TicketerConfig,
|
|
20
|
-
)
|
|
11
|
+
from ..core.project_config import (AdapterConfig, AdapterType, ConfigResolver,
|
|
12
|
+
ConfigValidator, HybridConfig, SyncStrategy,
|
|
13
|
+
TicketerConfig)
|
|
21
14
|
|
|
22
15
|
console = Console()
|
|
23
16
|
|
|
@@ -440,10 +433,10 @@ def show_current_config() -> None:
|
|
|
440
433
|
|
|
441
434
|
|
|
442
435
|
def set_adapter_config(
|
|
443
|
-
adapter:
|
|
444
|
-
api_key:
|
|
445
|
-
project_id:
|
|
446
|
-
team_id:
|
|
436
|
+
adapter: str | None = None,
|
|
437
|
+
api_key: str | None = None,
|
|
438
|
+
project_id: str | None = None,
|
|
439
|
+
team_id: str | None = None,
|
|
447
440
|
global_scope: bool = False,
|
|
448
441
|
**kwargs,
|
|
449
442
|
) -> None:
|
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
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
@@ -795,7 +795,7 @@ class SystemDiagnostics:
|
|
|
795
795
|
|
|
796
796
|
|
|
797
797
|
async def run_diagnostics(
|
|
798
|
-
output_file:
|
|
798
|
+
output_file: str | None = None,
|
|
799
799
|
json_output: bool = False,
|
|
800
800
|
) -> None:
|
|
801
801
|
"""Run comprehensive system diagnostics."""
|
mcp_ticketer/cli/discover.py
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
"""CLI command for auto-discovering configuration from .env files."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich.console import Console
|
|
8
7
|
|
|
9
8
|
from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
|
|
10
|
-
from ..core.project_config import (
|
|
11
|
-
|
|
12
|
-
ConfigResolver,
|
|
13
|
-
ConfigValidator,
|
|
14
|
-
TicketerConfig,
|
|
15
|
-
)
|
|
9
|
+
from ..core.project_config import (AdapterConfig, ConfigResolver,
|
|
10
|
+
ConfigValidator, TicketerConfig)
|
|
16
11
|
|
|
17
12
|
console = Console()
|
|
18
13
|
app = typer.Typer(help="Auto-discover configuration from .env files")
|
|
@@ -93,7 +88,7 @@ def _display_discovered_adapter(
|
|
|
93
88
|
|
|
94
89
|
@app.command()
|
|
95
90
|
def show(
|
|
96
|
-
project_path:
|
|
91
|
+
project_path: Path | None = typer.Option(
|
|
97
92
|
None,
|
|
98
93
|
"--path",
|
|
99
94
|
"-p",
|
|
@@ -148,7 +143,7 @@ def show(
|
|
|
148
143
|
|
|
149
144
|
@app.command()
|
|
150
145
|
def save(
|
|
151
|
-
adapter:
|
|
146
|
+
adapter: str | None = typer.Option(
|
|
152
147
|
None, "--adapter", "-a", help="Which adapter to save (defaults to recommended)"
|
|
153
148
|
),
|
|
154
149
|
global_config: bool = typer.Option(
|
|
@@ -157,7 +152,7 @@ def save(
|
|
|
157
152
|
dry_run: bool = typer.Option(
|
|
158
153
|
False, "--dry-run", help="Show what would be saved without saving"
|
|
159
154
|
),
|
|
160
|
-
project_path:
|
|
155
|
+
project_path: Path | None = typer.Option(
|
|
161
156
|
None,
|
|
162
157
|
"--path",
|
|
163
158
|
"-p",
|
|
@@ -261,7 +256,7 @@ def save(
|
|
|
261
256
|
|
|
262
257
|
@app.command()
|
|
263
258
|
def interactive(
|
|
264
|
-
project_path:
|
|
259
|
+
project_path: Path | None = typer.Option(
|
|
265
260
|
None,
|
|
266
261
|
"--path",
|
|
267
262
|
"-p",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Literal
|
|
5
|
+
from typing import Literal
|
|
6
6
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
@@ -73,7 +73,7 @@ def save_gemini_config(config_path: Path, config: dict) -> None:
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def create_gemini_server_config(
|
|
76
|
-
binary_path: str, project_config: dict, cwd:
|
|
76
|
+
binary_path: str, project_config: dict, cwd: str | None = None
|
|
77
77
|
) -> dict:
|
|
78
78
|
"""Create Gemini MCP server configuration for mcp-ticketer.
|
|
79
79
|
|
|
@@ -147,6 +147,72 @@ def create_gemini_server_config(
|
|
|
147
147
|
return config
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def remove_gemini_mcp(
|
|
151
|
+
scope: Literal["project", "user"] = "project", dry_run: bool = False
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Remove mcp-ticketer from Gemini CLI configuration.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
scope: Configuration scope - "project" or "user"
|
|
157
|
+
dry_run: Show what would be removed without making changes
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
# Step 1: Find Gemini config location
|
|
161
|
+
config_type = "user-level" if scope == "user" else "project-level"
|
|
162
|
+
console.print(f"[cyan]🔍 Removing {config_type} Gemini CLI configuration...[/cyan]")
|
|
163
|
+
|
|
164
|
+
gemini_config_path = find_gemini_config(scope)
|
|
165
|
+
console.print(f"[dim]Config location: {gemini_config_path}[/dim]")
|
|
166
|
+
|
|
167
|
+
# Step 2: Check if config file exists
|
|
168
|
+
if not gemini_config_path.exists():
|
|
169
|
+
console.print(
|
|
170
|
+
f"[yellow]⚠ No configuration found at {gemini_config_path}[/yellow]"
|
|
171
|
+
)
|
|
172
|
+
console.print("[dim]mcp-ticketer is not configured for Gemini CLI[/dim]")
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
# Step 3: Load existing Gemini configuration
|
|
176
|
+
gemini_config = load_gemini_config(gemini_config_path)
|
|
177
|
+
|
|
178
|
+
# Step 4: Check if mcp-ticketer is configured
|
|
179
|
+
if "mcp-ticketer" not in gemini_config.get("mcpServers", {}):
|
|
180
|
+
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
181
|
+
console.print(f"[dim]No mcp-ticketer entry found in {gemini_config_path}[/dim]")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Step 5: Show what would be removed (dry run or actual removal)
|
|
185
|
+
if dry_run:
|
|
186
|
+
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
187
|
+
console.print(" Server name: mcp-ticketer")
|
|
188
|
+
console.print(f" From: {gemini_config_path}")
|
|
189
|
+
console.print(f" Scope: {config_type}")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Step 6: Remove mcp-ticketer from configuration
|
|
193
|
+
del gemini_config["mcpServers"]["mcp-ticketer"]
|
|
194
|
+
|
|
195
|
+
# Step 7: Save updated configuration
|
|
196
|
+
try:
|
|
197
|
+
save_gemini_config(gemini_config_path, gemini_config)
|
|
198
|
+
console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
|
|
199
|
+
console.print(f"[dim]Configuration updated: {gemini_config_path}[/dim]")
|
|
200
|
+
|
|
201
|
+
# Next steps
|
|
202
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
203
|
+
if scope == "user":
|
|
204
|
+
console.print("1. Gemini CLI global configuration updated")
|
|
205
|
+
console.print("2. mcp-ticketer will no longer be available in any project")
|
|
206
|
+
else:
|
|
207
|
+
console.print("1. Gemini CLI project configuration updated")
|
|
208
|
+
console.print("2. mcp-ticketer will no longer be available in this project")
|
|
209
|
+
console.print("3. Restart Gemini CLI if currently running")
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
|
|
150
216
|
def configure_gemini_mcp(
|
|
151
217
|
scope: Literal["project", "user"] = "project", force: bool = False
|
|
152
218
|
) -> None:
|