backlog-mcp 1.0.3__tar.gz → 1.0.4__tar.gz
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.
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/PKG-INFO +1 -1
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/tools/get_issue_details.py +4 -1
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/utils/ultils.py +38 -29
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/pyproject.toml +1 -1
- backlog_mcp-1.0.4/scripts/install-backlog-mcp.sh +702 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/uv.lock +1 -1
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/.env.example +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/.gitignore +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/.mise.toml +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/README.md +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/constants/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/constants/constants.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/core/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/logging_config.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/main.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/models/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/models/models.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/server_settings.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/tools/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/tools/get_user_issue_list.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/utils/__init__.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/app/utils/di.py +0 -0
- {backlog_mcp-1.0.3 → backlog_mcp-1.0.4}/docs/mcp-installer-template.md +0 -0
|
@@ -4,13 +4,15 @@ from app.utils.ultils import get_issue_detail_handler
|
|
|
4
4
|
|
|
5
5
|
async def get_issue_details(
|
|
6
6
|
issue_key: str,
|
|
7
|
-
|
|
7
|
+
include_comments: bool,
|
|
8
|
+
timezone: str = "UTC",
|
|
8
9
|
):
|
|
9
10
|
"""
|
|
10
11
|
Get details of a Backlog issue by its key.
|
|
11
12
|
|
|
12
13
|
Args:
|
|
13
14
|
issue_key (str): The key of the Backlog issue to retrieve.
|
|
15
|
+
include_comments (bool): Whether to include comments in the response.
|
|
14
16
|
timezone (str, optional): The timezone to format datetime fields. Defaults to "UTC".
|
|
15
17
|
"""
|
|
16
18
|
try:
|
|
@@ -22,6 +24,7 @@ async def get_issue_details(
|
|
|
22
24
|
api_key=ctx.api_key,
|
|
23
25
|
issue_key=issue_key,
|
|
24
26
|
timezone=timezone,
|
|
27
|
+
include_comments=include_comments,
|
|
25
28
|
)
|
|
26
29
|
return result
|
|
27
30
|
except Exception as e:
|
|
@@ -46,28 +46,29 @@ def time_in_range(time: str, start_range: str, end_range: str):
|
|
|
46
46
|
return start_range_time <= time_to_be_compared <= end_range_time
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def process_issue_detail(issue_detail, timezone, issue_key):
|
|
49
|
+
def process_issue_detail(issue_detail, timezone, issue_key, include_comments: bool = True):
|
|
50
50
|
processed_issue = {
|
|
51
51
|
"issue_key": issue_key,
|
|
52
52
|
"summary": issue_detail["summary"],
|
|
53
53
|
"description": issue_detail["description"]
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
if include_comments:
|
|
57
|
+
comments = issue_detail.get("comments", [])
|
|
58
|
+
if comments:
|
|
59
|
+
# Sort comments by created_at (created field)
|
|
60
|
+
sorted_comments = sorted(comments, key=lambda c: convert_to_timezone(timezone, c["created"]))
|
|
61
|
+
# Create list of {content, created_by} where created_by is just the name
|
|
62
|
+
processed_comments = [
|
|
63
|
+
{
|
|
64
|
+
"content": c["content"],
|
|
65
|
+
"created_by": c["createdUser"]["name"] if c.get("createdUser") else None
|
|
66
|
+
}
|
|
67
|
+
for c in sorted_comments if c.get("content")
|
|
68
|
+
]
|
|
69
|
+
# Filter out None created_by if any (though should not happen)
|
|
70
|
+
processed_comments = [c for c in processed_comments if c["created_by"]]
|
|
71
|
+
processed_issue["comments"] = processed_comments
|
|
71
72
|
|
|
72
73
|
return processed_issue
|
|
73
74
|
|
|
@@ -77,6 +78,7 @@ async def get_issue_detail_handler(
|
|
|
77
78
|
api_key: str,
|
|
78
79
|
issue_key: str,
|
|
79
80
|
timezone: str,
|
|
81
|
+
include_comments: bool = True,
|
|
80
82
|
):
|
|
81
83
|
issue_comments_url = f"{backlog_domain}api/v2/issues/{issue_key}/comments"
|
|
82
84
|
issue_detail_url = f"{backlog_domain}api/v2/issues/{issue_key}"
|
|
@@ -85,31 +87,38 @@ async def get_issue_detail_handler(
|
|
|
85
87
|
async with httpx.AsyncClient() as client:
|
|
86
88
|
try:
|
|
87
89
|
issue_detail_response = client.get(issue_detail_url, params=params)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
|
|
91
|
+
if include_comments:
|
|
92
|
+
comments_response = client.get(issue_comments_url, params=params)
|
|
93
|
+
results = await asyncio.gather(issue_detail_response, comments_response)
|
|
94
|
+
issue_detail_result = results[0]
|
|
95
|
+
comments_result = results[1]
|
|
96
|
+
issue_detail = issue_detail_result.json()
|
|
97
|
+
issue_comment = comments_result.json()
|
|
98
|
+
else:
|
|
99
|
+
issue_detail_result = await issue_detail_response
|
|
100
|
+
issue_detail = issue_detail_result.json()
|
|
101
|
+
issue_comment = []
|
|
94
102
|
|
|
95
|
-
if not
|
|
103
|
+
if not issue_detail_result.is_success:
|
|
96
104
|
error_code = issue_detail["errors"][0]["code"]
|
|
97
105
|
return {
|
|
98
106
|
"error_msg": BacklogApiError.get_description_by_code(error_code),
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
if not
|
|
109
|
+
if include_comments and not comments_result.is_success:
|
|
102
110
|
error_code = issue_comment["errors"][0]["code"]
|
|
103
111
|
return {
|
|
104
112
|
"error_msg": BacklogApiError.get_description_by_code(error_code),
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
if include_comments:
|
|
116
|
+
comments_in_time_range = []
|
|
117
|
+
for comment in issue_comment:
|
|
118
|
+
comments_in_time_range.append(comment)
|
|
119
|
+
issue_detail.update({"comments": comments_in_time_range})
|
|
110
120
|
|
|
111
|
-
issue_detail
|
|
112
|
-
processed_detail = process_issue_detail(issue_detail, timezone, issue_key)
|
|
121
|
+
processed_detail = process_issue_detail(issue_detail, timezone, issue_key, include_comments)
|
|
113
122
|
return processed_detail
|
|
114
123
|
|
|
115
124
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "backlog-mcp"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.4"
|
|
4
4
|
description = "A Model Context Protocol (MCP) server for Backlog project management integration"
|
|
5
5
|
authors = [{name = "BaoNguyen", email = "baonguyen@teqnological.asia"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# Colors
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
BLUE='\033[0;34m'
|
|
10
|
+
CYAN='\033[0;36m'
|
|
11
|
+
MAGENTA='\033[0;35m'
|
|
12
|
+
WHITE='\033[1;37m'
|
|
13
|
+
BOLD_CYAN='\033[1;36m'
|
|
14
|
+
BOLD_YELLOW='\033[1;33m'
|
|
15
|
+
BOLD_ORANGE='\033[1;38;5;166m'
|
|
16
|
+
NC='\033[0m' # No Color
|
|
17
|
+
|
|
18
|
+
clear
|
|
19
|
+
echo ""
|
|
20
|
+
echo -e "${BOLD_ORANGE}"
|
|
21
|
+
cat << "EOF"
|
|
22
|
+
_____ _____ ___ _ ____ ___ _
|
|
23
|
+
|_ _| ____/ _ \ / \ / ___|_ _| / \
|
|
24
|
+
| | | __| | | | / _ \ \___ \| | / _ \
|
|
25
|
+
| | | |__| |_| | / ___ \ ___) | | / ___ \
|
|
26
|
+
|_| |_____\__\_\ /_/ \_\____/___/_/ \_\
|
|
27
|
+
|
|
28
|
+
Backlog MCP
|
|
29
|
+
Installer v1.0
|
|
30
|
+
EOF
|
|
31
|
+
echo -e "${NC}"
|
|
32
|
+
|
|
33
|
+
# ============================================
|
|
34
|
+
# SYSTEM REQUIREMENTS CHECK
|
|
35
|
+
# ============================================
|
|
36
|
+
echo -e "${BOLD_CYAN}🔍 Checking system requirements...${NC}"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# Check Python 3
|
|
40
|
+
echo -e "${WHITE} Checking Python 3...${NC}"
|
|
41
|
+
if command -v python3 &> /dev/null; then
|
|
42
|
+
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
|
|
43
|
+
echo -e "${GREEN} ✓ Python 3 found: ${PYTHON_VERSION}${NC}"
|
|
44
|
+
else
|
|
45
|
+
echo -e "${RED} ✗ Python 3 not found${NC}"
|
|
46
|
+
echo -e "${YELLOW} Please install Python 3 first: https://www.python.org/downloads/${NC}"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Check UV
|
|
51
|
+
echo -e "${WHITE} Checking UV (Python package installer)...${NC}"
|
|
52
|
+
if command -v uv &> /dev/null; then
|
|
53
|
+
UV_VERSION=$(uv --version 2>&1 | awk '{print $2}')
|
|
54
|
+
echo -e "${GREEN} ✓ UV found: ${UV_VERSION}${NC}"
|
|
55
|
+
elif command -v uvx &> /dev/null; then
|
|
56
|
+
UV_VERSION=$(uvx --version 2>&1 | head -n 1)
|
|
57
|
+
echo -e "${GREEN} ✓ UV found: ${UV_VERSION}${NC}"
|
|
58
|
+
else
|
|
59
|
+
echo -e "${YELLOW} ⚠️ UV not found on your system${NC}"
|
|
60
|
+
echo ""
|
|
61
|
+
echo -e "${WHITE} UV is required to run this MCP server.${NC}"
|
|
62
|
+
echo -e "${CYAN} UV is a fast Python package installer and resolver.${NC}"
|
|
63
|
+
echo -e " 🔗 More info: ${BLUE}https://github.com/astral-sh/uv${NC}"
|
|
64
|
+
echo ""
|
|
65
|
+
echo -e "${BOLD_YELLOW} Would you like to install UV now?${NC}"
|
|
66
|
+
echo ""
|
|
67
|
+
echo " 1) ✅ Yes - Install UV automatically"
|
|
68
|
+
echo " 2) ❌ No - Exit (you can install manually later)"
|
|
69
|
+
echo ""
|
|
70
|
+
|
|
71
|
+
while true; do
|
|
72
|
+
read -p " ➤ Enter your choice (1-2): " install_uv_choice
|
|
73
|
+
|
|
74
|
+
case $install_uv_choice in
|
|
75
|
+
1)
|
|
76
|
+
echo ""
|
|
77
|
+
echo -e "${CYAN} 📥 Installing UV...${NC}"
|
|
78
|
+
echo ""
|
|
79
|
+
|
|
80
|
+
# Detect OS and install accordingly
|
|
81
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
82
|
+
# macOS
|
|
83
|
+
echo -e "${WHITE} Detected: macOS${NC}"
|
|
84
|
+
|
|
85
|
+
# Check if Homebrew is available
|
|
86
|
+
if command -v brew &> /dev/null; then
|
|
87
|
+
echo -e "${CYAN} Installing via Homebrew...${NC}"
|
|
88
|
+
brew install uv
|
|
89
|
+
else
|
|
90
|
+
echo -e "${CYAN} Installing via official installer...${NC}"
|
|
91
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
95
|
+
# Linux
|
|
96
|
+
echo -e "${WHITE} Detected: Linux${NC}"
|
|
97
|
+
echo -e "${CYAN} Installing via official installer...${NC}"
|
|
98
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
99
|
+
|
|
100
|
+
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
|
101
|
+
# Windows (Git Bash/Cygwin)
|
|
102
|
+
echo -e "${WHITE} Detected: Windows${NC}"
|
|
103
|
+
echo -e "${CYAN} Installing via PowerShell...${NC}"
|
|
104
|
+
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
105
|
+
|
|
106
|
+
else
|
|
107
|
+
echo -e "${RED} ✗ Unsupported OS: $OSTYPE${NC}"
|
|
108
|
+
echo -e "${YELLOW} Please install UV manually: https://github.com/astral-sh/uv${NC}"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Verify installation
|
|
113
|
+
echo ""
|
|
114
|
+
echo -e "${WHITE} Verifying UV installation...${NC}"
|
|
115
|
+
|
|
116
|
+
# Reload PATH
|
|
117
|
+
export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
|
|
118
|
+
|
|
119
|
+
if command -v uv &> /dev/null; then
|
|
120
|
+
UV_VERSION=$(uv --version 2>&1 | awk '{print $2}')
|
|
121
|
+
echo -e "${GREEN} ✓ UV successfully installed: ${UV_VERSION}${NC}"
|
|
122
|
+
elif command -v uvx &> /dev/null; then
|
|
123
|
+
UV_VERSION=$(uvx --version 2>&1 | head -n 1)
|
|
124
|
+
echo -e "${GREEN} ✓ UV successfully installed: ${UV_VERSION}${NC}"
|
|
125
|
+
else
|
|
126
|
+
echo -e "${YELLOW} ⚠️ UV installed but not found in PATH${NC}"
|
|
127
|
+
echo -e "${CYAN} Please restart your terminal and run this script again.${NC}"
|
|
128
|
+
echo ""
|
|
129
|
+
echo -e "${WHITE} Or add this to your PATH:${NC}"
|
|
130
|
+
echo -e " ${BLUE}export PATH=\"\$HOME/.cargo/bin:\$HOME/.local/bin:\$PATH\"${NC}"
|
|
131
|
+
echo ""
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
break
|
|
136
|
+
;;
|
|
137
|
+
2)
|
|
138
|
+
echo ""
|
|
139
|
+
echo -e "${YELLOW} Installation cancelled${NC}"
|
|
140
|
+
echo -e "${CYAN} 💡 You can install UV manually:${NC}"
|
|
141
|
+
echo -e " ${BLUE}https://github.com/astral-sh/uv${NC}"
|
|
142
|
+
echo ""
|
|
143
|
+
echo -e "${WHITE} Quick install commands:${NC}"
|
|
144
|
+
echo -e " ${CYAN}macOS/Linux:${NC} curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
145
|
+
echo -e " ${CYAN}Windows:${NC} powershell -c \"irm https://astral.sh/uv/install.ps1 | iex\""
|
|
146
|
+
echo ""
|
|
147
|
+
exit 0
|
|
148
|
+
;;
|
|
149
|
+
*)
|
|
150
|
+
echo -e "${RED} ✗ Invalid choice. Please enter 1 or 2.${NC}"
|
|
151
|
+
;;
|
|
152
|
+
esac
|
|
153
|
+
done
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
echo ""
|
|
157
|
+
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
# ============================================
|
|
161
|
+
# SET IDE CONSTANTS (VSCode only)
|
|
162
|
+
# ============================================
|
|
163
|
+
IDE_NAME="VS Code"
|
|
164
|
+
IDE_KEY="vscode"
|
|
165
|
+
|
|
166
|
+
# ============================================
|
|
167
|
+
# QUESTION 1: Which Agent?
|
|
168
|
+
# ============================================
|
|
169
|
+
echo -e "${BOLD_CYAN}🤖 Which AI coding agent are you using in VS Code?${NC}"
|
|
170
|
+
echo ""
|
|
171
|
+
echo " 1) 🔷 Cline"
|
|
172
|
+
echo " 2) 🤖 GitHub Copilot"
|
|
173
|
+
echo " 3) 🟣 Claude Code"
|
|
174
|
+
echo ""
|
|
175
|
+
read -p " ➤ Enter your choice (1-3): " agent_choice
|
|
176
|
+
|
|
177
|
+
# ============================================
|
|
178
|
+
# AGENT CONFIGURATION DEFINITIONS
|
|
179
|
+
# ============================================
|
|
180
|
+
# Each agent has different configuration structure
|
|
181
|
+
# Flags control which fields to include in the final JSON
|
|
182
|
+
|
|
183
|
+
case $agent_choice in
|
|
184
|
+
1)
|
|
185
|
+
echo -e "${GREEN} ✓ Cline selected${NC}"
|
|
186
|
+
AGENT_NAME="Cline"
|
|
187
|
+
AGENT_KEY="cline"
|
|
188
|
+
CONFIG_FORMAT="standard"
|
|
189
|
+
SERVERS_KEY="mcpServers"
|
|
190
|
+
TIMEOUT_VALUE="60"
|
|
191
|
+
HAS_TYPE="true"
|
|
192
|
+
HAS_AUTO_APPROVE="true"
|
|
193
|
+
HAS_DISABLED="true"
|
|
194
|
+
HAS_TIMEOUT="true"
|
|
195
|
+
CONFIG_FILE="$HOME/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
196
|
+
;;
|
|
197
|
+
2)
|
|
198
|
+
echo -e "${GREEN} ✓ GitHub Copilot selected${NC}"
|
|
199
|
+
AGENT_NAME="GitHub Copilot"
|
|
200
|
+
AGENT_KEY="copilot"
|
|
201
|
+
CONFIG_FORMAT="copilot"
|
|
202
|
+
SERVERS_KEY="servers"
|
|
203
|
+
TIMEOUT_VALUE="60"
|
|
204
|
+
HAS_TYPE="true"
|
|
205
|
+
HAS_AUTO_APPROVE="false"
|
|
206
|
+
HAS_DISABLED="false"
|
|
207
|
+
HAS_TIMEOUT="true"
|
|
208
|
+
CONFIG_FILE="$HOME/Library/Application Support/Code/User/mcp.json"
|
|
209
|
+
;;
|
|
210
|
+
3)
|
|
211
|
+
echo -e "${GREEN} ✓ Claude Code selected${NC}"
|
|
212
|
+
AGENT_NAME="Claude Code"
|
|
213
|
+
AGENT_KEY="claude-code"
|
|
214
|
+
CONFIG_FORMAT="claude-code"
|
|
215
|
+
SERVERS_KEY="mcpServers"
|
|
216
|
+
TIMEOUT_VALUE=""
|
|
217
|
+
HAS_TYPE="false"
|
|
218
|
+
HAS_AUTO_APPROVE="false"
|
|
219
|
+
HAS_DISABLED="false"
|
|
220
|
+
HAS_TIMEOUT="false"
|
|
221
|
+
CONFIG_FILE="$HOME/.claude.json"
|
|
222
|
+
;;
|
|
223
|
+
*)
|
|
224
|
+
echo -e "${RED} ✗ Invalid choice${NC}"
|
|
225
|
+
exit 1
|
|
226
|
+
;;
|
|
227
|
+
esac
|
|
228
|
+
|
|
229
|
+
echo ""
|
|
230
|
+
echo -e "${CYAN} 📁 Configuration file:${NC}"
|
|
231
|
+
echo -e " ${BLUE}$CONFIG_FILE${NC}"
|
|
232
|
+
|
|
233
|
+
echo ""
|
|
234
|
+
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
235
|
+
echo ""
|
|
236
|
+
|
|
237
|
+
# ============================================
|
|
238
|
+
# Check if MCP already exists
|
|
239
|
+
# ============================================
|
|
240
|
+
OVERWRITE_MODE="false"
|
|
241
|
+
MCP_EXISTS="false"
|
|
242
|
+
|
|
243
|
+
# Create a temporary Python script to check if MCP exists
|
|
244
|
+
CHECK_RESULT=$(python3 - "$CONFIG_FILE" "$SERVERS_KEY" "backlog-mcp" << 'PYTHON_CHECK_EOF'
|
|
245
|
+
import json
|
|
246
|
+
import sys
|
|
247
|
+
import os
|
|
248
|
+
|
|
249
|
+
def strip_json_comments(text):
|
|
250
|
+
"""Remove comments from JSON-like text line by line"""
|
|
251
|
+
lines = text.split('\n')
|
|
252
|
+
cleaned_lines = []
|
|
253
|
+
in_multiline_comment = False
|
|
254
|
+
|
|
255
|
+
for line_idx, line in enumerate(lines):
|
|
256
|
+
original_line = line
|
|
257
|
+
# Handle multi-line comments
|
|
258
|
+
if '/*' in line:
|
|
259
|
+
in_multiline_comment = True
|
|
260
|
+
line = line[:line.index('/*')]
|
|
261
|
+
|
|
262
|
+
if in_multiline_comment:
|
|
263
|
+
if '*/' in line:
|
|
264
|
+
in_multiline_comment = False
|
|
265
|
+
line = line[line.index('*/') + 2:]
|
|
266
|
+
else:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
# Handle single-line comments
|
|
270
|
+
if '//' in line:
|
|
271
|
+
# Check if // is inside a string
|
|
272
|
+
in_string = False
|
|
273
|
+
quote_char = None
|
|
274
|
+
for i, char in enumerate(line):
|
|
275
|
+
if char in ['"', "'"] and (i == 0 or line[i-1] != '\\'):
|
|
276
|
+
if not in_string:
|
|
277
|
+
in_string = True
|
|
278
|
+
quote_char = char
|
|
279
|
+
elif char == quote_char:
|
|
280
|
+
in_string = False
|
|
281
|
+
quote_char = None
|
|
282
|
+
|
|
283
|
+
if not in_string and i < len(line) - 1 and line[i:i+2] == '//':
|
|
284
|
+
line = line[:i]
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
# Remove trailing commas before } or ]
|
|
288
|
+
line = line.rstrip()
|
|
289
|
+
if line.endswith(','):
|
|
290
|
+
temp_line = line[:-1].rstrip()
|
|
291
|
+
# Check if next significant character is } or ]
|
|
292
|
+
remaining = '\n'.join(lines[line_idx+1:])
|
|
293
|
+
stripped_remaining = remaining.lstrip()
|
|
294
|
+
if stripped_remaining and stripped_remaining[0] in '}]':
|
|
295
|
+
line = temp_line
|
|
296
|
+
|
|
297
|
+
if line.strip():
|
|
298
|
+
cleaned_lines.append(line)
|
|
299
|
+
|
|
300
|
+
return '\n'.join(cleaned_lines)
|
|
301
|
+
|
|
302
|
+
config_file = sys.argv[1]
|
|
303
|
+
servers_key = sys.argv[2]
|
|
304
|
+
mcp_name = sys.argv[3]
|
|
305
|
+
|
|
306
|
+
if not os.path.exists(config_file):
|
|
307
|
+
print("NOT_EXISTS")
|
|
308
|
+
sys.exit(0)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
312
|
+
content = f.read()
|
|
313
|
+
|
|
314
|
+
# Strip comments before parsing
|
|
315
|
+
clean_content = strip_json_comments(content)
|
|
316
|
+
config = json.loads(clean_content)
|
|
317
|
+
|
|
318
|
+
if servers_key in config and mcp_name in config[servers_key]:
|
|
319
|
+
print("EXISTS")
|
|
320
|
+
# Print current config details
|
|
321
|
+
mcp_config = config[servers_key][mcp_name]
|
|
322
|
+
print(f"COMMAND:{mcp_config.get('command', 'N/A')}")
|
|
323
|
+
print(f"TYPE:{mcp_config.get('type', 'N/A')}")
|
|
324
|
+
print(f"DISABLED:{mcp_config.get('disabled', False)}")
|
|
325
|
+
|
|
326
|
+
if 'env' in mcp_config:
|
|
327
|
+
print("ENV_VARS:")
|
|
328
|
+
for key, value in mcp_config['env'].items():
|
|
329
|
+
if any(sensitive in key.upper() for sensitive in ['TOKEN', 'PASSWORD', 'KEY', 'SECRET', 'API_KEY']):
|
|
330
|
+
print(f" {key}: *** (hidden)")
|
|
331
|
+
else:
|
|
332
|
+
print(f" {key}: {value}")
|
|
333
|
+
else:
|
|
334
|
+
print("NOT_EXISTS")
|
|
335
|
+
except json.JSONDecodeError as e:
|
|
336
|
+
print(f"ERROR:JSON Parse Error - {str(e)}")
|
|
337
|
+
except Exception as e:
|
|
338
|
+
print(f"ERROR:{str(e)}")
|
|
339
|
+
PYTHON_CHECK_EOF
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Parse the check result
|
|
343
|
+
if echo "$CHECK_RESULT" | grep -q "^EXISTS"; then
|
|
344
|
+
MCP_EXISTS="true"
|
|
345
|
+
|
|
346
|
+
echo -e "${YELLOW}⚠️ WARNING: backlog-mcp already exists!${NC}"
|
|
347
|
+
echo ""
|
|
348
|
+
echo -e "${WHITE} Configuration file:${NC}"
|
|
349
|
+
echo -e " ${CYAN}$CONFIG_FILE${NC}"
|
|
350
|
+
echo ""
|
|
351
|
+
echo -e "${WHITE} 📋 Current MCP configuration:${NC}"
|
|
352
|
+
|
|
353
|
+
# Display the configuration details
|
|
354
|
+
echo "$CHECK_RESULT" | grep -v "^EXISTS" | while IFS= read -r line; do
|
|
355
|
+
if [[ $line == COMMAND:* ]]; then
|
|
356
|
+
echo " Command: ${line#COMMAND:}"
|
|
357
|
+
elif [[ $line == TYPE:* ]]; then
|
|
358
|
+
echo " Type: ${line#TYPE:}"
|
|
359
|
+
elif [[ $line == DISABLED:* ]]; then
|
|
360
|
+
echo " Disabled: ${line#DISABLED:}"
|
|
361
|
+
elif [[ $line == "ENV_VARS:" ]]; then
|
|
362
|
+
echo " Environment variables:"
|
|
363
|
+
elif [[ $line == " "* ]]; then
|
|
364
|
+
echo " $line"
|
|
365
|
+
fi
|
|
366
|
+
done
|
|
367
|
+
|
|
368
|
+
echo ""
|
|
369
|
+
echo -e "${BOLD_YELLOW} ❓ Do you want to overwrite the existing configuration?${NC}"
|
|
370
|
+
echo ""
|
|
371
|
+
echo " 1) ✅ Yes - Overwrite with new configuration"
|
|
372
|
+
echo " 2) ❌ No - Cancel installation (keep existing)"
|
|
373
|
+
echo ""
|
|
374
|
+
|
|
375
|
+
while true; do
|
|
376
|
+
read -p " ➤ Enter your choice (1-2): " overwrite_choice
|
|
377
|
+
|
|
378
|
+
case $overwrite_choice in
|
|
379
|
+
1)
|
|
380
|
+
echo -e "${GREEN} ✓ Will overwrite existing configuration${NC}"
|
|
381
|
+
OVERWRITE_MODE="true"
|
|
382
|
+
break
|
|
383
|
+
;;
|
|
384
|
+
2)
|
|
385
|
+
echo ""
|
|
386
|
+
echo -e "${YELLOW} ✓ Installation cancelled${NC}"
|
|
387
|
+
echo -e "${CYAN} 💡 No changes were made to your configuration.${NC}"
|
|
388
|
+
echo ""
|
|
389
|
+
exit 0
|
|
390
|
+
;;
|
|
391
|
+
*)
|
|
392
|
+
echo -e "${RED} ✗ Invalid choice. Please enter 1 or 2.${NC}"
|
|
393
|
+
;;
|
|
394
|
+
esac
|
|
395
|
+
done
|
|
396
|
+
|
|
397
|
+
echo ""
|
|
398
|
+
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
399
|
+
echo ""
|
|
400
|
+
elif echo "$CHECK_RESULT" | grep -q "^ERROR:"; then
|
|
401
|
+
ERROR_MSG=$(echo "$CHECK_RESULT" | grep "^ERROR:" | cut -d: -f2-)
|
|
402
|
+
echo ""
|
|
403
|
+
echo -e "${RED}✗ Error: Cannot read config file${NC}"
|
|
404
|
+
echo ""
|
|
405
|
+
echo -e "${WHITE} Configuration file:${NC}"
|
|
406
|
+
echo -e " ${CYAN}$CONFIG_FILE${NC}"
|
|
407
|
+
echo ""
|
|
408
|
+
echo -e "${WHITE} Error details:${NC}"
|
|
409
|
+
echo -e " ${RED}$ERROR_MSG${NC}"
|
|
410
|
+
echo ""
|
|
411
|
+
echo -e "${YELLOW} 💡 Please fix the JSON syntax manually:${NC}"
|
|
412
|
+
echo -e " 1. Open the file in your editor"
|
|
413
|
+
echo -e " 2. Remove any comments (// or /* */)"
|
|
414
|
+
echo -e " 3. Fix any JSON syntax errors"
|
|
415
|
+
echo -e " 4. Or delete the file and run this script again"
|
|
416
|
+
echo ""
|
|
417
|
+
exit 1
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
# ============================================
|
|
421
|
+
# QUESTION 2: Environment Variables Configuration
|
|
422
|
+
# ============================================
|
|
423
|
+
echo -e "${BOLD_CYAN}🔑 Backlog MCP Configuration${NC}"
|
|
424
|
+
echo ""
|
|
425
|
+
echo -e "${WHITE} Please configure the following environment variables:${NC}"
|
|
426
|
+
echo ""
|
|
427
|
+
|
|
428
|
+
# BACKLOG_API_KEY (Variable - sensitive, will be prompted)
|
|
429
|
+
echo -e "${CYAN} ℹ️ BACKLOG_API_KEY${NC}"
|
|
430
|
+
echo -e "${YELLOW} ⚠️ Required: Please enter your BACKLOG_API_KEY${NC}"
|
|
431
|
+
while [ -z "$BACKLOG_API_KEY" ]; do
|
|
432
|
+
read -sp " 🔐 Enter BACKLOG_API_KEY: " BACKLOG_API_KEY
|
|
433
|
+
echo ""
|
|
434
|
+
if [ -z "$BACKLOG_API_KEY" ]; then
|
|
435
|
+
echo -e "${RED} ✗ BACKLOG_API_KEY cannot be empty. Please try again.${NC}"
|
|
436
|
+
else
|
|
437
|
+
echo -e "${GREEN} ✓ BACKLOG_API_KEY set (${#BACKLOG_API_KEY} characters)${NC}"
|
|
438
|
+
fi
|
|
439
|
+
done
|
|
440
|
+
echo ""
|
|
441
|
+
|
|
442
|
+
echo ""
|
|
443
|
+
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
444
|
+
echo ""
|
|
445
|
+
|
|
446
|
+
# ============================================
|
|
447
|
+
# Configuration Summary
|
|
448
|
+
# ============================================
|
|
449
|
+
echo -e "${BOLD_CYAN} 📊 Configuration Summary:${NC}"
|
|
450
|
+
echo -e " ${WHITE}Agent:${NC} $AGENT_NAME (VS Code)"
|
|
451
|
+
echo -e " ${WHITE}Config file:${NC} $CONFIG_FILE"
|
|
452
|
+
echo -e " ${WHITE}Format:${NC} $CONFIG_FORMAT ($SERVERS_KEY)"
|
|
453
|
+
if [ "$HAS_TIMEOUT" = "true" ] && [ -n "$TIMEOUT_VALUE" ]; then
|
|
454
|
+
echo -e " ${WHITE}Timeout:${NC} $TIMEOUT_VALUE"
|
|
455
|
+
fi
|
|
456
|
+
if [ "$OVERWRITE_MODE" = "true" ]; then
|
|
457
|
+
echo -e " ${WHITE}Mode:${NC} ${YELLOW}⚠️ OVERWRITE${NC}"
|
|
458
|
+
else
|
|
459
|
+
echo -e " ${WHITE}Mode:${NC} ${GREEN}✨ NEW INSTALLATION${NC}"
|
|
460
|
+
fi
|
|
461
|
+
echo ""
|
|
462
|
+
echo -e " ${WHITE}MCP Server:${NC}"
|
|
463
|
+
echo -e " ${CYAN}Name:${NC} backlog-mcp"
|
|
464
|
+
echo -e " ${CYAN}Command:${NC} uvx"
|
|
465
|
+
echo -e " ${CYAN}Environment:${NC} ${GREEN}✓ Configured${NC}"
|
|
466
|
+
echo ""
|
|
467
|
+
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
468
|
+
echo ""
|
|
469
|
+
|
|
470
|
+
# Create directory
|
|
471
|
+
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
472
|
+
|
|
473
|
+
# Validate and merge using Python
|
|
474
|
+
python3 - "$CONFIG_FILE" "$OVERWRITE_MODE" "$SERVERS_KEY" "$TIMEOUT_VALUE" "$HAS_TYPE" "$HAS_AUTO_APPROVE" "$HAS_DISABLED" "$HAS_TIMEOUT" "$CONFIG_FORMAT" "backlog-mcp" "uvx" "backlog-mcp@latest" "ENV_START" "BACKLOG_API_KEY=$BACKLOG_API_KEY" "BACKLOG_DOMAIN=teq-dev.backlog.com" << 'PYTHON_EOF'
|
|
475
|
+
import json
|
|
476
|
+
import sys
|
|
477
|
+
import os
|
|
478
|
+
|
|
479
|
+
def strip_json_comments(text):
|
|
480
|
+
"""Remove comments from JSON-like text line by line"""
|
|
481
|
+
lines = text.split('\n')
|
|
482
|
+
cleaned_lines = []
|
|
483
|
+
in_multiline_comment = False
|
|
484
|
+
|
|
485
|
+
for line_idx, line in enumerate(lines):
|
|
486
|
+
original_line = line
|
|
487
|
+
# Handle multi-line comments
|
|
488
|
+
if '/*' in line:
|
|
489
|
+
in_multiline_comment = True
|
|
490
|
+
line = line[:line.index('/*')]
|
|
491
|
+
|
|
492
|
+
if in_multiline_comment:
|
|
493
|
+
if '*/' in line:
|
|
494
|
+
in_multiline_comment = False
|
|
495
|
+
line = line[line.index('*/') + 2:]
|
|
496
|
+
else:
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
# Handle single-line comments
|
|
500
|
+
if '//' in line:
|
|
501
|
+
# Check if // is inside a string
|
|
502
|
+
in_string = False
|
|
503
|
+
quote_char = None
|
|
504
|
+
for i, char in enumerate(line):
|
|
505
|
+
if char in ['"', "'"] and (i == 0 or line[i-1] != '\\'):
|
|
506
|
+
if not in_string:
|
|
507
|
+
in_string = True
|
|
508
|
+
quote_char = char
|
|
509
|
+
elif char == quote_char:
|
|
510
|
+
in_string = False
|
|
511
|
+
quote_char = None
|
|
512
|
+
|
|
513
|
+
if not in_string and i < len(line) - 1 and line[i:i+2] == '//':
|
|
514
|
+
line = line[:i]
|
|
515
|
+
break
|
|
516
|
+
|
|
517
|
+
# Remove trailing commas before } or ]
|
|
518
|
+
line = line.rstrip()
|
|
519
|
+
if line.endswith(','):
|
|
520
|
+
temp_line = line[:-1].rstrip()
|
|
521
|
+
# Check if next significant character is } or ]
|
|
522
|
+
remaining = '\n'.join(lines[line_idx+1:])
|
|
523
|
+
stripped_remaining = remaining.lstrip()
|
|
524
|
+
if stripped_remaining and stripped_remaining[0] in '}]':
|
|
525
|
+
line = temp_line
|
|
526
|
+
|
|
527
|
+
if line.strip():
|
|
528
|
+
cleaned_lines.append(line)
|
|
529
|
+
|
|
530
|
+
return '\n'.join(cleaned_lines)
|
|
531
|
+
|
|
532
|
+
config_file = sys.argv[1]
|
|
533
|
+
overwrite_mode = sys.argv[2] == "true"
|
|
534
|
+
servers_key = sys.argv[3]
|
|
535
|
+
timeout_value = sys.argv[4]
|
|
536
|
+
has_type = sys.argv[5] == "true"
|
|
537
|
+
has_auto_approve = sys.argv[6] == "true"
|
|
538
|
+
has_disabled = sys.argv[7] == "true"
|
|
539
|
+
has_timeout = sys.argv[8] == "true"
|
|
540
|
+
config_format = sys.argv[9]
|
|
541
|
+
mcp_name = sys.argv[10]
|
|
542
|
+
command = sys.argv[11]
|
|
543
|
+
|
|
544
|
+
# Parse args (starting from index 12)
|
|
545
|
+
args = []
|
|
546
|
+
env_vars = {}
|
|
547
|
+
arg_index = 12
|
|
548
|
+
|
|
549
|
+
# Parse args until we hit the env vars marker
|
|
550
|
+
while arg_index < len(sys.argv):
|
|
551
|
+
arg = sys.argv[arg_index]
|
|
552
|
+
if arg == "ENV_START":
|
|
553
|
+
arg_index += 1
|
|
554
|
+
break
|
|
555
|
+
args.append(arg)
|
|
556
|
+
arg_index += 1
|
|
557
|
+
|
|
558
|
+
# Parse env vars (format: KEY=VALUE)
|
|
559
|
+
while arg_index < len(sys.argv):
|
|
560
|
+
env_pair = sys.argv[arg_index]
|
|
561
|
+
if '=' in env_pair:
|
|
562
|
+
key, value = env_pair.split('=', 1)
|
|
563
|
+
env_vars[key] = value
|
|
564
|
+
arg_index += 1
|
|
565
|
+
|
|
566
|
+
# ANSI colors
|
|
567
|
+
RED = '\033[0;31m'
|
|
568
|
+
GREEN = '\033[0;32m'
|
|
569
|
+
YELLOW = '\033[1;33m'
|
|
570
|
+
BLUE = '\033[0;34m'
|
|
571
|
+
CYAN = '\033[1;36m'
|
|
572
|
+
WHITE = '\033[1;37m'
|
|
573
|
+
NC = '\033[0m'
|
|
574
|
+
|
|
575
|
+
def print_color(color, message):
|
|
576
|
+
print(f"{color}{message}{NC}")
|
|
577
|
+
|
|
578
|
+
# Check if file exists
|
|
579
|
+
if os.path.exists(config_file):
|
|
580
|
+
try:
|
|
581
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
582
|
+
content = f.read()
|
|
583
|
+
|
|
584
|
+
# Strip comments before parsing
|
|
585
|
+
clean_content = strip_json_comments(content)
|
|
586
|
+
config = json.loads(clean_content)
|
|
587
|
+
|
|
588
|
+
# Validate structure
|
|
589
|
+
if not isinstance(config, dict):
|
|
590
|
+
print_color(RED, " ✗ Error: Config file is not a valid JSON object")
|
|
591
|
+
print(f" 📝 File: {config_file}")
|
|
592
|
+
print(f" 🔧 Expected: {{ \"{servers_key}\": {{...}} }}")
|
|
593
|
+
sys.exit(1)
|
|
594
|
+
|
|
595
|
+
if servers_key in config:
|
|
596
|
+
if not isinstance(config[servers_key], dict):
|
|
597
|
+
print_color(RED, f" ✗ Error: '{servers_key}' must be an object")
|
|
598
|
+
print(f" 📝 File: {config_file}")
|
|
599
|
+
sys.exit(1)
|
|
600
|
+
else:
|
|
601
|
+
config[servers_key] = {}
|
|
602
|
+
|
|
603
|
+
if overwrite_mode:
|
|
604
|
+
print_color(YELLOW, " ⚠️ Overwriting existing configuration...")
|
|
605
|
+
else:
|
|
606
|
+
print_color(CYAN, f" 📋 Found {len(config[servers_key])} existing MCP server(s)")
|
|
607
|
+
|
|
608
|
+
except json.JSONDecodeError as e:
|
|
609
|
+
print_color(RED, " ✗ Error: Invalid JSON in config file")
|
|
610
|
+
print(f" 📝 File: {config_file}")
|
|
611
|
+
print(f" 🔍 Error: {str(e)}")
|
|
612
|
+
print("")
|
|
613
|
+
print_color(YELLOW, " 💡 Please fix the JSON syntax manually:")
|
|
614
|
+
print(" 1. Open the file in your editor")
|
|
615
|
+
print(" 2. Remove any comments (// or /* */)")
|
|
616
|
+
print(" 3. Fix any JSON syntax errors")
|
|
617
|
+
print(" 4. Or delete the file and run this script again")
|
|
618
|
+
sys.exit(1)
|
|
619
|
+
except Exception as e:
|
|
620
|
+
print_color(RED, f" ✗ Error reading config: {str(e)}")
|
|
621
|
+
sys.exit(1)
|
|
622
|
+
else:
|
|
623
|
+
print_color(WHITE, " 📝 Config file not found, creating new one...")
|
|
624
|
+
config = {servers_key: {}}
|
|
625
|
+
|
|
626
|
+
# Prepare the MCP configuration (base config - always included)
|
|
627
|
+
mcp_config = {
|
|
628
|
+
"command": command,
|
|
629
|
+
"args": args,
|
|
630
|
+
"env": env_vars
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
# Add optional fields based on agent configuration flags
|
|
634
|
+
if has_type:
|
|
635
|
+
mcp_config["type"] = "stdio"
|
|
636
|
+
|
|
637
|
+
if has_auto_approve:
|
|
638
|
+
mcp_config["autoApprove"] = ["get_issue_details", "get_user_issue_list"]
|
|
639
|
+
|
|
640
|
+
if has_disabled:
|
|
641
|
+
mcp_config["disabled"] = False
|
|
642
|
+
|
|
643
|
+
if has_timeout and timeout_value:
|
|
644
|
+
# Convert timeout based on agent type:
|
|
645
|
+
# - Copilot: milliseconds (convert seconds to milliseconds)
|
|
646
|
+
# - Cline: seconds (keep as is)
|
|
647
|
+
# - Claude Code: not supported (has_timeout is false)
|
|
648
|
+
timeout_int = int(timeout_value)
|
|
649
|
+
if config_format == "copilot":
|
|
650
|
+
# Copilot uses milliseconds, convert from seconds
|
|
651
|
+
mcp_config["timeout"] = timeout_int * 1000
|
|
652
|
+
else:
|
|
653
|
+
# Cline uses seconds
|
|
654
|
+
mcp_config["timeout"] = timeout_int
|
|
655
|
+
|
|
656
|
+
# Add/Update MCP server
|
|
657
|
+
config[servers_key][mcp_name] = mcp_config
|
|
658
|
+
|
|
659
|
+
# Write back (clean JSON without comments)
|
|
660
|
+
try:
|
|
661
|
+
with open(config_file, 'w', encoding='utf-8') as f:
|
|
662
|
+
json.dump(config, f, indent=4, ensure_ascii=False)
|
|
663
|
+
|
|
664
|
+
if overwrite_mode:
|
|
665
|
+
print_color(GREEN, f" ✓ Successfully overwritten {mcp_name}")
|
|
666
|
+
else:
|
|
667
|
+
print_color(GREEN, f" ✓ Successfully added {mcp_name}")
|
|
668
|
+
|
|
669
|
+
print(f" 📝 Config: {config_file}")
|
|
670
|
+
print_color(CYAN, f" 🔢 Total MCP servers: {len(config[servers_key])}")
|
|
671
|
+
|
|
672
|
+
# List all servers
|
|
673
|
+
print_color(WHITE, f"\n 📋 All MCP servers (format: {config_format}):")
|
|
674
|
+
for server_name in config[servers_key].keys():
|
|
675
|
+
server_config = config[servers_key][server_name]
|
|
676
|
+
status = "disabled" if server_config.get('disabled', False) else "enabled"
|
|
677
|
+
emoji = "🔴" if server_config.get('disabled', False) else "🟢"
|
|
678
|
+
has_env = "🔐" if server_config.get('env') else ""
|
|
679
|
+
marker = "⚡" if server_name == mcp_name else " "
|
|
680
|
+
print(f" {marker} {emoji} {server_name} ({status}) {has_env}")
|
|
681
|
+
|
|
682
|
+
print_color(GREEN, "\n ✅ Installation complete!")
|
|
683
|
+
print_color(YELLOW, " 🔄 Please restart VS Code and your agent to apply changes")
|
|
684
|
+
|
|
685
|
+
if config_format != "claude-code":
|
|
686
|
+
print_color(CYAN, "\n 💡 Note: JSON comments were removed from the file")
|
|
687
|
+
print_color(CYAN, " (This is normal - the file will still work perfectly)")
|
|
688
|
+
|
|
689
|
+
except Exception as e:
|
|
690
|
+
print_color(RED, f" ✗ Error writing config: {str(e)}")
|
|
691
|
+
sys.exit(1)
|
|
692
|
+
PYTHON_EOF
|
|
693
|
+
|
|
694
|
+
if [ $? -eq 0 ]; then
|
|
695
|
+
echo ""
|
|
696
|
+
echo -e "${WHITE}"
|
|
697
|
+
cat << "EOF"
|
|
698
|
+
✨ Installation Success! ✨
|
|
699
|
+
🎉 All Done! 🎉
|
|
700
|
+
EOF
|
|
701
|
+
echo -e "${NC}"
|
|
702
|
+
fi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|