quickcall-integrations 0.3.8__py3-none-any.whl → 0.4.0__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 +903 -0
- mcp_server/resources/github_resources.py +124 -0
- mcp_server/tools/auth_tools.py +30 -13
- mcp_server/tools/github_tools.py +238 -2
- {quickcall_integrations-0.3.8.dist-info → quickcall_integrations-0.4.0.dist-info}/METADATA +105 -6
- {quickcall_integrations-0.3.8.dist-info → quickcall_integrations-0.4.0.dist-info}/RECORD +8 -8
- {quickcall_integrations-0.3.8.dist-info → quickcall_integrations-0.4.0.dist-info}/WHEEL +0 -0
- {quickcall_integrations-0.3.8.dist-info → quickcall_integrations-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -114,3 +114,127 @@ def create_github_resources(mcp: FastMCP) -> None:
|
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
return "\n".join(lines)
|
|
117
|
+
|
|
118
|
+
@mcp.resource("github://projects")
|
|
119
|
+
def get_github_projects() -> str:
|
|
120
|
+
"""
|
|
121
|
+
List of GitHub Projects V2 with their fields and options.
|
|
122
|
+
|
|
123
|
+
Use these for project management operations like updating issue status.
|
|
124
|
+
"""
|
|
125
|
+
store = get_credential_store()
|
|
126
|
+
|
|
127
|
+
# Check if authenticated via PAT or QuickCall
|
|
128
|
+
pat_token, pat_source = get_github_pat()
|
|
129
|
+
has_pat = pat_token is not None
|
|
130
|
+
|
|
131
|
+
if not has_pat and not store.is_authenticated():
|
|
132
|
+
return "GitHub not connected. Options:\n- Run connect_github_via_pat with a Personal Access Token\n- Run connect_quickcall to use QuickCall"
|
|
133
|
+
|
|
134
|
+
# Check QuickCall GitHub App connection
|
|
135
|
+
has_app = False
|
|
136
|
+
if store.is_authenticated():
|
|
137
|
+
creds = store.get_api_credentials()
|
|
138
|
+
if creds and creds.github_connected and creds.github_token:
|
|
139
|
+
has_app = True
|
|
140
|
+
|
|
141
|
+
if not has_pat and not has_app:
|
|
142
|
+
return "GitHub not connected. Connect at quickcall.dev/assistant or use connect_github_via_pat."
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
# Import here to avoid circular imports
|
|
146
|
+
from mcp_server.tools.github_tools import _get_client
|
|
147
|
+
|
|
148
|
+
client = _get_client()
|
|
149
|
+
|
|
150
|
+
# Determine auth mode for display
|
|
151
|
+
auth_mode = "PAT" if has_pat else "GitHub App"
|
|
152
|
+
|
|
153
|
+
# Get the authenticated user
|
|
154
|
+
username = client.get_authenticated_user()
|
|
155
|
+
|
|
156
|
+
# Collect all projects from user and their orgs
|
|
157
|
+
all_projects = []
|
|
158
|
+
|
|
159
|
+
# 1. Try user projects first
|
|
160
|
+
try:
|
|
161
|
+
user_projects = client.list_projects_with_fields(
|
|
162
|
+
owner=username, is_org=False, limit=100
|
|
163
|
+
)
|
|
164
|
+
all_projects.extend(user_projects)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
# 2. Get unique orgs from repos the user has access to
|
|
169
|
+
try:
|
|
170
|
+
repos = client.list_repos(limit=100)
|
|
171
|
+
org_counts: dict = {}
|
|
172
|
+
for repo in repos:
|
|
173
|
+
if repo.owner != username:
|
|
174
|
+
org_counts[repo.owner] = org_counts.get(repo.owner, 0) + 1
|
|
175
|
+
|
|
176
|
+
# Sort orgs by repo count (most repos first)
|
|
177
|
+
sorted_orgs = sorted(
|
|
178
|
+
org_counts.keys(), key=lambda x: org_counts[x], reverse=True
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Fetch projects from all orgs
|
|
182
|
+
for org in sorted_orgs:
|
|
183
|
+
try:
|
|
184
|
+
org_projects = client.list_projects_with_fields(
|
|
185
|
+
owner=org, is_org=True, limit=100
|
|
186
|
+
)
|
|
187
|
+
all_projects.extend(org_projects)
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
if not all_projects:
|
|
194
|
+
return (
|
|
195
|
+
f"GitHub Projects (via {auth_mode}):\n\n"
|
|
196
|
+
f"No projects found for {username} or accessible orgs.\n\n"
|
|
197
|
+
"To list projects for a specific org, use:\n"
|
|
198
|
+
" manage_issues(action='list_projects', owner='org-name')"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
projects = all_projects
|
|
202
|
+
|
|
203
|
+
lines = [f"GitHub Projects (via {auth_mode}):", ""]
|
|
204
|
+
|
|
205
|
+
for proj in projects:
|
|
206
|
+
status = "closed" if proj["closed"] else "open"
|
|
207
|
+
lines.append(f"- #{proj['number']}: {proj['title']} ({status})")
|
|
208
|
+
lines.append(f" URL: {proj['url']}")
|
|
209
|
+
|
|
210
|
+
# Show fields
|
|
211
|
+
fields = proj.get("fields", [])
|
|
212
|
+
if fields:
|
|
213
|
+
lines.append(" Fields:")
|
|
214
|
+
for field in fields:
|
|
215
|
+
name = field.get("name", "Unknown")
|
|
216
|
+
data_type = field.get("data_type", "UNKNOWN")
|
|
217
|
+
|
|
218
|
+
if data_type == "SINGLE_SELECT":
|
|
219
|
+
options = field.get("options", [])
|
|
220
|
+
option_names = [opt["name"] for opt in options]
|
|
221
|
+
lines.append(
|
|
222
|
+
f" - {name} (SINGLE_SELECT): {', '.join(option_names)}"
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
lines.append(f" - {name} ({data_type})")
|
|
226
|
+
|
|
227
|
+
lines.append("")
|
|
228
|
+
|
|
229
|
+
lines.append("Usage:")
|
|
230
|
+
lines.append(
|
|
231
|
+
" manage_projects(action='update_fields', issue_numbers=[42], project='1',"
|
|
232
|
+
)
|
|
233
|
+
lines.append(
|
|
234
|
+
" fields={'Status': 'In Progress', 'Priority': 'High'})"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return "\n".join(lines)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Failed to fetch GitHub projects: {e}")
|
|
240
|
+
return f"Error fetching projects: {str(e)}"
|
mcp_server/tools/auth_tools.py
CHANGED
|
@@ -10,7 +10,7 @@ Also provides GitHub PAT authentication for users who can't install the GitHub A
|
|
|
10
10
|
import os
|
|
11
11
|
import logging
|
|
12
12
|
import webbrowser
|
|
13
|
-
from typing import Dict, Any
|
|
13
|
+
from typing import Dict, Any, Optional
|
|
14
14
|
|
|
15
15
|
import httpx
|
|
16
16
|
from github import Github, Auth, GithubException
|
|
@@ -20,6 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
from mcp_server.auth import (
|
|
21
21
|
get_credential_store,
|
|
22
22
|
DeviceFlowAuth,
|
|
23
|
+
get_github_pat,
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
@@ -532,9 +533,10 @@ def create_auth_tools(mcp: FastMCP):
|
|
|
532
533
|
|
|
533
534
|
@mcp.tool(tags={"auth", "github"})
|
|
534
535
|
def connect_github_via_pat(
|
|
535
|
-
token: str = Field(
|
|
536
|
-
|
|
537
|
-
description="GitHub Personal Access Token (ghp_xxx or github_pat_xxx)"
|
|
536
|
+
token: Optional[str] = Field(
|
|
537
|
+
default=None,
|
|
538
|
+
description="GitHub Personal Access Token (ghp_xxx or github_pat_xxx). "
|
|
539
|
+
"If not provided, auto-detects from .quickcall.env or GITHUB_TOKEN env var.",
|
|
538
540
|
),
|
|
539
541
|
) -> Dict[str, Any]:
|
|
540
542
|
"""
|
|
@@ -543,17 +545,13 @@ def create_auth_tools(mcp: FastMCP):
|
|
|
543
545
|
Use this if your organization can't install the QuickCall GitHub App.
|
|
544
546
|
This is an alternative to the standard connect_github flow.
|
|
545
547
|
|
|
546
|
-
|
|
547
|
-
1.
|
|
548
|
-
2.
|
|
549
|
-
3.
|
|
550
|
-
|
|
551
|
-
After connecting, you can use GitHub tools like list_repos, list_prs, etc.
|
|
548
|
+
Token auto-detection locations (in order):
|
|
549
|
+
1. GITHUB_TOKEN or GITHUB_PAT environment variable
|
|
550
|
+
2. .quickcall.env in your project root (where .git is)
|
|
551
|
+
3. ~/.quickcall.env in your home directory
|
|
552
552
|
|
|
553
553
|
Create a PAT at: https://github.com/settings/tokens
|
|
554
|
-
Required scopes:
|
|
555
|
-
- repo (full access to private repos)
|
|
556
|
-
- OR public_repo (public repos only)
|
|
554
|
+
Required scopes (classic PAT): project, read:user, repo
|
|
557
555
|
|
|
558
556
|
Note: PAT mode works independently of QuickCall. You don't need
|
|
559
557
|
to run connect_quickcall first. However, Slack tools still require
|
|
@@ -571,11 +569,29 @@ def create_auth_tools(mcp: FastMCP):
|
|
|
571
569
|
"hint": "Use disconnect_github_pat to remove it, then connect again with a new token.",
|
|
572
570
|
}
|
|
573
571
|
|
|
572
|
+
# Auto-detect token if not provided
|
|
573
|
+
token_source = "provided directly"
|
|
574
|
+
if not token:
|
|
575
|
+
token, token_source = get_github_pat()
|
|
576
|
+
if not token:
|
|
577
|
+
return {
|
|
578
|
+
"status": "error",
|
|
579
|
+
"message": "No token provided and none found automatically.",
|
|
580
|
+
"searched_locations": [
|
|
581
|
+
"GITHUB_TOKEN / GITHUB_PAT environment variables",
|
|
582
|
+
".quickcall.env in project root (where .git is located)",
|
|
583
|
+
"~/.quickcall.env in home directory",
|
|
584
|
+
],
|
|
585
|
+
"hint": "Either provide the token directly, set GITHUB_TOKEN env var, "
|
|
586
|
+
"or create .quickcall.env with GITHUB_TOKEN=ghp_xxx",
|
|
587
|
+
}
|
|
588
|
+
|
|
574
589
|
# Validate token format
|
|
575
590
|
if not token.startswith(("ghp_", "github_pat_")):
|
|
576
591
|
return {
|
|
577
592
|
"status": "error",
|
|
578
593
|
"message": "Invalid token format. GitHub PATs start with 'ghp_' or 'github_pat_'",
|
|
594
|
+
"token_source": token_source,
|
|
579
595
|
"hint": "Create a new token at https://github.com/settings/tokens",
|
|
580
596
|
}
|
|
581
597
|
|
|
@@ -619,6 +635,7 @@ def create_auth_tools(mcp: FastMCP):
|
|
|
619
635
|
"message": f"Successfully connected GitHub as {username}!",
|
|
620
636
|
"username": username,
|
|
621
637
|
"mode": "pat",
|
|
638
|
+
"token_source": token_source,
|
|
622
639
|
"hint": "You can now use GitHub tools. Run check_github_connection to verify.",
|
|
623
640
|
}
|
|
624
641
|
|
mcp_server/tools/github_tools.py
CHANGED
|
@@ -270,7 +270,7 @@ def _get_client() -> GitHubClient:
|
|
|
270
270
|
"2. Run connect_quickcall to use QuickCall (GitHub App + Slack)\n"
|
|
271
271
|
"3. Set GITHUB_TOKEN environment variable\n\n"
|
|
272
272
|
"For PAT: Create token at https://github.com/settings/tokens\n"
|
|
273
|
-
"Required scopes
|
|
273
|
+
"Required scopes (classic PAT): project, read:user, repo"
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
|
|
@@ -575,7 +575,7 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
575
575
|
),
|
|
576
576
|
issue_numbers: Optional[List[int]] = Field(
|
|
577
577
|
default=None,
|
|
578
|
-
description="Issue number(s). Required for view/update/close/reopen/comment/sub-issue ops.",
|
|
578
|
+
description="Issue number(s). Required for view/update/close/reopen/comment/sub-issue/project ops.",
|
|
579
579
|
),
|
|
580
580
|
title: Optional[str] = Field(
|
|
581
581
|
default=None,
|
|
@@ -647,6 +647,8 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
647
647
|
- add sub-issues: manage_issues(action="add_sub_issue", issue_numbers=[43,44], parent_issue=42)
|
|
648
648
|
- remove sub-issue: manage_issues(action="remove_sub_issue", issue_numbers=[43], parent_issue=42)
|
|
649
649
|
- list sub-issues: manage_issues(action="list_sub_issues", parent_issue=42)
|
|
650
|
+
|
|
651
|
+
For project operations (add to project, update fields), use manage_projects() instead.
|
|
650
652
|
"""
|
|
651
653
|
try:
|
|
652
654
|
client = _get_client()
|
|
@@ -1091,3 +1093,237 @@ def create_github_tools(mcp: FastMCP) -> None:
|
|
|
1091
1093
|
"connected": False,
|
|
1092
1094
|
"error": str(e),
|
|
1093
1095
|
}
|
|
1096
|
+
|
|
1097
|
+
@mcp.tool(tags={"github", "projects"})
|
|
1098
|
+
def manage_projects(
|
|
1099
|
+
action: str = Field(
|
|
1100
|
+
...,
|
|
1101
|
+
description="Action: 'list', 'add', 'remove', 'update_fields'",
|
|
1102
|
+
),
|
|
1103
|
+
issue_numbers: Optional[List[int]] = Field(
|
|
1104
|
+
default=None,
|
|
1105
|
+
description="Issue number(s) for add/remove/update_fields actions.",
|
|
1106
|
+
),
|
|
1107
|
+
project: Optional[str] = Field(
|
|
1108
|
+
default=None,
|
|
1109
|
+
description="Project number or title. Required for add/remove/update_fields.",
|
|
1110
|
+
),
|
|
1111
|
+
fields: Optional[Dict[str, str]] = Field(
|
|
1112
|
+
default=None,
|
|
1113
|
+
description="Dict of field names to values for 'add' or 'update_fields'. "
|
|
1114
|
+
"Example: {'Status': 'In Progress', 'Priority': 'High'}",
|
|
1115
|
+
),
|
|
1116
|
+
owner: Optional[str] = Field(
|
|
1117
|
+
default=None,
|
|
1118
|
+
description="Repository owner (for the issues).",
|
|
1119
|
+
),
|
|
1120
|
+
repo: Optional[str] = Field(
|
|
1121
|
+
default=None,
|
|
1122
|
+
description="Repository name (for the issues).",
|
|
1123
|
+
),
|
|
1124
|
+
project_owner: Optional[str] = Field(
|
|
1125
|
+
default=None,
|
|
1126
|
+
description="Owner of the project (org or user). Defaults to repo owner.",
|
|
1127
|
+
),
|
|
1128
|
+
limit: Optional[int] = Field(
|
|
1129
|
+
default=20,
|
|
1130
|
+
description="Maximum projects to return for 'list' action.",
|
|
1131
|
+
),
|
|
1132
|
+
) -> dict:
|
|
1133
|
+
"""
|
|
1134
|
+
Manage GitHub Projects V2: list projects, add/remove issues, update fields.
|
|
1135
|
+
|
|
1136
|
+
IMPORTANT: Use 'add' with 'fields' to add issue AND set fields in ONE call.
|
|
1137
|
+
Don't make separate calls for add + update_fields.
|
|
1138
|
+
|
|
1139
|
+
Examples:
|
|
1140
|
+
- list: manage_projects(action="list", owner="org-name")
|
|
1141
|
+
- add with fields: manage_projects(action="add", issue_numbers=[42], project="1",
|
|
1142
|
+
fields={"Status": "Triage", "Priority": "High"}, repo="my-repo")
|
|
1143
|
+
- add only: manage_projects(action="add", issue_numbers=[42], project="1", repo="my-repo")
|
|
1144
|
+
- remove: manage_projects(action="remove", issue_numbers=[42], project="1", repo="my-repo")
|
|
1145
|
+
- update fields: manage_projects(action="update_fields", issue_numbers=[42], project="1",
|
|
1146
|
+
fields={"Status": "In Progress"}, repo="my-repo")
|
|
1147
|
+
"""
|
|
1148
|
+
try:
|
|
1149
|
+
client = _get_client()
|
|
1150
|
+
|
|
1151
|
+
# === LIST ACTION ===
|
|
1152
|
+
if action == "list":
|
|
1153
|
+
proj_owner = project_owner or owner
|
|
1154
|
+
if not proj_owner:
|
|
1155
|
+
# Use authenticated user if no owner specified
|
|
1156
|
+
proj_owner = client.get_authenticated_user()
|
|
1157
|
+
|
|
1158
|
+
projects = client.list_projects(
|
|
1159
|
+
owner=proj_owner,
|
|
1160
|
+
is_org=True, # Try org first, falls back to user
|
|
1161
|
+
limit=limit or 20,
|
|
1162
|
+
)
|
|
1163
|
+
return {
|
|
1164
|
+
"action": "list",
|
|
1165
|
+
"owner": proj_owner,
|
|
1166
|
+
"count": len(projects),
|
|
1167
|
+
"projects": projects,
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
# === ADD ACTION ===
|
|
1171
|
+
if action == "add":
|
|
1172
|
+
if not project:
|
|
1173
|
+
raise ToolError("'project' is required for 'add' action")
|
|
1174
|
+
if not issue_numbers:
|
|
1175
|
+
raise ToolError("'issue_numbers' is required for 'add' action")
|
|
1176
|
+
|
|
1177
|
+
results = []
|
|
1178
|
+
for issue_number in issue_numbers:
|
|
1179
|
+
issue_result = {
|
|
1180
|
+
"number": issue_number,
|
|
1181
|
+
"project": project,
|
|
1182
|
+
}
|
|
1183
|
+
try:
|
|
1184
|
+
result = client.add_issue_to_project(
|
|
1185
|
+
issue_number=issue_number,
|
|
1186
|
+
project=project,
|
|
1187
|
+
owner=owner,
|
|
1188
|
+
repo=repo,
|
|
1189
|
+
project_owner=project_owner,
|
|
1190
|
+
)
|
|
1191
|
+
issue_result["status"] = "added"
|
|
1192
|
+
issue_result["project_item_id"] = result.get("project_item_id")
|
|
1193
|
+
|
|
1194
|
+
# If fields provided, set them after adding
|
|
1195
|
+
if fields:
|
|
1196
|
+
issue_result["fields_updated"] = []
|
|
1197
|
+
issue_result["field_errors"] = []
|
|
1198
|
+
for field_name, value in fields.items():
|
|
1199
|
+
try:
|
|
1200
|
+
client.update_project_item_field(
|
|
1201
|
+
issue_number=issue_number,
|
|
1202
|
+
project=project,
|
|
1203
|
+
field_name=field_name,
|
|
1204
|
+
value=value,
|
|
1205
|
+
owner=owner,
|
|
1206
|
+
repo=repo,
|
|
1207
|
+
project_owner=project_owner,
|
|
1208
|
+
)
|
|
1209
|
+
issue_result["fields_updated"].append(
|
|
1210
|
+
{"field": field_name, "value": value}
|
|
1211
|
+
)
|
|
1212
|
+
except Exception as e:
|
|
1213
|
+
issue_result["field_errors"].append(
|
|
1214
|
+
{"field": field_name, "error": str(e)}
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
results.append(issue_result)
|
|
1218
|
+
except Exception as e:
|
|
1219
|
+
issue_result["status"] = "error"
|
|
1220
|
+
issue_result["error"] = str(e)
|
|
1221
|
+
results.append(issue_result)
|
|
1222
|
+
|
|
1223
|
+
return {
|
|
1224
|
+
"action": "add",
|
|
1225
|
+
"project": project,
|
|
1226
|
+
"count": len(results),
|
|
1227
|
+
"results": results,
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
# === REMOVE ACTION ===
|
|
1231
|
+
if action == "remove":
|
|
1232
|
+
if not project:
|
|
1233
|
+
raise ToolError("'project' is required for 'remove' action")
|
|
1234
|
+
if not issue_numbers:
|
|
1235
|
+
raise ToolError("'issue_numbers' is required for 'remove' action")
|
|
1236
|
+
|
|
1237
|
+
results = []
|
|
1238
|
+
for issue_number in issue_numbers:
|
|
1239
|
+
try:
|
|
1240
|
+
client.remove_issue_from_project(
|
|
1241
|
+
issue_number=issue_number,
|
|
1242
|
+
project=project,
|
|
1243
|
+
owner=owner,
|
|
1244
|
+
repo=repo,
|
|
1245
|
+
project_owner=project_owner,
|
|
1246
|
+
)
|
|
1247
|
+
results.append(
|
|
1248
|
+
{
|
|
1249
|
+
"number": issue_number,
|
|
1250
|
+
"status": "removed",
|
|
1251
|
+
"project": project,
|
|
1252
|
+
}
|
|
1253
|
+
)
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
results.append(
|
|
1256
|
+
{
|
|
1257
|
+
"number": issue_number,
|
|
1258
|
+
"status": "error",
|
|
1259
|
+
"error": str(e),
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
return {
|
|
1264
|
+
"action": "remove",
|
|
1265
|
+
"project": project,
|
|
1266
|
+
"count": len(results),
|
|
1267
|
+
"results": results,
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
# === UPDATE FIELDS ACTION ===
|
|
1271
|
+
if action == "update_fields":
|
|
1272
|
+
if not project:
|
|
1273
|
+
raise ToolError("'project' is required for 'update_fields' action")
|
|
1274
|
+
if not issue_numbers:
|
|
1275
|
+
raise ToolError(
|
|
1276
|
+
"'issue_numbers' is required for 'update_fields' action"
|
|
1277
|
+
)
|
|
1278
|
+
if not fields:
|
|
1279
|
+
raise ToolError("'fields' is required for 'update_fields' action")
|
|
1280
|
+
|
|
1281
|
+
results = []
|
|
1282
|
+
for issue_number in issue_numbers:
|
|
1283
|
+
issue_result = {
|
|
1284
|
+
"number": issue_number,
|
|
1285
|
+
"fields_updated": [],
|
|
1286
|
+
"errors": [],
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
for field_name, value in fields.items():
|
|
1290
|
+
try:
|
|
1291
|
+
client.update_project_item_field(
|
|
1292
|
+
issue_number=issue_number,
|
|
1293
|
+
project=project,
|
|
1294
|
+
field_name=field_name,
|
|
1295
|
+
value=value,
|
|
1296
|
+
owner=owner,
|
|
1297
|
+
repo=repo,
|
|
1298
|
+
project_owner=project_owner,
|
|
1299
|
+
)
|
|
1300
|
+
issue_result["fields_updated"].append(
|
|
1301
|
+
{"field": field_name, "value": value}
|
|
1302
|
+
)
|
|
1303
|
+
except Exception as e:
|
|
1304
|
+
issue_result["errors"].append(
|
|
1305
|
+
{"field": field_name, "error": str(e)}
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
issue_result["status"] = (
|
|
1309
|
+
"success" if not issue_result["errors"] else "partial"
|
|
1310
|
+
)
|
|
1311
|
+
results.append(issue_result)
|
|
1312
|
+
|
|
1313
|
+
return {
|
|
1314
|
+
"action": "update_fields",
|
|
1315
|
+
"project": project,
|
|
1316
|
+
"count": len(results),
|
|
1317
|
+
"results": results,
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
raise ToolError(
|
|
1321
|
+
f"Invalid action: {action}. Valid actions: list, add, remove, update_fields"
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
except ToolError:
|
|
1325
|
+
raise
|
|
1326
|
+
except ValueError as e:
|
|
1327
|
+
raise ToolError(f"Invalid parameters: {str(e)}")
|
|
1328
|
+
except Exception as e:
|
|
1329
|
+
raise ToolError(f"Failed to {action} project: {str(e)}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickcall-integrations
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -90,7 +90,7 @@ Add to MCP config (`~/.cursor/mcp.json` or `.cursor/mcp.json`):
|
|
|
90
90
|
| **Slack** | Read/send messages, threads, channels | Yes |
|
|
91
91
|
|
|
92
92
|
<details>
|
|
93
|
-
<summary><strong>Available Tools (
|
|
93
|
+
<summary><strong>Available Tools (24)</strong></summary>
|
|
94
94
|
|
|
95
95
|
### Git
|
|
96
96
|
| Tool | Description |
|
|
@@ -102,10 +102,11 @@ Add to MCP config (`~/.cursor/mcp.json` or `.cursor/mcp.json`):
|
|
|
102
102
|
|------|-------------|
|
|
103
103
|
| `list_repos` | List accessible repositories |
|
|
104
104
|
| `list_prs` | List pull requests (open/closed/all) |
|
|
105
|
-
| `
|
|
105
|
+
| `get_prs` | Get PR details (title, description, files changed) |
|
|
106
106
|
| `list_commits` | List commits with optional filters |
|
|
107
107
|
| `get_commit` | Get commit details (message, stats, files) |
|
|
108
108
|
| `list_branches` | List repository branches |
|
|
109
|
+
| `manage_issues` | List, view, create, update, close, reopen, comment on issues + sub-issues |
|
|
109
110
|
| `check_github_connection` | Verify GitHub connection |
|
|
110
111
|
|
|
111
112
|
### Slack
|
|
@@ -172,9 +173,13 @@ GITHUB_USERNAME=your-username # Optional: for better UX
|
|
|
172
173
|
|
|
173
174
|
**Create a PAT at:** https://github.com/settings/tokens
|
|
174
175
|
|
|
175
|
-
**Required scopes:**
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
**Required scopes (classic PAT):**
|
|
177
|
+
|
|
178
|
+
| Scope | Used For |
|
|
179
|
+
|-------|----------|
|
|
180
|
+
| `project` | GitHub Projects access |
|
|
181
|
+
| `read:user` | Read user profile data |
|
|
182
|
+
| `repo` | PRs, commits, branches, issues |
|
|
178
183
|
|
|
179
184
|
**Note:** PAT mode provides access to GitHub tools only. For Slack integration, use QuickCall authentication.
|
|
180
185
|
|
|
@@ -228,6 +233,100 @@ List open PRs on [repo] and send titles to #updates channel
|
|
|
228
233
|
What did I work on this week? Send summary to #standup
|
|
229
234
|
```
|
|
230
235
|
|
|
236
|
+
## Issue Management
|
|
237
|
+
|
|
238
|
+
The `manage_issues` tool provides full issue lifecycle management:
|
|
239
|
+
|
|
240
|
+
### Actions
|
|
241
|
+
|
|
242
|
+
| Action | Description |
|
|
243
|
+
|--------|-------------|
|
|
244
|
+
| `list` | List issues with filters |
|
|
245
|
+
| `view` | View issue details |
|
|
246
|
+
| `create` | Create new issue (with optional template) |
|
|
247
|
+
| `update` | Update issue title/body/labels |
|
|
248
|
+
| `close` | Close issue(s) |
|
|
249
|
+
| `reopen` | Reopen issue(s) |
|
|
250
|
+
| `comment` | Add comment to issue(s) |
|
|
251
|
+
| `add_sub_issue` | Add child issue to parent |
|
|
252
|
+
| `remove_sub_issue` | Remove child from parent |
|
|
253
|
+
| `list_sub_issues` | List sub-issues of a parent |
|
|
254
|
+
|
|
255
|
+
### List Filters
|
|
256
|
+
|
|
257
|
+
| Filter | Description |
|
|
258
|
+
|--------|-------------|
|
|
259
|
+
| `state` | `'open'`, `'closed'`, or `'all'` (default: `'open'`) |
|
|
260
|
+
| `labels` | Filter by one or more labels |
|
|
261
|
+
| `assignees` | Filter by assignee |
|
|
262
|
+
| `creator` | Filter by issue creator username |
|
|
263
|
+
| `milestone` | Filter by milestone: number, title, `'*'` (any), or `'none'` |
|
|
264
|
+
| `sort` | Sort by: `'created'`, `'updated'`, or `'comments'` (default: `'updated'`) |
|
|
265
|
+
| `limit` | Max issues to return (default: 30) |
|
|
266
|
+
|
|
267
|
+
**Examples:**
|
|
268
|
+
```
|
|
269
|
+
List open issues in milestone v1.0
|
|
270
|
+
List issues created by sagar
|
|
271
|
+
Show closed bugs sorted by comments
|
|
272
|
+
List issues without a milestone
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Issue Templates
|
|
276
|
+
|
|
277
|
+
QuickCall supports issue templates from two sources:
|
|
278
|
+
|
|
279
|
+
**1. GitHub Native Templates** (`.github/ISSUE_TEMPLATE/*.yml`)
|
|
280
|
+
|
|
281
|
+
Standard GitHub issue templates are automatically detected:
|
|
282
|
+
```yaml
|
|
283
|
+
# .github/ISSUE_TEMPLATE/bug_report.yml
|
|
284
|
+
name: Bug Report
|
|
285
|
+
description: Report a bug
|
|
286
|
+
labels: [bug]
|
|
287
|
+
body:
|
|
288
|
+
- type: textarea
|
|
289
|
+
attributes:
|
|
290
|
+
label: Description
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**2. Custom Templates** (`.quickcall.env`)
|
|
294
|
+
|
|
295
|
+
Define custom templates in your project config:
|
|
296
|
+
```bash
|
|
297
|
+
# .quickcall.env
|
|
298
|
+
ISSUE_TEMPLATE_PATH=/path/to/templates.yml
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
# templates.yml
|
|
303
|
+
bug_report:
|
|
304
|
+
name: Bug Report
|
|
305
|
+
description: Report a bug
|
|
306
|
+
labels: [bug]
|
|
307
|
+
title_prefix: "[BUG] "
|
|
308
|
+
body: |
|
|
309
|
+
## Description
|
|
310
|
+
|
|
311
|
+
## Steps to Reproduce
|
|
312
|
+
|
|
313
|
+
## Expected Behavior
|
|
314
|
+
|
|
315
|
+
feature_request:
|
|
316
|
+
name: Feature Request
|
|
317
|
+
labels: [enhancement]
|
|
318
|
+
body: |
|
|
319
|
+
## Problem
|
|
320
|
+
|
|
321
|
+
## Proposed Solution
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Usage:**
|
|
325
|
+
```
|
|
326
|
+
Create a bug report issue titled "Login fails on Safari"
|
|
327
|
+
Create issue with feature_request template
|
|
328
|
+
```
|
|
329
|
+
|
|
231
330
|
## Troubleshooting
|
|
232
331
|
|
|
233
332
|
### Clean Reinstall
|
|
@@ -1,21 +1,21 @@
|
|
|
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=Y3P2yRRv8GxG0xjql6IaVB5aodGDeTPQUC_lHQmgVWM,70829
|
|
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
|
|
8
8
|
mcp_server/auth/device_flow.py,sha256=NXNWHzd-CA4dlhEVCgUhwfpe9TpMKpLSJuyFCh70xKs,8371
|
|
9
9
|
mcp_server/resources/__init__.py,sha256=JrMa3Kf-DmeCB4GwVNfmfw9OGnxF9pJJxCw9Y7u7ujQ,35
|
|
10
|
-
mcp_server/resources/github_resources.py,sha256=
|
|
10
|
+
mcp_server/resources/github_resources.py,sha256=b7S5Jd-xcS6OjceOdwsJZ-BLfIolQXZ9LnQhlhigfb0,8850
|
|
11
11
|
mcp_server/resources/slack_resources.py,sha256=b_CPxAicwkF3PsBXIat4QoLbDUHM2g_iPzgzvVpwjaw,1687
|
|
12
12
|
mcp_server/tools/__init__.py,sha256=vIR2ujAaTXm2DgpTsVNz3brI4G34p-Jeg44Qe0uvWc0,405
|
|
13
|
-
mcp_server/tools/auth_tools.py,sha256=
|
|
13
|
+
mcp_server/tools/auth_tools.py,sha256=BPuj9M0pZOvvWHxH0HPdiVm-Y6DJyD-PEvtrIh68vbc,25409
|
|
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=U1auskC17nJWVmqFO9EITe-OcDSeDBuhM3d4iox7Ix8,50007
|
|
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.
|
|
19
|
-
quickcall_integrations-0.
|
|
20
|
-
quickcall_integrations-0.
|
|
21
|
-
quickcall_integrations-0.
|
|
18
|
+
quickcall_integrations-0.4.0.dist-info/METADATA,sha256=pOcoe0a2JKylPL6tpzYk2lUor_hgfsXPyDgjwjn45JM,9415
|
|
19
|
+
quickcall_integrations-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
20
|
+
quickcall_integrations-0.4.0.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
|
|
21
|
+
quickcall_integrations-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
{quickcall_integrations-0.3.8.dist-info → quickcall_integrations-0.4.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|