quickcall-integrations 0.3.6__tar.gz → 0.3.7__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 (43) hide show
  1. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/PKG-INFO +1 -1
  2. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/api_clients/github_client.py +226 -0
  3. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/github_tools.py +128 -10
  4. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/.claude-plugin/plugin.json +1 -1
  5. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/pyproject.toml +1 -1
  6. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/uv.lock +1 -1
  7. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/.claude-plugin/marketplace.json +0 -0
  8. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/.github/workflows/publish-pypi.yml +0 -0
  9. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/.gitignore +0 -0
  10. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/.pre-commit-config.yaml +0 -0
  11. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/.quickcall-issue-template.yaml +0 -0
  12. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/Dockerfile +0 -0
  13. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/README.md +0 -0
  14. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/assets/logo.png +0 -0
  15. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/__init__.py +0 -0
  16. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/api_clients/__init__.py +0 -0
  17. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/api_clients/slack_client.py +0 -0
  18. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/auth/__init__.py +0 -0
  19. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/auth/credentials.py +0 -0
  20. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/auth/device_flow.py +0 -0
  21. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/resources/__init__.py +0 -0
  22. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/resources/github_resources.py +0 -0
  23. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/resources/slack_resources.py +0 -0
  24. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/server.py +0 -0
  25. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/__init__.py +0 -0
  26. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/auth_tools.py +0 -0
  27. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/git_tools.py +0 -0
  28. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/slack_tools.py +0 -0
  29. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/mcp_server/tools/utility_tools.py +0 -0
  30. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/appraisal.md +0 -0
  31. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/connect-github-pat.md +0 -0
  32. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/connect.md +0 -0
  33. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/slack-summary.md +0 -0
  34. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/status.md +0 -0
  35. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/plugins/quickcall/commands/updates.md +0 -0
  36. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/requirements.txt +0 -0
  37. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/README.md +0 -0
  38. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/appraisal/__init__.py +0 -0
  39. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/appraisal/setup_test_data.py +0 -0
  40. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/test_appraisal_integration.py +0 -0
  41. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/test_appraisal_tools.py +0 -0
  42. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/test_integrations.py +0 -0
  43. {quickcall_integrations-0.3.6 → quickcall_integrations-0.3.7}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickcall-integrations
3
- Version: 0.3.6
3
+ Version: 0.3.7
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
@@ -686,6 +686,232 @@ class GitHubClient:
686
686
  "issue_number": issue_number,
687
687
  }
688
688
 
