backlog-mcp 1.0.2__tar.gz → 1.0.3__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.2 → backlog_mcp-1.0.3}/.env.example +0 -1
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/PKG-INFO +3 -18
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/README.md +2 -17
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/server_settings.py +0 -3
- backlog_mcp-1.0.3/app/tools/get_user_issue_list.py +28 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/ultils.py +24 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/pyproject.toml +1 -1
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/uv.lock +2 -2
- backlog_mcp-1.0.2/app/tools/get_user_issue_list.py +0 -70
- backlog_mcp-1.0.2/scripts/install-backlog-mcp.sh +0 -717
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/.gitignore +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/.mise.toml +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/constants/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/constants/constants.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/core/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/logging_config.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/main.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/models/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/models/models.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/tools/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/tools/get_issue_details.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/__init__.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/di.py +0 -0
- {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/docs/mcp-installer-template.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlog-mcp
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: A Model Context Protocol (MCP) server for Backlog project management integration
|
|
5
5
|
Author-email: BaoNguyen <baonguyen@teqnological.asia>
|
|
6
6
|
Requires-Python: <3.14,>=3.13
|
|
@@ -55,22 +55,9 @@ Get detailed information about a Backlog issue by its issue key.
|
|
|
55
55
|
|
|
56
56
|
### `get_user_issue_list`
|
|
57
57
|
|
|
58
|
-
Retrieve a
|
|
58
|
+
Retrieve a list of issues assigned to the current user.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
- `project_ids` (List[int], optional): List of project IDs.
|
|
62
|
-
- `assignee_ids` (List[int], optional): List of assignee IDs (defaults to current user).
|
|
63
|
-
- `status_ids` (List[int], optional): List of status IDs (defaults to non-closed).
|
|
64
|
-
- `milestone_ids` (List[int], optional): List of milestone IDs.
|
|
65
|
-
- `parent_issue_ids` (List[int], optional): List of parent issue IDs.
|
|
66
|
-
- `created_since` (str, optional): Created since (YYYY-MM-DD).
|
|
67
|
-
- `created_until` (str, optional): Created until (YYYY-MM-DD).
|
|
68
|
-
- `updated_since` (str, optional): Updated since (YYYY-MM-DD).
|
|
69
|
-
- `updated_until` (str, optional): Updated until (YYYY-MM-DD).
|
|
70
|
-
- `start_date_since` (str, optional): Start Date since (YYYY-MM-DD).
|
|
71
|
-
- `start_date_until` (str, optional): Start Date until (YYYY-MM-DD).
|
|
72
|
-
- `due_date_since` (str, optional): Due Date since (YYYY-MM-DD).
|
|
73
|
-
- `due_date_until` (str, optional): Due Date until (YYYY-MM-DD).
|
|
60
|
+
This tool automatically determines the current user's ID and returns only issues assigned to that user. No parameters are required.
|
|
74
61
|
|
|
75
62
|
## Running the Server Locally
|
|
76
63
|
|
|
@@ -89,13 +76,11 @@ Create a `.env` file in the root directory and add the following environment var
|
|
|
89
76
|
# Backlog API Settings
|
|
90
77
|
BACKLOG_API_KEY=your_backlog_api_key_here
|
|
91
78
|
BACKLOG_DOMAIN=your-space.backlog.com
|
|
92
|
-
USER_ID=your_user_id
|
|
93
79
|
```
|
|
94
80
|
|
|
95
81
|
> **Important:**
|
|
96
82
|
> - Replace `your_backlog_api_key_here` with your actual Backlog API key
|
|
97
83
|
> - Replace `your-space.backlog.com` with your actual Backlog domain
|
|
98
|
-
> - Replace `your_user_id` with your actual Backlog user ID
|
|
99
84
|
|
|
100
85
|
### 3. Start the server
|
|
101
86
|
|
|
@@ -39,22 +39,9 @@ Get detailed information about a Backlog issue by its issue key.
|
|
|
39
39
|
|
|
40
40
|
### `get_user_issue_list`
|
|
41
41
|
|
|
42
|
-
Retrieve a
|
|
42
|
+
Retrieve a list of issues assigned to the current user.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
- `project_ids` (List[int], optional): List of project IDs.
|
|
46
|
-
- `assignee_ids` (List[int], optional): List of assignee IDs (defaults to current user).
|
|
47
|
-
- `status_ids` (List[int], optional): List of status IDs (defaults to non-closed).
|
|
48
|
-
- `milestone_ids` (List[int], optional): List of milestone IDs.
|
|
49
|
-
- `parent_issue_ids` (List[int], optional): List of parent issue IDs.
|
|
50
|
-
- `created_since` (str, optional): Created since (YYYY-MM-DD).
|
|
51
|
-
- `created_until` (str, optional): Created until (YYYY-MM-DD).
|
|
52
|
-
- `updated_since` (str, optional): Updated since (YYYY-MM-DD).
|
|
53
|
-
- `updated_until` (str, optional): Updated until (YYYY-MM-DD).
|
|
54
|
-
- `start_date_since` (str, optional): Start Date since (YYYY-MM-DD).
|
|
55
|
-
- `start_date_until` (str, optional): Start Date until (YYYY-MM-DD).
|
|
56
|
-
- `due_date_since` (str, optional): Due Date since (YYYY-MM-DD).
|
|
57
|
-
- `due_date_until` (str, optional): Due Date until (YYYY-MM-DD).
|
|
44
|
+
This tool automatically determines the current user's ID and returns only issues assigned to that user. No parameters are required.
|
|
58
45
|
|
|
59
46
|
## Running the Server Locally
|
|
60
47
|
|
|
@@ -73,13 +60,11 @@ Create a `.env` file in the root directory and add the following environment var
|
|
|
73
60
|
# Backlog API Settings
|
|
74
61
|
BACKLOG_API_KEY=your_backlog_api_key_here
|
|
75
62
|
BACKLOG_DOMAIN=your-space.backlog.com
|
|
76
|
-
USER_ID=your_user_id
|
|
77
63
|
```
|
|
78
64
|
|
|
79
65
|
> **Important:**
|
|
80
66
|
> - Replace `your_backlog_api_key_here` with your actual Backlog API key
|
|
81
67
|
> - Replace `your-space.backlog.com` with your actual Backlog domain
|
|
82
|
-
> - Replace `your_user_id` with your actual Backlog user ID
|
|
83
68
|
|
|
84
69
|
### 3. Start the server
|
|
85
70
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from app.utils.di import create_backlog_context
|
|
2
|
+
from app.utils.ultils import get_user_task, get_current_user
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def get_user_issue_list():
|
|
6
|
+
"""
|
|
7
|
+
Retrieves a list of issues assigned to the current user from Backlog.
|
|
8
|
+
|
|
9
|
+
This function automatically determines the current user's ID via API
|
|
10
|
+
and returns only issues assigned to that user.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
ctx = create_backlog_context()
|
|
15
|
+
|
|
16
|
+
# Fetch current user information
|
|
17
|
+
current_user = await get_current_user(ctx.backlog_domain, ctx.api_key)
|
|
18
|
+
current_user_id = current_user["id"]
|
|
19
|
+
|
|
20
|
+
# Get issues assigned to current user
|
|
21
|
+
issue_list = await get_user_task(
|
|
22
|
+
backlog_domain=ctx.backlog_domain,
|
|
23
|
+
api_key=ctx.api_key,
|
|
24
|
+
assignee_ids=[current_user_id]
|
|
25
|
+
)
|
|
26
|
+
return issue_list
|
|
27
|
+
except Exception as e:
|
|
28
|
+
raise e
|
|
@@ -267,3 +267,27 @@ async def get_project_list(backlog_domain: str, api_key: str) -> list[int]:
|
|
|
267
267
|
raise ValueError(f"Failed to get project list: {e}") from e
|
|
268
268
|
except Exception as e:
|
|
269
269
|
raise ValueError(f"Unexpected error while getting project list: {e}") from e
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def get_current_user(backlog_domain: str, api_key: str) -> dict:
|
|
273
|
+
"""Get current user information from Backlog API.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
dict: {"id": int, "name": str}
|
|
277
|
+
"""
|
|
278
|
+
url = f"{backlog_domain}api/v2/users/myself"
|
|
279
|
+
params = {"apiKey": api_key}
|
|
280
|
+
|
|
281
|
+
async with httpx.AsyncClient() as client:
|
|
282
|
+
try:
|
|
283
|
+
response = await client.get(url, params=params, timeout=10.0)
|
|
284
|
+
response.raise_for_status()
|
|
285
|
+
data = response.json()
|
|
286
|
+
return {
|
|
287
|
+
"id": data["id"],
|
|
288
|
+
"name": data["name"]
|
|
289
|
+
}
|
|
290
|
+
except httpx.HTTPError as e:
|
|
291
|
+
raise ValueError(f"Failed to get current user: {e}") from e
|
|
292
|
+
except Exception as e:
|
|
293
|
+
raise ValueError(f"Unexpected error while getting current user: {e}") from e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "backlog-mcp"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.3"
|
|
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"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = "==3.13.*"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -34,7 +34,7 @@ wheels = [
|
|
|
34
34
|
|
|
35
35
|
[[package]]
|
|
36
36
|
name = "backlog-mcp"
|
|
37
|
-
version = "
|
|
37
|
+
version = "1.0.2"
|
|
38
38
|
source = { editable = "." }
|
|
39
39
|
dependencies = [
|
|
40
40
|
{ name = "httpx" },
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
from app.utils.di import create_backlog_context
|
|
2
|
-
from app.utils.ultils import get_user_task
|
|
3
|
-
|
|
4
|
-
from app.server_settings import settings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
async def get_user_issue_list(
|
|
8
|
-
project_ids: list[int] | None = None,
|
|
9
|
-
assignee_ids: list[int] | None = None,
|
|
10
|
-
status_ids: list[int] | None = None,
|
|
11
|
-
milestone_ids: list[int] | None = None,
|
|
12
|
-
parent_issue_ids: list[int] | None = None,
|
|
13
|
-
created_since: str | None = None,
|
|
14
|
-
created_until: str | None = None,
|
|
15
|
-
updated_since: str | None = None,
|
|
16
|
-
updated_until: str | None = None,
|
|
17
|
-
start_date_since: str | None = None,
|
|
18
|
-
start_date_until: str | None = None,
|
|
19
|
-
due_date_since: str | None = None,
|
|
20
|
-
due_date_until: str | None = None,
|
|
21
|
-
):
|
|
22
|
-
"""
|
|
23
|
-
Retrieves a filtered list of issues from Backlog for the users.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
project_ids (list[int], optional): List of project IDs to filter issues.
|
|
27
|
-
assignee_ids (list[int], optional): List of assignee IDs to filter issues (defaults to current user).
|
|
28
|
-
status_ids (list[int], optional): List of status IDs to filter issues (defaults to all non-closed statuses).
|
|
29
|
-
milestone_ids (list[int], optional): List of milestone IDs to filter issues.
|
|
30
|
-
parent_issue_ids (list[int], optional): List of parent issue IDs to filter issues.
|
|
31
|
-
|
|
32
|
-
created_since (str, optional): Created since (YYYY-MM-DD).
|
|
33
|
-
created_until (str, optional): Created until (YYYY-MM-DD).
|
|
34
|
-
|
|
35
|
-
updated_since (str, optional): Updated since (YYYY-MM-DD).
|
|
36
|
-
updated_until (str, optional): Updated until (YYYY-MM-DD).
|
|
37
|
-
|
|
38
|
-
start_date_since (str, optional): Start Date since (YYYY-MM-DD).
|
|
39
|
-
start_date_until (str, optional): Start Date until (YYYY-MM-DD).
|
|
40
|
-
|
|
41
|
-
due_date_since (str, optional): Due Date since (YYYY-MM-DD).
|
|
42
|
-
due_date_until (str, optional): Due Date until (YYYY-MM-DD).
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
ctx = create_backlog_context()
|
|
47
|
-
|
|
48
|
-
if assignee_ids is None:
|
|
49
|
-
assignee_ids = [settings.USER_ID]
|
|
50
|
-
|
|
51
|
-
issue_list = await get_user_task(
|
|
52
|
-
backlog_domain=ctx.backlog_domain,
|
|
53
|
-
api_key=ctx.api_key,
|
|
54
|
-
project_ids=project_ids,
|
|
55
|
-
assignee_ids=assignee_ids,
|
|
56
|
-
status_ids=status_ids,
|
|
57
|
-
milestone_ids=milestone_ids,
|
|
58
|
-
parent_issue_ids=parent_issue_ids,
|
|
59
|
-
created_since=created_since,
|
|
60
|
-
created_until=created_until,
|
|
61
|
-
updated_since=updated_since,
|
|
62
|
-
updated_until=updated_until,
|
|
63
|
-
start_date_since=start_date_since,
|
|
64
|
-
start_date_until=start_date_until,
|
|
65
|
-
due_date_since=due_date_since,
|
|
66
|
-
due_date_until=due_date_until
|
|
67
|
-
)
|
|
68
|
-
return issue_list
|
|
69
|
-
except Exception as e:
|
|
70
|
-
raise e
|
|
@@ -1,717 +0,0 @@
|
|
|
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=""
|
|
191
|
-
HAS_TYPE="false"
|
|
192
|
-
HAS_AUTO_APPROVE="false"
|
|
193
|
-
HAS_DISABLED="false"
|
|
194
|
-
HAS_TIMEOUT="false"
|
|
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=""
|
|
204
|
-
HAS_TYPE="false"
|
|
205
|
-
HAS_AUTO_APPROVE="false"
|
|
206
|
-
HAS_DISABLED="false"
|
|
207
|
-
HAS_TIMEOUT="false"
|
|
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
|
-
# Note: BACKLOG_DOMAIN is Fixed - no prompt generated, value will be hardcoded
|
|
443
|
-
|
|
444
|
-
# USER_ID (Variable - will be prompted)
|
|
445
|
-
echo -e "${CYAN} ℹ️ USER_ID${NC}"
|
|
446
|
-
echo -e "${YELLOW} ⚠️ Required: Please enter your USER_ID${NC}"
|
|
447
|
-
while [ -z "$USER_ID" ]; do
|
|
448
|
-
read -p " 🔐 Enter USER_ID: " USER_ID
|
|
449
|
-
if [ -z "$USER_ID" ]; then
|
|
450
|
-
echo -e "${RED} ✗ USER_ID cannot be empty. Please try again.${NC}"
|
|
451
|
-
else
|
|
452
|
-
echo -e "${GREEN} ✓ USER_ID set: $USER_ID${NC}"
|
|
453
|
-
fi
|
|
454
|
-
done
|
|
455
|
-
echo ""
|
|
456
|
-
|
|
457
|
-
echo ""
|
|
458
|
-
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
459
|
-
echo ""
|
|
460
|
-
|
|
461
|
-
# ============================================
|
|
462
|
-
# Configuration Summary
|
|
463
|
-
# ============================================
|
|
464
|
-
echo -e "${BOLD_CYAN} 📊 Configuration Summary:${NC}"
|
|
465
|
-
echo -e " ${WHITE}Agent:${NC} $AGENT_NAME (VS Code)"
|
|
466
|
-
echo -e " ${WHITE}Config file:${NC} $CONFIG_FILE"
|
|
467
|
-
echo -e " ${WHITE}Format:${NC} $CONFIG_FORMAT ($SERVERS_KEY)"
|
|
468
|
-
if [ "$HAS_TIMEOUT" = "true" ] && [ -n "$TIMEOUT_VALUE" ]; then
|
|
469
|
-
echo -e " ${WHITE}Timeout:${NC} $TIMEOUT_VALUE"
|
|
470
|
-
fi
|
|
471
|
-
if [ "$OVERWRITE_MODE" = "true" ]; then
|
|
472
|
-
echo -e " ${WHITE}Mode:${NC} ${YELLOW}⚠️ OVERWRITE${NC}"
|
|
473
|
-
else
|
|
474
|
-
echo -e " ${WHITE}Mode:${NC} ${GREEN}✨ NEW INSTALLATION${NC}"
|
|
475
|
-
fi
|
|
476
|
-
echo ""
|
|
477
|
-
echo -e " ${WHITE}MCP Server:${NC}"
|
|
478
|
-
echo -e " ${CYAN}Name:${NC} backlog-mcp"
|
|
479
|
-
echo -e " ${CYAN}Command:${NC} uvx"
|
|
480
|
-
echo -e " ${CYAN}Environment:${NC} ${GREEN}✓ Configured${NC}"
|
|
481
|
-
echo ""
|
|
482
|
-
echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
|
|
483
|
-
echo ""
|
|
484
|
-
|
|
485
|
-
# Create directory
|
|
486
|
-
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
487
|
-
|
|
488
|
-
# Validate and merge using Python
|
|
489
|
-
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" "USER_ID=$USER_ID" << 'PYTHON_EOF'
|
|
490
|
-
import json
|
|
491
|
-
import sys
|
|
492
|
-
import os
|
|
493
|
-
|
|
494
|
-
def strip_json_comments(text):
|
|
495
|
-
"""Remove comments from JSON-like text line by line"""
|
|
496
|
-
lines = text.split('\n')
|
|
497
|
-
cleaned_lines = []
|
|
498
|
-
in_multiline_comment = False
|
|
499
|
-
|
|
500
|
-
for line_idx, line in enumerate(lines):
|
|
501
|
-
original_line = line
|
|
502
|
-
# Handle multi-line comments
|
|
503
|
-
if '/*' in line:
|
|
504
|
-
in_multiline_comment = True
|
|
505
|
-
line = line[:line.index('/*')]
|
|
506
|
-
|
|
507
|
-
if in_multiline_comment:
|
|
508
|
-
if '*/' in line:
|
|
509
|
-
in_multiline_comment = False
|
|
510
|
-
line = line[line.index('*/') + 2:]
|
|
511
|
-
else:
|
|
512
|
-
continue
|
|
513
|
-
|
|
514
|
-
# Handle single-line comments
|
|
515
|
-
if '//' in line:
|
|
516
|
-
# Check if // is inside a string
|
|
517
|
-
in_string = False
|
|
518
|
-
quote_char = None
|
|
519
|
-
for i, char in enumerate(line):
|
|
520
|
-
if char in ['"', "'"] and (i == 0 or line[i-1] != '\\'):
|
|
521
|
-
if not in_string:
|
|
522
|
-
in_string = True
|
|
523
|
-
quote_char = char
|
|
524
|
-
elif char == quote_char:
|
|
525
|
-
in_string = False
|
|
526
|
-
quote_char = None
|
|
527
|
-
|
|
528
|
-
if not in_string and i < len(line) - 1 and line[i:i+2] == '//':
|
|
529
|
-
line = line[:i]
|
|
530
|
-
break
|
|
531
|
-
|
|
532
|
-
# Remove trailing commas before } or ]
|
|
533
|
-
line = line.rstrip()
|
|
534
|
-
if line.endswith(','):
|
|
535
|
-
temp_line = line[:-1].rstrip()
|
|
536
|
-
# Check if next significant character is } or ]
|
|
537
|
-
remaining = '\n'.join(lines[line_idx+1:])
|
|
538
|
-
stripped_remaining = remaining.lstrip()
|
|
539
|
-
if stripped_remaining and stripped_remaining[0] in '}]':
|
|
540
|
-
line = temp_line
|
|
541
|
-
|
|
542
|
-
if line.strip():
|
|
543
|
-
cleaned_lines.append(line)
|
|
544
|
-
|
|
545
|
-
return '\n'.join(cleaned_lines)
|
|
546
|
-
|
|
547
|
-
config_file = sys.argv[1]
|
|
548
|
-
overwrite_mode = sys.argv[2] == "true"
|
|
549
|
-
servers_key = sys.argv[3]
|
|
550
|
-
timeout_value = sys.argv[4]
|
|
551
|
-
has_type = sys.argv[5] == "true"
|
|
552
|
-
has_auto_approve = sys.argv[6] == "true"
|
|
553
|
-
has_disabled = sys.argv[7] == "true"
|
|
554
|
-
has_timeout = sys.argv[8] == "true"
|
|
555
|
-
config_format = sys.argv[9]
|
|
556
|
-
mcp_name = sys.argv[10]
|
|
557
|
-
command = sys.argv[11]
|
|
558
|
-
|
|
559
|
-
# Parse args (starting from index 12)
|
|
560
|
-
args = []
|
|
561
|
-
env_vars = {}
|
|
562
|
-
arg_index = 12
|
|
563
|
-
|
|
564
|
-
# Parse args until we hit the env vars marker
|
|
565
|
-
while arg_index < len(sys.argv):
|
|
566
|
-
arg = sys.argv[arg_index]
|
|
567
|
-
if arg == "ENV_START":
|
|
568
|
-
arg_index += 1
|
|
569
|
-
break
|
|
570
|
-
args.append(arg)
|
|
571
|
-
arg_index += 1
|
|
572
|
-
|
|
573
|
-
# Parse env vars (format: KEY=VALUE)
|
|
574
|
-
while arg_index < len(sys.argv):
|
|
575
|
-
env_pair = sys.argv[arg_index]
|
|
576
|
-
if '=' in env_pair:
|
|
577
|
-
key, value = env_pair.split('=', 1)
|
|
578
|
-
env_vars[key] = value
|
|
579
|
-
arg_index += 1
|
|
580
|
-
|
|
581
|
-
# ANSI colors
|
|
582
|
-
RED = '\033[0;31m'
|
|
583
|
-
GREEN = '\033[0;32m'
|
|
584
|
-
YELLOW = '\033[1;33m'
|
|
585
|
-
BLUE = '\033[0;34m'
|
|
586
|
-
CYAN = '\033[1;36m'
|
|
587
|
-
WHITE = '\033[1;37m'
|
|
588
|
-
NC = '\033[0m'
|
|
589
|
-
|
|
590
|
-
def print_color(color, message):
|
|
591
|
-
print(f"{color}{message}{NC}")
|
|
592
|
-
|
|
593
|
-
# Check if file exists
|
|
594
|
-
if os.path.exists(config_file):
|
|
595
|
-
try:
|
|
596
|
-
with open(config_file, 'r', encoding='utf-8') as f:
|
|
597
|
-
content = f.read()
|
|
598
|
-
|
|
599
|
-
# Strip comments before parsing
|
|
600
|
-
clean_content = strip_json_comments(content)
|
|
601
|
-
config = json.loads(clean_content)
|
|
602
|
-
|
|
603
|
-
# Validate structure
|
|
604
|
-
if not isinstance(config, dict):
|
|
605
|
-
print_color(RED, " ✗ Error: Config file is not a valid JSON object")
|
|
606
|
-
print(f" 📝 File: {config_file}")
|
|
607
|
-
print(f" 🔧 Expected: {{ \"{servers_key}\": {{...}} }}")
|
|
608
|
-
sys.exit(1)
|
|
609
|
-
|
|
610
|
-
if servers_key in config:
|
|
611
|
-
if not isinstance(config[servers_key], dict):
|
|
612
|
-
print_color(RED, f" ✗ Error: '{servers_key}' must be an object")
|
|
613
|
-
print(f" 📝 File: {config_file}")
|
|
614
|
-
sys.exit(1)
|
|
615
|
-
else:
|
|
616
|
-
config[servers_key] = {}
|
|
617
|
-
|
|
618
|
-
if overwrite_mode:
|
|
619
|
-
print_color(YELLOW, " ⚠️ Overwriting existing configuration...")
|
|
620
|
-
else:
|
|
621
|
-
print_color(CYAN, f" 📋 Found {len(config[servers_key])} existing MCP server(s)")
|
|
622
|
-
|
|
623
|
-
except json.JSONDecodeError as e:
|
|
624
|
-
print_color(RED, " ✗ Error: Invalid JSON in config file")
|
|
625
|
-
print(f" 📝 File: {config_file}")
|
|
626
|
-
print(f" 🔍 Error: {str(e)}")
|
|
627
|
-
print("")
|
|
628
|
-
print_color(YELLOW, " 💡 Please fix the JSON syntax manually:")
|
|
629
|
-
print(" 1. Open the file in your editor")
|
|
630
|
-
print(" 2. Remove any comments (// or /* */)")
|
|
631
|
-
print(" 3. Fix any JSON syntax errors")
|
|
632
|
-
print(" 4. Or delete the file and run this script again")
|
|
633
|
-
sys.exit(1)
|
|
634
|
-
except Exception as e:
|
|
635
|
-
print_color(RED, f" ✗ Error reading config: {str(e)}")
|
|
636
|
-
sys.exit(1)
|
|
637
|
-
else:
|
|
638
|
-
print_color(WHITE, " 📝 Config file not found, creating new one...")
|
|
639
|
-
config = {servers_key: {}}
|
|
640
|
-
|
|
641
|
-
# Prepare the MCP configuration (base config - always included)
|
|
642
|
-
mcp_config = {
|
|
643
|
-
"command": command,
|
|
644
|
-
"args": args,
|
|
645
|
-
"env": env_vars
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
# Add optional fields based on agent configuration flags
|
|
649
|
-
if has_type:
|
|
650
|
-
mcp_config["type"] = "stdio"
|
|
651
|
-
|
|
652
|
-
if has_auto_approve:
|
|
653
|
-
mcp_config["autoApprove"] = []
|
|
654
|
-
|
|
655
|
-
if has_disabled:
|
|
656
|
-
mcp_config["disabled"] = False
|
|
657
|
-
|
|
658
|
-
if has_timeout and timeout_value:
|
|
659
|
-
# Convert timeout based on agent type:
|
|
660
|
-
# - Copilot: milliseconds (convert seconds to milliseconds)
|
|
661
|
-
# - Cline: seconds (keep as is)
|
|
662
|
-
# - Claude Code: not supported (has_timeout is false)
|
|
663
|
-
timeout_int = int(timeout_value)
|
|
664
|
-
if config_format == "copilot":
|
|
665
|
-
# Copilot uses milliseconds, convert from seconds
|
|
666
|
-
mcp_config["timeout"] = timeout_int * 1000
|
|
667
|
-
else:
|
|
668
|
-
# Cline uses seconds
|
|
669
|
-
mcp_config["timeout"] = timeout_int
|
|
670
|
-
|
|
671
|
-
# Add/Update MCP server
|
|
672
|
-
config[servers_key][mcp_name] = mcp_config
|
|
673
|
-
|
|
674
|
-
# Write back (clean JSON without comments)
|
|
675
|
-
try:
|
|
676
|
-
with open(config_file, 'w', encoding='utf-8') as f:
|
|
677
|
-
json.dump(config, f, indent=4, ensure_ascii=False)
|
|
678
|
-
|
|
679
|
-
if overwrite_mode:
|
|
680
|
-
print_color(GREEN, f" ✓ Successfully overwritten {mcp_name}")
|
|
681
|
-
else:
|
|
682
|
-
print_color(GREEN, f" ✓ Successfully added {mcp_name}")
|
|
683
|
-
|
|
684
|
-
print(f" 📝 Config: {config_file}")
|
|
685
|
-
print_color(CYAN, f" 🔢 Total MCP servers: {len(config[servers_key])}")
|
|
686
|
-
|
|
687
|
-
# List all servers
|
|
688
|
-
print_color(WHITE, f"\n 📋 All MCP servers (format: {config_format}):")
|
|
689
|
-
for server_name in config[servers_key].keys():
|
|
690
|
-
server_config = config[servers_key][server_name]
|
|
691
|
-
status = "disabled" if server_config.get('disabled', False) else "enabled"
|
|
692
|
-
emoji = "🔴" if server_config.get('disabled', False) else "🟢"
|
|
693
|
-
has_env = "🔐" if server_config.get('env') else ""
|
|
694
|
-
marker = "⚡" if server_name == mcp_name else " "
|
|
695
|
-
print(f" {marker} {emoji} {server_name} ({status}) {has_env}")
|
|
696
|
-
|
|
697
|
-
print_color(GREEN, "\n ✅ Installation complete!")
|
|
698
|
-
print_color(YELLOW, " 🔄 Please restart VS Code and your agent to apply changes")
|
|
699
|
-
|
|
700
|
-
if config_format != "claude-code":
|
|
701
|
-
print_color(CYAN, "\n 💡 Note: JSON comments were removed from the file")
|
|
702
|
-
print_color(CYAN, " (This is normal - the file will still work perfectly)")
|
|
703
|
-
|
|
704
|
-
except Exception as e:
|
|
705
|
-
print_color(RED, f" ✗ Error writing config: {str(e)}")
|
|
706
|
-
sys.exit(1)
|
|
707
|
-
PYTHON_EOF
|
|
708
|
-
|
|
709
|
-
if [ $? -eq 0 ]; then
|
|
710
|
-
echo ""
|
|
711
|
-
echo -e "${WHITE}"
|
|
712
|
-
cat << "EOF"
|
|
713
|
-
✨ Installation Success! ✨
|
|
714
|
-
🎉 All Done! 🎉
|
|
715
|
-
EOF
|
|
716
|
-
echo -e "${NC}"
|
|
717
|
-
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
|