mcp-ticketer 0.1.23__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/jira.py +6 -2
- mcp_ticketer/adapters/linear.py +4 -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 +135 -2
- mcp_ticketer/cli/migrate_config.py +1 -5
- mcp_ticketer/core/project_config.py +4 -1
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/METADATA +58 -8
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/RECORD +16 -13
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.23.dist-info → mcp_ticketer-0.1.24.dist-info}/top_level.txt +0 -0
mcp_ticketer/__init__.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"""MCP Ticketer - Universal ticket management interface."""
|
|
2
2
|
|
|
3
3
|
from .__version__ import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
__author__,
|
|
5
|
+
__author_email__,
|
|
6
|
+
__copyright__,
|
|
7
|
+
__description__,
|
|
8
|
+
__license__,
|
|
9
|
+
__title__,
|
|
10
|
+
__version__,
|
|
11
|
+
__version_info__,
|
|
12
|
+
get_user_agent,
|
|
13
|
+
get_version,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
mcp_ticketer/__version__.py
CHANGED
mcp_ticketer/adapters/jira.py
CHANGED
|
@@ -806,7 +806,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
806
806
|
"custom_fields": custom_fields,
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
-
async def execute_jql(
|
|
809
|
+
async def execute_jql(
|
|
810
|
+
self, jql: str, limit: int = 50
|
|
811
|
+
) -> builtins.list[Union[Epic, Task]]:
|
|
810
812
|
"""Execute a raw JQL query.
|
|
811
813
|
|
|
812
814
|
Args:
|
|
@@ -831,7 +833,9 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
|
|
|
831
833
|
issues = data.get("issues", [])
|
|
832
834
|
return [self._issue_to_ticket(issue) for issue in issues]
|
|
833
835
|
|
|
834
|
-
async def get_sprints(
|
|
836
|
+
async def get_sprints(
|
|
837
|
+
self, board_id: Optional[int] = None
|
|
838
|
+
) -> builtins.list[dict[str, Any]]:
|
|
835
839
|
"""Get active sprints for a board (requires JIRA Software).
|
|
836
840
|
|
|
837
841
|
Args:
|
mcp_ticketer/adapters/linear.py
CHANGED
|
@@ -1547,7 +1547,9 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
1547
1547
|
|
|
1548
1548
|
return result["projectCreate"]["project"]["id"]
|
|
1549
1549
|
|
|
1550
|
-
async def get_cycles(
|
|
1550
|
+
async def get_cycles(
|
|
1551
|
+
self, active_only: bool = True
|
|
1552
|
+
) -> builtins.list[dict[str, Any]]:
|
|
1551
1553
|
"""Get Linear cycles (sprints) for the team."""
|
|
1552
1554
|
team_id = await self._ensure_team_id()
|
|
1553
1555
|
|
|
@@ -1858,7 +1860,7 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
1858
1860
|
search_query, variable_values={"identifier": identifier}
|
|
1859
1861
|
)
|
|
1860
1862
|
return result.get("issue")
|
|
1861
|
-
except:
|
|
1863
|
+
except Exception:
|
|
1862
1864
|
return None
|
|
1863
1865
|
|
|
1864
1866
|
# Epic/Issue/Task Hierarchy Methods (Linear: Project = Epic, Issue = Issue, Sub-issue = Task)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Auggie CLI configuration for mcp-ticketer integration.
|
|
2
|
+
|
|
3
|
+
IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
|
|
4
|
+
There is no project-level configuration support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from .mcp_configure import find_mcp_ticketer_binary, load_project_config
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_auggie_config() -> Path:
|
|
19
|
+
"""Find or create Auggie CLI configuration file.
|
|
20
|
+
|
|
21
|
+
Auggie CLI only supports global user-level configuration.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Path to Auggie settings file at ~/.augment/settings.json
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
# Global user-level configuration (ONLY option for Auggie)
|
|
28
|
+
config_path = Path.home() / ".augment" / "settings.json"
|
|
29
|
+
return config_path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_auggie_config(config_path: Path) -> dict[str, Any]:
|
|
33
|
+
"""Load existing Auggie configuration or return empty structure.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
config_path: Path to Auggie settings file
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Auggie configuration dict
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
if config_path.exists():
|
|
43
|
+
try:
|
|
44
|
+
with open(config_path) as f:
|
|
45
|
+
config: dict[str, Any] = json.load(f)
|
|
46
|
+
return config
|
|
47
|
+
except json.JSONDecodeError as e:
|
|
48
|
+
console.print(
|
|
49
|
+
f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
|
|
50
|
+
)
|
|
51
|
+
console.print("[yellow]Creating new configuration...[/yellow]")
|
|
52
|
+
|
|
53
|
+
# Return empty structure with mcpServers section
|
|
54
|
+
return {"mcpServers": {}}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def save_auggie_config(config_path: Path, config: dict[str, Any]) -> None:
|
|
58
|
+
"""Save Auggie configuration to file.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
config_path: Path to Auggie settings file
|
|
62
|
+
config: Configuration to save
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
# Ensure directory exists
|
|
66
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
# Write with 2-space indentation (JSON standard)
|
|
69
|
+
with open(config_path, "w") as f:
|
|
70
|
+
json.dump(config, f, indent=2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def create_auggie_server_config(
|
|
74
|
+
binary_path: str, project_config: dict[str, Any]
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
"""Create Auggie MCP server configuration for mcp-ticketer.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
binary_path: Path to mcp-ticketer binary
|
|
80
|
+
project_config: Project configuration from .mcp-ticketer/config.json
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Auggie MCP server configuration dict
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
# Get adapter configuration
|
|
87
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
88
|
+
adapters_config = project_config.get("adapters", {})
|
|
89
|
+
adapter_config = adapters_config.get(adapter, {})
|
|
90
|
+
|
|
91
|
+
# Build environment variables
|
|
92
|
+
env_vars = {}
|
|
93
|
+
|
|
94
|
+
# Add adapter type
|
|
95
|
+
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
96
|
+
|
|
97
|
+
# Add adapter-specific environment variables
|
|
98
|
+
if adapter == "aitrackdown":
|
|
99
|
+
# Set base path for local adapter
|
|
100
|
+
base_path = adapter_config.get("base_path", ".aitrackdown")
|
|
101
|
+
# Use absolute path to home directory for global config
|
|
102
|
+
# Since Auggie is global, we can't rely on project-specific paths
|
|
103
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = str(
|
|
104
|
+
Path.home() / ".mcp-ticketer" / base_path
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
elif adapter == "linear":
|
|
108
|
+
if "api_key" in adapter_config:
|
|
109
|
+
env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
|
|
110
|
+
if "team_id" in adapter_config:
|
|
111
|
+
env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
|
|
112
|
+
|
|
113
|
+
elif adapter == "github":
|
|
114
|
+
if "token" in adapter_config:
|
|
115
|
+
env_vars["GITHUB_TOKEN"] = adapter_config["token"]
|
|
116
|
+
if "owner" in adapter_config:
|
|
117
|
+
env_vars["GITHUB_OWNER"] = adapter_config["owner"]
|
|
118
|
+
if "repo" in adapter_config:
|
|
119
|
+
env_vars["GITHUB_REPO"] = adapter_config["repo"]
|
|
120
|
+
|
|
121
|
+
elif adapter == "jira":
|
|
122
|
+
if "api_token" in adapter_config:
|
|
123
|
+
env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
|
|
124
|
+
if "email" in adapter_config:
|
|
125
|
+
env_vars["JIRA_EMAIL"] = adapter_config["email"]
|
|
126
|
+
if "server" in adapter_config:
|
|
127
|
+
env_vars["JIRA_SERVER"] = adapter_config["server"]
|
|
128
|
+
if "project_key" in adapter_config:
|
|
129
|
+
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
130
|
+
|
|
131
|
+
# Create server configuration (simpler than Gemini - no timeout/trust)
|
|
132
|
+
config = {
|
|
133
|
+
"command": binary_path,
|
|
134
|
+
"args": ["serve"],
|
|
135
|
+
"env": env_vars,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return config
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def configure_auggie_mcp(force: bool = False) -> None:
|
|
142
|
+
"""Configure Auggie CLI to use mcp-ticketer.
|
|
143
|
+
|
|
144
|
+
IMPORTANT: Auggie CLI ONLY supports global configuration.
|
|
145
|
+
This will configure ~/.augment/settings.json for all projects.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
force: Overwrite existing configuration
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
FileNotFoundError: If binary or project config not found
|
|
152
|
+
ValueError: If configuration is invalid
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
# Step 1: Find mcp-ticketer binary
|
|
156
|
+
console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
|
|
157
|
+
try:
|
|
158
|
+
binary_path = find_mcp_ticketer_binary()
|
|
159
|
+
console.print(f"[green]✓[/green] Found: {binary_path}")
|
|
160
|
+
except FileNotFoundError as e:
|
|
161
|
+
console.print(f"[red]✗[/red] {e}")
|
|
162
|
+
raise
|
|
163
|
+
|
|
164
|
+
# Step 2: Load project configuration
|
|
165
|
+
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
166
|
+
try:
|
|
167
|
+
project_config = load_project_config()
|
|
168
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
169
|
+
console.print(f"[green]✓[/green] Adapter: {adapter}")
|
|
170
|
+
except (FileNotFoundError, ValueError) as e:
|
|
171
|
+
console.print(f"[red]✗[/red] {e}")
|
|
172
|
+
raise
|
|
173
|
+
|
|
174
|
+
# Step 3: Find Auggie config location
|
|
175
|
+
console.print("\n[cyan]🔧 Configuring global Auggie CLI...[/cyan]")
|
|
176
|
+
console.print(
|
|
177
|
+
"[yellow]⚠ NOTE: Auggie only supports global configuration (affects all projects)[/yellow]"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
auggie_config_path = find_auggie_config()
|
|
181
|
+
console.print(f"[dim]Config location: {auggie_config_path}[/dim]")
|
|
182
|
+
|
|
183
|
+
# Step 4: Load existing Auggie configuration
|
|
184
|
+
auggie_config = load_auggie_config(auggie_config_path)
|
|
185
|
+
|
|
186
|
+
# Step 5: Check if mcp-ticketer already configured
|
|
187
|
+
if "mcp-ticketer" in auggie_config.get("mcpServers", {}):
|
|
188
|
+
if not force:
|
|
189
|
+
console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
|
|
190
|
+
console.print("[dim]Use --force to overwrite existing configuration[/dim]")
|
|
191
|
+
return
|
|
192
|
+
else:
|
|
193
|
+
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
194
|
+
|
|
195
|
+
# Step 6: Create mcp-ticketer server config
|
|
196
|
+
server_config = create_auggie_server_config(
|
|
197
|
+
binary_path=binary_path, project_config=project_config
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Step 7: Update Auggie configuration
|
|
201
|
+
if "mcpServers" not in auggie_config:
|
|
202
|
+
auggie_config["mcpServers"] = {}
|
|
203
|
+
|
|
204
|
+
auggie_config["mcpServers"]["mcp-ticketer"] = server_config
|
|
205
|
+
|
|
206
|
+
# Step 8: Save configuration
|
|
207
|
+
try:
|
|
208
|
+
save_auggie_config(auggie_config_path, auggie_config)
|
|
209
|
+
console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
|
|
210
|
+
console.print(f"[dim]Configuration saved to: {auggie_config_path}[/dim]")
|
|
211
|
+
|
|
212
|
+
# Print configuration details
|
|
213
|
+
console.print("\n[bold]Configuration Details:[/bold]")
|
|
214
|
+
console.print(" Server name: mcp-ticketer")
|
|
215
|
+
console.print(f" Adapter: {adapter}")
|
|
216
|
+
console.print(f" Binary: {binary_path}")
|
|
217
|
+
console.print(" Scope: Global (affects all projects)")
|
|
218
|
+
if "env" in server_config:
|
|
219
|
+
console.print(
|
|
220
|
+
f" Environment variables: {list(server_config['env'].keys())}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Next steps
|
|
224
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
225
|
+
console.print("1. Restart Auggie CLI for changes to take effect")
|
|
226
|
+
console.print("2. Run 'auggie' command in any directory")
|
|
227
|
+
console.print("3. mcp-ticketer tools will be available via MCP")
|
|
228
|
+
console.print(
|
|
229
|
+
"\n[yellow]⚠ Warning: This is a global configuration affecting all projects[/yellow]"
|
|
230
|
+
)
|
|
231
|
+
console.print(
|
|
232
|
+
"[dim]If you need project-specific configuration, use Claude or Gemini instead[/dim]"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
|
|
237
|
+
raise
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Codex CLI configuration for mcp-ticketer integration.
|
|
2
|
+
|
|
3
|
+
Codex CLI only supports global configuration at ~/.codex/config.toml.
|
|
4
|
+
Unlike Claude Code and Gemini CLI, there is no project-level configuration support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
if sys.version_info >= (3, 11):
|
|
12
|
+
import tomllib
|
|
13
|
+
else:
|
|
14
|
+
import tomli as tomllib
|
|
15
|
+
|
|
16
|
+
import tomli_w
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
from .mcp_configure import find_mcp_ticketer_binary, load_project_config
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_codex_config() -> Path:
|
|
25
|
+
"""Find Codex CLI configuration file location.
|
|
26
|
+
|
|
27
|
+
Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
28
|
+
No project-level or user-scoped configuration is available.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Path to Codex global config file at ~/.codex/config.toml
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
# Codex only supports global config (no project-level support)
|
|
35
|
+
config_path = Path.home() / ".codex" / "config.toml"
|
|
36
|
+
return config_path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_codex_config(config_path: Path) -> Dict[str, Any]:
|
|
40
|
+
"""Load existing Codex configuration or return empty structure.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
config_path: Path to Codex config.toml file
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Codex configuration dict with mcp_servers section
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
if config_path.exists():
|
|
50
|
+
try:
|
|
51
|
+
with open(config_path, "rb") as f:
|
|
52
|
+
return tomllib.load(f)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
console.print(
|
|
55
|
+
f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
|
|
56
|
+
)
|
|
57
|
+
console.print("[yellow]Creating new configuration...[/yellow]")
|
|
58
|
+
|
|
59
|
+
# Return empty structure with mcp_servers section
|
|
60
|
+
# NOTE: Use underscore mcp_servers, not camelCase mcpServers
|
|
61
|
+
return {"mcp_servers": {}}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
|
|
65
|
+
"""Save Codex configuration to TOML file.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config_path: Path to Codex config.toml file
|
|
69
|
+
config: Configuration to save
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
# Ensure directory exists
|
|
73
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
# Write TOML with proper formatting
|
|
76
|
+
with open(config_path, "wb") as f:
|
|
77
|
+
tomli_w.dump(config, f)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_codex_server_config(
|
|
81
|
+
binary_path: str, project_config: dict, cwd: Optional[str] = None
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
"""Create Codex MCP server configuration for mcp-ticketer.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
binary_path: Path to mcp-ticketer binary
|
|
87
|
+
project_config: Project configuration from .mcp-ticketer/config.json
|
|
88
|
+
cwd: Working directory for server (optional, not used for global config)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Codex MCP server configuration dict
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
# Get adapter configuration
|
|
95
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
96
|
+
adapters_config = project_config.get("adapters", {})
|
|
97
|
+
adapter_config = adapters_config.get(adapter, {})
|
|
98
|
+
|
|
99
|
+
# Build environment variables
|
|
100
|
+
env_vars: Dict[str, str] = {}
|
|
101
|
+
|
|
102
|
+
# Add PYTHONPATH if running from development environment
|
|
103
|
+
if cwd:
|
|
104
|
+
env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
|
|
105
|
+
|
|
106
|
+
# Add adapter type
|
|
107
|
+
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
108
|
+
|
|
109
|
+
# Add adapter-specific environment variables
|
|
110
|
+
if adapter == "aitrackdown":
|
|
111
|
+
# Set base path for local adapter
|
|
112
|
+
base_path = adapter_config.get("base_path", ".aitrackdown")
|
|
113
|
+
if cwd:
|
|
114
|
+
# Use absolute path if cwd is provided
|
|
115
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_path)
|
|
116
|
+
else:
|
|
117
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = base_path
|
|
118
|
+
|
|
119
|
+
elif adapter == "linear":
|
|
120
|
+
if "api_key" in adapter_config:
|
|
121
|
+
env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
|
|
122
|
+
if "team_id" in adapter_config:
|
|
123
|
+
env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
|
|
124
|
+
|
|
125
|
+
elif adapter == "github":
|
|
126
|
+
if "token" in adapter_config:
|
|
127
|
+
env_vars["GITHUB_TOKEN"] = adapter_config["token"]
|
|
128
|
+
if "owner" in adapter_config:
|
|
129
|
+
env_vars["GITHUB_OWNER"] = adapter_config["owner"]
|
|
130
|
+
if "repo" in adapter_config:
|
|
131
|
+
env_vars["GITHUB_REPO"] = adapter_config["repo"]
|
|
132
|
+
|
|
133
|
+
elif adapter == "jira":
|
|
134
|
+
if "api_token" in adapter_config:
|
|
135
|
+
env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
|
|
136
|
+
if "email" in adapter_config:
|
|
137
|
+
env_vars["JIRA_EMAIL"] = adapter_config["email"]
|
|
138
|
+
if "server" in adapter_config:
|
|
139
|
+
env_vars["JIRA_SERVER"] = adapter_config["server"]
|
|
140
|
+
if "project_key" in adapter_config:
|
|
141
|
+
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
142
|
+
|
|
143
|
+
# Create server configuration with Codex-specific structure
|
|
144
|
+
# NOTE: Codex uses nested dict structure for env vars
|
|
145
|
+
config: Dict[str, Any] = {
|
|
146
|
+
"command": binary_path,
|
|
147
|
+
"args": ["serve"],
|
|
148
|
+
"env": env_vars,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return config
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def configure_codex_mcp(force: bool = False) -> None:
|
|
155
|
+
"""Configure Codex CLI to use mcp-ticketer.
|
|
156
|
+
|
|
157
|
+
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
158
|
+
There is no project-level or user-scoped configuration available.
|
|
159
|
+
|
|
160
|
+
After configuration, you must restart Codex CLI for changes to take effect.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
force: Overwrite existing configuration
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
FileNotFoundError: If binary or project config not found
|
|
167
|
+
ValueError: If configuration is invalid
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
# Step 1: Find mcp-ticketer binary
|
|
171
|
+
console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
|
|
172
|
+
try:
|
|
173
|
+
binary_path = find_mcp_ticketer_binary()
|
|
174
|
+
console.print(f"[green]✓[/green] Found: {binary_path}")
|
|
175
|
+
except FileNotFoundError as e:
|
|
176
|
+
console.print(f"[red]✗[/red] {e}")
|
|
177
|
+
raise
|
|
178
|
+
|
|
179
|
+
# Step 2: Load project configuration
|
|
180
|
+
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
181
|
+
try:
|
|
182
|
+
project_config = load_project_config()
|
|
183
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
184
|
+
console.print(f"[green]✓[/green] Adapter: {adapter}")
|
|
185
|
+
except (FileNotFoundError, ValueError) as e:
|
|
186
|
+
console.print(f"[red]✗[/red] {e}")
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
# Step 3: Find Codex config location (always global)
|
|
190
|
+
console.print("\n[cyan]🔧 Configuring Codex CLI (global-only)...[/cyan]")
|
|
191
|
+
console.print(
|
|
192
|
+
"[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
codex_config_path = find_codex_config()
|
|
196
|
+
console.print(f"[dim]Config location: {codex_config_path}[/dim]")
|
|
197
|
+
|
|
198
|
+
# Step 4: Load existing Codex configuration
|
|
199
|
+
codex_config = load_codex_config(codex_config_path)
|
|
200
|
+
|
|
201
|
+
# Step 5: Check if mcp-ticketer already configured
|
|
202
|
+
# NOTE: Use underscore mcp_servers, not camelCase
|
|
203
|
+
mcp_servers = codex_config.get("mcp_servers", {})
|
|
204
|
+
if "mcp-ticketer" in mcp_servers:
|
|
205
|
+
if not force:
|
|
206
|
+
console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
|
|
207
|
+
console.print("[dim]Use --force to overwrite existing configuration[/dim]")
|
|
208
|
+
return
|
|
209
|
+
else:
|
|
210
|
+
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
211
|
+
|
|
212
|
+
# Step 6: Create mcp-ticketer server config
|
|
213
|
+
# For global config, include current working directory for context
|
|
214
|
+
cwd = str(Path.cwd())
|
|
215
|
+
server_config = create_codex_server_config(
|
|
216
|
+
binary_path=binary_path, project_config=project_config, cwd=cwd
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Step 7: Update Codex configuration
|
|
220
|
+
if "mcp_servers" not in codex_config:
|
|
221
|
+
codex_config["mcp_servers"] = {}
|
|
222
|
+
|
|
223
|
+
codex_config["mcp_servers"]["mcp-ticketer"] = server_config
|
|
224
|
+
|
|
225
|
+
# Step 8: Save configuration
|
|
226
|
+
try:
|
|
227
|
+
save_codex_config(codex_config_path, codex_config)
|
|
228
|
+
console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
|
|
229
|
+
console.print(f"[dim]Configuration saved to: {codex_config_path}[/dim]")
|
|
230
|
+
|
|
231
|
+
# Print configuration details
|
|
232
|
+
console.print("\n[bold]Configuration Details:[/bold]")
|
|
233
|
+
console.print(" Server name: mcp-ticketer")
|
|
234
|
+
console.print(f" Adapter: {adapter}")
|
|
235
|
+
console.print(f" Binary: {binary_path}")
|
|
236
|
+
console.print(" Scope: global (Codex only supports global config)")
|
|
237
|
+
console.print(f" Working directory: {cwd}")
|
|
238
|
+
if "env" in server_config:
|
|
239
|
+
console.print(
|
|
240
|
+
f" Environment variables: {list(server_config['env'].keys())}"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Next steps
|
|
244
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
245
|
+
console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
|
|
246
|
+
console.print("2. Run 'codex' command from any directory")
|
|
247
|
+
console.print("3. mcp-ticketer tools will be available via MCP")
|
|
248
|
+
console.print(
|
|
249
|
+
"\n[yellow]⚠ Warning: This is a global configuration that affects all Codex sessions[/yellow]"
|
|
250
|
+
)
|
|
251
|
+
console.print(
|
|
252
|
+
"[yellow] The configuration includes paths from your current project directory[/yellow]"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
|
|
257
|
+
raise
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Gemini CLI configuration for mcp-ticketer integration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from .mcp_configure import find_mcp_ticketer_binary, load_project_config
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def find_gemini_config(scope: Literal["project", "user"] = "project") -> Path:
|
|
15
|
+
"""Find or create Gemini CLI configuration file.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
scope: Configuration scope - "project" for .gemini/settings.json
|
|
19
|
+
or "user" for ~/.gemini/settings.json
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Path to Gemini settings file
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
if scope == "user":
|
|
26
|
+
# User-level configuration
|
|
27
|
+
config_path = Path.home() / ".gemini" / "settings.json"
|
|
28
|
+
else:
|
|
29
|
+
# Project-level configuration
|
|
30
|
+
config_path = Path.cwd() / ".gemini" / "settings.json"
|
|
31
|
+
|
|
32
|
+
return config_path
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_gemini_config(config_path: Path) -> dict:
|
|
36
|
+
"""Load existing Gemini configuration or return empty structure.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config_path: Path to Gemini settings file
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Gemini configuration dict
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
if config_path.exists():
|
|
46
|
+
try:
|
|
47
|
+
with open(config_path) as f:
|
|
48
|
+
return json.load(f)
|
|
49
|
+
except json.JSONDecodeError as e:
|
|
50
|
+
console.print(
|
|
51
|
+
f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
|
|
52
|
+
)
|
|
53
|
+
console.print("[yellow]Creating new configuration...[/yellow]")
|
|
54
|
+
|
|
55
|
+
# Return empty structure with mcpServers section
|
|
56
|
+
return {"mcpServers": {}}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def save_gemini_config(config_path: Path, config: dict) -> None:
|
|
60
|
+
"""Save Gemini configuration to file.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
config_path: Path to Gemini settings file
|
|
64
|
+
config: Configuration to save
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
# Ensure directory exists
|
|
68
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
# Write with 2-space indentation (Gemini CLI standard)
|
|
71
|
+
with open(config_path, "w") as f:
|
|
72
|
+
json.dump(config, f, indent=2)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_gemini_server_config(
|
|
76
|
+
binary_path: str, project_config: dict, cwd: Optional[str] = None
|
|
77
|
+
) -> dict:
|
|
78
|
+
"""Create Gemini MCP server configuration for mcp-ticketer.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
binary_path: Path to mcp-ticketer binary
|
|
82
|
+
project_config: Project configuration from .mcp-ticketer/config.json
|
|
83
|
+
cwd: Working directory for server (optional)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Gemini MCP server configuration dict
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
# Get adapter configuration
|
|
90
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
91
|
+
adapters_config = project_config.get("adapters", {})
|
|
92
|
+
adapter_config = adapters_config.get(adapter, {})
|
|
93
|
+
|
|
94
|
+
# Build environment variables
|
|
95
|
+
env_vars = {}
|
|
96
|
+
|
|
97
|
+
# Add PYTHONPATH if running from development environment
|
|
98
|
+
if cwd:
|
|
99
|
+
env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
|
|
100
|
+
|
|
101
|
+
# Add adapter type
|
|
102
|
+
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
103
|
+
|
|
104
|
+
# Add adapter-specific environment variables
|
|
105
|
+
if adapter == "aitrackdown":
|
|
106
|
+
# Set base path for local adapter
|
|
107
|
+
base_path = adapter_config.get("base_path", ".aitrackdown")
|
|
108
|
+
if cwd:
|
|
109
|
+
# Use absolute path if cwd is provided
|
|
110
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_path)
|
|
111
|
+
else:
|
|
112
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = base_path
|
|
113
|
+
|
|
114
|
+
elif adapter == "linear":
|
|
115
|
+
if "api_key" in adapter_config:
|
|
116
|
+
env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
|
|
117
|
+
if "team_id" in adapter_config:
|
|
118
|
+
env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
|
|
119
|
+
|
|
120
|
+
elif adapter == "github":
|
|
121
|
+
if "token" in adapter_config:
|
|
122
|
+
env_vars["GITHUB_TOKEN"] = adapter_config["token"]
|
|
123
|
+
if "owner" in adapter_config:
|
|
124
|
+
env_vars["GITHUB_OWNER"] = adapter_config["owner"]
|
|
125
|
+
if "repo" in adapter_config:
|
|
126
|
+
env_vars["GITHUB_REPO"] = adapter_config["repo"]
|
|
127
|
+
|
|
128
|
+
elif adapter == "jira":
|
|
129
|
+
if "api_token" in adapter_config:
|
|
130
|
+
env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
|
|
131
|
+
if "email" in adapter_config:
|
|
132
|
+
env_vars["JIRA_EMAIL"] = adapter_config["email"]
|
|
133
|
+
if "server" in adapter_config:
|
|
134
|
+
env_vars["JIRA_SERVER"] = adapter_config["server"]
|
|
135
|
+
if "project_key" in adapter_config:
|
|
136
|
+
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
137
|
+
|
|
138
|
+
# Create server configuration with Gemini-specific options
|
|
139
|
+
config = {
|
|
140
|
+
"command": binary_path,
|
|
141
|
+
"args": ["serve"],
|
|
142
|
+
"env": env_vars,
|
|
143
|
+
"timeout": 15000, # 15 seconds timeout
|
|
144
|
+
"trust": False, # Don't trust by default (security)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return config
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def configure_gemini_mcp(
|
|
151
|
+
scope: Literal["project", "user"] = "project", force: bool = False
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Configure Gemini CLI to use mcp-ticketer.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
scope: Configuration scope - "project" or "user"
|
|
157
|
+
force: Overwrite existing configuration
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
FileNotFoundError: If binary or project config not found
|
|
161
|
+
ValueError: If configuration is invalid
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
# Step 1: Find mcp-ticketer binary
|
|
165
|
+
console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
|
|
166
|
+
try:
|
|
167
|
+
binary_path = find_mcp_ticketer_binary()
|
|
168
|
+
console.print(f"[green]✓[/green] Found: {binary_path}")
|
|
169
|
+
except FileNotFoundError as e:
|
|
170
|
+
console.print(f"[red]✗[/red] {e}")
|
|
171
|
+
raise
|
|
172
|
+
|
|
173
|
+
# Step 2: Load project configuration
|
|
174
|
+
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
175
|
+
try:
|
|
176
|
+
project_config = load_project_config()
|
|
177
|
+
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
178
|
+
console.print(f"[green]✓[/green] Adapter: {adapter}")
|
|
179
|
+
except (FileNotFoundError, ValueError) as e:
|
|
180
|
+
console.print(f"[red]✗[/red] {e}")
|
|
181
|
+
raise
|
|
182
|
+
|
|
183
|
+
# Step 3: Find Gemini config location
|
|
184
|
+
config_type = "user-level" if scope == "user" else "project-level"
|
|
185
|
+
console.print(f"\n[cyan]🔧 Configuring {config_type} Gemini CLI...[/cyan]")
|
|
186
|
+
|
|
187
|
+
gemini_config_path = find_gemini_config(scope)
|
|
188
|
+
console.print(f"[dim]Config location: {gemini_config_path}[/dim]")
|
|
189
|
+
|
|
190
|
+
# Step 4: Load existing Gemini configuration
|
|
191
|
+
gemini_config = load_gemini_config(gemini_config_path)
|
|
192
|
+
|
|
193
|
+
# Step 5: Check if mcp-ticketer already configured
|
|
194
|
+
if "mcp-ticketer" in gemini_config.get("mcpServers", {}):
|
|
195
|
+
if not force:
|
|
196
|
+
console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
|
|
197
|
+
console.print("[dim]Use --force to overwrite existing configuration[/dim]")
|
|
198
|
+
return
|
|
199
|
+
else:
|
|
200
|
+
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
201
|
+
|
|
202
|
+
# Step 6: Create mcp-ticketer server config
|
|
203
|
+
cwd = str(Path.cwd()) if scope == "project" else None
|
|
204
|
+
server_config = create_gemini_server_config(
|
|
205
|
+
binary_path=binary_path, project_config=project_config, cwd=cwd
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Step 7: Update Gemini configuration
|
|
209
|
+
if "mcpServers" not in gemini_config:
|
|
210
|
+
gemini_config["mcpServers"] = {}
|
|
211
|
+
|
|
212
|
+
gemini_config["mcpServers"]["mcp-ticketer"] = server_config
|
|
213
|
+
|
|
214
|
+
# Step 8: Save configuration
|
|
215
|
+
try:
|
|
216
|
+
save_gemini_config(gemini_config_path, gemini_config)
|
|
217
|
+
console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
|
|
218
|
+
console.print(f"[dim]Configuration saved to: {gemini_config_path}[/dim]")
|
|
219
|
+
|
|
220
|
+
# Print configuration details
|
|
221
|
+
console.print("\n[bold]Configuration Details:[/bold]")
|
|
222
|
+
console.print(" Server name: mcp-ticketer")
|
|
223
|
+
console.print(f" Adapter: {adapter}")
|
|
224
|
+
console.print(f" Binary: {binary_path}")
|
|
225
|
+
console.print(f" Timeout: {server_config['timeout']}ms")
|
|
226
|
+
console.print(f" Trust: {server_config['trust']}")
|
|
227
|
+
if cwd:
|
|
228
|
+
console.print(f" Working directory: {cwd}")
|
|
229
|
+
if "env" in server_config:
|
|
230
|
+
console.print(
|
|
231
|
+
f" Environment variables: {list(server_config['env'].keys())}"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Next steps
|
|
235
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
236
|
+
if scope == "user":
|
|
237
|
+
console.print("1. Gemini CLI will use this configuration globally")
|
|
238
|
+
console.print("2. Run 'gemini' command in any directory")
|
|
239
|
+
else:
|
|
240
|
+
console.print("1. Run 'gemini' command in this project directory")
|
|
241
|
+
console.print("2. Gemini CLI will detect project-level configuration")
|
|
242
|
+
console.print("3. mcp-ticketer tools will be available via MCP")
|
|
243
|
+
|
|
244
|
+
# Add .gemini to .gitignore for project-level config
|
|
245
|
+
if scope == "project":
|
|
246
|
+
gitignore_path = Path.cwd() / ".gitignore"
|
|
247
|
+
if gitignore_path.exists():
|
|
248
|
+
gitignore_content = gitignore_path.read_text()
|
|
249
|
+
if ".gemini" not in gitignore_content:
|
|
250
|
+
with open(gitignore_path, "a") as f:
|
|
251
|
+
f.write("\n# Gemini CLI\n.gemini/\n")
|
|
252
|
+
console.print("\n[dim]✓ Added .gemini/ to .gitignore[/dim]")
|
|
253
|
+
else:
|
|
254
|
+
# Create .gitignore if it doesn't exist
|
|
255
|
+
with open(gitignore_path, "w") as f:
|
|
256
|
+
f.write("# Gemini CLI\n.gemini/\n")
|
|
257
|
+
console.print("\n[dim]✓ Created .gitignore with .gemini/[/dim]")
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
|
|
261
|
+
raise
|
mcp_ticketer/cli/main.py
CHANGED
|
@@ -1118,6 +1118,13 @@ app.add_typer(queue_app, name="queue")
|
|
|
1118
1118
|
# Add discover command to main app
|
|
1119
1119
|
app.add_typer(discover_app, name="discover")
|
|
1120
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
|
+
|
|
1121
1128
|
|
|
1122
1129
|
@app.command()
|
|
1123
1130
|
def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
|
|
@@ -1233,8 +1240,8 @@ def serve(
|
|
|
1233
1240
|
sys.exit(1)
|
|
1234
1241
|
|
|
1235
1242
|
|
|
1236
|
-
@
|
|
1237
|
-
def
|
|
1243
|
+
@mcp_app.command(name="claude")
|
|
1244
|
+
def mcp_claude(
|
|
1238
1245
|
global_config: bool = typer.Option(
|
|
1239
1246
|
False,
|
|
1240
1247
|
"--global",
|
|
@@ -1252,6 +1259,17 @@ def mcp(
|
|
|
1252
1259
|
|
|
1253
1260
|
By default, configures project-level (.mcp/config.json).
|
|
1254
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
|
+
|
|
1255
1273
|
"""
|
|
1256
1274
|
from ..cli.mcp_configure import configure_claude_mcp
|
|
1257
1275
|
|
|
@@ -1262,6 +1280,121 @@ def mcp(
|
|
|
1262
1280
|
raise typer.Exit(1)
|
|
1263
1281
|
|
|
1264
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
|
+
|
|
1265
1398
|
def main():
|
|
1266
1399
|
"""Main entry point."""
|
|
1267
1400
|
app()
|
|
@@ -8,11 +8,7 @@ from typing import Any
|
|
|
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
|
|
|
@@ -14,7 +14,10 @@ import os
|
|
|
14
14
|
from dataclasses import asdict, dataclass, field
|
|
15
15
|
from enum import Enum
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Any, Optional
|
|
17
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .env_discovery import DiscoveryResult
|
|
18
21
|
|
|
19
22
|
logger = logging.getLogger(__name__)
|
|
20
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-ticketer
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: Universal ticket management interface for AI agents with MCP support
|
|
5
5
|
Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
6
6
|
Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
@@ -39,6 +39,8 @@ Requires-Dist: psutil>=5.9.0
|
|
|
39
39
|
Requires-Dist: pydantic>=2.0
|
|
40
40
|
Requires-Dist: python-dotenv>=1.0.0
|
|
41
41
|
Requires-Dist: rich>=13.0.0
|
|
42
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
43
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
42
44
|
Requires-Dist: typer>=0.9.0
|
|
43
45
|
Requires-Dist: typing-extensions>=4.8.0
|
|
44
46
|
Provides-Extra: all
|
|
@@ -129,6 +131,39 @@ pip install -e .
|
|
|
129
131
|
- Python 3.9+
|
|
130
132
|
- Virtual environment (recommended)
|
|
131
133
|
|
|
134
|
+
## 🤖 Supported AI Clients
|
|
135
|
+
|
|
136
|
+
MCP Ticketer integrates with multiple AI clients via the Model Context Protocol (MCP):
|
|
137
|
+
|
|
138
|
+
| AI Client | Support | Config Type | Project-Level | Setup Command |
|
|
139
|
+
|-----------|---------|-------------|---------------|---------------|
|
|
140
|
+
| **Claude Code** | ✅ Native | JSON | ✅ Yes | `mcp-ticketer mcp claude` |
|
|
141
|
+
| **Gemini CLI** | ✅ Full | JSON | ✅ Yes | `mcp-ticketer mcp gemini` |
|
|
142
|
+
| **Codex CLI** | ✅ Full | TOML | ❌ Global only | `mcp-ticketer mcp codex` |
|
|
143
|
+
| **Auggie** | ✅ Full | JSON | ❌ Global only | `mcp-ticketer mcp auggie` |
|
|
144
|
+
|
|
145
|
+
### Quick MCP Setup
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Claude Code (recommended for project-specific workflows)
|
|
149
|
+
mcp-ticketer init --adapter aitrackdown # First, initialize an adapter
|
|
150
|
+
mcp-ticketer mcp claude # Then configure MCP
|
|
151
|
+
|
|
152
|
+
# Gemini CLI (Google's AI client)
|
|
153
|
+
mcp-ticketer init --adapter aitrackdown
|
|
154
|
+
mcp-ticketer mcp gemini --scope project
|
|
155
|
+
|
|
156
|
+
# Codex CLI (global configuration, requires restart)
|
|
157
|
+
mcp-ticketer init --adapter aitrackdown
|
|
158
|
+
mcp-ticketer mcp codex
|
|
159
|
+
|
|
160
|
+
# Auggie (simple global setup)
|
|
161
|
+
mcp-ticketer init --adapter aitrackdown
|
|
162
|
+
mcp-ticketer mcp auggie
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**See [AI Client Integration Guide](docs/AI_CLIENT_INTEGRATION.md) for detailed setup instructions.**
|
|
166
|
+
|
|
132
167
|
## 🚀 Quick Start
|
|
133
168
|
|
|
134
169
|
### 1. Initialize Configuration
|
|
@@ -179,28 +214,43 @@ mcp-ticketer search "login bug" --state open
|
|
|
179
214
|
|
|
180
215
|
## 🤖 MCP Server Integration
|
|
181
216
|
|
|
182
|
-
|
|
217
|
+
MCP Ticketer provides seamless integration with AI clients through automatic configuration:
|
|
183
218
|
|
|
184
219
|
```bash
|
|
185
|
-
|
|
220
|
+
# Run MCP server manually (for testing)
|
|
221
|
+
mcp-ticketer serve
|
|
222
|
+
|
|
223
|
+
# Or configure your AI client automatically (recommended)
|
|
224
|
+
mcp-ticketer mcp claude # For Claude Code
|
|
225
|
+
mcp-ticketer mcp gemini # For Gemini CLI
|
|
226
|
+
mcp-ticketer mcp codex # For Codex CLI
|
|
227
|
+
mcp-ticketer mcp auggie # For Auggie
|
|
186
228
|
```
|
|
187
229
|
|
|
188
|
-
|
|
230
|
+
**Configuration is automatic** - the commands above will:
|
|
231
|
+
1. Detect your mcp-ticketer installation
|
|
232
|
+
2. Read your adapter configuration
|
|
233
|
+
3. Generate the appropriate MCP server config
|
|
234
|
+
4. Save it to the correct location for your AI client
|
|
235
|
+
|
|
236
|
+
**Manual Configuration Example** (Claude Code):
|
|
189
237
|
|
|
190
238
|
```json
|
|
191
239
|
{
|
|
192
240
|
"mcpServers": {
|
|
193
|
-
"ticketer": {
|
|
194
|
-
"command": "mcp-ticketer
|
|
195
|
-
"args": [],
|
|
241
|
+
"mcp-ticketer": {
|
|
242
|
+
"command": "/path/to/mcp-ticketer",
|
|
243
|
+
"args": ["serve"],
|
|
196
244
|
"env": {
|
|
197
|
-
"MCP_TICKETER_ADAPTER": "
|
|
245
|
+
"MCP_TICKETER_ADAPTER": "aitrackdown"
|
|
198
246
|
}
|
|
199
247
|
}
|
|
200
248
|
}
|
|
201
249
|
}
|
|
202
250
|
```
|
|
203
251
|
|
|
252
|
+
**See [AI Client Integration Guide](docs/AI_CLIENT_INTEGRATION.md) for client-specific details.**
|
|
253
|
+
|
|
204
254
|
## 📚 Documentation
|
|
205
255
|
|
|
206
256
|
Full documentation is available at [https://mcp-ticketerer.readthedocs.io](https://mcp-ticketerer.readthedocs.io)
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
mcp_ticketer/__init__.py,sha256=
|
|
2
|
-
mcp_ticketer/__version__.py,sha256=
|
|
1
|
+
mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
|
|
2
|
+
mcp_ticketer/__version__.py,sha256=jSY7C2Ap-dss0EXCJjKddzTwqfO8125m1PW4jz1lXkU,1118
|
|
3
3
|
mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
|
|
5
5
|
mcp_ticketer/adapters/aitrackdown.py,sha256=24h3UqMj5CltCl271kmqnDFOicCVp9w7yMTKK8s_dTA,16283
|
|
6
6
|
mcp_ticketer/adapters/github.py,sha256=X0lEWBCfy-vztX2vauuVSYsOCa9_ezt9hGa5BsCQTu8,46663
|
|
7
7
|
mcp_ticketer/adapters/hybrid.py,sha256=XFqHtWTLguE61ZGuZ156gxoz-wMr21AxLSADI1QVxcU,19025
|
|
8
|
-
mcp_ticketer/adapters/jira.py,sha256=
|
|
9
|
-
mcp_ticketer/adapters/linear.py,sha256=
|
|
8
|
+
mcp_ticketer/adapters/jira.py,sha256=W2pU-YxrSqgjm1gVt2eGc8We-G0MbRMSggQ2gWkThME,30602
|
|
9
|
+
mcp_ticketer/adapters/linear.py,sha256=C6rHIxRaWvOBjX6ue2Nt-IPTcwFAwBkksstb7zV3YJw,71484
|
|
10
10
|
mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d4,138
|
|
11
11
|
mcp_ticketer/cache/memory.py,sha256=2yBqGi9i0SanlUhJoOC7nijWjoMa3_ntPe-V-AV-LfU,5042
|
|
12
12
|
mcp_ticketer/cli/__init__.py,sha256=l9Q8iKmfGkTu0cssHBVqNZTsL4eAtFzOB25AED_0G6g,89
|
|
13
|
+
mcp_ticketer/cli/auggie_configure.py,sha256=MXKzLtqe3K_UTQ2GacHAWbvf_B0779KL325smiAKE0Q,8212
|
|
14
|
+
mcp_ticketer/cli/codex_configure.py,sha256=xDppHouT6_-cYXswyAggoPX5bSlRXMvCoM_x9PQ-42A,9086
|
|
13
15
|
mcp_ticketer/cli/configure.py,sha256=BsA_pSHQMQS0t1bJO_wMM8LWsd5sWJDASjEPRHvwC18,16198
|
|
14
16
|
mcp_ticketer/cli/discover.py,sha256=AF_qlQc1Oo0UkWayoF5pmRChS5J3fJjH6f2YZzd_k8w,13188
|
|
15
|
-
mcp_ticketer/cli/
|
|
17
|
+
mcp_ticketer/cli/gemini_configure.py,sha256=ZNSA1lBW-itVToza-JxW95Po7daVXKiZAh7lp6pmXMU,9343
|
|
18
|
+
mcp_ticketer/cli/main.py,sha256=AvWLtlKrAtgWv6cfXNRzESqJE7CRhHdKeDuNWOxj5GM,47111
|
|
16
19
|
mcp_ticketer/cli/mcp_configure.py,sha256=RzV50UjXgOmvMp-9S0zS39psuvjffVByaMrqrUaAGAM,9594
|
|
17
|
-
mcp_ticketer/cli/migrate_config.py,sha256=
|
|
20
|
+
mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
|
|
18
21
|
mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
|
|
19
22
|
mcp_ticketer/cli/utils.py,sha256=2ptUrp2ELZsox0kSxAI5DFrHonOU999qh4MxbLv6VBQ,21155
|
|
20
23
|
mcp_ticketer/core/__init__.py,sha256=eXovsaJymQRP2AwOBuOy6mFtI3I68D7gGenZ5V-IMqo,349
|
|
@@ -24,7 +27,7 @@ mcp_ticketer/core/env_discovery.py,sha256=wKp2Pi5vQMGOTrM1690IBv_eoABly-pD8ah7n1
|
|
|
24
27
|
mcp_ticketer/core/http_client.py,sha256=s5ikMiwEJ8TJjNn73wu3gv3OdAtyBEpAqPnSroRMW2k,13971
|
|
25
28
|
mcp_ticketer/core/mappers.py,sha256=1aG1jFsHTCwmGRVgOlXW-VOSTGzc86gv7qjDfiR1ups,17462
|
|
26
29
|
mcp_ticketer/core/models.py,sha256=DRuJoYbjp9fcPV9GwQfhVcNUB0XmwQB3vuqW8hQWZ_k,6491
|
|
27
|
-
mcp_ticketer/core/project_config.py,sha256=
|
|
30
|
+
mcp_ticketer/core/project_config.py,sha256=yYxlgxjcEPeOwx-b-SXFpe0k9pW9xzBRAK72PsItG-o,23346
|
|
28
31
|
mcp_ticketer/core/registry.py,sha256=ShYLDPE62KFJpB0kj_zFyQzRxSH3LkQEEuo1jaakb1k,3483
|
|
29
32
|
mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,123
|
|
30
33
|
mcp_ticketer/mcp/server.py,sha256=U0bkHYe_DGa7wNPtuawsA8i_llHmpADgtq-OkYMjoBo,37041
|
|
@@ -34,9 +37,9 @@ mcp_ticketer/queue/manager.py,sha256=qqUqq_JtH8jfg-MDfc-UIgFaa7gYsA1eBaR2KsCw48c
|
|
|
34
37
|
mcp_ticketer/queue/queue.py,sha256=zD7SRDP7zfGm4gokqzgL0CLuPUPxbBNmddsOqLMCbjQ,13162
|
|
35
38
|
mcp_ticketer/queue/run_worker.py,sha256=_IBezjvhbJJ7gn0evTBIMbSPjvfFZwxEdT-1DLo_bRk,799
|
|
36
39
|
mcp_ticketer/queue/worker.py,sha256=2wusez3Wxmun6qAmup3WsGjBD-vNgtLwxygYviXdECQ,14634
|
|
37
|
-
mcp_ticketer-0.1.
|
|
38
|
-
mcp_ticketer-0.1.
|
|
39
|
-
mcp_ticketer-0.1.
|
|
40
|
-
mcp_ticketer-0.1.
|
|
41
|
-
mcp_ticketer-0.1.
|
|
42
|
-
mcp_ticketer-0.1.
|
|
40
|
+
mcp_ticketer-0.1.24.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
|
|
41
|
+
mcp_ticketer-0.1.24.dist-info/METADATA,sha256=wpkFwzRgF21v3TfGDLmF0ZCJbFwCXYi1Ft6Ikh1-Bu8,13191
|
|
42
|
+
mcp_ticketer-0.1.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
mcp_ticketer-0.1.24.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
|
|
44
|
+
mcp_ticketer-0.1.24.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
|
|
45
|
+
mcp_ticketer-0.1.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|