689
+ def get_issue(
690
+ self,
691
+ issue_number: int,
692
+ owner: Optional[str] = None,
693
+ repo: Optional[str] = None,
694
+ include_sub_issues: bool = True,
695
+ ) -> Dict[str, Any]:
696
+ """
697
+ Get detailed information about a GitHub issue.
698
+
699
+ Args:
700
+ issue_number: Issue number
701
+ owner: Repository owner
702
+ repo: Repository name
703
+ include_sub_issues: Whether to fetch sub-issues list
704
+
705
+ Returns:
706
+ Issue details including sub-issues if requested
707
+ """
708
+ gh_repo = self._get_repo(owner, repo)
709
+ issue = gh_repo.get_issue(issue_number)
710
+
711
+ result = {
712
+ "number": issue.number,
713
+ "id": issue.id, # Internal ID needed for sub-issues API
714
+ "title": issue.title,
715
+ "body": issue.body,
716
+ "state": issue.state,
717
+ "html_url": issue.html_url,
718
+ "labels": [label.name for label in issue.labels],
719
+ "assignees": [a.login for a in issue.assignees],
720
+ "created_at": issue.created_at.isoformat(),
721
+ "updated_at": issue.updated_at.isoformat() if issue.updated_at else None,
722
+ "closed_at": issue.closed_at.isoformat() if issue.closed_at else None,
723
+ "comments_count": issue.comments,
724
+ "author": issue.user.login if issue.user else "unknown",
725
+ }
726
+
727
+ # Fetch sub-issues if requested
728
+ if include_sub_issues:
729
+ owner = owner or self.default_owner
730
+ repo_name = repo or self.default_repo
731
+ sub_issues = self.list_sub_issues(issue_number, owner=owner, repo=repo_name)
732
+ result["sub_issues"] = sub_issues
733
+ result["sub_issues_count"] = len(sub_issues)
734
+
735
+ return result
736
+
737
+ # ========================================================================
738
+ # Sub-Issue Operations (GitHub's native sub-issues feature)
739
+ # ========================================================================
740
+
741
+ def list_sub_issues(
742
+ self,
743
+ parent_issue_number: int,
744
+ owner: Optional[str] = None,
745
+ repo: Optional[str] = None,
746
+ ) -> List[Dict[str, Any]]:
747
+ """
748
+ List sub-issues of a parent issue.
749
+
750
+ Args:
751
+ parent_issue_number: Parent issue number
752
+ owner: Repository owner
753
+ repo: Repository name
754
+
755
+ Returns:
756
+ List of sub-issue summaries
757
+ """
758
+ owner = owner or self.default_owner
759
+ repo = repo or self.default_repo
760
+
761
+ if not owner or not repo:
762
+ raise ValueError("Repository owner and name must be specified")
763
+
764
+ try:
765
+ with httpx.Client() as client:
766
+ response = client.get(
767
+ f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues",
768
+ headers={
769
+ "Authorization": f"Bearer {self.token}",
770
+ "Accept": "application/vnd.github+json",
771
+ "X-GitHub-Api-Version": "2022-11-28",
772
+ },
773
+ timeout=30.0,
774
+ )
775
+ response.raise_for_status()
776
+ data = response.json()
777
+
778
+ return [
779
+ {
780
+ "number": item["number"],
781
+ "id": item["id"],
782
+ "title": item["title"],
783
+ "state": item["state"],
784
+ "html_url": item["html_url"],
785
+ }
786
+ for item in data
787
+ ]
788
+ except httpx.HTTPStatusError as e:
789
+ if e.response.status_code == 404:
790
+ # No sub-issues or feature not enabled
791
+ return []
792
+ logger.error(f"Failed to list sub-issues: HTTP {e.response.status_code}")
793
+ raise GithubException(e.response.status_code, e.response.json())
794
+ except Exception as e:
795
+ logger.error(f"Failed to list sub-issues: {e}")
796
+ return []
797
+
798
+ def add_sub_issue(
799
+ self,
800
+ parent_issue_number: int,
801
+ child_issue_number: int,
802
+ owner: Optional[str] = None,
803
+ repo: Optional[str] = None,
804
+ ) -> Dict[str, Any]:
805
+ """
806
+ Add an existing issue as a sub-issue to a parent.
807
+
808
+ Args:
809
+ parent_issue_number: Parent issue number
810
+ child_issue_number: Child issue number to add as sub-issue
811
+ owner: Repository owner
812
+ repo: Repository name
813
+
814
+ Returns:
815
+ Result with parent and child info
816
+ """
817
+ owner = owner or self.default_owner
818
+ repo = repo or self.default_repo
819
+
820
+ if not owner or not repo:
821
+ raise ValueError("Repository owner and name must be specified")
822
+
823
+ # First, get the child issue's internal ID (required by API)
824
+ gh_repo = self._get_repo(owner, repo)
825
+ child_issue = gh_repo.get_issue(child_issue_number)
826
+ child_id = child_issue.id
827
+
828
+ try:
829
+ with httpx.Client() as client:
830
+ response = client.post(
831
+ f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues",
832
+ headers={
833
+ "Authorization": f"Bearer {self.token}",
834
+ "Accept": "application/vnd.github+json",
835
+ "X-GitHub-Api-Version": "2022-11-28",
836
+ },
837
+ json={"sub_issue_id": child_id},
838
+ timeout=30.0,
839
+ )
840
+ response.raise_for_status()
841
+
842
+ return {
843
+ "success": True,
844
+ "parent_issue": parent_issue_number,
845
+ "child_issue": child_issue_number,
846
+ "child_id": child_id,
847
+ }
848
+ except httpx.HTTPStatusError as e:
849
+ logger.error(f"Failed to add sub-issue: HTTP {e.response.status_code}")
850
+ error_data = e.response.json() if e.response.content else {}
851
+ raise GithubException(
852
+ e.response.status_code,
853
+ error_data,
854
+ message=f"Failed to add #{child_issue_number} as sub-issue of #{parent_issue_number}",
855
+ )
856
+
857
+ def remove_sub_issue(
858
+ self,
859
+ parent_issue_number: int,
860
+ child_issue_number: int,
861
+ owner: Optional[str] = None,
862
+ repo: Optional[str] = None,
863
+ ) -> Dict[str, Any]:
864
+ """
865
+ Remove a sub-issue from a parent.
866
+
867
+ Args:
868
+ parent_issue_number: Parent issue number
869
+ child_issue_number: Child issue number to remove
870
+ owner: Repository owner
871
+ repo: Repository name
872
+
873
+ Returns:
874
+ Result with parent and child info
875
+ """
876
+ owner = owner or self.default_owner
877
+ repo = repo or self.default_repo
878
+
879
+ if not owner or not repo:
880
+ raise ValueError("Repository owner and name must be specified")
881
+
882
+ # Get the child issue's internal ID
883
+ gh_repo = self._get_repo(owner, repo)
884
+ child_issue = gh_repo.get_issue(child_issue_number)
885
+ child_id = child_issue.id
886
+
887
+ try:
888
+ with httpx.Client() as client:
889
+ response = client.delete(
890
+ f"https://api.github.com/repos/{owner}/{repo}/issues/{parent_issue_number}/sub_issues/{child_id}",
891
+ headers={
892
+ "Authorization": f"Bearer {self.token}",
893
+ "Accept": "application/vnd.github+json",
894
+ "X-GitHub-Api-Version": "2022-11-28",
895
+ },
896
+ timeout=30.0,
897
+ )
898
+ response.raise_for_status()
899
+
900
+ return {
901
+ "success": True,
902
+ "parent_issue": parent_issue_number,
903
+ "child_issue": child_issue_number,
904
+ "removed": True,
905
+ }
906
+ except httpx.HTTPStatusError as e:
907
+ logger.error(f"Failed to remove sub-issue: HTTP {e.response.status_code}")
908
+ error_data = e.response.json() if e.response.content else {}
909
+ raise GithubException(
910
+ e.response.status_code,
911
+ error_data,
912
+ message=f"Failed to remove #{child_issue_number} from #{parent_issue_number}",
913
+ )
914
+
689
915
  # ========================================================================
