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.
Files changed (25) hide show
  1. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/.env.example +0 -1
  2. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/PKG-INFO +3 -18
  3. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/README.md +2 -17
  4. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/server_settings.py +0 -3
  5. backlog_mcp-1.0.3/app/tools/get_user_issue_list.py +28 -0
  6. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/ultils.py +24 -0
  7. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/pyproject.toml +1 -1
  8. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/uv.lock +2 -2
  9. backlog_mcp-1.0.2/app/tools/get_user_issue_list.py +0 -70
  10. backlog_mcp-1.0.2/scripts/install-backlog-mcp.sh +0 -717
  11. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/.gitignore +0 -0
  12. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/.mise.toml +0 -0
  13. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/__init__.py +0 -0
  14. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/constants/__init__.py +0 -0
  15. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/constants/constants.py +0 -0
  16. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/core/__init__.py +0 -0
  17. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/logging_config.py +0 -0
  18. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/main.py +0 -0
  19. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/models/__init__.py +0 -0
  20. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/models/models.py +0 -0
  21. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/tools/__init__.py +0 -0
  22. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/tools/get_issue_details.py +0 -0
  23. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/__init__.py +0 -0
  24. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/app/utils/di.py +0 -0
  25. {backlog_mcp-1.0.2 → backlog_mcp-1.0.3}/docs/mcp-installer-template.md +0 -0
@@ -1,4 +1,3 @@
1
1
  # Backlog API Settings
2
2
  BACKLOG_API_KEY=your_backlog_api_key_here
3
3
  BACKLOG_DOMAIN=your-space.backlog.com
4
- USER_ID=your_user_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlog-mcp
3
- Version: 1.0.2
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 filtered list of issues based on project, assignees, milestones, and other criteria.
58
+ Retrieve a list of issues assigned to the current user.
59
59
 
60
- **Parameters:**
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 filtered list of issues based on project, assignees, milestones, and other criteria.
42
+ Retrieve a list of issues assigned to the current user.
43
43
 
44
- **Parameters:**
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
 
@@ -13,7 +13,4 @@ class ServerSettings(BaseSettings):
13
13
  BACKLOG_API_KEY: str
14
14
  BACKLOG_DOMAIN: str # e.g., "your-space.backlog.com"
15
15
 
16
- # User Settings
17
- USER_ID: int
18
-
19
16
  settings = ServerSettings()
@@ -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.2"
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
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 = "0.0.10"
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