quickcall-integrations 0.3.2__tar.gz → 0.3.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.
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/PKG-INFO +2 -1
- quickcall_integrations-0.3.4/mcp_server/__init__.py +12 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/api_clients/github_client.py +108 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/auth/credentials.py +3 -3
- quickcall_integrations-0.3.4/mcp_server/resources/github_resources.py +147 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/server.py +2 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/github_tools.py +248 -54
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/utility_tools.py +19 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/.claude-plugin/plugin.json +1 -1
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/appraisal.md +9 -12
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/pyproject.toml +2 -1
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/uv.lock +3 -1
- quickcall_integrations-0.3.2/mcp_server/__init__.py +0 -6
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.claude-plugin/marketplace.json +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.github/workflows/publish-pypi.yml +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.gitignore +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.pre-commit-config.yaml +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/Dockerfile +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/README.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/assets/logo.png +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/api_clients/__init__.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/api_clients/slack_client.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/auth/__init__.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/auth/device_flow.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/resources/__init__.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/resources/slack_resources.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/__init__.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/auth_tools.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/git_tools.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/slack_tools.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/connect-github-pat.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/connect.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/slack-summary.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/status.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/updates.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/requirements.txt +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/README.md +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/appraisal/__init__.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/appraisal/setup_test_data.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/test_appraisal_integration.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/test_appraisal_tools.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/test_integrations.py +0 -0
- {quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/test_tools.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickcall-integrations
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
7
7
|
Requires-Dist: httpx>=0.28.0
|
|
8
8
|
Requires-Dist: pydantic>=2.11.7
|
|
9
9
|
Requires-Dist: pygithub>=2.8.1
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
11
|
Requires-Dist: rapidfuzz>=3.0.0
|
|
11
12
|
Description-Content-Type: text/markdown
|
|
12
13
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Server for QuickCall
|
|
3
|
+
GitHub integration tools for AI assistant
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version("quickcall-integrations")
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
# Package not installed (development mode)
|
|
12
|
+
__version__ = "0.0.0-dev"
|
|
@@ -578,6 +578,114 @@ class GitHubClient:
|
|
|
578
578
|
|
|
579
579
|
return branches
|
|
580
580
|
|
|
581
|
+
# ========================================================================
|
|
582
|
+
# Issue Operations
|
|
583
|
+
# ========================================================================
|
|
584
|
+
|
|
585
|
+
def _issue_to_dict(self, issue) -> Dict[str, Any]:
|
|
586
|
+
"""Convert PyGithub Issue to dict."""
|
|
587
|
+
return {
|
|
588
|
+
"number": issue.number,
|
|
589
|
+
"title": issue.title,
|
|
590
|
+
"body": issue.body,
|
|
591
|
+
"state": issue.state,
|
|
592
|
+
"html_url": issue.html_url,
|
|
593
|
+
"labels": [label.name for label in issue.labels],
|
|
594
|
+
"assignees": [a.login for a in issue.assignees],
|
|
595
|
+
"created_at": issue.created_at.isoformat(),
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
def create_issue(
|
|
599
|
+
self,
|
|
600
|
+
title: str,
|
|
601
|
+
body: Optional[str] = None,
|
|
602
|
+
labels: Optional[List[str]] = None,
|
|
603
|
+
assignees: Optional[List[str]] = None,
|
|
604
|
+
owner: Optional[str] = None,
|
|
605
|
+
repo: Optional[str] = None,
|
|
606
|
+
) -> Dict[str, Any]:
|
|
607
|
+
"""Create a GitHub issue."""
|
|
608
|
+
gh_repo = self._get_repo(owner, repo)
|
|
609
|
+
issue = gh_repo.create_issue(
|
|
610
|
+
title=title,
|
|
611
|
+
body=body or "",
|
|
612
|
+
labels=labels or [],
|
|
613
|
+
assignees=assignees or [],
|
|
614
|
+
)
|
|
615
|
+
return self._issue_to_dict(issue)
|
|
616
|
+
|
|
617
|
+
def update_issue(
|
|
618
|
+
self,
|
|
619
|
+
issue_number: int,
|
|
620
|
+
title: Optional[str] = None,
|
|
621
|
+
body: Optional[str] = None,
|
|
622
|
+
labels: Optional[List[str]] = None,
|
|
623
|
+
assignees: Optional[List[str]] = None,
|
|
624
|
+
owner: Optional[str] = None,
|
|
625
|
+
repo: Optional[str] = None,
|
|
626
|
+
) -> Dict[str, Any]:
|
|
627
|
+
"""Update a GitHub issue."""
|
|
628
|
+
gh_repo = self._get_repo(owner, repo)
|
|
629
|
+
issue = gh_repo.get_issue(issue_number)
|
|
630
|
+
|
|
631
|
+
kwargs = {}
|
|
632
|
+
if title is not None:
|
|
633
|
+
kwargs["title"] = title
|
|
634
|
+
if body is not None:
|
|
635
|
+
kwargs["body"] = body
|
|
636
|
+
if labels is not None:
|
|
637
|
+
kwargs["labels"] = labels
|
|
638
|
+
if assignees is not None:
|
|
639
|
+
kwargs["assignees"] = assignees
|
|
640
|
+
|
|
641
|
+
if kwargs:
|
|
642
|
+
issue.edit(**kwargs)
|
|
643
|
+
|
|
644
|
+
return self._issue_to_dict(issue)
|
|
645
|
+
|
|
646
|
+
def close_issue(
|
|
647
|
+
self,
|
|
648
|
+
issue_number: int,
|
|
649
|
+
owner: Optional[str] = None,
|
|
650
|
+
repo: Optional[str] = None,
|
|
651
|
+
) -> Dict[str, Any]:
|
|
652
|
+
"""Close a GitHub issue."""
|
|
653
|
+
gh_repo = self._get_repo(owner, repo)
|
|
654
|
+
issue = gh_repo.get_issue(issue_number)
|
|
655
|
+
issue.edit(state="closed")
|
|
656
|
+
return self._issue_to_dict(issue)
|
|
657
|
+
|
|
658
|
+
def reopen_issue(
|
|
659
|
+
self,
|
|
660
|
+
issue_number: int,
|
|
661
|
+
owner: Optional[str] = None,
|
|
662
|
+
repo: Optional[str] = None,
|
|
663
|
+
) -> Dict[str, Any]:
|
|
664
|
+
"""Reopen a GitHub issue."""
|
|
665
|
+
gh_repo = self._get_repo(owner, repo)
|
|
666
|
+
issue = gh_repo.get_issue(issue_number)
|
|
667
|
+
issue.edit(state="open")
|
|
668
|
+
return self._issue_to_dict(issue)
|
|
669
|
+
|
|
670
|
+
def comment_on_issue(
|
|
671
|
+
self,
|
|
672
|
+
issue_number: int,
|
|
673
|
+
body: str,
|
|
674
|
+
owner: Optional[str] = None,
|
|
675
|
+
repo: Optional[str] = None,
|
|
676
|
+
) -> Dict[str, Any]:
|
|
677
|
+
"""Add a comment to a GitHub issue."""
|
|
678
|
+
gh_repo = self._get_repo(owner, repo)
|
|
679
|
+
issue = gh_repo.get_issue(issue_number)
|
|
680
|
+
comment = issue.create_comment(body)
|
|
681
|
+
return {
|
|
682
|
+
"id": comment.id,
|
|
683
|
+
"body": comment.body,
|
|
684
|
+
"html_url": comment.html_url,
|
|
685
|
+
"created_at": comment.created_at.isoformat(),
|
|
686
|
+
"issue_number": issue_number,
|
|
687
|
+
}
|
|
688
|
+
|
|
581
689
|
# ========================================================================
|
|
582
690
|
# Search Operations (for Appraisals)
|
|
583
691
|
# ========================================================================
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/auth/credentials.py
RENAMED
|
@@ -353,9 +353,9 @@ class CredentialStore:
|
|
|
353
353
|
api_creds = self.get_api_credentials(force_refresh=True)
|
|
354
354
|
result["github"] = {
|
|
355
355
|
"connected": api_creds.github_connected if api_creds else False,
|
|
356
|
-
"mode":
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
"mode": (
|
|
357
|
+
"github_app" if (api_creds and api_creds.github_connected) else None
|
|
358
|
+
),
|
|
359
359
|
"username": api_creds.github_username if api_creds else None,
|
|
360
360
|
}
|
|
361
361
|
result["slack"] = {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub MCP Resources - Exposes GitHub data for Claude's context.
|
|
3
|
+
|
|
4
|
+
Resources are automatically available in Claude's context when connected.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
from mcp_server.auth import get_credential_store, get_github_pat
|
|
14
|
+
from mcp_server.auth.credentials import _find_project_root, _parse_env_file
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_issue_templates_config() -> Optional[Dict[str, Any]]:
|
|
20
|
+
"""
|
|
21
|
+
Load issue templates from ISSUE_TEMPLATE_PATH in .quickcall.env.
|
|
22
|
+
Returns None if not configured or file doesn't exist.
|
|
23
|
+
"""
|
|
24
|
+
import os
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
template_path = os.getenv("ISSUE_TEMPLATE_PATH")
|
|
28
|
+
|
|
29
|
+
# Check .quickcall.env in project root
|
|
30
|
+
if not template_path:
|
|
31
|
+
project_root = _find_project_root()
|
|
32
|
+
if project_root:
|
|
33
|
+
config_path = project_root / ".quickcall.env"
|
|
34
|
+
if config_path.exists():
|
|
35
|
+
env_vars = _parse_env_file(config_path)
|
|
36
|
+
if "ISSUE_TEMPLATE_PATH" in env_vars:
|
|
37
|
+
template_path = env_vars["ISSUE_TEMPLATE_PATH"]
|
|
38
|
+
if not Path(template_path).is_absolute():
|
|
39
|
+
template_path = str(project_root / template_path)
|
|
40
|
+
|
|
41
|
+
if not template_path:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
with open(template_path) as f:
|
|
46
|
+
return yaml.safe_load(f) or {}
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.warning(f"Failed to load issue templates: {e}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def create_github_resources(mcp: FastMCP) -> None:
|
|
53
|
+
"""Add GitHub resources to the MCP server."""
|
|
54
|
+
|
|
55
|
+
@mcp.resource("github://repositories")
|
|
56
|
+
def get_github_repositories() -> str:
|
|
57
|
+
"""
|
|
58
|
+
List of GitHub repositories the user has access to.
|
|
59
|
+
|
|
60
|
+
Use these when working with GitHub operations.
|
|
61
|
+
"""
|
|
62
|
+
store = get_credential_store()
|
|
63
|
+
|
|
64
|
+
# Check if authenticated via PAT or QuickCall
|
|
65
|
+
pat_token, pat_source = get_github_pat()
|
|
66
|
+
has_pat = pat_token is not None
|
|
67
|
+
|
|
68
|
+
if not has_pat and not store.is_authenticated():
|
|
69
|
+
return "GitHub not connected. Options:\n- Run connect_github_via_pat with a Personal Access Token\n- Run connect_quickcall to use QuickCall"
|
|
70
|
+
|
|
71
|
+
# Check QuickCall GitHub App connection
|
|
72
|
+
has_app = False
|
|
73
|
+
if store.is_authenticated():
|
|
74
|
+
creds = store.get_api_credentials()
|
|
75
|
+
if creds and creds.github_connected and creds.github_token:
|
|
76
|
+
has_app = True
|
|
77
|
+
|
|
78
|
+
if not has_pat and not has_app:
|
|
79
|
+
return "GitHub not connected. Connect at quickcall.dev/assistant or use connect_github_via_pat."
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Import here to avoid circular imports
|
|
83
|
+
from mcp_server.tools.github_tools import _get_client
|
|
84
|
+
|
|
85
|
+
client = _get_client()
|
|
86
|
+
repos = client.list_repos(limit=50)
|
|
87
|
+
|
|
88
|
+
# Determine auth mode for display
|
|
89
|
+
auth_mode = "PAT" if has_pat else "GitHub App"
|
|
90
|
+
|
|
91
|
+
lines = [f"GitHub Repositories (via {auth_mode}):", ""]
|
|
92
|
+
for repo in repos:
|
|
93
|
+
visibility = "private" if repo.private else "public"
|
|
94
|
+
lines.append(f"- {repo.full_name} ({visibility})")
|
|
95
|
+
|
|
96
|
+
if len(repos) >= 50:
|
|
97
|
+
lines.append("")
|
|
98
|
+
lines.append("(Showing first 50 repos)")
|
|
99
|
+
|
|
100
|
+
return "\n".join(lines)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"Failed to fetch GitHub repositories: {e}")
|
|
103
|
+
return f"Error fetching repositories: {str(e)}"
|
|
104
|
+
|
|
105
|
+
@mcp.resource("github://issue-templates")
|
|
106
|
+
def get_issue_templates() -> str:
|
|
107
|
+
"""
|
|
108
|
+
Available issue templates from project configuration.
|
|
109
|
+
|
|
110
|
+
Use template names when creating issues with manage_issues.
|
|
111
|
+
"""
|
|
112
|
+
config = _load_issue_templates_config()
|
|
113
|
+
|
|
114
|
+
if not config:
|
|
115
|
+
return "No issue templates configured.\n\nTo configure:\n1. Create a YAML file with your templates\n2. Add ISSUE_TEMPLATE_PATH=/path/to/templates.yaml to .quickcall.env"
|
|
116
|
+
|
|
117
|
+
templates = config.get("templates", {})
|
|
118
|
+
defaults = config.get("defaults", {})
|
|
119
|
+
|
|
120
|
+
if not templates:
|
|
121
|
+
lines = ["Issue Templates:", ""]
|
|
122
|
+
if defaults:
|
|
123
|
+
labels = defaults.get("labels", [])
|
|
124
|
+
lines.append("Default template:")
|
|
125
|
+
if labels:
|
|
126
|
+
lines.append(f" Labels: {', '.join(labels)}")
|
|
127
|
+
if defaults.get("body"):
|
|
128
|
+
lines.append(f" Body template: {defaults['body'][:100]}...")
|
|
129
|
+
return "\n".join(lines)
|
|
130
|
+
|
|
131
|
+
lines = ["Available Issue Templates:", ""]
|
|
132
|
+
|
|
133
|
+
for name, template in templates.items():
|
|
134
|
+
labels = template.get("labels", [])
|
|
135
|
+
body_preview = template.get("body", "")[:80]
|
|
136
|
+
lines.append(f"- {name}")
|
|
137
|
+
if labels:
|
|
138
|
+
lines.append(f" Labels: {', '.join(labels)}")
|
|
139
|
+
if body_preview:
|
|
140
|
+
lines.append(f" Body: {body_preview}...")
|
|
141
|
+
|
|
142
|
+
lines.append("")
|
|
143
|
+
lines.append(
|
|
144
|
+
"Usage: manage_issues(action='create', title='...', template='<name>')"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return "\n".join(lines)
|
|
@@ -24,6 +24,7 @@ from mcp_server.tools.github_tools import create_github_tools
|
|
|
24
24
|
from mcp_server.tools.slack_tools import create_slack_tools
|
|
25
25
|
from mcp_server.tools.auth_tools import create_auth_tools
|
|
26
26
|
from mcp_server.resources.slack_resources import create_slack_resources
|
|
27
|
+
from mcp_server.resources.github_resources import create_github_resources
|
|
27
28
|
|
|
28
29
|
# Configure logging
|
|
29
30
|
logging.basicConfig(
|
|
@@ -57,6 +58,7 @@ def create_server() -> FastMCP:
|
|
|
57
58
|
|
|
58
59
|
# Register resources (available in Claude's context)
|
|
59
60
|
create_slack_resources(mcp)
|
|
61
|
+
create_github_resources(mcp)
|
|
60
62
|
|
|
61
63
|
# Log current status
|
|
62
64
|
if is_authenticated:
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/github_tools.py
RENAMED
|
@@ -2,95 +2,173 @@
|
|
|
2
2
|
GitHub Tools - Pull requests and commits via GitHub API.
|
|
3
3
|
|
|
4
4
|
Authentication (in priority order):
|
|
5
|
-
1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
5
|
+
1. Personal Access Token (PAT) - more permissions, user's default choice
|
|
6
|
+
- Set via connect_github_via_pat command
|
|
7
|
+
- Or GITHUB_TOKEN env var / .quickcall.env file
|
|
8
|
+
2. QuickCall GitHub App - fallback if no PAT configured
|
|
9
|
+
|
|
10
|
+
PAT is preferred because:
|
|
11
|
+
- Users have direct control over permissions
|
|
12
|
+
- Works with any repository the user has access to
|
|
13
|
+
- No GitHub App installation required
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
|
-
from typing import List, Optional, Tuple
|
|
15
16
|
import logging
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
16
20
|
|
|
21
|
+
import yaml
|
|
17
22
|
from fastmcp import FastMCP
|
|
18
23
|
from fastmcp.exceptions import ToolError
|
|
19
24
|
from pydantic import Field
|
|
20
25
|
|
|
26
|
+
from mcp_server.api_clients.github_client import GitHubClient
|
|
21
27
|
from mcp_server.auth import (
|
|
22
28
|
get_credential_store,
|
|
23
29
|
get_github_pat,
|
|
24
30
|
get_github_pat_username,
|
|
25
31
|
)
|
|
26
|
-
from mcp_server.
|
|
32
|
+
from mcp_server.auth.credentials import _find_project_root, _parse_env_file
|
|
27
33
|
|
|
28
34
|
logger = logging.getLogger(__name__)
|
|
29
35
|
|
|
30
36
|
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# Issue Template Support
|
|
39
|
+
# ============================================================================
|
|
40
|
+
|
|
41
|
+
DEFAULT_ISSUE_TEMPLATE: Dict[str, Any] = {
|
|
42
|
+
"labels": [],
|
|
43
|
+
"body": "## Description\n\n## Details\n",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_issue_template(template_type: Optional[str] = None) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Load issue template from ISSUE_TEMPLATE_PATH in .quickcall.env.
|
|
50
|
+
Returns defaults if not configured.
|
|
51
|
+
"""
|
|
52
|
+
template_path = os.getenv("ISSUE_TEMPLATE_PATH")
|
|
53
|
+
|
|
54
|
+
# Check .quickcall.env in project root
|
|
55
|
+
if not template_path:
|
|
56
|
+
project_root = _find_project_root()
|
|
57
|
+
if project_root:
|
|
58
|
+
config_path = project_root / ".quickcall.env"
|
|
59
|
+
if config_path.exists():
|
|
60
|
+
env_vars = _parse_env_file(config_path)
|
|
61
|
+
if "ISSUE_TEMPLATE_PATH" in env_vars:
|
|
62
|
+
template_path = env_vars["ISSUE_TEMPLATE_PATH"]
|
|
63
|
+
if not Path(template_path).is_absolute():
|
|
64
|
+
template_path = str(project_root / template_path)
|
|
65
|
+
|
|
66
|
+
if not template_path:
|
|
67
|
+
return DEFAULT_ISSUE_TEMPLATE
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
with open(template_path) as f:
|
|
71
|
+
config = yaml.safe_load(f) or {}
|
|
72
|
+
|
|
73
|
+
# If template_type specified, look for it in templates section
|
|
74
|
+
if template_type and "templates" in config:
|
|
75
|
+
return config["templates"].get(
|
|
76
|
+
template_type, config.get("defaults", DEFAULT_ISSUE_TEMPLATE)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return config.get("defaults", DEFAULT_ISSUE_TEMPLATE)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning(f"Failed to load issue template: {e}")
|
|
82
|
+
return DEFAULT_ISSUE_TEMPLATE
|
|
83
|
+
|
|
84
|
+
|
|
31
85
|
# Track whether we're using PAT mode for status reporting
|
|
32
86
|
_using_pat_mode: bool = False
|
|
33
87
|
_pat_source: Optional[str] = None
|
|
34
88
|
|
|
89
|
+
# Module-level client cache (keyed by token hash for security)
|
|
90
|
+
_client_cache: Optional[Tuple[int, GitHubClient]] = None
|
|
91
|
+
|
|
35
92
|
|
|
36
93
|
def _get_client() -> GitHubClient:
|
|
37
94
|
"""
|
|
38
95
|
Get the GitHub client using the best available authentication method.
|
|
39
96
|
|
|
40
97
|
Authentication priority:
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
98
|
+
1. Personal Access Token (PAT) - preferred, more permissions
|
|
99
|
+
2. QuickCall GitHub App - fallback if no PAT
|
|
100
|
+
|
|
101
|
+
Uses cached client if token hasn't changed.
|
|
43
102
|
|
|
44
103
|
Raises:
|
|
45
104
|
ToolError: If no authentication method is available
|
|
46
105
|
"""
|
|
47
|
-
global _using_pat_mode, _pat_source
|
|
106
|
+
global _using_pat_mode, _pat_source, _client_cache
|
|
48
107
|
|
|
49
108
|
store = get_credential_store()
|
|
50
109
|
|
|
51
|
-
# Try
|
|
110
|
+
# Try PAT first (preferred - user has more control)
|
|
111
|
+
pat_token, pat_source_str = get_github_pat()
|
|
112
|
+
if pat_token:
|
|
113
|
+
token_hash = hash(pat_token)
|
|
114
|
+
|
|
115
|
+
# Return cached client if token matches
|
|
116
|
+
if _client_cache and _client_cache[0] == token_hash:
|
|
117
|
+
return _client_cache[1]
|
|
118
|
+
|
|
119
|
+
_using_pat_mode = True
|
|
120
|
+
_pat_source = pat_source_str
|
|
121
|
+
pat_username = get_github_pat_username()
|
|
122
|
+
logger.info(f"Using GitHub PAT from {pat_source_str}")
|
|
123
|
+
|
|
124
|
+
client = GitHubClient(
|
|
125
|
+
token=pat_token,
|
|
126
|
+
default_owner=pat_username,
|
|
127
|
+
installation_id=None, # No installation ID for PAT
|
|
128
|
+
)
|
|
129
|
+
_client_cache = (token_hash, client)
|
|
130
|
+
return client
|
|
131
|
+
|
|
132
|
+
# Fall back to QuickCall GitHub App
|
|
52
133
|
if store.is_authenticated():
|
|
53
134
|
creds = store.get_api_credentials()
|
|
54
135
|
if creds and creds.github_connected and creds.github_token:
|
|
136
|
+
token_hash = hash(creds.github_token)
|
|
137
|
+
|
|
138
|
+
# Return cached client if token matches
|
|
139
|
+
if _client_cache and _client_cache[0] == token_hash:
|
|
140
|
+
return _client_cache[1]
|
|
141
|
+
|
|
55
142
|
_using_pat_mode = False
|
|
56
143
|
_pat_source = None
|
|
57
|
-
|
|
144
|
+
|
|
145
|
+
client = GitHubClient(
|
|
58
146
|
token=creds.github_token,
|
|
59
147
|
default_owner=creds.github_username,
|
|
60
148
|
installation_id=creds.github_installation_id,
|
|
61
149
|
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
pat_token, pat_source = get_github_pat()
|
|
65
|
-
if pat_token:
|
|
66
|
-
_using_pat_mode = True
|
|
67
|
-
_pat_source = pat_source
|
|
68
|
-
pat_username = get_github_pat_username()
|
|
69
|
-
logger.info(f"Using GitHub PAT from {pat_source}")
|
|
70
|
-
return GitHubClient(
|
|
71
|
-
token=pat_token,
|
|
72
|
-
default_owner=pat_username,
|
|
73
|
-
installation_id=None, # No installation ID for PAT
|
|
74
|
-
)
|
|
150
|
+
_client_cache = (token_hash, client)
|
|
151
|
+
return client
|
|
75
152
|
|
|
76
153
|
# No authentication available - provide helpful error message
|
|
77
154
|
_using_pat_mode = False
|
|
78
155
|
_pat_source = None
|
|
156
|
+
_client_cache = None
|
|
79
157
|
|
|
80
158
|
if store.is_authenticated():
|
|
81
159
|
# Connected to QuickCall but GitHub not connected
|
|
82
160
|
raise ToolError(
|
|
83
161
|
"GitHub not connected. Options:\n"
|
|
84
|
-
"1.
|
|
85
|
-
"2.
|
|
162
|
+
"1. Run connect_github_via_pat with your Personal Access Token (recommended)\n"
|
|
163
|
+
"2. Connect GitHub App at quickcall.dev/assistant\n"
|
|
86
164
|
"3. Set GITHUB_TOKEN environment variable"
|
|
87
165
|
)
|
|
88
166
|
else:
|
|
89
167
|
# Not connected to QuickCall at all
|
|
90
168
|
raise ToolError(
|
|
91
169
|
"GitHub authentication required. Options:\n"
|
|
92
|
-
"1. Run
|
|
93
|
-
"2. Run
|
|
170
|
+
"1. Run connect_github_via_pat with a Personal Access Token (recommended)\n"
|
|
171
|
+
"2. Run connect_quickcall to use QuickCall (GitHub App + Slack)\n"
|
|
94
172
|
"3. Set GITHUB_TOKEN environment variable\n\n"
|
|
95
173
|
"For PAT: Create token at https://github.com/settings/tokens\n"
|
|
96
174
|
"Required scopes: repo (private) or public_repo (public only)"
|
|
@@ -164,10 +242,8 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
164
242
|
List pull requests for a GitHub repository.
|
|
165
243
|
|
|
166
244
|
Returns PRs sorted by last updated.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Use detail_level='summary' (default) to avoid context overflow with large result sets.
|
|
170
|
-
Use get_pr(number) to get full details for specific PRs when needed.
|
|
245
|
+
Use detail_level='summary' (default) to avoid context overflow.
|
|
246
|
+
Use get_prs() to fetch full details for specific PRs.
|
|
171
247
|
"""
|
|
172
248
|
try:
|
|
173
249
|
client = _get_client()
|
|
@@ -286,10 +362,8 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
286
362
|
List commits for a GitHub repository.
|
|
287
363
|
|
|
288
364
|
Returns commits sorted by date (newest first).
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Use detail_level='summary' (default) to avoid context overflow with large result sets.
|
|
292
|
-
Use get_commit(sha) to get full details for specific commits when needed.
|
|
365
|
+
Use detail_level='summary' (default) to avoid context overflow.
|
|
366
|
+
Use get_commit(sha) for full details on a specific commit.
|
|
293
367
|
"""
|
|
294
368
|
try:
|
|
295
369
|
client = _get_client()
|
|
@@ -393,6 +467,128 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
393
467
|
except Exception as e:
|
|
394
468
|
raise ToolError(f"Failed to list branches: {str(e)}")
|
|
395
469
|
|
|
470
|
+
@mcp.tool(tags={"github", "issues"})
|
|
471
|
+
def manage_issues(
|
|
472
|
+
action: str = Field(
|
|
473
|
+
...,
|
|
474
|
+
description="Action: 'create', 'update', 'close', 'reopen', or 'comment'",
|
|
475
|
+
),
|
|
476
|
+
issue_numbers: Optional[List[int]] = Field(
|
|
477
|
+
default=None,
|
|
478
|
+
description="Issue number(s). Required for update/close/reopen/comment. Supports bulk operations.",
|
|
479
|
+
),
|
|
480
|
+
title: Optional[str] = Field(
|
|
481
|
+
default=None,
|
|
482
|
+
description="Issue title (for 'create' or 'update')",
|
|
483
|
+
),
|
|
484
|
+
body: Optional[str] = Field(
|
|
485
|
+
default=None,
|
|
486
|
+
description="Issue body (for 'create'/'update') or comment text (for 'comment')",
|
|
487
|
+
),
|
|
488
|
+
labels: Optional[List[str]] = Field(
|
|
489
|
+
default=None,
|
|
490
|
+
description="Labels (for 'create' or 'update')",
|
|
491
|
+
),
|
|
492
|
+
assignees: Optional[List[str]] = Field(
|
|
493
|
+
default=None,
|
|
494
|
+
description="GitHub usernames to assign",
|
|
495
|
+
),
|
|
496
|
+
template: Optional[str] = Field(
|
|
497
|
+
default=None,
|
|
498
|
+
description="Template name for 'create' (e.g., 'bug', 'feature')",
|
|
499
|
+
),
|
|
500
|
+
owner: Optional[str] = Field(
|
|
501
|
+
default=None,
|
|
502
|
+
description="Repository owner",
|
|
503
|
+
),
|
|
504
|
+
repo: Optional[str] = Field(
|
|
505
|
+
default=None,
|
|
506
|
+
description="Repository name. Required.",
|
|
507
|
+
),
|
|
508
|
+
) -> dict:
|
|
509
|
+
"""
|
|
510
|
+
Manage GitHub issues: create, update, close, reopen, or comment.
|
|
511
|
+
|
|
512
|
+
Supports bulk operations for close/reopen/comment via issue_numbers list.
|
|
513
|
+
|
|
514
|
+
Examples:
|
|
515
|
+
- create: manage_issues(action="create", title="Bug", template="bug")
|
|
516
|
+
- close multiple: manage_issues(action="close", issue_numbers=[1, 2, 3])
|
|
517
|
+
- comment: manage_issues(action="comment", issue_numbers=[42], body="Fixed!")
|
|
518
|
+
"""
|
|
519
|
+
try:
|
|
520
|
+
client = _get_client()
|
|
521
|
+
|
|
522
|
+
if action == "create":
|
|
523
|
+
if not title:
|
|
524
|
+
raise ToolError("'title' is required for 'create' action")
|
|
525
|
+
|
|
526
|
+
tpl = _load_issue_template(template)
|
|
527
|
+
final_body = body if body is not None else tpl.get("body", "")
|
|
528
|
+
final_labels = labels if labels is not None else tpl.get("labels", [])
|
|
529
|
+
|
|
530
|
+
issue = client.create_issue(
|
|
531
|
+
title=title,
|
|
532
|
+
body=final_body,
|
|
533
|
+
labels=final_labels,
|
|
534
|
+
assignees=assignees,
|
|
535
|
+
owner=owner,
|
|
536
|
+
repo=repo,
|
|
537
|
+
)
|
|
538
|
+
return {"action": "created", "issue": issue}
|
|
539
|
+
|
|
540
|
+
# All other actions require issue_numbers
|
|
541
|
+
if not issue_numbers:
|
|
542
|
+
raise ToolError(f"'issue_numbers' required for '{action}' action")
|
|
543
|
+
|
|
544
|
+
results = []
|
|
545
|
+
for issue_number in issue_numbers:
|
|
546
|
+
if action == "update":
|
|
547
|
+
client.update_issue(
|
|
548
|
+
issue_number=issue_number,
|
|
549
|
+
title=title,
|
|
550
|
+
body=body,
|
|
551
|
+
labels=labels,
|
|
552
|
+
assignees=assignees,
|
|
553
|
+
owner=owner,
|
|
554
|
+
repo=repo,
|
|
555
|
+
)
|
|
556
|
+
results.append({"number": issue_number, "status": "updated"})
|
|
557
|
+
|
|
558
|
+
elif action == "close":
|
|
559
|
+
client.close_issue(issue_number, owner=owner, repo=repo)
|
|
560
|
+
results.append({"number": issue_number, "status": "closed"})
|
|
561
|
+
|
|
562
|
+
elif action == "reopen":
|
|
563
|
+
client.reopen_issue(issue_number, owner=owner, repo=repo)
|
|
564
|
+
results.append({"number": issue_number, "status": "reopened"})
|
|
565
|
+
|
|
566
|
+
elif action == "comment":
|
|
567
|
+
if not body:
|
|
568
|
+
raise ToolError("'body' is required for 'comment' action")
|
|
569
|
+
comment = client.comment_on_issue(
|
|
570
|
+
issue_number, body=body, owner=owner, repo=repo
|
|
571
|
+
)
|
|
572
|
+
results.append(
|
|
573
|
+
{
|
|
574
|
+
"number": issue_number,
|
|
575
|
+
"status": "commented",
|
|
576
|
+
"comment_url": comment["html_url"],
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
else:
|
|
581
|
+
raise ToolError(f"Invalid action: {action}")
|
|
582
|
+
|
|
583
|
+
return {"action": action, "count": len(results), "results": results}
|
|
584
|
+
|
|
585
|
+
except ToolError:
|
|
586
|
+
raise
|
|
587
|
+
except ValueError as e:
|
|
588
|
+
raise ToolError(f"Repository not specified: {str(e)}")
|
|
589
|
+
except Exception as e:
|
|
590
|
+
raise ToolError(f"Failed to {action} issue(s): {str(e)}")
|
|
591
|
+
|
|
396
592
|
@mcp.tool(tags={"github", "prs", "appraisal"})
|
|
397
593
|
def prepare_appraisal_data(
|
|
398
594
|
author: Optional[str] = Field(
|
|
@@ -413,18 +609,17 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
413
609
|
),
|
|
414
610
|
) -> dict:
|
|
415
611
|
"""
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
USE THIS TOOL FOR APPRAISALS AND PERFORMANCE REVIEWS!
|
|
612
|
+
Fetch all merged PRs for appraisals/performance reviews.
|
|
419
613
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
4. Then use get_appraisal_pr_details(file_path, pr_numbers) for selected PRs
|
|
614
|
+
Returns:
|
|
615
|
+
- file_path: temp file with full PR data (additions, deletions, files)
|
|
616
|
+
- pr_titles: list of {number, title, repo} for Claude to review
|
|
617
|
+
- count: total PRs found
|
|
425
618
|
|
|
426
|
-
|
|
427
|
-
|
|
619
|
+
Workflow:
|
|
620
|
+
1. Call this tool → get file_path and pr_titles
|
|
621
|
+
2. Review pr_titles, pick significant PRs
|
|
622
|
+
3. Call get_appraisal_pr_details(file_path, [pr_numbers]) for full details
|
|
428
623
|
"""
|
|
429
624
|
import json
|
|
430
625
|
import tempfile
|
|
@@ -533,13 +728,12 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
533
728
|
),
|
|
534
729
|
) -> dict:
|
|
535
730
|
"""
|
|
536
|
-
|
|
731
|
+
Read full PR details from the appraisal data file.
|
|
537
732
|
|
|
538
|
-
|
|
539
|
-
|
|
733
|
+
Call this after prepare_appraisal_data with selected PR numbers.
|
|
734
|
+
Reads from the cached file - no API calls made.
|
|
540
735
|
|
|
541
|
-
|
|
542
|
-
that Claude has identified as important for the appraisal.
|
|
736
|
+
Returns: additions, deletions, files changed, body for selected PRs.
|
|
543
737
|
"""
|
|
544
738
|
import json
|
|
545
739
|
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/utility_tools.py
RENAMED
|
@@ -5,6 +5,7 @@ Provides datetime helpers useful for constructing queries:
|
|
|
5
5
|
- Get current datetime
|
|
6
6
|
- Calculate date ranges (e.g., "last 7 days")
|
|
7
7
|
- Add/subtract time from dates
|
|
8
|
+
- Get MCP server version
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from datetime import datetime, timezone, timedelta
|
|
@@ -13,6 +14,8 @@ from typing import Optional
|
|
|
13
14
|
from fastmcp import FastMCP
|
|
14
15
|
from pydantic import Field
|
|
15
16
|
|
|
17
|
+
from mcp_server import __version__
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def create_utility_tools(mcp: FastMCP) -> None:
|
|
18
21
|
"""
|
|
@@ -113,3 +116,19 @@ def create_utility_tools(mcp: FastMCP) -> None:
|
|
|
113
116
|
"base_date": base.isoformat().replace("+00:00", "Z"),
|
|
114
117
|
"offset": f"{days} days, {hours} hours",
|
|
115
118
|
}
|
|
119
|
+
|
|
120
|
+
@mcp.tool(tags={"utility", "version"})
|
|
121
|
+
def get_mcp_version() -> dict:
|
|
122
|
+
"""
|
|
123
|
+
Get the QuickCall MCP server version.
|
|
124
|
+
|
|
125
|
+
Returns the version from pyproject.toml (single source of truth).
|
|
126
|
+
Useful for debugging and verifying which version is running.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Version info including version string and package name
|
|
130
|
+
"""
|
|
131
|
+
return {
|
|
132
|
+
"package": "quickcall-integrations",
|
|
133
|
+
"version": __version__,
|
|
134
|
+
}
|
|
@@ -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.
|
|
4
|
+
"version": "0.6.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sagar Sarkale"
|
|
7
7
|
}
|
|
@@ -16,11 +16,10 @@ Parse `$ARGUMENTS` for time period:
|
|
|
16
16
|
|
|
17
17
|
## Instructions
|
|
18
18
|
|
|
19
|
-
**
|
|
20
|
-
1. `prepare_appraisal_data` - fetches and dumps all PR data to a temp file
|
|
21
|
-
2. `get_appraisal_pr_details` - reads specific PRs from that file (no API calls)
|
|
19
|
+
**Two-step flow (avoids context overflow):**
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
1. `prepare_appraisal_data` → fetches all PRs, dumps to file, returns titles + PR numbers
|
|
22
|
+
2. `get_appraisal_pr_details` → Claude picks which PRs to get details for, reads from file
|
|
24
23
|
|
|
25
24
|
---
|
|
26
25
|
|
|
@@ -28,9 +27,8 @@ Do NOT use `get_prs`, `list_prs`, or any other tools - they will overflow contex
|
|
|
28
27
|
|
|
29
28
|
**Option A - GitHub API (preferred):**
|
|
30
29
|
- Call `prepare_appraisal_data(days=X)` with the parsed time period
|
|
31
|
-
- This fetches ALL merged PRs with full stats in PARALLEL
|
|
32
|
-
-
|
|
33
|
-
- Returns: `file_path` + list of `pr_titles` (number, title, repo only)
|
|
30
|
+
- This fetches ALL merged PRs with full stats in PARALLEL and dumps to a temp file
|
|
31
|
+
- Returns: `file_path` + `pr_titles` (number, title, repo for each PR)
|
|
34
32
|
- Optional: pass `org` or `repo` parameter to filter
|
|
35
33
|
|
|
36
34
|
**Option B - Local Git (fallback):**
|
|
@@ -44,11 +42,10 @@ Do NOT use `get_prs`, `list_prs`, or any other tools - they will overflow contex
|
|
|
44
42
|
- **Bug fixes**: (fix:, bugfix:, hotfix:, resolve, patch)
|
|
45
43
|
- **Chores**: Maintenance work (docs:, test:, ci:, chore:, refactor:, bump)
|
|
46
44
|
|
|
47
|
-
3. **Get details for
|
|
48
|
-
-
|
|
49
|
-
- Call `get_appraisal_pr_details(file_path, [pr_numbers])`
|
|
50
|
-
-
|
|
51
|
-
- Returns full details: additions, deletions, files, body
|
|
45
|
+
3. **Get full details for selected PRs:**
|
|
46
|
+
- Based on the titles, pick the top 5-10 significant PRs worth highlighting
|
|
47
|
+
- Call `get_appraisal_pr_details(file_path, [pr_numbers])` with the selected PR numbers
|
|
48
|
+
- Returns: full details (additions, deletions, files, body) for those PRs only
|
|
52
49
|
|
|
53
50
|
4. **Calculate summary metrics:**
|
|
54
51
|
- Total PRs merged by category
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quickcall-integrations"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "MCP server with developer integrations for Claude Code and Cursor"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -8,6 +8,7 @@ dependencies = [
|
|
|
8
8
|
"fastmcp>=2.13.0",
|
|
9
9
|
"pydantic>=2.11.7",
|
|
10
10
|
"PyGithub>=2.8.1",
|
|
11
|
+
"PyYAML>=6.0",
|
|
11
12
|
"httpx>=0.28.0",
|
|
12
13
|
"rapidfuzz>=3.0.0",
|
|
13
14
|
]
|
|
@@ -1459,13 +1459,14 @@ wheels = [
|
|
|
1459
1459
|
|
|
1460
1460
|
[[package]]
|
|
1461
1461
|
name = "quickcall-integrations"
|
|
1462
|
-
version = "0.3.
|
|
1462
|
+
version = "0.3.4"
|
|
1463
1463
|
source = { editable = "." }
|
|
1464
1464
|
dependencies = [
|
|
1465
1465
|
{ name = "fastmcp" },
|
|
1466
1466
|
{ name = "httpx" },
|
|
1467
1467
|
{ name = "pydantic" },
|
|
1468
1468
|
{ name = "pygithub" },
|
|
1469
|
+
{ name = "pyyaml" },
|
|
1469
1470
|
{ name = "rapidfuzz" },
|
|
1470
1471
|
]
|
|
1471
1472
|
|
|
@@ -1484,6 +1485,7 @@ requires-dist = [
|
|
|
1484
1485
|
{ name = "httpx", specifier = ">=0.28.0" },
|
|
1485
1486
|
{ name = "pydantic", specifier = ">=2.11.7" },
|
|
1486
1487
|
{ name = "pygithub", specifier = ">=2.8.1" },
|
|
1488
|
+
{ name = "pyyaml", specifier = ">=6.0" },
|
|
1487
1489
|
{ name = "rapidfuzz", specifier = ">=3.0.0" },
|
|
1488
1490
|
]
|
|
1489
1491
|
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.claude-plugin/marketplace.json
RENAMED
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/.github/workflows/publish-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/api_clients/__init__.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/api_clients/slack_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/auth/device_flow.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/auth_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/mcp_server/tools/slack_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/connect.md
RENAMED
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/status.md
RENAMED
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/plugins/quickcall/commands/updates.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/appraisal/setup_test_data.py
RENAMED
|
File without changes
|
{quickcall_integrations-0.3.2 → quickcall_integrations-0.3.4}/tests/test_appraisal_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|