690
916
  # Search Operations (for Appraisals)
691
917
  # ========================================================================
@@ -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', or 'comment'",
573
+ description="Action: '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. Supports bulk operations.",
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., 'bug', 'feature')",
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,
@@ -606,18 +612,24 @@ def create_github_tools(mcp: FastMCP) -> None:
606
612
  ),
607
613
  ) -> dict:
608
614
  """
609
- Manage GitHub issues: create, update, close, reopen, or comment.
615
+ Manage GitHub issues: view, create, update, close, reopen, comment, and sub-issues.
610
616
 
611
- Supports bulk operations for close/reopen/comment via issue_numbers list.
617
+ Supports bulk operations for view/close/reopen/comment via issue_numbers list.
612
618
 
613
619
  Examples:
614
- - create: manage_issues(action="create", title="Bug", template="bug")
620
+ - view: manage_issues(action="view", issue_numbers=[42])
621
+ - create: manage_issues(action="create", title="Bug", template="bug_report")
622
+ - create as sub-issue: manage_issues(action="create", title="Task 1", parent_issue=42)
615
623
  - close multiple: manage_issues(action="close", issue_numbers=[1, 2, 3])
616
624
  - comment: manage_issues(action="comment", issue_numbers=[42], body="Fixed!")
625
+ - add sub-issues: manage_issues(action="add_sub_issue", issue_numbers=[43,44], parent_issue=42)
626
+ - remove sub-issue: manage_issues(action="remove_sub_issue", issue_numbers=[43], parent_issue=42)
627
+ - list sub-issues: manage_issues(action="list_sub_issues", parent_issue=42)
617
628
  """
618
629
  try:
619
630
  client = _get_client()
620
631
 
632
+ # === CREATE ACTION ===
621
633
  if action == "create":
622
634
  if not title:
623
635
  raise ToolError("'title' is required for 'create' action")
@@ -625,24 +637,79 @@ def create_github_tools(mcp: FastMCP) -> None:
625
637
  tpl = _load_issue_template(template)
626
638
  final_body = body if body is not None else tpl.get("body", "")
627
639
  final_labels = labels if labels is not None else tpl.get("labels", [])
640
+ final_assignees = (
641
+ assignees if assignees is not None else tpl.get("assignees", [])
642
+ )
643
+
644
+ # Apply title prefix from template if present
645
+ title_prefix = tpl.get("title_prefix", "")
646
+ if title_prefix and not title.startswith(title_prefix):
647
+ title = f"{title_prefix}{title}"
628
648
 
629
649
  issue = client.create_issue(
630
650
  title=title,
631
651
  body=final_body,
632
652
  labels=final_labels,
633
- assignees=assignees,
653
+ assignees=final_assignees,
654
+ owner=owner,
655
+ repo=repo,
656
+ )
657
+
658
+ result = {"action": "created", "issue": issue}
659
+
660
+ # If parent_issue specified, add as sub-issue
661
+ if parent_issue:
662
+ try:
663
+ sub_result = client.add_sub_issue(
664
+ parent_issue_number=parent_issue,
665
+ child_issue_number=issue["number"],
666
+ owner=owner,
667
+ repo=repo,
668
+ )
669
+ result["sub_issue_of"] = parent_issue
670
+ result["sub_issue_linked"] = sub_result.get("success", False)
671
+ except Exception as e:
672
+ result["sub_issue_error"] = str(e)
673
+
674
+ return result
675
+
676
+ # === LIST SUB-ISSUES ACTION ===
677
+ if action == "list_sub_issues":
678
+ if not parent_issue:
679
+ raise ToolError(
680
+ "'parent_issue' is required for 'list_sub_issues' action"
681
+ )
682
+
683
+ sub_issues = client.list_sub_issues(
684
+ parent_issue_number=parent_issue,
634
685
  owner=owner,
635
686
  repo=repo,
636
687
  )
