backlog-mcp 1.0.2__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.
Files changed (25) hide show
  1. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/.env.example +0 -1
  2. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/PKG-INFO +3 -18
  3. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/README.md +2 -17
  4. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/server_settings.py +0 -3
  5. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/tools/get_issue_details.py +4 -1
  6. backlog_mcp-1.0.4/app/tools/get_user_issue_list.py +28 -0
  7. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/utils/ultils.py +62 -29
  8. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/pyproject.toml +1 -1
  9. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/scripts/install-backlog-mcp.sh +10 -25
  10. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/uv.lock +2 -2
  11. backlog_mcp-1.0.2/app/tools/get_user_issue_list.py +0 -70
  12. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/.gitignore +0 -0
  13. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/.mise.toml +0 -0
  14. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/__init__.py +0 -0
  15. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/constants/__init__.py +0 -0
  16. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/constants/constants.py +0 -0
  17. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/core/__init__.py +0 -0
  18. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/logging_config.py +0 -0
  19. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/main.py +0 -0
  20. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/models/__init__.py +0 -0
  21. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/models/models.py +0 -0
  22. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/tools/__init__.py +0 -0
  23. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/utils/__init__.py +0 -0
  24. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/app/utils/di.py +0 -0
  25. {backlog_mcp-1.0.2 → backlog_mcp-1.0.4}/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.4
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()
@@ -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
- timezone: str = "UTC"
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:
@@ -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
@@ -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
- comments = issue_detail.get("comments", [])
57
- if comments:
58
- # Sort comments by created_at (created field)
59
- sorted_comments = sorted(comments, key=lambda c: convert_to_timezone(timezone, c["created"]))
60
- # Create list of {content, created_by} where created_by is just the name
61
- processed_comments = [
62
- {
63
- "content": c["content"],
64
- "created_by": c["createdUser"]["name"] if c.get("createdUser") else None
65
- }
66
- for c in sorted_comments if c.get("content")
67
- ]
68
- # Filter out None created_by if any (though should not happen)
69
- processed_comments = [c for c in processed_comments if c["created_by"]]
70
- processed_issue["comments"] = processed_comments
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
- comments_response = client.get(issue_comments_url, params=params)
89
-
90
- results = await asyncio.gather(issue_detail_response, comments_response)
91
-
92
- issue_detail = results[0].json()
93
- issue_comment = results[1].json()
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 results[0].is_success:
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 results[1].is_success:
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
- comments_in_time_range = []
108
- for comment in issue_comment:
109
- comments_in_time_range.append(comment)
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.update({"comments": comments_in_time_range})
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:
@@ -267,3 +276,27 @@ async def get_project_list(backlog_domain: str, api_key: str) -> list[int]:
267
276
  raise ValueError(f"Failed to get project list: {e}") from e
268
277
  except Exception as e:
269
278
  raise ValueError(f"Unexpected error while getting project list: {e}") from e
279
+
280
+
281
+ async def get_current_user(backlog_domain: str, api_key: str) -> dict:
282
+ """Get current user information from Backlog API.
283
+
284
+ Returns:
285
+ dict: {"id": int, "name": str}
286
+ """
287
+ url = f"{backlog_domain}api/v2/users/myself"
288
+ params = {"apiKey": api_key}
289
+
290
+ async with httpx.AsyncClient() as client:
291
+ try:
292
+ response = await client.get(url, params=params, timeout=10.0)
293
+ response.raise_for_status()
294
+ data = response.json()
295
+ return {
296
+ "id": data["id"],
297
+ "name": data["name"]
298
+ }
299
+ except httpx.HTTPError as e:
300
+ raise ValueError(f"Failed to get current user: {e}") from e
301
+ except Exception as e:
302
+ 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.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"
@@ -187,11 +187,11 @@ case $agent_choice in
187
187
  AGENT_KEY="cline"
188
188
  CONFIG_FORMAT="standard"
189
189
  SERVERS_KEY="mcpServers"
190
- TIMEOUT_VALUE=""
191
- HAS_TYPE="false"
192
- HAS_AUTO_APPROVE="false"
193
- HAS_DISABLED="false"
194
- HAS_TIMEOUT="false"
190
+ TIMEOUT_VALUE="60"
191
+ HAS_TYPE="true"
192
+ HAS_AUTO_APPROVE="true"
193
+ HAS_DISABLED="true"
194
+ HAS_TIMEOUT="true"
195
195
  CONFIG_FILE="$HOME/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
196
196
  ;;
197
197
  2)
@@ -200,11 +200,11 @@ case $agent_choice in
200
200
  AGENT_KEY="copilot"
201
201
  CONFIG_FORMAT="copilot"
202
202
  SERVERS_KEY="servers"
203
- TIMEOUT_VALUE=""
204
- HAS_TYPE="false"
203
+ TIMEOUT_VALUE="60"
204
+ HAS_TYPE="true"
205
205
  HAS_AUTO_APPROVE="false"
206
206
  HAS_DISABLED="false"
207
- HAS_TIMEOUT="false"
207
+ HAS_TIMEOUT="true"
208
208
  CONFIG_FILE="$HOME/Library/Application Support/Code/User/mcp.json"
209
209
  ;;
210
210
  3)
@@ -439,21 +439,6 @@ while [ -z "$BACKLOG_API_KEY" ]; do
439
439
  done
440
440
  echo ""
441
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
442
  echo ""
458
443
  echo -e "${WHITE} ════════════════════════════════════════════════════════${NC}"
459
444
  echo ""
@@ -486,7 +471,7 @@ echo ""
486
471
  mkdir -p "$(dirname "$CONFIG_FILE")"
487
472
 
488
473
  # 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'
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'
490
475
  import json
491
476
  import sys
492
477
  import os
@@ -650,7 +635,7 @@ if has_type:
650
635
  mcp_config["type"] = "stdio"
651
636
 
652
637
  if has_auto_approve:
653
- mcp_config["autoApprove"] = []
638
+ mcp_config["autoApprove"] = ["get_issue_details", "get_user_issue_list"]
654
639
 
655
640
  if has_disabled:
656
641
  mcp_config["disabled"] = False
@@ -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.3"
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
File without changes
File without changes
File without changes
File without changes
File without changes