quickcall-integrations 0.5.0__tar.gz → 0.6.0__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.
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/PKG-INFO +19 -1
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/README.md +18 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/api_clients/github_client.py +139 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/github_tools.py +86 -2
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/.claude-plugin/plugin.json +1 -1
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/pyproject.toml +1 -1
- quickcall_integrations-0.6.0/tests/test_comment_management.py +271 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.claude-plugin/marketplace.json +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/ISSUE_TEMPLATE/task.yml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/workflows/publish-pypi.yml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.gitignore +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.pre-commit-config.yaml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.quickcall-issue-template.yaml +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/Dockerfile +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/assets/logo.png +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/api_clients/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/api_clients/slack_client.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/auth/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/auth/credentials.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/auth/device_flow.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/resources/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/resources/github_resources.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/resources/slack_resources.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/server.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/auth_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/git_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/slack_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/utility_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/appraisal.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/connect-github-pat.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/connect.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/pr-summary.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/projects.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/slack-summary.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/status.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/updates.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/requirements.txt +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/README.md +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/appraisal/__init__.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/appraisal/setup_test_data.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_appraisal_integration.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_appraisal_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_integrations.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_pr_integration.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_project_integration.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_project_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_tools.py +0 -0
- {quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickcall-integrations
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: MCP server with developer integrations for Claude Code and Cursor
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: fastmcp>=2.13.0
|
|
@@ -252,10 +252,28 @@ The `manage_issues` tool provides full issue lifecycle management:
|
|
|
252
252
|
| `close` | Close issue(s) |
|
|
253
253
|
| `reopen` | Reopen issue(s) |
|
|
254
254
|
| `comment` | Add comment to issue(s) |
|
|
255
|
+
| `list_comments` | List comments with limit and order |
|
|
256
|
+
| `update_comment` | Edit existing comment by ID |
|
|
257
|
+
| `delete_comment` | Delete comment by ID |
|
|
255
258
|
| `add_sub_issue` | Add child issue to parent |
|
|
256
259
|
| `remove_sub_issue` | Remove child from parent |
|
|
257
260
|
| `list_sub_issues` | List sub-issues of a parent |
|
|
258
261
|
|
|
262
|
+
### Comment Management
|
|
263
|
+
|
|
264
|
+
| Parameter | Description |
|
|
265
|
+
|-----------|-------------|
|
|
266
|
+
| `comment_id` | Comment ID for update/delete operations |
|
|
267
|
+
| `comments_limit` | Max comments to return (default: 10) |
|
|
268
|
+
| `comments_order` | `'asc'` (oldest first) or `'desc'` (newest first) |
|
|
269
|
+
|
|
270
|
+
**Examples:**
|
|
271
|
+
```
|
|
272
|
+
List last 5 comments on issue #42 (newest first)
|
|
273
|
+
Update comment 123456 on issue #42 with new text
|
|
274
|
+
Delete comment 123456 from issue #42
|
|
275
|
+
```
|
|
276
|
+
|
|
259
277
|
### List Filters
|
|
260
278
|
|
|
261
279
|
| Filter | Description |
|
|
@@ -239,10 +239,28 @@ The `manage_issues` tool provides full issue lifecycle management:
|
|
|
239
239
|
| `close` | Close issue(s) |
|
|
240
240
|
| `reopen` | Reopen issue(s) |
|
|
241
241
|
| `comment` | Add comment to issue(s) |
|
|
242
|
+
| `list_comments` | List comments with limit and order |
|
|
243
|
+
| `update_comment` | Edit existing comment by ID |
|
|
244
|
+
| `delete_comment` | Delete comment by ID |
|
|
242
245
|
| `add_sub_issue` | Add child issue to parent |
|
|
243
246
|
| `remove_sub_issue` | Remove child from parent |
|
|
244
247
|
| `list_sub_issues` | List sub-issues of a parent |
|
|
245
248
|
|
|
249
|
+
### Comment Management
|
|
250
|
+
|
|
251
|
+
| Parameter | Description |
|
|
252
|
+
|-----------|-------------|
|
|
253
|
+
| `comment_id` | Comment ID for update/delete operations |
|
|
254
|
+
| `comments_limit` | Max comments to return (default: 10) |
|
|
255
|
+
| `comments_order` | `'asc'` (oldest first) or `'desc'` (newest first) |
|
|
256
|
+
|
|
257
|
+
**Examples:**
|
|
258
|
+
```
|
|
259
|
+
List last 5 comments on issue #42 (newest first)
|
|
260
|
+
Update comment 123456 on issue #42 with new text
|
|
261
|
+
Delete comment 123456 from issue #42
|
|
262
|
+
```
|
|
263
|
+
|
|
246
264
|
### List Filters
|
|
247
265
|
|
|
248
266
|
| Filter | Description |
|
|
@@ -1301,6 +1301,145 @@ class GitHubClient:
|
|
|
1301
1301
|
"issue_number": issue_number,
|
|
1302
1302
|
}
|
|
1303
1303
|
|
|
1304
|
+
def list_issue_comments(
|
|
1305
|
+
self,
|
|
1306
|
+
issue_number: int,
|
|
1307
|
+
owner: Optional[str] = None,
|
|
1308
|
+
repo: Optional[str] = None,
|
|
1309
|
+
limit: int = 10,
|
|
1310
|
+
order: str = "asc",
|
|
1311
|
+
) -> List[Dict[str, Any]]:
|
|
1312
|
+
"""
|
|
1313
|
+
List comments on a GitHub issue.
|
|
1314
|
+
|
|
1315
|
+
Args:
|
|
1316
|
+
issue_number: Issue number
|
|
1317
|
+
owner: Repository owner
|
|
1318
|
+
repo: Repository name
|
|
1319
|
+
limit: Maximum comments to return (default: 10)
|
|
1320
|
+
order: 'asc' for oldest first, 'desc' for newest first (default: 'asc')
|
|
1321
|
+
|
|
1322
|
+
Returns:
|
|
1323
|
+
List of comment dicts with id, body, author, timestamps, url
|
|
1324
|
+
"""
|
|
1325
|
+
gh_repo = self._get_repo(owner, repo)
|
|
1326
|
+
issue = gh_repo.get_issue(issue_number)
|
|
1327
|
+
|
|
1328
|
+
comments = []
|
|
1329
|
+
all_comments = list(issue.get_comments())
|
|
1330
|
+
|
|
1331
|
+
# Apply order
|
|
1332
|
+
if order == "desc":
|
|
1333
|
+
all_comments = all_comments[::-1]
|
|
1334
|
+
|
|
1335
|
+
# Apply limit
|
|
1336
|
+
for comment in all_comments[:limit]:
|
|
1337
|
+
comments.append(
|
|
1338
|
+
{
|
|
1339
|
+
"id": comment.id,
|
|
1340
|
+
"body": comment.body,
|
|
1341
|
+
"author": comment.user.login if comment.user else "unknown",
|
|
1342
|
+
"created_at": comment.created_at.isoformat(),
|
|
1343
|
+
"updated_at": comment.updated_at.isoformat()
|
|
1344
|
+
if comment.updated_at
|
|
1345
|
+
else None,
|
|
1346
|
+
"html_url": comment.html_url,
|
|
1347
|
+
}
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
return comments
|
|
1351
|
+
|
|
1352
|
+
def get_issue_comment(
|
|
1353
|
+
self,
|
|
1354
|
+
comment_id: int,
|
|
1355
|
+
owner: Optional[str] = None,
|
|
1356
|
+
repo: Optional[str] = None,
|
|
1357
|
+
) -> Dict[str, Any]:
|
|
1358
|
+
"""
|
|
1359
|
+
Get a specific comment by ID.
|
|
1360
|
+
|
|
1361
|
+
Args:
|
|
1362
|
+
comment_id: Comment ID
|
|
1363
|
+
owner: Repository owner
|
|
1364
|
+
repo: Repository name
|
|
1365
|
+
|
|
1366
|
+
Returns:
|
|
1367
|
+
Comment dict with id, body, author, timestamps, url
|
|
1368
|
+
"""
|
|
1369
|
+
gh_repo = self._get_repo(owner, repo)
|
|
1370
|
+
comment = gh_repo.get_issue_comment(comment_id)
|
|
1371
|
+
|
|
1372
|
+
return {
|
|
1373
|
+
"id": comment.id,
|
|
1374
|
+
"body": comment.body,
|
|
1375
|
+
"author": comment.user.login if comment.user else "unknown",
|
|
1376
|
+
"created_at": comment.created_at.isoformat(),
|
|
1377
|
+
"updated_at": comment.updated_at.isoformat()
|
|
1378
|
+
if comment.updated_at
|
|
1379
|
+
else None,
|
|
1380
|
+
"html_url": comment.html_url,
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
def update_issue_comment(
|
|
1384
|
+
self,
|
|
1385
|
+
comment_id: int,
|
|
1386
|
+
body: str,
|
|
1387
|
+
owner: Optional[str] = None,
|
|
1388
|
+
repo: Optional[str] = None,
|
|
1389
|
+
) -> Dict[str, Any]:
|
|
1390
|
+
"""
|
|
1391
|
+
Update an existing comment.
|
|
1392
|
+
|
|
1393
|
+
Args:
|
|
1394
|
+
comment_id: Comment ID
|
|
1395
|
+
body: New comment body
|
|
1396
|
+
owner: Repository owner
|
|
1397
|
+
repo: Repository name
|
|
1398
|
+
|
|
1399
|
+
Returns:
|
|
1400
|
+
Updated comment dict
|
|
1401
|
+
"""
|
|
1402
|
+
gh_repo = self._get_repo(owner, repo)
|
|
1403
|
+
comment = gh_repo.get_issue_comment(comment_id)
|
|
1404
|
+
comment.edit(body)
|
|
1405
|
+
|
|
1406
|
+
return {
|
|
1407
|
+
"id": comment.id,
|
|
1408
|
+
"body": comment.body,
|
|
1409
|
+
"author": comment.user.login if comment.user else "unknown",
|
|
1410
|
+
"created_at": comment.created_at.isoformat(),
|
|
1411
|
+
"updated_at": comment.updated_at.isoformat()
|
|
1412
|
+
if comment.updated_at
|
|
1413
|
+
else None,
|
|
1414
|
+
"html_url": comment.html_url,
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
def delete_issue_comment(
|
|
1418
|
+
self,
|
|
1419
|
+
comment_id: int,
|
|
1420
|
+
owner: Optional[str] = None,
|
|
1421
|
+
repo: Optional[str] = None,
|
|
1422
|
+
) -> Dict[str, Any]:
|
|
1423
|
+
"""
|
|
1424
|
+
Delete a comment.
|
|
1425
|
+
|
|
1426
|
+
Args:
|
|
1427
|
+
comment_id: Comment ID
|
|
1428
|
+
owner: Repository owner
|
|
1429
|
+
repo: Repository name
|
|
1430
|
+
|
|
1431
|
+
Returns:
|
|
1432
|
+
Dict with deleted comment_id
|
|
1433
|
+
"""
|
|
1434
|
+
gh_repo = self._get_repo(owner, repo)
|
|
1435
|
+
comment = gh_repo.get_issue_comment(comment_id)
|
|
1436
|
+
comment.delete()
|
|
1437
|
+
|
|
1438
|
+
return {
|
|
1439
|
+
"deleted": True,
|
|
1440
|
+
"comment_id": comment_id,
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1304
1443
|
def get_issue(
|
|
1305
1444
|
self,
|
|
1306
1445
|
issue_number: int,
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/github_tools.py
RENAMED
|
@@ -571,11 +571,12 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
571
571
|
action: str = Field(
|
|
572
572
|
...,
|
|
573
573
|
description="Action: 'list', 'view', 'create', 'update', 'close', 'reopen', 'comment', "
|
|
574
|
+
"'list_comments', 'update_comment', 'delete_comment', "
|
|
574
575
|
"'add_sub_issue', 'remove_sub_issue', 'list_sub_issues'",
|
|
575
576
|
),
|
|
576
577
|
issue_numbers: Optional[List[int]] = Field(
|
|
577
578
|
default=None,
|
|
578
|
-
description="Issue number(s). Required for view/update/close/reopen/comment/sub-issue/
|
|
579
|
+
description="Issue number(s). Required for view/update/close/reopen/comment/sub-issue/comment ops.",
|
|
579
580
|
),
|
|
580
581
|
title: Optional[str] = Field(
|
|
581
582
|
default=None,
|
|
@@ -583,7 +584,7 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
583
584
|
),
|
|
584
585
|
body: Optional[str] = Field(
|
|
585
586
|
default=None,
|
|
586
|
-
description="Issue body (for 'create'/'update') or comment text (for 'comment')",
|
|
587
|
+
description="Issue body (for 'create'/'update') or comment text (for 'comment'/'update_comment')",
|
|
587
588
|
),
|
|
588
589
|
labels: Optional[List[str]] = Field(
|
|
589
590
|
default=None,
|
|
@@ -602,6 +603,18 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
602
603
|
description="Parent issue number. For 'create': attach new issue as sub-issue. "
|
|
603
604
|
"For 'add_sub_issue'/'remove_sub_issue'/'list_sub_issues': the parent issue.",
|
|
604
605
|
),
|
|
606
|
+
comment_id: Optional[int] = Field(
|
|
607
|
+
default=None,
|
|
608
|
+
description="Comment ID for 'update_comment' or 'delete_comment' actions.",
|
|
609
|
+
),
|
|
610
|
+
comments_limit: Optional[int] = Field(
|
|
611
|
+
default=10,
|
|
612
|
+
description="Max comments to return for 'list_comments' (default: 10).",
|
|
613
|
+
),
|
|
614
|
+
comments_order: Optional[str] = Field(
|
|
615
|
+
default="asc",
|
|
616
|
+
description="Comment order for 'list_comments': 'asc' (oldest first) or 'desc' (newest first).",
|
|
617
|
+
),
|
|
605
618
|
owner: Optional[str] = Field(
|
|
606
619
|
default=None,
|
|
607
620
|
description="Repository owner",
|
|
@@ -644,6 +657,10 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
644
657
|
- create as sub-issue: manage_issues(action="create", title="Task 1", parent_issue=42)
|
|
645
658
|
- close multiple: manage_issues(action="close", issue_numbers=[1, 2, 3])
|
|
646
659
|
- comment: manage_issues(action="comment", issue_numbers=[42], body="Fixed!")
|
|
660
|
+
- list comments: manage_issues(action="list_comments", issue_numbers=[42], comments_limit=5)
|
|
661
|
+
- list newest comments: manage_issues(action="list_comments", issue_numbers=[42], comments_order="desc")
|
|
662
|
+
- update comment: manage_issues(action="update_comment", issue_numbers=[42], comment_id=123, body="Updated")
|
|
663
|
+
- delete comment: manage_issues(action="delete_comment", issue_numbers=[42], comment_id=123)
|
|
647
664
|
- add sub-issues: manage_issues(action="add_sub_issue", issue_numbers=[43,44], parent_issue=42)
|
|
648
665
|
- remove sub-issue: manage_issues(action="remove_sub_issue", issue_numbers=[43], parent_issue=42)
|
|
649
666
|
- list sub-issues: manage_issues(action="list_sub_issues", parent_issue=42)
|
|
@@ -830,6 +847,66 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
830
847
|
}
|
|
831
848
|
)
|
|
832
849
|
|
|
850
|
+
# === LIST COMMENTS ACTION ===
|
|
851
|
+
elif action == "list_comments":
|
|
852
|
+
comments = client.list_issue_comments(
|
|
853
|
+
issue_number=issue_number,
|
|
854
|
+
owner=owner,
|
|
855
|
+
repo=repo,
|
|
856
|
+
limit=comments_limit or 10,
|
|
857
|
+
order=comments_order or "asc",
|
|
858
|
+
)
|
|
859
|
+
results.append(
|
|
860
|
+
{
|
|
861
|
+
"number": issue_number,
|
|
862
|
+
"comments_count": len(comments),
|
|
863
|
+
"comments": comments,
|
|
864
|
+
}
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
# === UPDATE COMMENT ACTION ===
|
|
868
|
+
elif action == "update_comment":
|
|
869
|
+
if not comment_id:
|
|
870
|
+
raise ToolError(
|
|
871
|
+
"'comment_id' is required for 'update_comment' action"
|
|
872
|
+
)
|
|
873
|
+
if not body:
|
|
874
|
+
raise ToolError(
|
|
875
|
+
"'body' is required for 'update_comment' action"
|
|
876
|
+
)
|
|
877
|
+
updated = client.update_issue_comment(
|
|
878
|
+
comment_id=comment_id,
|
|
879
|
+
body=body,
|
|
880
|
+
owner=owner,
|
|
881
|
+
repo=repo,
|
|
882
|
+
)
|
|
883
|
+
results.append(
|
|
884
|
+
{
|
|
885
|
+
"number": issue_number,
|
|
886
|
+
"status": "comment_updated",
|
|
887
|
+
"comment": updated,
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# === DELETE COMMENT ACTION ===
|
|
892
|
+
elif action == "delete_comment":
|
|
893
|
+
if not comment_id:
|
|
894
|
+
raise ToolError(
|
|
895
|
+
"'comment_id' is required for 'delete_comment' action"
|
|
896
|
+
)
|
|
897
|
+
client.delete_issue_comment(
|
|
898
|
+
comment_id=comment_id,
|
|
899
|
+
owner=owner,
|
|
900
|
+
repo=repo,
|
|
901
|
+
)
|
|
902
|
+
results.append(
|
|
903
|
+
{
|
|
904
|
+
"number": issue_number,
|
|
905
|
+
"status": "comment_deleted",
|
|
906
|
+
"comment_id": comment_id,
|
|
907
|
+
}
|
|
908
|
+
)
|
|
909
|
+
|
|
833
910
|
else:
|
|
834
911
|
raise ToolError(f"Invalid action: {action}")
|
|
835
912
|
|
|
@@ -841,6 +918,13 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
841
918
|
"issues": results,
|
|
842
919
|
}
|
|
843
920
|
|
|
921
|
+
if action == "list_comments":
|
|
922
|
+
return {
|
|
923
|
+
"action": "list_comments",
|
|
924
|
+
"count": len(results),
|
|
925
|
+
"issues": results,
|
|
926
|
+
}
|
|
927
|
+
|
|
844
928
|
return {"action": action, "count": len(results), "results": results}
|
|
845
929
|
|
|
846
930
|
except ToolError:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickcall",
|
|
3
3
|
"description": "Integrate quickcall into dev workflows - eliminate interruptions for developers. Ask about your work, get instant answers. No more context switching.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sagar Sarkale"
|
|
7
7
|
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Integration test for Issue Comment Management.
|
|
4
|
+
|
|
5
|
+
This test performs a full round-trip:
|
|
6
|
+
1. Creates a test issue
|
|
7
|
+
2. Adds multiple comments
|
|
8
|
+
3. Lists comments (asc and desc order)
|
|
9
|
+
4. Updates a comment
|
|
10
|
+
5. Deletes a comment
|
|
11
|
+
6. Closes/cleans up the test issue
|
|
12
|
+
|
|
13
|
+
Run with: uv run python tests/test_comment_management.py
|
|
14
|
+
|
|
15
|
+
Requires:
|
|
16
|
+
- GITHUB_TOKEN env var or .quickcall.env with GitHub PAT
|
|
17
|
+
- Access to quickcall-dev/quickcall-integrations repo
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_integration_test():
|
|
25
|
+
"""Run the full comment management integration test."""
|
|
26
|
+
from mcp_server.api_clients.github_client import GitHubClient
|
|
27
|
+
from mcp_server.auth import get_github_pat
|
|
28
|
+
|
|
29
|
+
print("=" * 60)
|
|
30
|
+
print("Issue Comment Management Integration Test")
|
|
31
|
+
print("=" * 60)
|
|
32
|
+
|
|
33
|
+
# Check for credentials
|
|
34
|
+
pat_token, source = get_github_pat()
|
|
35
|
+
if not pat_token:
|
|
36
|
+
print("\n❌ No GitHub PAT found!")
|
|
37
|
+
print(" Set GITHUB_TOKEN env var or create .quickcall.env")
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
print(f"\n✅ Using PAT from: {source}")
|
|
41
|
+
|
|
42
|
+
client = GitHubClient(token=pat_token)
|
|
43
|
+
username = client.get_authenticated_user()
|
|
44
|
+
print(f"✅ Authenticated as: {username}")
|
|
45
|
+
|
|
46
|
+
# Configuration for test
|
|
47
|
+
TEST_ORG = "quickcall-dev"
|
|
48
|
+
TEST_REPO = "quickcall-integrations"
|
|
49
|
+
TEST_ISSUE_TITLE = "[TEST] Comment Management Test - Safe to Delete"
|
|
50
|
+
|
|
51
|
+
created_issue_number = None
|
|
52
|
+
comment_ids = []
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
# Step 1: Create a test issue
|
|
56
|
+
print("\n--- Step 1: Create test issue ---")
|
|
57
|
+
issue = client.create_issue(
|
|
58
|
+
title=TEST_ISSUE_TITLE,
|
|
59
|
+
body="This is an automated test issue for comment management.\n\n"
|
|
60
|
+
"**This issue will be closed automatically after the test.**",
|
|
61
|
+
labels=["test"],
|
|
62
|
+
owner=TEST_ORG,
|
|
63
|
+
repo=TEST_REPO,
|
|
64
|
+
)
|
|
65
|
+
created_issue_number = issue["number"]
|
|
66
|
+
print(f"✅ Created issue #{created_issue_number}")
|
|
67
|
+
print(f" URL: {issue['html_url']}")
|
|
68
|
+
|
|
69
|
+
time.sleep(1)
|
|
70
|
+
|
|
71
|
+
# Step 2: Add multiple comments
|
|
72
|
+
print("\n--- Step 2: Add 3 comments ---")
|
|
73
|
+
for i in range(1, 4):
|
|
74
|
+
comment = client.comment_on_issue(
|
|
75
|
+
issue_number=created_issue_number,
|
|
76
|
+
body=f"Test comment #{i}",
|
|
77
|
+
owner=TEST_ORG,
|
|
78
|
+
repo=TEST_REPO,
|
|
79
|
+
)
|
|
80
|
+
comment_ids.append(comment["id"])
|
|
81
|
+
print(f"✅ Added comment #{i} (ID: {comment['id']})")
|
|
82
|
+
time.sleep(0.5)
|
|
83
|
+
|
|
84
|
+
# Step 3: List comments (ascending - oldest first)
|
|
85
|
+
print("\n--- Step 3: List comments (oldest first) ---")
|
|
86
|
+
comments_asc = client.list_issue_comments(
|
|
87
|
+
issue_number=created_issue_number,
|
|
88
|
+
owner=TEST_ORG,
|
|
89
|
+
repo=TEST_REPO,
|
|
90
|
+
limit=10,
|
|
91
|
+
order="asc",
|
|
92
|
+
)
|
|
93
|
+
print(f"✅ Found {len(comments_asc)} comments (asc)")
|
|
94
|
+
for c in comments_asc:
|
|
95
|
+
print(f" - {c['body'][:50]} (by {c['author']})")
|
|
96
|
+
|
|
97
|
+
# Step 4: List comments (descending - newest first)
|
|
98
|
+
print("\n--- Step 4: List comments (newest first) ---")
|
|
99
|
+
comments_desc = client.list_issue_comments(
|
|
100
|
+
issue_number=created_issue_number,
|
|
101
|
+
owner=TEST_ORG,
|
|
102
|
+
repo=TEST_REPO,
|
|
103
|
+
limit=10,
|
|
104
|
+
order="desc",
|
|
105
|
+
)
|
|
106
|
+
print(f"✅ Found {len(comments_desc)} comments (desc)")
|
|
107
|
+
for c in comments_desc:
|
|
108
|
+
print(f" - {c['body'][:50]} (by {c['author']})")
|
|
109
|
+
|
|
110
|
+
# Verify order is reversed
|
|
111
|
+
if comments_asc and comments_desc:
|
|
112
|
+
assert comments_asc[0]["id"] == comments_desc[-1]["id"], (
|
|
113
|
+
"Order should be reversed"
|
|
114
|
+
)
|
|
115
|
+
print("✅ Order verification passed")
|
|
116
|
+
|
|
117
|
+
# Step 5: List with limit
|
|
118
|
+
print("\n--- Step 5: List with limit=2 ---")
|
|
119
|
+
comments_limited = client.list_issue_comments(
|
|
120
|
+
issue_number=created_issue_number,
|
|
121
|
+
owner=TEST_ORG,
|
|
122
|
+
repo=TEST_REPO,
|
|
123
|
+
limit=2,
|
|
124
|
+
order="asc",
|
|
125
|
+
)
|
|
126
|
+
print(f"✅ Found {len(comments_limited)} comments (limited to 2)")
|
|
127
|
+
assert len(comments_limited) == 2, "Should return exactly 2 comments"
|
|
128
|
+
|
|
129
|
+
# Step 6: Update a comment
|
|
130
|
+
print("\n--- Step 6: Update comment ---")
|
|
131
|
+
comment_to_update = comment_ids[1] # Update the second comment
|
|
132
|
+
updated = client.update_issue_comment(
|
|
133
|
+
comment_id=comment_to_update,
|
|
134
|
+
body="Test comment #2 (UPDATED)",
|
|
135
|
+
owner=TEST_ORG,
|
|
136
|
+
repo=TEST_REPO,
|
|
137
|
+
)
|
|
138
|
+
print(f"✅ Updated comment ID {comment_to_update}")
|
|
139
|
+
print(f" New body: {updated['body']}")
|
|
140
|
+
assert "UPDATED" in updated["body"], "Comment should be updated"
|
|
141
|
+
|
|
142
|
+
# Step 7: Get specific comment
|
|
143
|
+
print("\n--- Step 7: Get specific comment ---")
|
|
144
|
+
fetched = client.get_issue_comment(
|
|
145
|
+
comment_id=comment_to_update,
|
|
146
|
+
owner=TEST_ORG,
|
|
147
|
+
repo=TEST_REPO,
|
|
148
|
+
)
|
|
149
|
+
print(f"✅ Fetched comment ID {fetched['id']}")
|
|
150
|
+
print(f" Body: {fetched['body']}")
|
|
151
|
+
|
|
152
|
+
# Step 8: Delete a comment
|
|
153
|
+
print("\n--- Step 8: Delete comment ---")
|
|
154
|
+
comment_to_delete = comment_ids[2] # Delete the third comment
|
|
155
|
+
result = client.delete_issue_comment(
|
|
156
|
+
comment_id=comment_to_delete,
|
|
157
|
+
owner=TEST_ORG,
|
|
158
|
+
repo=TEST_REPO,
|
|
159
|
+
)
|
|
160
|
+
print(f"✅ Deleted comment ID {comment_to_delete}")
|
|
161
|
+
assert result["deleted"] is True, "Should return deleted=True"
|
|
162
|
+
|
|
163
|
+
# Verify deletion
|
|
164
|
+
time.sleep(1)
|
|
165
|
+
comments_after = client.list_issue_comments(
|
|
166
|
+
issue_number=created_issue_number,
|
|
167
|
+
owner=TEST_ORG,
|
|
168
|
+
repo=TEST_REPO,
|
|
169
|
+
limit=10,
|
|
170
|
+
)
|
|
171
|
+
print(f"✅ Verified: now {len(comments_after)} comments (was 3)")
|
|
172
|
+
assert len(comments_after) == 2, "Should have 2 comments after deletion"
|
|
173
|
+
|
|
174
|
+
# Step 9: Cleanup - close issue
|
|
175
|
+
print("\n--- Step 9: Close test issue ---")
|
|
176
|
+
client.close_issue(created_issue_number, owner=TEST_ORG, repo=TEST_REPO)
|
|
177
|
+
print(f"✅ Closed issue #{created_issue_number}")
|
|
178
|
+
|
|
179
|
+
print("\n" + "=" * 60)
|
|
180
|
+
print("✅ ALL COMMENT MANAGEMENT TESTS PASSED!")
|
|
181
|
+
print("=" * 60)
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"\n❌ Test failed with error: {e}")
|
|
186
|
+
import traceback
|
|
187
|
+
|
|
188
|
+
traceback.print_exc()
|
|
189
|
+
|
|
190
|
+
# Cleanup
|
|
191
|
+
if created_issue_number:
|
|
192
|
+
try:
|
|
193
|
+
print(f"\n--- Cleanup: Closing issue #{created_issue_number} ---")
|
|
194
|
+
client.close_issue(created_issue_number, owner=TEST_ORG, repo=TEST_REPO)
|
|
195
|
+
print(f"✅ Closed issue #{created_issue_number}")
|
|
196
|
+
except Exception as cleanup_error:
|
|
197
|
+
print(f"⚠️ Cleanup failed: {cleanup_error}")
|
|
198
|
+
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_error_handling():
|
|
203
|
+
"""Test error handling for invalid comment operations."""
|
|
204
|
+
from mcp_server.api_clients.github_client import GitHubClient
|
|
205
|
+
from github import GithubException, UnknownObjectException
|
|
206
|
+
from mcp_server.auth import get_github_pat
|
|
207
|
+
|
|
208
|
+
print("\n" + "=" * 60)
|
|
209
|
+
print("Test: Error Handling")
|
|
210
|
+
print("=" * 60)
|
|
211
|
+
|
|
212
|
+
pat_token, _ = get_github_pat()
|
|
213
|
+
if not pat_token:
|
|
214
|
+
print("⚠️ Skipping - no PAT")
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
client = GitHubClient(token=pat_token)
|
|
218
|
+
|
|
219
|
+
# Test 1: Get non-existent comment
|
|
220
|
+
print("\nTest 1: Get non-existent comment")
|
|
221
|
+
try:
|
|
222
|
+
client.get_issue_comment(
|
|
223
|
+
comment_id=999999999,
|
|
224
|
+
owner="quickcall-dev",
|
|
225
|
+
repo="quickcall-integrations",
|
|
226
|
+
)
|
|
227
|
+
print("❌ Should have raised an exception")
|
|
228
|
+
return False
|
|
229
|
+
except (GithubException, UnknownObjectException):
|
|
230
|
+
print("✅ Correctly raised exception for non-existent comment")
|
|
231
|
+
|
|
232
|
+
# Test 2: Update non-existent comment
|
|
233
|
+
print("\nTest 2: Update non-existent comment")
|
|
234
|
+
try:
|
|
235
|
+
client.update_issue_comment(
|
|
236
|
+
comment_id=999999999,
|
|
237
|
+
body="test",
|
|
238
|
+
owner="quickcall-dev",
|
|
239
|
+
repo="quickcall-integrations",
|
|
240
|
+
)
|
|
241
|
+
print("❌ Should have raised an exception")
|
|
242
|
+
return False
|
|
243
|
+
except (GithubException, UnknownObjectException):
|
|
244
|
+
print("✅ Correctly raised exception for update")
|
|
245
|
+
|
|
246
|
+
print("\n✅ All error handling tests passed!")
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
results = []
|
|
252
|
+
|
|
253
|
+
# Run main integration test
|
|
254
|
+
results.append(("Full Comment Management Test", run_integration_test()))
|
|
255
|
+
|
|
256
|
+
# Run error handling test
|
|
257
|
+
results.append(("Error Handling", test_error_handling()))
|
|
258
|
+
|
|
259
|
+
# Summary
|
|
260
|
+
print("\n" + "=" * 60)
|
|
261
|
+
print("TEST SUMMARY")
|
|
262
|
+
print("=" * 60)
|
|
263
|
+
all_passed = True
|
|
264
|
+
for name, passed in results:
|
|
265
|
+
status = "✅ PASSED" if passed else "❌ FAILED"
|
|
266
|
+
print(f" {status}: {name}")
|
|
267
|
+
if not passed:
|
|
268
|
+
all_passed = False
|
|
269
|
+
|
|
270
|
+
print("=" * 60)
|
|
271
|
+
sys.exit(0 if all_passed else 1)
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.claude-plugin/marketplace.json
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/ISSUE_TEMPLATE/task.yml
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.github/workflows/publish-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/.quickcall-issue-template.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/api_clients/__init__.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/api_clients/slack_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/auth/credentials.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/auth/device_flow.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/auth_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/slack_tools.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/mcp_server/tools/utility_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/connect.md
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/projects.md
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/status.md
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/plugins/quickcall/commands/updates.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/appraisal/setup_test_data.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_appraisal_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.5.0 → quickcall_integrations-0.6.0}/tests/test_project_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|