637
- return {"action": "created", "issue": issue}
688
+ return {
689
+ "action": "list_sub_issues",
690
+ "parent_issue": parent_issue,
691
+ "count": len(sub_issues),
692
+ "sub_issues": sub_issues,
693
+ }
638
694
 
639
- # All other actions require issue_numbers
695
+ # === ALL OTHER ACTIONS REQUIRE issue_numbers ===
640
696
  if not issue_numbers:
641
697
  raise ToolError(f"'issue_numbers' required for '{action}' action")
642
698
 
643
699
  results = []
644
700
  for issue_number in issue_numbers:
645
- if action == "update":
701
+ # === VIEW ACTION ===
702
+ if action == "view":
703
+ issue_data = client.get_issue(
704
+ issue_number=issue_number,
705
+ owner=owner,
706
+ repo=repo,
707
+ include_sub_issues=True,
708
+ )
709
+ results.append(issue_data)
710
+
711
+ # === UPDATE ACTION ===
712
+ elif action == "update":
646
713
  client.update_issue(
647
714
  issue_number=issue_number,
648
715
  title=title,
@@ -654,14 +721,17 @@ def create_github_tools(mcp: FastMCP) -> None:
654
721
  )
655
722
  results.append({"number": issue_number, "status": "updated"})
656
723
 
724
+ # === CLOSE ACTION ===
657
725
  elif action == "close":
658
726
  client.close_issue(issue_number, owner=owner, repo=repo)
659
727
  results.append({"number": issue_number, "status": "closed"})
660
728
 
729
+ # === REOPEN ACTION ===
661
730
  elif action == "reopen":
662
731
  client.reopen_issue(issue_number, owner=owner, repo=repo)
663
732
  results.append({"number": issue_number, "status": "reopened"})
664
733
 
734
+ # === COMMENT ACTION ===
665
735
  elif action == "comment":
666
736
  if not body:
667
737
  raise ToolError("'body' is required for 'comment' action")
@@ -676,9 +746,57 @@ def create_github_tools(mcp: FastMCP) -> None:
676
746
  }
677
747
  )
678
748
 
749
+ # === ADD SUB-ISSUE ACTION ===
750
+ elif action == "add_sub_issue":
751
+ if not parent_issue:
752
+ raise ToolError(
753
+ "'parent_issue' is required for 'add_sub_issue' action"
754
+ )
755
+ sub_result = client.add_sub_issue(
756
+ parent_issue_number=parent_issue,
757
+ child_issue_number=issue_number,
758
+ owner=owner,
759
+ repo=repo,
760
+ )
761
+ results.append(
762
+ {
763
+ "number": issue_number,
764
+ "status": "added_as_sub_issue",
765
+ "parent_issue": parent_issue,
766
+ }
767
+ )
768
+
769
+ # === REMOVE SUB-ISSUE ACTION ===
770
+ elif action == "remove_sub_issue":
771
+ if not parent_issue:
772
+ raise ToolError(
773
+ "'parent_issue' is required for 'remove_sub_issue' action"
774
+ )
775
+ client.remove_sub_issue(
776
+ parent_issue_number=parent_issue,
777
+ child_issue_number=issue_number,
778
+ owner=owner,
779
+ repo=repo,
780
+ )
781
+ results.append(
782
+ {
783
+ "number": issue_number,
784
+ "status": "removed_from_parent",
785
+ "parent_issue": parent_issue,
786
+ }
787
+ )
788
+
679
789
  else:
680
790
  raise ToolError(f"Invalid action: {action}")
681
791
 
792
+ # Return format depends on action
793
+ if action == "view":
794
+ return {
795
+ "action": "view",
796
+ "count": len(results),
797
+ "issues": results,
798
+ }
799
+
682
800
  return {"action": action, "count": len(results), "results": results}
683
801
 
684
802
  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.6.5",
4
+ "version": "0.6.6",
5
5
  "author": {
6
6
  "name": "Sagar Sarkale"
7
7
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quickcall-integrations"
3
- version = "0.3.6"
3
+ version = "0.3.7"
4
4
  description = "MCP server with developer integrations for Claude Code and Cursor"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1459,7 +1459,7 @@ wheels = [
1459
1459
 
1460
1460
  [[package]]
1461
1461
  name = "quickcall-integrations"
1462
- version = "0.3.5"
1462
+ version = "0.3.6"
1463
1463
  source = { editable = "." }
1464
1464
  dependencies = [
1465
1465
  { name = "fastmcp" },