quickcall-integrations 0.3.6__py3-none-any.whl → 0.3.8__py3-none-any.whl
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.
- mcp_server/api_clients/github_client.py +299 -1
- mcp_server/tools/github_tools.py +170 -10
- {quickcall_integrations-0.3.6.dist-info → quickcall_integrations-0.3.8.dist-info}/METADATA +1 -1
- {quickcall_integrations-0.3.6.dist-info → quickcall_integrations-0.3.8.dist-info}/RECORD +6 -6
- {quickcall_integrations-0.3.6.dist-info → quickcall_integrations-0.3.8.dist-info}/WHEEL +0 -0
- {quickcall_integrations-0.3.6.dist-info → quickcall_integrations-0.3.8.dist-info}/entry_points.txt +0 -0
|
@@ -582,8 +582,16 @@ class GitHubClient:
|
|
|
582
582
|
# Issue Operations
|
|
583
583
|
# ========================================================================
|
|
584
584
|
|
|
585
|
-
def _issue_to_dict(self, issue) -> Dict[str, Any]:
|
|
585
|
+
def _issue_to_dict(self, issue, summary: bool = False) -> Dict[str, Any]:
|
|
586
586
|
"""Convert PyGithub Issue to dict."""
|
|
587
|
+
if summary:
|
|
588
|
+
return {
|
|
589
|
+
"number": issue.number,
|
|
590
|
+
"title": issue.title,
|
|
591
|
+
"state": issue.state,
|
|
592
|
+
"labels": [label.name for label in issue.labels],
|
|
593
|
+
"html_url": issue.html_url,
|
|
594
|
+
}
|
|
587
595
|
return {
|
|
588
596
|
"number": issue.number,
|
|
589
597
|
"title": issue.title,
|
|
@@ -595,6 +603,70 @@ class GitHubClient:
|
|
|
595
603
|
"created_at": issue.created_at.isoformat(),
|
|
596
604
|
}
|
|
597
605
|
|
|
606
|
+
def list_issues(
|
|
607
|
+
self,
|
|
608
|
+
owner: Optional[str] = None,
|
|
609
|
+
repo: Optional[str] = None,
|
|
610
|
+
state: str = "open",
|
|
611
|
+
labels: Optional[List[str]] = None,
|
|
612
|
+
assignee: Optional[str] = None,
|
|
613
|
+
creator: Optional[str] = None,
|
|
614
|
+
milestone: Optional[str] = None,
|
|
615
|
+
sort: str = "updated",
|
|
616
|
+
limit: int = 30,
|
|
617
|
+
) -> List[Dict[str, Any]]:
|
|
618
|
+
"""
|
|
619
|
+
List issues in a repository.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
owner: Repository owner
|
|
623
|
+
repo: Repository name
|
|
624
|
+
state: Issue state: 'open', 'closed', or 'all'
|
|
625
|
+
labels: Filter by labels
|
|
626
|
+
assignee: Filter by assignee username
|
|
627
|
+
creator: Filter by issue creator username
|
|
628
|
+
milestone: Filter by milestone (number, title, or '*' for any, 'none' for no milestone)
|
|
629
|
+
sort: Sort by 'created', 'updated', or 'comments'
|
|
630
|
+
limit: Maximum issues to return
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
List of issue summaries
|
|
634
|
+
"""
|
|
635
|
+
gh_repo = self._get_repo(owner, repo)
|
|
636
|
+
|
|
637
|
+
kwargs = {"state": state, "sort": sort, "direction": "desc"}
|
|
638
|
+
if labels:
|
|
639
|
+
kwargs["labels"] = labels
|
|
640
|
+
if assignee:
|
|
641
|
+
kwargs["assignee"] = assignee
|
|
642
|
+
if creator:
|
|
643
|
+
kwargs["creator"] = creator
|
|
644
|
+
if milestone:
|
|
645
|
+
# Handle milestone - can be number, '*', 'none', or title
|
|
646
|
+
if milestone == "*" or milestone == "none":
|
|
647
|
+
kwargs["milestone"] = milestone
|
|
648
|
+
elif milestone.isdigit():
|
|
649
|
+
kwargs["milestone"] = gh_repo.get_milestone(int(milestone))
|
|
650
|
+
else:
|
|
651
|
+
# Search by title
|
|
652
|
+
for ms in gh_repo.get_milestones(state="all"):
|
|
653
|
+
if ms.title.lower() == milestone.lower():
|
|
654
|
+
kwargs["milestone"] = ms
|
|
655
|
+
break
|
|
656
|
+
|
|
657
|
+
issues = []
|
|
658
|
+
count = 0
|
|
659
|
+
for issue in gh_repo.get_issues(**kwargs):
|
|
660
|
+
# Skip pull requests (GitHub API returns PRs in issues endpoint)
|
|
661
|
+
if issue.pull_request is not None:
|
|
662
|
+
continue
|
|
663
|
+
issues.append(self._issue_to_dict(issue, summary=True))
|
|
664
|
+
count += 1
|
|
665
|
+
if count >= limit:
|
|
666
|
+
break
|
|
667
|
+
|
|
668
|
+
return issues
|
|
669
|
+
|
|
598
670
|
def create_issue(
|
|
599
671
|
self,
|
|
600
672
|
title: str,
|
|
@@ -686,6 +758,232 @@ class GitHubClient:
|
|
|
686
758
|
"issue_number": issue_number,
|
|
687
759
|
}
|
|
688
760
|
|
|
761
|
+
def get_issue(
|
|
762
|
+
self,
|
|
763
|
+
issue_number: int,
|
|
764
|
+
owner: Optional[str] = None,
|
|
765
|
+
repo: Optional[str] = None,
|
|
766
|
+
include_sub_issues: bool = True,
|
|
767
|
+
) -> Dict[str, Any]:
|
|
768
|
+
"""
|
|
769
|
+
Get detailed information about a GitHub issue.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
issue_number: Issue number
|
|
773
|
+
owner: Repository owner
|
|
774
|
+
repo: Repository name
|
|
775
|
+
include_sub_issues: Whether to fetch sub-issues list
|
|
776
|
+
|
|
777
|
+
Returns:
|
|
778
|
+
Issue details including sub-issues if requested
|
|
779
|
+
"""
|
|
780
|
+
gh_repo = self._get_repo(owner, repo)
|
|
781
|
+
issue = gh_repo.get_issue(issue_number)
|
|
782
|
+
|
|
783
|
+
result = {
|
|
784
|
+
"number": issue.number,
|
|
785
|
+
"id": issue.id, # Internal ID needed for sub-issues API
|
|
786
|
+
"title": issue.title,
|
|
787
|
+
"body": issue.body,
|
|
788
|
+
"state": issue.state,
|
|
789
|
+
"html_url": issue.html_url,
|
|
790
|
+
"labels": [label.name for label in issue.labels],
|
|
791
|
+
"assignees": [a.login for a in issue.assignees],
|
|
792
|
+
"created_at": issue.created_at.isoformat(),
|
|
793
|
+
"updated_at": issue.updated_at.isoformat() if issue.updated_at else None,
|
|
794
|
+
"closed_at": issue.closed_at.isoformat() if issue.closed_at else None,
|
|
795
|
+
"comments_count": issue.comments,
|
|
796
|
+
"author": issue.user.login if issue.user else "unknown",
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
# Fetch sub-issues if requested
|
|
800
|
+
if include_sub_issues:
|
|
801
|
+
owner = owner or self.default_owner
|
|
802
|
+
repo_name = repo or self.default_repo
|
|
803
|
+
sub_issues = self.list_sub_issues(issue_number, owner=owner, repo=repo_name)
|
|
804
|
+
result["sub_issues"] = sub_issues
|
|
805
|
+
result["sub_issues_count"] = len(sub_issues)
|
|
806
|
+
|
|
807
|
+
return result
|
|
808
|
+
|
|
809
|
+
# ========================================================================
|
|
810
|
+
# Sub-Issue Operations (GitHub's native sub-issues feature)
|
|
811
|
+
# ========================================================================
|
|
812
|
+
|
|
813
|
+
def list_sub_issues(
|
|
814
|
+
self,
|
|
815
|
+
parent_issue_number: int,
|
|
816
|
+
owner: Optional[str] = None,
|
|
817
|
+
repo: Optional[str] = None,
|
|
818
|
+
) -> List[Dict[str, Any]]:
|
|
819
|
+
"""
|
|
820
|
+
List sub-issues of a parent issue.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
parent_issue_number: Parent issue number
|
|
824
|
+
owner: Repository owner
|
|
825
|
+
repo: Repository name
|
|
826
|
+
|
|
827
|
+
Returns:
|
|
828
|
+
List of sub-issue summaries
|
|
829
|
+
"""
|
|
830
|
+
owner = owner or self.default_owner
|
|
831
|
+
repo = repo or self.default_repo
|
|
832
|
+
|
|
833
|
+
if not owner or not repo:
|
|
834
|
+
raise ValueError("Repository owner and name must be specified")
|
|
835
|
+
|
|
836
|
+
try:
|
|
837
|
+
with httpx.Client() as client:
|
|
838
|
+
response = client.get(
|
|
839
|
+
f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues",
|
|
840
|
+
headers={
|
|
841
|
+
"Authorization": f"Bearer {self.token}",
|
|
842
|
+
"Accept": "application/vnd.github+json",
|
|
843
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
844
|
+
},
|
|
845
|
+
timeout=30.0,
|
|
846
|
+
)
|
|
847
|
+
response.raise_for_status()
|
|
848
|
+
data = response.json()
|
|
849
|
+
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
"number": item["number"],
|
|
853
|
+
"id": item["id"],
|
|
854
|
+
"title": item["title"],
|
|
855
|
+
"state": item["state"],
|
|
856
|
+
"html_url": item["html_url"],
|
|
857
|
+
}
|
|
858
|
+
for item in data
|
|
859
|
+
]
|
|
860
|
+
except httpx.HTTPStatusError as e:
|
|
861
|
+
if e.response.status_code == 404:
|
|
862
|
+
# No sub-issues or feature not enabled
|
|
863
|
+
return []
|
|
864
|
+
logger.error(f"Failed to list sub-issues: HTTP {e.response.status_code}")
|
|
865
|
+
raise GithubException(e.response.status_code, e.response.json())
|
|
866
|
+
except Exception as e:
|
|
867
|
+
logger.error(f"Failed to list sub-issues: {e}")
|
|
868
|
+
return []
|
|
869
|
+
|
|
870
|
+
def add_sub_issue(
|
|
871
|
+
self,
|
|
872
|
+
parent_issue_number: int,
|
|
873
|
+
child_issue_number: int,
|
|
874
|
+
owner: Optional[str] = None,
|
|
875
|
+
repo: Optional[str] = None,
|
|
876
|
+
) -> Dict[str, Any]:
|
|
877
|
+
"""
|
|
878
|
+
Add an existing issue as a sub-issue to a parent.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
parent_issue_number: Parent issue number
|
|
882
|
+
child_issue_number: Child issue number to add as sub-issue
|
|
883
|
+
owner: Repository owner
|
|
884
|
+
repo: Repository name
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
Result with parent and child info
|
|
888
|
+
"""
|
|
889
|
+
owner = owner or self.default_owner
|
|
890
|
+
repo = repo or self.default_repo
|
|
891
|
+
|
|
892
|
+
if not owner or not repo:
|
|
893
|
+
raise ValueError("Repository owner and name must be specified")
|
|
894
|
+
|
|
895
|
+
# First, get the child issue's internal ID (required by API)
|
|
896
|
+
gh_repo = self._get_repo(owner, repo)
|
|
897
|
+
child_issue = gh_repo.get_issue(child_issue_number)
|
|
898
|
+
child_id = child_issue.id
|
|
899
|
+
|
|
900
|
+
try:
|
|
901
|
+
with httpx.Client() as client:
|
|
902
|
+
response = client.post(
|
|
903
|
+
f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues",
|
|
904
|
+
headers={
|
|
905
|
+
"Authorization": f"Bearer {self.token}",
|
|
906
|
+
"Accept": "application/vnd.github+json",
|
|
907
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
908
|
+
},
|
|
909
|
+
json={"sub_issue_id": child_id},
|
|
910
|
+
timeout=30.0,
|
|
911
|
+
)
|
|
912
|
+
response.raise_for_status()
|
|
913
|
+
|
|
914
|
+
return {
|
|
915
|
+
"success": True,
|
|
916
|
+
"parent_issue": parent_issue_number,
|
|
917
|
+
"child_issue": child_issue_number,
|
|
918
|
+
"child_id": child_id,
|
|
919
|
+
}
|
|
920
|
+
except httpx.HTTPStatusError as e:
|
|
921
|
+
logger.error(f"Failed to add sub-issue: HTTP {e.response.status_code}")
|
|
922
|
+
error_data = e.response.json() if e.response.content else {}
|
|
923
|
+
raise GithubException(
|
|
924
|
+
e.response.status_code,
|
|
925
|
+
error_data,
|
|
926
|
+
message=f"Failed to add #{child_issue_number} as sub-issue of #{parent_issue_number}",
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
def remove_sub_issue(
|
|
930
|
+
self,
|
|
931
|
+
parent_issue_number: int,
|
|
932
|
+
child_issue_number: int,
|
|
933
|
+
owner: Optional[str] = None,
|
|
934
|
+
repo: Optional[str] = None,
|
|
935
|
+
) -> Dict[str, Any]:
|
|
936
|
+
"""
|
|
937
|
+
Remove a sub-issue from a parent.
|
|
938
|
+
|
|
939
|
+
Args:
|
|
940
|
+
parent_issue_number: Parent issue number
|
|
941
|
+
child_issue_number: Child issue number to remove
|
|
942
|
+
owner: Repository owner
|
|
943
|
+
repo: Repository name
|
|
944
|
+
|
|
945
|
+
Returns:
|
|
946
|
+
Result with parent and child info
|
|
947
|
+
"""
|
|
948
|
+
owner = owner or self.default_owner
|
|
949
|
+
repo = repo or self.default_repo
|
|
950
|
+
|
|
951
|
+
if not owner or not repo:
|
|
952
|
+
raise ValueError("Repository owner and name must be specified")
|
|
953
|
+
|
|
954
|
+
# Get the child issue's internal ID
|
|
955
|
+
gh_repo = self._get_repo(owner, repo)
|
|
956
|
+
child_issue = gh_repo.get_issue(child_issue_number)
|
|
957
|
+
child_id = child_issue.id
|
|
958
|
+
|
|
959
|
+
try:
|
|
960
|
+
with httpx.Client() as client:
|
|
961
|
+
response = client.delete(
|
|
962
|
+
f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues/{child_id}",
|
|
963
|
+
headers={
|
|
964
|
+
"Authorization": f"Bearer {self.token}",
|
|
965
|
+
"Accept": "application/vnd.github+json",
|
|
966
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
967
|
+
},
|
|
968
|
+
timeout=30.0,
|
|
969
|
+
)
|
|
970
|
+
response.raise_for_status()
|
|
971
|
+
|
|
972
|
+
return {
|
|
973
|
+
"success": True,
|
|
974
|
+
"parent_issue": parent_issue_number,
|
|
975
|
+
"child_issue": child_issue_number,
|
|
976
|
+
"removed": True,
|
|
977
|
+
}
|
|
978
|
+
except httpx.HTTPStatusError as e:
|
|
979
|
+
logger.error(f"Failed to remove sub-issue: HTTP {e.response.status_code}")
|
|
980
|
+
error_data = e.response.json() if e.response.content else {}
|
|
981
|
+
raise GithubException(
|
|
982
|
+
e.response.status_code,
|
|
983
|
+
error_data,
|
|
984
|
+
message=f"Failed to remove #{child_issue_number} from #{parent_issue_number}",
|
|
985
|
+
)
|
|
986
|
+
|
|
689
987
|
# ========================================================================
|
|
690
988
|
# Search Operations (for Appraisals)
|
|
691
989
|
# ========================================================================
|
mcp_server/tools/github_tools.py
CHANGED
|
@@ -570,11 +570,12 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
570
570
|
def manage_issues(
|
|
571
571
|
action: str = Field(
|
|
572
572
|
...,
|
|
573
|
-
description="Action: 'create', 'update', 'close', 'reopen',
|
|
573
|
+
description="Action: 'list', 'view', 'create', 'update', 'close', 'reopen', 'comment', "
|
|
574
|
+
"'add_sub_issue', 'remove_sub_issue', 'list_sub_issues'",
|
|
574
575
|
),
|
|
575
576
|
issue_numbers: Optional[List[int]] = Field(
|
|
576
577
|
default=None,
|
|
577
|
-
description="Issue number(s). Required for update/close/reopen/comment
|
|
578
|
+
description="Issue number(s). Required for view/update/close/reopen/comment/sub-issue ops.",
|
|
578
579
|
),
|
|
579
580
|
title: Optional[str] = Field(
|
|
580
581
|
default=None,
|
|
@@ -594,7 +595,12 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
594
595
|
),
|
|
595
596
|
template: Optional[str] = Field(
|
|
596
597
|
default=None,
|
|
597
|
-
description="Template name for 'create' (e.g., '
|
|
598
|
+
description="Template name for 'create' (e.g., 'bug_report', 'feature_request')",
|
|
599
|
+
),
|
|
600
|
+
parent_issue: Optional[int] = Field(
|
|
601
|
+
default=None,
|
|
602
|
+
description="Parent issue number. For 'create': attach new issue as sub-issue. "
|
|
603
|
+
"For 'add_sub_issue'/'remove_sub_issue'/'list_sub_issues': the parent issue.",
|
|
598
604
|
),
|
|
599
605
|
owner: Optional[str] = Field(
|
|
600
606
|
default=None,
|
|
@@ -604,20 +610,68 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
604
610
|
default=None,
|
|
605
611
|
description="Repository name. Required.",
|
|
606
612
|
),
|
|
613
|
+
state: Optional[str] = Field(
|
|
614
|
+
default="open",
|
|
615
|
+
description="Issue state filter for 'list': 'open', 'closed', or 'all' (default: 'open')",
|
|
616
|
+
),
|
|
617
|
+
creator: Optional[str] = Field(
|
|
618
|
+
default=None,
|
|
619
|
+
description="Filter by issue creator username (for 'list')",
|
|
620
|
+
),
|
|
621
|
+
milestone: Optional[str] = Field(
|
|
622
|
+
default=None,
|
|
623
|
+
description="Filter by milestone: number, title, '*' (any), or 'none' (for 'list')",
|
|
624
|
+
),
|
|
625
|
+
sort: Optional[str] = Field(
|
|
626
|
+
default="updated",
|
|
627
|
+
description="Sort by: 'created', 'updated', or 'comments' (for 'list', default: 'updated')",
|
|
628
|
+
),
|
|
629
|
+
limit: Optional[int] = Field(
|
|
630
|
+
default=30,
|
|
631
|
+
description="Maximum issues to return for 'list' action (default: 30)",
|
|
632
|
+
),
|
|
607
633
|
) -> dict:
|
|
608
634
|
"""
|
|
609
|
-
Manage GitHub issues: create, update, close, reopen,
|
|
635
|
+
Manage GitHub issues: list, view, create, update, close, reopen, comment, and sub-issues.
|
|
610
636
|
|
|
611
|
-
Supports bulk operations for close/reopen/comment via issue_numbers list.
|
|
637
|
+
Supports bulk operations for view/close/reopen/comment via issue_numbers list.
|
|
612
638
|
|
|
613
639
|
Examples:
|
|
614
|
-
-
|
|
640
|
+
- list: manage_issues(action="list", state="open", milestone="v1.0")
|
|
641
|
+
- list by creator: manage_issues(action="list", creator="username")
|
|
642
|
+
- view: manage_issues(action="view", issue_numbers=[42])
|
|
643
|
+
- create: manage_issues(action="create", title="Bug", template="bug_report")
|
|
644
|
+
- create as sub-issue: manage_issues(action="create", title="Task 1", parent_issue=42)
|
|
615
645
|
- close multiple: manage_issues(action="close", issue_numbers=[1, 2, 3])
|
|
616
646
|
- comment: manage_issues(action="comment", issue_numbers=[42], body="Fixed!")
|
|
647
|
+
- add sub-issues: manage_issues(action="add_sub_issue", issue_numbers=[43,44], parent_issue=42)
|
|
648
|
+
- remove sub-issue: manage_issues(action="remove_sub_issue", issue_numbers=[43], parent_issue=42)
|
|
649
|
+
- list sub-issues: manage_issues(action="list_sub_issues", parent_issue=42)
|
|
617
650
|
"""
|
|
618
651
|
try:
|
|
619
652
|
client = _get_client()
|
|
620
653
|
|
|
654
|
+
# === LIST ACTION ===
|
|
655
|
+
if action == "list":
|
|
656
|
+
issues = client.list_issues(
|
|
657
|
+
owner=owner,
|
|
658
|
+
repo=repo,
|
|
659
|
+
state=state or "open",
|
|
660
|
+
labels=labels,
|
|
661
|
+
assignee=assignees[0] if assignees else None,
|
|
662
|
+
creator=creator,
|
|
663
|
+
milestone=milestone,
|
|
664
|
+
sort=sort or "updated",
|
|
665
|
+
limit=limit or 30,
|
|
666
|
+
)
|
|
667
|
+
return {
|
|
668
|
+
"action": "list",
|
|
669
|
+
"state": state or "open",
|
|
670
|
+
"count": len(issues),
|
|
671
|
+
"issues": issues,
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
# === CREATE ACTION ===
|
|
621
675
|
if action == "create":
|
|
622
676
|
if not title:
|
|
623
677
|
raise ToolError("'title' is required for 'create' action")
|
|
@@ -625,24 +679,79 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
625
679
|
tpl = _load_issue_template(template)
|
|
626
680
|
final_body = body if body is not None else tpl.get("body", "")
|
|
627
681
|
final_labels = labels if labels is not None else tpl.get("labels", [])
|
|
682
|
+
final_assignees = (
|
|
683
|
+
assignees if assignees is not None else tpl.get("assignees", [])
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# Apply title prefix from template if present
|
|
687
|
+
title_prefix = tpl.get("title_prefix", "")
|
|
688
|
+
if title_prefix and not title.startswith(title_prefix):
|
|
689
|
+
title = f"{title_prefix}{title}"
|
|
628
690
|
|
|
629
691
|
issue = client.create_issue(
|
|
630
692
|
title=title,
|
|
631
693
|
body=final_body,
|
|
632
694
|
labels=final_labels,
|
|
633
|
-
assignees=
|
|
695
|
+
assignees=final_assignees,
|
|
634
696
|
owner=owner,
|
|
635
697
|
repo=repo,
|
|
636
698
|
)
|
|
637
|
-
return {"action": "created", "issue": issue}
|
|
638
699
|
|
|
639
|
-
|
|
700
|
+
result = {"action": "created", "issue": issue}
|
|
701
|
+
|
|
702
|
+
# If parent_issue specified, add as sub-issue
|
|
703
|
+
if parent_issue:
|
|
704
|
+
try:
|
|
705
|
+
sub_result = client.add_sub_issue(
|
|
706
|
+
parent_issue_number=parent_issue,
|
|
707
|
+
child_issue_number=issue["number"],
|
|
708
|
+
owner=owner,
|
|
709
|
+
repo=repo,
|
|
710
|
+
)
|
|
711
|
+
result["sub_issue_of"] = parent_issue
|
|
712
|
+
result["sub_issue_linked"] = sub_result.get("success", False)
|
|
713
|
+
except Exception as e:
|
|
714
|
+
result["sub_issue_error"] = str(e)
|
|
715
|
+
|
|
716
|
+
return result
|
|
717
|
+
|
|
718
|
+
# === LIST SUB-ISSUES ACTION ===
|
|
719
|
+
if action == "list_sub_issues":
|
|
720
|
+
if not parent_issue:
|
|
721
|
+
raise ToolError(
|
|
722
|
+
"'parent_issue' is required for 'list_sub_issues' action"
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
sub_issues = client.list_sub_issues(
|
|
726
|
+
parent_issue_number=parent_issue,
|
|
727
|
+
owner=owner,
|
|
728
|
+
repo=repo,
|
|
729
|
+
)
|
|
730
|
+
return {
|
|
731
|
+
"action": "list_sub_issues",
|
|
732
|
+
"parent_issue": parent_issue,
|
|
733
|
+
"count": len(sub_issues),
|
|
734
|
+
"sub_issues": sub_issues,
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
# === ALL OTHER ACTIONS REQUIRE issue_numbers ===
|
|
640
738
|
if not issue_numbers:
|
|
641
739
|
raise ToolError(f"'issue_numbers' required for '{action}' action")
|
|
642
740
|
|
|
643
741
|
results = []
|
|
644
742
|
for issue_number in issue_numbers:
|
|
645
|
-
|
|
743
|
+
# === VIEW ACTION ===
|
|
744
|
+
if action == "view":
|
|
745
|
+
issue_data = client.get_issue(
|
|
746
|
+
issue_number=issue_number,
|
|
747
|
+
owner=owner,
|
|
748
|
+
repo=repo,
|
|
749
|
+
include_sub_issues=True,
|
|
750
|
+
)
|
|
751
|
+
results.append(issue_data)
|
|
752
|
+
|
|
753
|
+
# === UPDATE ACTION ===
|
|
754
|
+
elif action == "update":
|
|
646
755
|
client.update_issue(
|
|
647
756
|
issue_number=issue_number,
|
|
648
757
|
title=title,
|
|
@@ -654,14 +763,17 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
654
763
|
)
|
|
655
764
|
results.append({"number": issue_number, "status": "updated"})
|
|
656
765
|
|
|
766
|
+
# === CLOSE ACTION ===
|
|
657
767
|
elif action == "close":
|
|
658
768
|
client.close_issue(issue_number, owner=owner, repo=repo)
|
|
659
769
|
results.append({"number": issue_number, "status": "closed"})
|
|
660
770
|
|
|
771
|
+
# === REOPEN ACTION ===
|
|
661
772
|
elif action == "reopen":
|
|
662
773
|
client.reopen_issue(issue_number, owner=owner, repo=repo)
|
|
663
774
|
results.append({"number": issue_number, "status": "reopened"})
|
|
664
775
|
|
|
776
|
+
# === COMMENT ACTION ===
|
|
665
777
|
elif action == "comment":
|
|
666
778
|
if not body:
|
|
667
779
|
raise ToolError("'body' is required for 'comment' action")
|
|
@@ -676,9 +788,57 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
676
788
|
}
|
|
677
789
|
)
|
|
678
790
|
|
|
791
|
+
# === ADD SUB-ISSUE ACTION ===
|
|
792
|
+
elif action == "add_sub_issue":
|
|
793
|
+
if not parent_issue:
|
|
794
|
+
raise ToolError(
|
|
795
|
+
"'parent_issue' is required for 'add_sub_issue' action"
|
|
796
|
+
)
|
|
797
|
+
sub_result = client.add_sub_issue(
|
|
798
|
+
parent_issue_number=parent_issue,
|
|
799
|
+
child_issue_number=issue_number,
|
|
800
|
+
owner=owner,
|
|
801
|
+
repo=repo,
|
|
802
|
+
)
|
|
803
|
+
results.append(
|
|
804
|
+
{
|
|
805
|
+
"number": issue_number,
|
|
806
|
+
"status": "added_as_sub_issue",
|
|
807
|
+
"parent_issue": parent_issue,
|
|
808
|
+
}
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# === REMOVE SUB-ISSUE ACTION ===
|
|
812
|
+
elif action == "remove_sub_issue":
|
|
813
|
+
if not parent_issue:
|
|
814
|
+
raise ToolError(
|
|
815
|
+
"'parent_issue' is required for 'remove_sub_issue' action"
|
|
816
|
+
)
|
|
817
|
+
client.remove_sub_issue(
|
|
818
|
+
parent_issue_number=parent_issue,
|
|
819
|
+
child_issue_number=issue_number,
|
|
820
|
+
owner=owner,
|
|
821
|
+
repo=repo,
|
|
822
|
+
)
|
|
823
|
+
results.append(
|
|
824
|
+
{
|
|
825
|
+
"number": issue_number,
|
|
826
|
+
"status": "removed_from_parent",
|
|
827
|
+
"parent_issue": parent_issue,
|
|
828
|
+
}
|
|
829
|
+
)
|
|
830
|
+
|
|
679
831
|
else:
|
|
680
832
|
raise ToolError(f"Invalid action: {action}")
|
|
681
833
|
|
|
834
|
+
# Return format depends on action
|
|
835
|
+
if action == "view":
|
|
836
|
+
return {
|
|
837
|
+
"action": "view",
|
|
838
|
+
"count": len(results),
|
|
839
|
+
"issues": results,
|
|
840
|
+
}
|
|
841
|
+
|
|
682
842
|
return {"action": action, "count": len(results), "results": results}
|
|
683
843
|
|
|
684
844
|
except ToolError:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
mcp_server/__init__.py,sha256=6KGzjSPyVB6vQh150DwBjINM_CsZNDhOzwSQFWpXz0U,301
|
|
2
2
|
mcp_server/server.py,sha256=kv5hh0J-M7yENUBBNI1bkq1y7MB0zn5R_-R1tib6_sk,3108
|
|
3
3
|
mcp_server/api_clients/__init__.py,sha256=kOG5_sxIVpAx_tvf1nq_P0QCkqojAVidRE-wenLS-Wc,207
|
|
4
|
-
mcp_server/api_clients/github_client.py,sha256=
|
|
4
|
+
mcp_server/api_clients/github_client.py,sha256=Mlh6BzMhZ05NqkX9A2O80eJIQXWuW4FnnN1DBM0WKC8,40135
|
|
5
5
|
mcp_server/api_clients/slack_client.py,sha256=w3rcGghttfYw8Ird2beNo2LEYLc3rCTbUKMH4X7QQuQ,16447
|
|
6
6
|
mcp_server/auth/__init__.py,sha256=D-JS0Qe7FkeJjYx92u_AqPx8ZRoB3dKMowzzJXlX6cc,780
|
|
7
7
|
mcp_server/auth/credentials.py,sha256=sDS0W5c16i_UGvhG8Sh1RO93FxRn-hHVAdI9hlWuhx0,20011
|
|
@@ -12,10 +12,10 @@ mcp_server/resources/slack_resources.py,sha256=b_CPxAicwkF3PsBXIat4QoLbDUHM2g_iP
|
|
|
12
12
|
mcp_server/tools/__init__.py,sha256=vIR2ujAaTXm2DgpTsVNz3brI4G34p-Jeg44Qe0uvWc0,405
|
|
13
13
|
mcp_server/tools/auth_tools.py,sha256=kCPjPC1jrVz0XaRAwPea-ue8ybjLLTxyILplBDJ9Mv4,24477
|
|
14
14
|
mcp_server/tools/git_tools.py,sha256=jyCTQR2eSzUFXMt0Y8x66758-VY8YCY14DDUJt7GY2U,13957
|
|
15
|
-
mcp_server/tools/github_tools.py,sha256=
|
|
15
|
+
mcp_server/tools/github_tools.py,sha256=ZBJzRhg1BSFdn9VS4qi0PYLCSGBXRfHviKzjR0LxO08,40058
|
|
16
16
|
mcp_server/tools/slack_tools.py,sha256=-HVE_x3Z1KMeYGi1xhyppEwz5ZF-I-ZD0-Up8yBeoYE,11796
|
|
17
17
|
mcp_server/tools/utility_tools.py,sha256=oxAXpdqtPeB5Ug5dvk54V504r-8v1AO4_px-sO6LFOw,3910
|
|
18
|
-
quickcall_integrations-0.3.
|
|
19
|
-
quickcall_integrations-0.3.
|
|
20
|
-
quickcall_integrations-0.3.
|
|
21
|
-
quickcall_integrations-0.3.
|
|
18
|
+
quickcall_integrations-0.3.8.dist-info/METADATA,sha256=d5t3CbdPI7v4PkgeoJ4x4i8KuB_RZ_GDZv2N6GhkghA,7070
|
|
19
|
+
quickcall_integrations-0.3.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
20
|
+
quickcall_integrations-0.3.8.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
|
|
21
|
+
quickcall_integrations-0.3.8.dist-info/RECORD,,
|
|
File without changes
|
{quickcall_integrations-0.3.6.dist-info → quickcall_integrations-0.3.8.dist-info}/entry_points.txt
RENAMED
|
File without changes
|