quickcall-integrations 0.3.9__py3-none-any.whl → 0.5.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 +1989 -543
- mcp_server/resources/github_resources.py +124 -0
- mcp_server/tools/github_tools.py +635 -1
- {quickcall_integrations-0.3.9.dist-info → quickcall_integrations-0.5.0.dist-info}/METADATA +46 -2
- {quickcall_integrations-0.3.9.dist-info → quickcall_integrations-0.5.0.dist-info}/RECORD +7 -7
- {quickcall_integrations-0.3.9.dist-info → quickcall_integrations-0.5.0.dist-info}/WHEEL +0 -0
- {quickcall_integrations-0.3.9.dist-info → quickcall_integrations-0.5.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)}"
|