mcp-ticketer 0.2.0__py3-none-any.whl → 2.2.9__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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +58 -16
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +421 -0
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1284
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -1895
- mcp_ticketer-0.2.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.2.0.dist-info/RECORD +0 -58
- mcp_ticketer-0.2.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
"""Setup command for mcp-ticketer - smart initialization with platform detection."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
# Mapping of adapter types to their required dependencies
|
|
14
|
+
ADAPTER_DEPENDENCIES = {
|
|
15
|
+
"linear": {"package": "gql[httpx]", "extras": "linear"},
|
|
16
|
+
"jira": {"package": "jira", "extras": "jira"},
|
|
17
|
+
"github": {"package": "PyGithub", "extras": "github"},
|
|
18
|
+
"aitrackdown": None, # No extra dependencies
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _check_package_installed(adapter_type: str) -> bool:
|
|
23
|
+
"""Check if adapter-specific package is installed.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
adapter_type: Type of adapter (linear, jira, github)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
True if package is installed, False otherwise
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
# Try to import the package
|
|
34
|
+
if adapter_type == "linear":
|
|
35
|
+
import gql # noqa: F401
|
|
36
|
+
elif adapter_type == "jira":
|
|
37
|
+
import jira # noqa: F401
|
|
38
|
+
elif adapter_type == "github":
|
|
39
|
+
import github # noqa: F401
|
|
40
|
+
return True
|
|
41
|
+
except ImportError:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _check_and_install_adapter_dependencies(
|
|
46
|
+
adapter_type: str, console: Console
|
|
47
|
+
) -> bool:
|
|
48
|
+
"""Check if adapter-specific dependencies are installed and offer to install.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
adapter_type: Type of adapter (linear, jira, github, aitrackdown)
|
|
52
|
+
console: Rich console for output
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if dependencies are satisfied (installed or not needed), False if failed
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
# Check if adapter needs extra dependencies
|
|
59
|
+
dependency_info = ADAPTER_DEPENDENCIES.get(adapter_type)
|
|
60
|
+
|
|
61
|
+
if dependency_info is None:
|
|
62
|
+
# No extra dependencies needed (e.g., aitrackdown)
|
|
63
|
+
console.print(
|
|
64
|
+
f"[green]✓[/green] No extra dependencies required for {adapter_type}\n"
|
|
65
|
+
)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Check if the required package is already installed
|
|
69
|
+
if _check_package_installed(adapter_type):
|
|
70
|
+
console.print(
|
|
71
|
+
f"[green]✓[/green] {adapter_type.capitalize()} dependencies already installed\n"
|
|
72
|
+
)
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# Dependencies not installed
|
|
76
|
+
console.print(
|
|
77
|
+
f"[yellow]⚠[/yellow] {adapter_type.capitalize()} adapter requires additional dependencies\n"
|
|
78
|
+
)
|
|
79
|
+
console.print(f"[dim]Required package: {dependency_info['package']}[/dim]\n")
|
|
80
|
+
|
|
81
|
+
# Prompt user to install
|
|
82
|
+
try:
|
|
83
|
+
if not typer.confirm("Install dependencies now?", default=True):
|
|
84
|
+
console.print(
|
|
85
|
+
"\n[yellow]Skipping installation. Install manually with:[/yellow]"
|
|
86
|
+
)
|
|
87
|
+
console.print(
|
|
88
|
+
f"[cyan] pip install mcp-ticketer[{dependency_info['extras']}][/cyan]\n"
|
|
89
|
+
)
|
|
90
|
+
return True # User declined, but we continue
|
|
91
|
+
|
|
92
|
+
except typer.Abort:
|
|
93
|
+
console.print("\n[yellow]Installation cancelled[/yellow]\n")
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
# Install dependencies
|
|
97
|
+
console.print(f"[cyan]Installing {adapter_type} dependencies...[/cyan]\n")
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Run pip install with the extras
|
|
101
|
+
subprocess.check_call(
|
|
102
|
+
[
|
|
103
|
+
sys.executable,
|
|
104
|
+
"-m",
|
|
105
|
+
"pip",
|
|
106
|
+
"install",
|
|
107
|
+
f"mcp-ticketer[{dependency_info['extras']}]",
|
|
108
|
+
],
|
|
109
|
+
stdout=subprocess.DEVNULL,
|
|
110
|
+
stderr=subprocess.PIPE,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
console.print(
|
|
114
|
+
f"[green]✓[/green] Successfully installed {adapter_type} dependencies\n"
|
|
115
|
+
)
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
except subprocess.CalledProcessError as e:
|
|
119
|
+
console.print(
|
|
120
|
+
f"[red]✗[/red] Failed to install dependencies: {e.stderr.decode() if e.stderr else 'Unknown error'}\n"
|
|
121
|
+
)
|
|
122
|
+
console.print("[yellow]Please install manually with:[/yellow]")
|
|
123
|
+
console.print(
|
|
124
|
+
f"[cyan] pip install mcp-ticketer[{dependency_info['extras']}][/cyan]\n"
|
|
125
|
+
)
|
|
126
|
+
return True # Continue even if installation failed
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _prompt_for_adapter_selection(console: Console) -> str:
|
|
130
|
+
"""Interactive prompt for adapter selection.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
console: Rich console for output
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Selected adapter type
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
console.print("\n[bold blue]🚀 MCP Ticketer Setup[/bold blue]")
|
|
140
|
+
console.print("Choose which ticket system you want to connect to:\n")
|
|
141
|
+
|
|
142
|
+
# Define adapter options with descriptions
|
|
143
|
+
adapters = [
|
|
144
|
+
{
|
|
145
|
+
"name": "linear",
|
|
146
|
+
"title": "Linear",
|
|
147
|
+
"description": "Modern project management (linear.app)",
|
|
148
|
+
"requirements": "API key and team ID",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "github",
|
|
152
|
+
"title": "GitHub Issues",
|
|
153
|
+
"description": "GitHub repository issues",
|
|
154
|
+
"requirements": "Personal access token, owner, and repo",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "jira",
|
|
158
|
+
"title": "JIRA",
|
|
159
|
+
"description": "Atlassian JIRA project management",
|
|
160
|
+
"requirements": "Server URL, email, and API token",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "aitrackdown",
|
|
164
|
+
"title": "Local Files (AITrackdown)",
|
|
165
|
+
"description": "Store tickets in local files (no external service)",
|
|
166
|
+
"requirements": "None - works offline",
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
# Display options
|
|
171
|
+
for i, adapter in enumerate(adapters, 1):
|
|
172
|
+
console.print(f"[cyan]{i}.[/cyan] [bold]{adapter['title']}[/bold]")
|
|
173
|
+
console.print(f" {adapter['description']}")
|
|
174
|
+
console.print(f" [dim]Requirements: {adapter['requirements']}[/dim]\n")
|
|
175
|
+
|
|
176
|
+
# Get user selection
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
choice = typer.prompt("Select adapter (1-4)", type=int, default=1)
|
|
180
|
+
if 1 <= choice <= len(adapters):
|
|
181
|
+
selected_adapter = adapters[choice - 1]
|
|
182
|
+
console.print(
|
|
183
|
+
f"\n[green]✓ Selected: {selected_adapter['title']}[/green]"
|
|
184
|
+
)
|
|
185
|
+
return selected_adapter["name"]
|
|
186
|
+
else:
|
|
187
|
+
console.print(
|
|
188
|
+
f"[red]Please enter a number between 1 and {len(adapters)}[/red]"
|
|
189
|
+
)
|
|
190
|
+
except (ValueError, typer.Abort):
|
|
191
|
+
console.print("[yellow]Setup cancelled.[/yellow]")
|
|
192
|
+
raise typer.Exit(0) from None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def setup(
|
|
196
|
+
project_path: str | None = typer.Option(
|
|
197
|
+
None, "--path", help="Project path (default: current directory)"
|
|
198
|
+
),
|
|
199
|
+
skip_platforms: bool = typer.Option(
|
|
200
|
+
False,
|
|
201
|
+
"--skip-platforms",
|
|
202
|
+
help="Skip platform installation (only initialize adapter)",
|
|
203
|
+
),
|
|
204
|
+
force_reinit: bool = typer.Option(
|
|
205
|
+
False,
|
|
206
|
+
"--force-reinit",
|
|
207
|
+
help="Force re-initialization even if config exists",
|
|
208
|
+
),
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Smart setup command - combines init + platform installation.
|
|
211
|
+
|
|
212
|
+
This command intelligently detects your current setup state and only
|
|
213
|
+
performs necessary configuration. It's the recommended way to get started.
|
|
214
|
+
|
|
215
|
+
Detection & Smart Actions:
|
|
216
|
+
- First run: Full setup (init + platform installation)
|
|
217
|
+
- Existing config: Skip init, offer platform installation
|
|
218
|
+
- Detects changes: Offers to update configurations
|
|
219
|
+
- Respects existing: Won't overwrite without confirmation
|
|
220
|
+
|
|
221
|
+
Examples:
|
|
222
|
+
# Smart setup (recommended for first-time setup)
|
|
223
|
+
mcp-ticketer setup
|
|
224
|
+
|
|
225
|
+
# Setup for different project
|
|
226
|
+
mcp-ticketer setup --path /path/to/project
|
|
227
|
+
|
|
228
|
+
# Re-initialize configuration
|
|
229
|
+
mcp-ticketer setup --force-reinit
|
|
230
|
+
|
|
231
|
+
# Only init adapter, skip platform installation
|
|
232
|
+
mcp-ticketer setup --skip-platforms
|
|
233
|
+
|
|
234
|
+
Note: For advanced configuration, use 'init' and 'install' separately.
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
from .platform_detection import PlatformDetector
|
|
238
|
+
|
|
239
|
+
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
240
|
+
config_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
241
|
+
|
|
242
|
+
console.print("[bold cyan]🚀 MCP Ticketer Smart Setup[/bold cyan]\n")
|
|
243
|
+
|
|
244
|
+
# Step 1: Detect existing configuration
|
|
245
|
+
config_exists = config_path.exists()
|
|
246
|
+
config_valid = False
|
|
247
|
+
current_adapter = None
|
|
248
|
+
|
|
249
|
+
if config_exists and not force_reinit:
|
|
250
|
+
try:
|
|
251
|
+
with open(config_path) as f:
|
|
252
|
+
config = json.load(f)
|
|
253
|
+
current_adapter = config.get("default_adapter")
|
|
254
|
+
config_valid = bool(current_adapter and config.get("adapters"))
|
|
255
|
+
except (json.JSONDecodeError, OSError):
|
|
256
|
+
config_valid = False
|
|
257
|
+
|
|
258
|
+
if config_valid:
|
|
259
|
+
console.print("[green]✓[/green] Configuration detected")
|
|
260
|
+
console.print(f"[dim] Adapter: {current_adapter}[/dim]")
|
|
261
|
+
console.print(f"[dim] Location: {config_path}[/dim]\n")
|
|
262
|
+
|
|
263
|
+
# Offer to reconfigure
|
|
264
|
+
if not typer.confirm(
|
|
265
|
+
"Configuration already exists. Keep existing settings?", default=True
|
|
266
|
+
):
|
|
267
|
+
console.print("[cyan]Re-initializing configuration...[/cyan]\n")
|
|
268
|
+
force_reinit = True
|
|
269
|
+
config_valid = False
|
|
270
|
+
else:
|
|
271
|
+
if config_exists:
|
|
272
|
+
console.print(
|
|
273
|
+
"[yellow]⚠[/yellow] Configuration file exists but is invalid\n"
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
console.print("[yellow]⚠[/yellow] No configuration found\n")
|
|
277
|
+
|
|
278
|
+
# Step 2: Initialize adapter configuration if needed
|
|
279
|
+
if not config_valid or force_reinit:
|
|
280
|
+
console.print("[bold]Step 1/2: Adapter Configuration[/bold]\n")
|
|
281
|
+
|
|
282
|
+
# Run init command non-interactively through function call
|
|
283
|
+
# We'll use the discover and prompt flow from init
|
|
284
|
+
from ..core.env_discovery import discover_config
|
|
285
|
+
from .init_command import _init_adapter_internal
|
|
286
|
+
|
|
287
|
+
discovered = discover_config(proj_path)
|
|
288
|
+
adapter_type = None
|
|
289
|
+
|
|
290
|
+
# Try auto-discovery
|
|
291
|
+
if discovered and discovered.adapters:
|
|
292
|
+
primary = discovered.get_primary_adapter()
|
|
293
|
+
if primary:
|
|
294
|
+
adapter_type = primary.adapter_type
|
|
295
|
+
console.print(f"[green]✓ Auto-detected {adapter_type} adapter[/green]")
|
|
296
|
+
console.print(f"[dim] Source: {primary.found_in}[/dim]")
|
|
297
|
+
console.print(f"[dim] Confidence: {primary.confidence:.0%}[/dim]\n")
|
|
298
|
+
|
|
299
|
+
if not typer.confirm(
|
|
300
|
+
f"Use detected {adapter_type} adapter?", default=True
|
|
301
|
+
):
|
|
302
|
+
adapter_type = None
|
|
303
|
+
|
|
304
|
+
# If no adapter detected, prompt for selection
|
|
305
|
+
if not adapter_type:
|
|
306
|
+
adapter_type = _prompt_for_adapter_selection(console)
|
|
307
|
+
|
|
308
|
+
# Now run the full init with the selected adapter
|
|
309
|
+
console.print(f"\n[cyan]Initializing {adapter_type} adapter...[/cyan]\n")
|
|
310
|
+
|
|
311
|
+
# Call internal init function programmatically (NOT the CLI command)
|
|
312
|
+
# Note: Only pass required parameters - all optional params should be None
|
|
313
|
+
# to avoid passing OptionInfo objects which are not JSON serializable
|
|
314
|
+
success = _init_adapter_internal(
|
|
315
|
+
adapter=adapter_type,
|
|
316
|
+
project_path=str(proj_path),
|
|
317
|
+
global_config=False,
|
|
318
|
+
base_path=None,
|
|
319
|
+
api_key=None,
|
|
320
|
+
team_id=None,
|
|
321
|
+
jira_server=None,
|
|
322
|
+
jira_email=None,
|
|
323
|
+
jira_project=None,
|
|
324
|
+
github_url=None,
|
|
325
|
+
github_token=None,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if not success:
|
|
329
|
+
console.print("[red]Failed to initialize adapter configuration[/red]")
|
|
330
|
+
raise typer.Exit(1) from None
|
|
331
|
+
|
|
332
|
+
# Check and install adapter-specific dependencies
|
|
333
|
+
_check_and_install_adapter_dependencies(adapter_type, console)
|
|
334
|
+
|
|
335
|
+
# Update existing MCP configurations with new credentials
|
|
336
|
+
_update_mcp_json_credentials(proj_path, console)
|
|
337
|
+
|
|
338
|
+
console.print("\n[green]✓ Adapter configuration complete[/green]\n")
|
|
339
|
+
else:
|
|
340
|
+
console.print("[green]✓ Step 1/2: Adapter already configured[/green]\n")
|
|
341
|
+
|
|
342
|
+
# Even though adapter is configured, prompt for default values
|
|
343
|
+
# This handles the case where credentials exist but defaults were never set
|
|
344
|
+
_prompt_and_update_default_values(config_path, current_adapter, console)
|
|
345
|
+
|
|
346
|
+
# Update existing MCP configurations with credentials
|
|
347
|
+
_update_mcp_json_credentials(proj_path, console)
|
|
348
|
+
|
|
349
|
+
# Step 3: Platform installation
|
|
350
|
+
if skip_platforms:
|
|
351
|
+
console.print(
|
|
352
|
+
"[yellow]⚠[/yellow] Skipping platform installation (--skip-platforms)\n"
|
|
353
|
+
)
|
|
354
|
+
_show_setup_complete_message(console, proj_path)
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
console.print("[bold]Step 2/2: Platform Installation[/bold]\n")
|
|
358
|
+
|
|
359
|
+
# Detect available platforms
|
|
360
|
+
detector = PlatformDetector()
|
|
361
|
+
detected = detector.detect_all(project_path=proj_path, exclude_desktop=True)
|
|
362
|
+
|
|
363
|
+
if not detected:
|
|
364
|
+
console.print("[yellow]No AI platforms detected on this system.[/yellow]")
|
|
365
|
+
console.print(
|
|
366
|
+
"\n[dim]Supported platforms: Claude Code, Claude Desktop, Gemini, Codex, Auggie[/dim]"
|
|
367
|
+
)
|
|
368
|
+
console.print(
|
|
369
|
+
"[dim]Install these platforms to use them with mcp-ticketer.[/dim]\n"
|
|
370
|
+
)
|
|
371
|
+
_show_setup_complete_message(console, proj_path)
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
# Filter to only installed platforms
|
|
375
|
+
installed = [p for p in detected if p.is_installed]
|
|
376
|
+
|
|
377
|
+
if not installed:
|
|
378
|
+
console.print(
|
|
379
|
+
"[yellow]AI platforms detected but have configuration issues.[/yellow]"
|
|
380
|
+
)
|
|
381
|
+
console.print(
|
|
382
|
+
"\n[dim]Run 'mcp-ticketer install --auto-detect' for details.[/dim]\n"
|
|
383
|
+
)
|
|
384
|
+
_show_setup_complete_message(console, proj_path)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Show detected platforms
|
|
388
|
+
console.print(f"[green]✓[/green] Detected {len(installed)} platform(s):\n")
|
|
389
|
+
for plat in installed:
|
|
390
|
+
console.print(f" • {plat.display_name} ({plat.scope})")
|
|
391
|
+
|
|
392
|
+
console.print()
|
|
393
|
+
|
|
394
|
+
# Check if mcp-ticketer is already configured for these platforms
|
|
395
|
+
already_configured = _check_existing_platform_configs(installed, proj_path)
|
|
396
|
+
|
|
397
|
+
if already_configured:
|
|
398
|
+
console.print(
|
|
399
|
+
f"[green]✓[/green] mcp-ticketer already configured for {len(already_configured)} platform(s)\n"
|
|
400
|
+
)
|
|
401
|
+
for plat_name in already_configured:
|
|
402
|
+
console.print(f" • {plat_name}")
|
|
403
|
+
console.print()
|
|
404
|
+
|
|
405
|
+
if not typer.confirm("Update platform configurations anyway?", default=False):
|
|
406
|
+
console.print("[yellow]Skipping platform installation[/yellow]\n")
|
|
407
|
+
_show_setup_complete_message(console, proj_path)
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# Offer to install for all or select specific
|
|
411
|
+
console.print("[bold]Platform Installation Options:[/bold]")
|
|
412
|
+
console.print("1. Install for all detected platforms")
|
|
413
|
+
console.print("2. Select specific platform")
|
|
414
|
+
console.print("3. Skip platform installation")
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
choice = typer.prompt("\nSelect option (1-3)", type=int, default=1)
|
|
418
|
+
except typer.Abort:
|
|
419
|
+
console.print("[yellow]Setup cancelled[/yellow]")
|
|
420
|
+
raise typer.Exit(0) from None
|
|
421
|
+
|
|
422
|
+
if choice == 3:
|
|
423
|
+
console.print("[yellow]Skipping platform installation[/yellow]\n")
|
|
424
|
+
_show_setup_complete_message(console, proj_path)
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
# Import configuration functions
|
|
428
|
+
from .auggie_configure import configure_auggie_mcp
|
|
429
|
+
from .codex_configure import configure_codex_mcp
|
|
430
|
+
from .gemini_configure import configure_gemini_mcp
|
|
431
|
+
from .mcp_configure import configure_claude_mcp
|
|
432
|
+
|
|
433
|
+
platform_mapping = {
|
|
434
|
+
"claude-code": lambda: configure_claude_mcp(global_config=False, force=True),
|
|
435
|
+
"claude-desktop": lambda: configure_claude_mcp(global_config=True, force=True),
|
|
436
|
+
"auggie": lambda: configure_auggie_mcp(force=True),
|
|
437
|
+
"gemini": lambda: configure_gemini_mcp(scope="project", force=True),
|
|
438
|
+
"codex": lambda: configure_codex_mcp(force=True),
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
platforms_to_install = []
|
|
442
|
+
|
|
443
|
+
if choice == 1:
|
|
444
|
+
# Install for all
|
|
445
|
+
platforms_to_install = installed
|
|
446
|
+
elif choice == 2:
|
|
447
|
+
# Select specific platform
|
|
448
|
+
console.print("\n[bold]Select platform:[/bold]")
|
|
449
|
+
for idx, plat in enumerate(installed, 1):
|
|
450
|
+
console.print(f" {idx}. {plat.display_name} ({plat.scope})")
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
plat_choice = typer.prompt("\nSelect platform number", type=int)
|
|
454
|
+
if 1 <= plat_choice <= len(installed):
|
|
455
|
+
platforms_to_install = [installed[plat_choice - 1]]
|
|
456
|
+
else:
|
|
457
|
+
console.print("[red]Invalid selection[/red]")
|
|
458
|
+
raise typer.Exit(1) from None
|
|
459
|
+
except typer.Abort:
|
|
460
|
+
console.print("[yellow]Setup cancelled[/yellow]")
|
|
461
|
+
raise typer.Exit(0) from None
|
|
462
|
+
|
|
463
|
+
# Install for selected platforms
|
|
464
|
+
console.print()
|
|
465
|
+
success_count = 0
|
|
466
|
+
failed = []
|
|
467
|
+
|
|
468
|
+
for plat in platforms_to_install:
|
|
469
|
+
config_func = platform_mapping.get(plat.name)
|
|
470
|
+
if not config_func:
|
|
471
|
+
console.print(f"[yellow]⚠[/yellow] No installer for {plat.display_name}")
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
console.print(f"[cyan]Installing for {plat.display_name}...[/cyan]")
|
|
476
|
+
config_func()
|
|
477
|
+
console.print(f"[green]✓[/green] {plat.display_name} configured\n")
|
|
478
|
+
success_count += 1
|
|
479
|
+
except Exception as e:
|
|
480
|
+
console.print(
|
|
481
|
+
f"[red]✗[/red] Failed to configure {plat.display_name}: {e}\n"
|
|
482
|
+
)
|
|
483
|
+
failed.append(plat.display_name)
|
|
484
|
+
|
|
485
|
+
# Summary
|
|
486
|
+
console.print(
|
|
487
|
+
f"[bold]Platform Installation:[/bold] {success_count}/{len(platforms_to_install)} succeeded"
|
|
488
|
+
)
|
|
489
|
+
if failed:
|
|
490
|
+
console.print(f"[red]Failed:[/red] {', '.join(failed)}")
|
|
491
|
+
|
|
492
|
+
console.print()
|
|
493
|
+
_show_setup_complete_message(console, proj_path)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _prompt_and_update_default_values(
|
|
497
|
+
config_path: Path, adapter_type: str, console: Console
|
|
498
|
+
) -> None:
|
|
499
|
+
"""Prompt user for default values and update configuration.
|
|
500
|
+
|
|
501
|
+
This function handles the case where adapter credentials exist but
|
|
502
|
+
default values (default_user, default_epic, default_project, default_tags)
|
|
503
|
+
need to be set or updated.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
config_path: Path to the configuration file (.mcp-ticketer/config.json)
|
|
507
|
+
adapter_type: Type of adapter (linear, jira, github, aitrackdown)
|
|
508
|
+
console: Rich console for output
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
typer.Exit: If configuration cannot be loaded or updated
|
|
512
|
+
|
|
513
|
+
"""
|
|
514
|
+
from .configure import prompt_default_values
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
# Load current config to get existing default values
|
|
518
|
+
with open(config_path) as f:
|
|
519
|
+
existing_config = json.load(f)
|
|
520
|
+
|
|
521
|
+
existing_defaults = {
|
|
522
|
+
"default_user": existing_config.get("default_user"),
|
|
523
|
+
"default_epic": existing_config.get("default_epic"),
|
|
524
|
+
"default_project": existing_config.get("default_project"),
|
|
525
|
+
"default_tags": existing_config.get("default_tags"),
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
# Prompt for default values
|
|
529
|
+
console.print("[bold]Configure Default Values[/bold] (for ticket creation)\n")
|
|
530
|
+
default_values = prompt_default_values(
|
|
531
|
+
adapter_type=adapter_type, existing_values=existing_defaults
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Update config with new default values
|
|
535
|
+
if default_values:
|
|
536
|
+
existing_config.update(default_values)
|
|
537
|
+
with open(config_path, "w") as f:
|
|
538
|
+
json.dump(existing_config, f, indent=2)
|
|
539
|
+
console.print("\n[green]✓ Default values updated[/green]\n")
|
|
540
|
+
else:
|
|
541
|
+
console.print("\n[dim]No default values set[/dim]\n")
|
|
542
|
+
|
|
543
|
+
except json.JSONDecodeError as e:
|
|
544
|
+
console.print(f"[red]✗ Invalid JSON in configuration file: {e}[/red]\n")
|
|
545
|
+
console.print(
|
|
546
|
+
"[yellow]Please fix the configuration file manually or run 'mcp-ticketer init --force'[/yellow]\n"
|
|
547
|
+
)
|
|
548
|
+
except OSError as e:
|
|
549
|
+
console.print(f"[red]✗ Could not read/write configuration file: {e}[/red]\n")
|
|
550
|
+
console.print("[yellow]Please check file permissions and try again[/yellow]\n")
|
|
551
|
+
except Exception as e:
|
|
552
|
+
console.print(f"[red]✗ Unexpected error updating default values: {e}[/red]\n")
|
|
553
|
+
console.print(
|
|
554
|
+
"[yellow]Configuration may be incomplete. Run 'mcp-ticketer doctor' to verify[/yellow]\n"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _check_existing_platform_configs(platforms: list, proj_path: Path) -> list[str]:
|
|
559
|
+
"""Check if mcp-ticketer is already configured for given platforms.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
platforms: List of DetectedPlatform objects
|
|
563
|
+
proj_path: Project path
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
List of platform display names that are already configured
|
|
567
|
+
|
|
568
|
+
"""
|
|
569
|
+
configured = []
|
|
570
|
+
|
|
571
|
+
for plat in platforms:
|
|
572
|
+
try:
|
|
573
|
+
if plat.name == "claude-code":
|
|
574
|
+
# Check both new and old locations
|
|
575
|
+
new_config = Path.home() / ".config" / "claude" / "mcp.json"
|
|
576
|
+
old_config = Path.home() / ".claude.json"
|
|
577
|
+
|
|
578
|
+
is_configured = False
|
|
579
|
+
|
|
580
|
+
# Check new global location (flat structure)
|
|
581
|
+
if new_config.exists():
|
|
582
|
+
with open(new_config) as f:
|
|
583
|
+
config = json.load(f)
|
|
584
|
+
if "mcp-ticketer" in config.get("mcpServers", {}):
|
|
585
|
+
is_configured = True
|
|
586
|
+
|
|
587
|
+
# Check old location (nested structure)
|
|
588
|
+
if not is_configured and old_config.exists():
|
|
589
|
+
with open(old_config) as f:
|
|
590
|
+
config = json.load(f)
|
|
591
|
+
projects = config.get("projects", {})
|
|
592
|
+
proj_key = str(proj_path)
|
|
593
|
+
if proj_key in projects:
|
|
594
|
+
mcp_servers = projects[proj_key].get("mcpServers", {})
|
|
595
|
+
if "mcp-ticketer" in mcp_servers:
|
|
596
|
+
is_configured = True
|
|
597
|
+
|
|
598
|
+
if is_configured:
|
|
599
|
+
configured.append(plat.display_name)
|
|
600
|
+
|
|
601
|
+
elif plat.name == "claude-desktop":
|
|
602
|
+
if plat.config_path.exists():
|
|
603
|
+
with open(plat.config_path) as f:
|
|
604
|
+
config = json.load(f)
|
|
605
|
+
if "mcp-ticketer" in config.get("mcpServers", {}):
|
|
606
|
+
configured.append(plat.display_name)
|
|
607
|
+
|
|
608
|
+
elif plat.name in ["auggie", "codex", "gemini"]:
|
|
609
|
+
if plat.config_path.exists():
|
|
610
|
+
# Check if mcp-ticketer is configured
|
|
611
|
+
# Implementation depends on each platform's config format
|
|
612
|
+
# For now, just check if config exists (simplified)
|
|
613
|
+
pass
|
|
614
|
+
|
|
615
|
+
except (json.JSONDecodeError, OSError):
|
|
616
|
+
pass
|
|
617
|
+
|
|
618
|
+
return configured
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _update_mcp_json_credentials(proj_path: Path, console: Console) -> None:
|
|
622
|
+
"""Update .mcp.json with adapter credentials if mcp-ticketer is already configured.
|
|
623
|
+
|
|
624
|
+
This function updates the existing MCP configuration with the latest credentials
|
|
625
|
+
from the project's .mcp-ticketer/config.json file. It also ensures .mcp.json is
|
|
626
|
+
added to .gitignore to prevent credential leaks.
|
|
627
|
+
|
|
628
|
+
Additionally, it updates the official @modelcontextprotocol/server-github MCP server
|
|
629
|
+
if found, since it also requires GITHUB_PERSONAL_ACCESS_TOKEN.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
proj_path: Project path
|
|
633
|
+
console: Rich console for output
|
|
634
|
+
|
|
635
|
+
"""
|
|
636
|
+
# Check multiple .mcp.json locations
|
|
637
|
+
new_mcp_json_path = Path.home() / ".config" / "claude" / "mcp.json"
|
|
638
|
+
old_mcp_json_path = Path.home() / ".claude.json"
|
|
639
|
+
legacy_mcp_json_path = proj_path / ".claude" / "mcp.local.json"
|
|
640
|
+
project_mcp_json_path = proj_path / ".mcp.json"
|
|
641
|
+
|
|
642
|
+
mcp_json_paths = [
|
|
643
|
+
(new_mcp_json_path, True), # (path, is_global_mcp_config)
|
|
644
|
+
(old_mcp_json_path, False),
|
|
645
|
+
(legacy_mcp_json_path, False),
|
|
646
|
+
(project_mcp_json_path, False),
|
|
647
|
+
]
|
|
648
|
+
|
|
649
|
+
# Also check parent directories for .mcp.json (Claude Code inheritance)
|
|
650
|
+
# This handles cases like /Users/masa/Projects/.mcp.json
|
|
651
|
+
current = proj_path.parent
|
|
652
|
+
home = Path.home()
|
|
653
|
+
checked_parents: set[Path] = set()
|
|
654
|
+
while current != home and current != current.parent:
|
|
655
|
+
parent_mcp = current / ".mcp.json"
|
|
656
|
+
if parent_mcp not in checked_parents and parent_mcp.exists():
|
|
657
|
+
mcp_json_paths.append((parent_mcp, False))
|
|
658
|
+
checked_parents.add(parent_mcp)
|
|
659
|
+
current = current.parent
|
|
660
|
+
|
|
661
|
+
# Import the helper function to get adapter credentials
|
|
662
|
+
from .mcp_configure import _get_adapter_env_vars
|
|
663
|
+
|
|
664
|
+
env_vars = _get_adapter_env_vars()
|
|
665
|
+
|
|
666
|
+
if not env_vars:
|
|
667
|
+
return
|
|
668
|
+
|
|
669
|
+
updated_count = 0
|
|
670
|
+
|
|
671
|
+
for mcp_json_path, is_global_mcp_config in mcp_json_paths:
|
|
672
|
+
if not mcp_json_path.exists():
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
try:
|
|
676
|
+
with open(mcp_json_path) as f:
|
|
677
|
+
mcp_config = json.load(f)
|
|
678
|
+
|
|
679
|
+
# Check if mcp-ticketer is configured
|
|
680
|
+
mcp_servers = None
|
|
681
|
+
if is_global_mcp_config:
|
|
682
|
+
# Global mcp.json uses flat structure
|
|
683
|
+
mcp_servers = mcp_config.get("mcpServers", {})
|
|
684
|
+
else:
|
|
685
|
+
# Old structure uses projects
|
|
686
|
+
projects = mcp_config.get("projects", {})
|
|
687
|
+
project_key = str(proj_path.resolve())
|
|
688
|
+
if project_key in projects:
|
|
689
|
+
mcp_servers = projects[project_key].get("mcpServers", {})
|
|
690
|
+
else:
|
|
691
|
+
# Try flat structure for backward compatibility
|
|
692
|
+
mcp_servers = mcp_config.get("mcpServers", {})
|
|
693
|
+
|
|
694
|
+
if mcp_servers is None:
|
|
695
|
+
continue
|
|
696
|
+
|
|
697
|
+
config_updated = False
|
|
698
|
+
|
|
699
|
+
# Update the mcp-ticketer server env vars if configured
|
|
700
|
+
if "mcp-ticketer" in mcp_servers:
|
|
701
|
+
current_env = mcp_servers["mcp-ticketer"].get("env", {})
|
|
702
|
+
current_env.update(env_vars)
|
|
703
|
+
mcp_servers["mcp-ticketer"]["env"] = current_env
|
|
704
|
+
config_updated = True
|
|
705
|
+
|
|
706
|
+
# Also update official @modelcontextprotocol/server-github if present
|
|
707
|
+
# This server uses GITHUB_PERSONAL_ACCESS_TOKEN instead of GITHUB_TOKEN
|
|
708
|
+
if "github" in mcp_servers and "GITHUB_TOKEN" in env_vars:
|
|
709
|
+
github_server = mcp_servers["github"]
|
|
710
|
+
# Check if it's the official GitHub MCP server (uses npx or server-github)
|
|
711
|
+
cmd = github_server.get("command", "")
|
|
712
|
+
args = github_server.get("args", [])
|
|
713
|
+
is_official_github = (
|
|
714
|
+
cmd == "npx"
|
|
715
|
+
and any("server-github" in str(arg) for arg in args)
|
|
716
|
+
) or "server-github" in cmd
|
|
717
|
+
|
|
718
|
+
if is_official_github:
|
|
719
|
+
current_env = github_server.get("env", {})
|
|
720
|
+
# The official server uses GITHUB_PERSONAL_ACCESS_TOKEN
|
|
721
|
+
current_env["GITHUB_PERSONAL_ACCESS_TOKEN"] = env_vars["GITHUB_TOKEN"]
|
|
722
|
+
github_server["env"] = current_env
|
|
723
|
+
config_updated = True
|
|
724
|
+
console.print(
|
|
725
|
+
f"[dim] Also updated official GitHub MCP server in {mcp_json_path}[/dim]"
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
if not config_updated:
|
|
729
|
+
continue
|
|
730
|
+
|
|
731
|
+
# Save updated config
|
|
732
|
+
with open(mcp_json_path, "w") as f:
|
|
733
|
+
json.dump(mcp_config, f, indent=2)
|
|
734
|
+
|
|
735
|
+
updated_count += 1
|
|
736
|
+
|
|
737
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
738
|
+
console.print(
|
|
739
|
+
f"[yellow]Warning: Could not update {mcp_json_path.name}: {e}[/yellow]"
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
if updated_count > 0:
|
|
743
|
+
console.print(
|
|
744
|
+
f"[green]✓[/green] Updated MCP configuration with adapter credentials ({updated_count} file(s))"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
# Ensure .mcp.json files are in .gitignore (only for project-local files)
|
|
748
|
+
gitignore_path = proj_path / ".gitignore"
|
|
749
|
+
patterns_to_add = [".claude/", ".mcp.json"]
|
|
750
|
+
|
|
751
|
+
if gitignore_path.exists():
|
|
752
|
+
content = gitignore_path.read_text()
|
|
753
|
+
patterns_added = []
|
|
754
|
+
|
|
755
|
+
for pattern in patterns_to_add:
|
|
756
|
+
if pattern not in content:
|
|
757
|
+
patterns_added.append(pattern)
|
|
758
|
+
|
|
759
|
+
if patterns_added:
|
|
760
|
+
with open(gitignore_path, "a") as f:
|
|
761
|
+
f.write("\n# MCP configuration (contains tokens)\n")
|
|
762
|
+
for pattern in patterns_added:
|
|
763
|
+
f.write(f"{pattern}\n")
|
|
764
|
+
|
|
765
|
+
console.print(
|
|
766
|
+
f"[dim]✓ Added {', '.join(patterns_added)} to .gitignore[/dim]"
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def _show_setup_complete_message(console: Console, proj_path: Path) -> None:
|
|
771
|
+
"""Show setup complete message with next steps.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
console: Rich console for output
|
|
775
|
+
proj_path: Project path
|
|
776
|
+
|
|
777
|
+
"""
|
|
778
|
+
console.print("[bold green]🎉 Setup Complete![/bold green]\n")
|
|
779
|
+
|
|
780
|
+
console.print("[bold]Quick Start:[/bold]")
|
|
781
|
+
console.print("1. Create a test ticket:")
|
|
782
|
+
console.print(" [cyan]mcp-ticketer create 'My first ticket'[/cyan]\n")
|
|
783
|
+
|
|
784
|
+
console.print("2. List tickets:")
|
|
785
|
+
console.print(" [cyan]mcp-ticketer list[/cyan]\n")
|
|
786
|
+
|
|
787
|
+
console.print("[bold]Useful Commands:[/bold]")
|
|
788
|
+
console.print(" [cyan]mcp-ticketer doctor[/cyan] - Validate configuration")
|
|
789
|
+
console.print(" [cyan]mcp-ticketer install <platform>[/cyan] - Add more platforms")
|
|
790
|
+
console.print(" [cyan]mcp-ticketer --help[/cyan] - See all commands\n")
|
|
791
|
+
|
|
792
|
+
console.print(
|
|
793
|
+
f"[dim]Configuration: {proj_path / '.mcp-ticketer' / 'config.json'}[/dim]"
|
|
794
|
+
)
|