quickcall-integrations 0.1.3__py3-none-any.whl → 0.1.4__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/__init__.py +6 -0
- mcp_server/api_clients/github_client.py +440 -0
- mcp_server/api_clients/slack_client.py +359 -0
- mcp_server/auth/__init__.py +24 -0
- mcp_server/auth/credentials.py +278 -0
- mcp_server/auth/device_flow.py +253 -0
- mcp_server/server.py +54 -2
- mcp_server/tools/__init__.py +12 -0
- mcp_server/tools/auth_tools.py +411 -0
- mcp_server/tools/git_tools.py +42 -18
- mcp_server/tools/github_tools.py +338 -0
- mcp_server/tools/slack_tools.py +203 -0
- {quickcall_integrations-0.1.3.dist-info → quickcall_integrations-0.1.4.dist-info}/METADATA +20 -5
- quickcall_integrations-0.1.4.dist-info/RECORD +18 -0
- mcp_server/config.py +0 -10
- quickcall_integrations-0.1.3.dist-info/RECORD +0 -10
- {quickcall_integrations-0.1.3.dist-info → quickcall_integrations-0.1.4.dist-info}/WHEEL +0 -0
- {quickcall_integrations-0.1.3.dist-info → quickcall_integrations-0.1.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub Tools - Pull requests and commits via GitHub API.
|
|
3
|
+
|
|
4
|
+
These tools require authentication via QuickCall.
|
|
5
|
+
Connect using connect_quickcall tool first.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from fastmcp.exceptions import ToolError
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from mcp_server.auth import get_credential_store, is_authenticated
|
|
16
|
+
from mcp_server.api_clients.github_client import GitHubClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_client() -> GitHubClient:
|
|
22
|
+
"""Get the GitHub client, raising error if not configured."""
|
|
23
|
+
store = get_credential_store()
|
|
24
|
+
|
|
25
|
+
if not store.is_authenticated():
|
|
26
|
+
raise ToolError(
|
|
27
|
+
"Not connected to QuickCall. "
|
|
28
|
+
"Run connect_quickcall to authenticate and enable GitHub tools."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Fetch fresh credentials from API
|
|
32
|
+
creds = store.get_api_credentials()
|
|
33
|
+
|
|
34
|
+
if not creds or not creds.github_connected:
|
|
35
|
+
raise ToolError(
|
|
36
|
+
"GitHub not connected. "
|
|
37
|
+
"Connect GitHub at quickcall.dev/assistant to enable GitHub tools."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not creds.github_token:
|
|
41
|
+
raise ToolError(
|
|
42
|
+
"Could not fetch GitHub token. "
|
|
43
|
+
"Try reconnecting GitHub at quickcall.dev/assistant."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Create client with fresh token and installation ID
|
|
47
|
+
return GitHubClient(
|
|
48
|
+
token=creds.github_token,
|
|
49
|
+
default_owner=creds.github_username,
|
|
50
|
+
installation_id=creds.github_installation_id,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_github_tools(mcp: FastMCP) -> None:
|
|
55
|
+
"""Add GitHub tools to the MCP server."""
|
|
56
|
+
|
|
57
|
+
@mcp.tool(tags={"github", "repos"})
|
|
58
|
+
def list_repos(
|
|
59
|
+
limit: int = Field(
|
|
60
|
+
default=20,
|
|
61
|
+
description="Maximum number of repositories to return (default: 20)",
|
|
62
|
+
),
|
|
63
|
+
) -> dict:
|
|
64
|
+
"""
|
|
65
|
+
List GitHub repositories accessible to the authenticated user.
|
|
66
|
+
|
|
67
|
+
Returns repositories sorted by last updated.
|
|
68
|
+
Requires QuickCall authentication with GitHub connected.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
client = _get_client()
|
|
72
|
+
repos = client.list_repos(limit=limit)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"count": len(repos),
|
|
76
|
+
"repos": [repo.model_dump() for repo in repos],
|
|
77
|
+
}
|
|
78
|
+
except ToolError:
|
|
79
|
+
raise
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise ToolError(f"Failed to list repositories: {str(e)}")
|
|
82
|
+
|
|
83
|
+
@mcp.tool(tags={"github", "prs"})
|
|
84
|
+
def list_prs(
|
|
85
|
+
owner: Optional[str] = Field(
|
|
86
|
+
default=None,
|
|
87
|
+
description="Repository owner (username or org). Uses your GitHub username if not specified.",
|
|
88
|
+
),
|
|
89
|
+
repo: Optional[str] = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description="Repository name. Required.",
|
|
92
|
+
),
|
|
93
|
+
state: str = Field(
|
|
94
|
+
default="open",
|
|
95
|
+
description="PR state: 'open', 'closed', or 'all' (default: 'open')",
|
|
96
|
+
),
|
|
97
|
+
limit: int = Field(
|
|
98
|
+
default=20,
|
|
99
|
+
description="Maximum number of PRs to return (default: 20)",
|
|
100
|
+
),
|
|
101
|
+
) -> dict:
|
|
102
|
+
"""
|
|
103
|
+
List pull requests for a GitHub repository.
|
|
104
|
+
|
|
105
|
+
Returns PRs sorted by last updated.
|
|
106
|
+
Requires QuickCall authentication with GitHub connected.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
client = _get_client()
|
|
110
|
+
prs = client.list_prs(owner=owner, repo=repo, state=state, limit=limit)
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
"count": len(prs),
|
|
114
|
+
"prs": [pr.model_dump() for pr in prs],
|
|
115
|
+
}
|
|
116
|
+
except ToolError:
|
|
117
|
+
raise
|
|
118
|
+
except ValueError as e:
|
|
119
|
+
raise ToolError(
|
|
120
|
+
f"Repository not specified: {str(e)}. "
|
|
121
|
+
f"Please provide both owner and repo parameters."
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise ToolError(f"Failed to list pull requests: {str(e)}")
|
|
125
|
+
|
|
126
|
+
@mcp.tool(tags={"github", "prs"})
|
|
127
|
+
def get_pr(
|
|
128
|
+
pr_number: int = Field(..., description="Pull request number", gt=0),
|
|
129
|
+
owner: Optional[str] = Field(
|
|
130
|
+
default=None,
|
|
131
|
+
description="Repository owner. Uses your GitHub username if not specified.",
|
|
132
|
+
),
|
|
133
|
+
repo: Optional[str] = Field(
|
|
134
|
+
default=None,
|
|
135
|
+
description="Repository name. Required.",
|
|
136
|
+
),
|
|
137
|
+
) -> dict:
|
|
138
|
+
"""
|
|
139
|
+
Get detailed information about a specific pull request.
|
|
140
|
+
|
|
141
|
+
Includes title, description, status, files changed, and review status.
|
|
142
|
+
Requires QuickCall authentication with GitHub connected.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
client = _get_client()
|
|
146
|
+
pr = client.get_pr(pr_number, owner=owner, repo=repo)
|
|
147
|
+
|
|
148
|
+
if not pr:
|
|
149
|
+
raise ToolError(f"Pull request #{pr_number} not found")
|
|
150
|
+
|
|
151
|
+
return {"pr": pr.model_dump()}
|
|
152
|
+
except ToolError:
|
|
153
|
+
raise
|
|
154
|
+
except ValueError as e:
|
|
155
|
+
raise ToolError(
|
|
156
|
+
f"Repository not specified: {str(e)}. "
|
|
157
|
+
f"Please provide both owner and repo parameters."
|
|
158
|
+
)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise ToolError(f"Failed to get pull request #{pr_number}: {str(e)}")
|
|
161
|
+
|
|
162
|
+
@mcp.tool(tags={"github", "commits"})
|
|
163
|
+
def list_commits(
|
|
164
|
+
owner: Optional[str] = Field(
|
|
165
|
+
default=None,
|
|
166
|
+
description="Repository owner. Uses your GitHub username if not specified.",
|
|
167
|
+
),
|
|
168
|
+
repo: Optional[str] = Field(
|
|
169
|
+
default=None,
|
|
170
|
+
description="Repository name. Required.",
|
|
171
|
+
),
|
|
172
|
+
branch: Optional[str] = Field(
|
|
173
|
+
default=None,
|
|
174
|
+
description="Branch name to list commits from. Defaults to default branch.",
|
|
175
|
+
),
|
|
176
|
+
author: Optional[str] = Field(
|
|
177
|
+
default=None,
|
|
178
|
+
description="Filter by author username",
|
|
179
|
+
),
|
|
180
|
+
since: Optional[str] = Field(
|
|
181
|
+
default=None,
|
|
182
|
+
description="ISO datetime - only commits after this date (e.g., '2024-01-01T00:00:00Z')",
|
|
183
|
+
),
|
|
184
|
+
limit: int = Field(
|
|
185
|
+
default=20,
|
|
186
|
+
description="Maximum number of commits to return (default: 20)",
|
|
187
|
+
),
|
|
188
|
+
) -> dict:
|
|
189
|
+
"""
|
|
190
|
+
List commits for a GitHub repository.
|
|
191
|
+
|
|
192
|
+
Returns commits sorted by date (newest first).
|
|
193
|
+
Requires QuickCall authentication with GitHub connected.
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
client = _get_client()
|
|
197
|
+
commits = client.list_commits(
|
|
198
|
+
owner=owner,
|
|
199
|
+
repo=repo,
|
|
200
|
+
sha=branch,
|
|
201
|
+
author=author,
|
|
202
|
+
since=since,
|
|
203
|
+
limit=limit,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"count": len(commits),
|
|
208
|
+
"commits": [commit.model_dump() for commit in commits],
|
|
209
|
+
}
|
|
210
|
+
except ToolError:
|
|
211
|
+
raise
|
|
212
|
+
except ValueError as e:
|
|
213
|
+
raise ToolError(
|
|
214
|
+
f"Repository not specified: {str(e)}. "
|
|
215
|
+
f"Please provide both owner and repo parameters."
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
raise ToolError(f"Failed to list commits: {str(e)}")
|
|
219
|
+
|
|
220
|
+
@mcp.tool(tags={"github", "commits"})
|
|
221
|
+
def get_commit(
|
|
222
|
+
sha: str = Field(..., description="Commit SHA (full or abbreviated)"),
|
|
223
|
+
owner: Optional[str] = Field(
|
|
224
|
+
default=None,
|
|
225
|
+
description="Repository owner. Uses your GitHub username if not specified.",
|
|
226
|
+
),
|
|
227
|
+
repo: Optional[str] = Field(
|
|
228
|
+
default=None,
|
|
229
|
+
description="Repository name. Required.",
|
|
230
|
+
),
|
|
231
|
+
) -> dict:
|
|
232
|
+
"""
|
|
233
|
+
Get detailed information about a specific commit.
|
|
234
|
+
|
|
235
|
+
Includes commit message, author, stats, and file changes.
|
|
236
|
+
Requires QuickCall authentication with GitHub connected.
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
client = _get_client()
|
|
240
|
+
commit = client.get_commit(sha, owner=owner, repo=repo)
|
|
241
|
+
|
|
242
|
+
if not commit:
|
|
243
|
+
raise ToolError(f"Commit {sha} not found")
|
|
244
|
+
|
|
245
|
+
return {"commit": commit}
|
|
246
|
+
except ToolError:
|
|
247
|
+
raise
|
|
248
|
+
except ValueError as e:
|
|
249
|
+
raise ToolError(
|
|
250
|
+
f"Repository not specified: {str(e)}. "
|
|
251
|
+
f"Please provide both owner and repo parameters."
|
|
252
|
+
)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
raise ToolError(f"Failed to get commit {sha}: {str(e)}")
|
|
255
|
+
|
|
256
|
+
@mcp.tool(tags={"github", "branches"})
|
|
257
|
+
def list_branches(
|
|
258
|
+
owner: Optional[str] = Field(
|
|
259
|
+
default=None,
|
|
260
|
+
description="Repository owner. Uses your GitHub username if not specified.",
|
|
261
|
+
),
|
|
262
|
+
repo: Optional[str] = Field(
|
|
263
|
+
default=None,
|
|
264
|
+
description="Repository name. Required.",
|
|
265
|
+
),
|
|
266
|
+
limit: int = Field(
|
|
267
|
+
default=30,
|
|
268
|
+
description="Maximum number of branches to return (default: 30)",
|
|
269
|
+
),
|
|
270
|
+
) -> dict:
|
|
271
|
+
"""
|
|
272
|
+
List branches for a GitHub repository.
|
|
273
|
+
|
|
274
|
+
Returns branch names with their latest commit SHA and protection status.
|
|
275
|
+
Requires QuickCall authentication with GitHub connected.
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
client = _get_client()
|
|
279
|
+
branches = client.list_branches(owner=owner, repo=repo, limit=limit)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"count": len(branches),
|
|
283
|
+
"branches": branches,
|
|
284
|
+
}
|
|
285
|
+
except ToolError:
|
|
286
|
+
raise
|
|
287
|
+
except ValueError as e:
|
|
288
|
+
raise ToolError(
|
|
289
|
+
f"Repository not specified: {str(e)}. "
|
|
290
|
+
f"Please provide both owner and repo parameters."
|
|
291
|
+
)
|
|
292
|
+
except Exception as e:
|
|
293
|
+
raise ToolError(f"Failed to list branches: {str(e)}")
|
|
294
|
+
|
|
295
|
+
@mcp.tool(tags={"github", "status"})
|
|
296
|
+
def check_github_connection() -> dict:
|
|
297
|
+
"""
|
|
298
|
+
Check if GitHub is connected and working.
|
|
299
|
+
|
|
300
|
+
Tests the GitHub connection by fetching your account info.
|
|
301
|
+
Use this to verify your GitHub integration is working.
|
|
302
|
+
"""
|
|
303
|
+
store = get_credential_store()
|
|
304
|
+
|
|
305
|
+
if not store.is_authenticated():
|
|
306
|
+
return {
|
|
307
|
+
"connected": False,
|
|
308
|
+
"error": "Not connected to QuickCall. Run connect_quickcall first.",
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
creds = store.get_api_credentials()
|
|
312
|
+
|
|
313
|
+
if not creds:
|
|
314
|
+
return {
|
|
315
|
+
"connected": False,
|
|
316
|
+
"error": "Could not fetch credentials from QuickCall.",
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if not creds.github_connected:
|
|
320
|
+
return {
|
|
321
|
+
"connected": False,
|
|
322
|
+
"error": "GitHub not connected. Connect at quickcall.dev/assistant.",
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
client = _get_client()
|
|
327
|
+
username = client.get_authenticated_user()
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
"connected": True,
|
|
331
|
+
"username": username,
|
|
332
|
+
"installation_id": creds.github_installation_id,
|
|
333
|
+
}
|
|
334
|
+
except Exception as e:
|
|
335
|
+
return {
|
|
336
|
+
"connected": False,
|
|
337
|
+
"error": str(e),
|
|
338
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Slack Tools - Messaging and channel operations.
|
|
3
|
+
|
|
4
|
+
These tools require authentication via QuickCall.
|
|
5
|
+
Connect using connect_quickcall tool first.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from fastmcp.exceptions import ToolError
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from mcp_server.auth import get_credential_store
|
|
16
|
+
from mcp_server.api_clients.slack_client import SlackClient, SlackAPIError
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_client() -> SlackClient:
|
|
22
|
+
"""Get the Slack client, raising error if not configured."""
|
|
23
|
+
store = get_credential_store()
|
|
24
|
+
|
|
25
|
+
if not store.is_authenticated():
|
|
26
|
+
raise ToolError(
|
|
27
|
+
"Not connected to QuickCall. "
|
|
28
|
+
"Run connect_quickcall to authenticate and enable Slack tools."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Fetch fresh credentials from API
|
|
32
|
+
creds = store.get_api_credentials()
|
|
33
|
+
|
|
34
|
+
if not creds or not creds.slack_connected:
|
|
35
|
+
raise ToolError(
|
|
36
|
+
"Slack not connected. "
|
|
37
|
+
"Connect Slack at quickcall.dev/assistant to enable Slack tools."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not creds.slack_bot_token:
|
|
41
|
+
raise ToolError(
|
|
42
|
+
"Could not fetch Slack token. "
|
|
43
|
+
"Try reconnecting Slack at quickcall.dev/assistant."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Create client with fresh token
|
|
47
|
+
return SlackClient(bot_token=creds.slack_bot_token)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_slack_tools(mcp: FastMCP) -> None:
|
|
51
|
+
"""Add Slack tools to the MCP server."""
|
|
52
|
+
|
|
53
|
+
@mcp.tool(tags={"slack", "channels"})
|
|
54
|
+
def list_slack_channels(
|
|
55
|
+
include_private: bool = Field(
|
|
56
|
+
default=True,
|
|
57
|
+
description="Include private channels the bot has access to (default: true)",
|
|
58
|
+
),
|
|
59
|
+
limit: int = Field(
|
|
60
|
+
default=100,
|
|
61
|
+
description="Maximum number of channels to return (default: 100)",
|
|
62
|
+
),
|
|
63
|
+
) -> dict:
|
|
64
|
+
"""
|
|
65
|
+
List Slack channels the bot has access to.
|
|
66
|
+
|
|
67
|
+
Returns channel names, IDs, and membership status.
|
|
68
|
+
Requires QuickCall authentication with Slack connected.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
client = _get_client()
|
|
72
|
+
channels = client.list_channels(
|
|
73
|
+
include_private=include_private, limit=limit
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"count": len(channels),
|
|
78
|
+
"channels": [ch.model_dump() for ch in channels],
|
|
79
|
+
}
|
|
80
|
+
except ToolError:
|
|
81
|
+
raise
|
|
82
|
+
except SlackAPIError as e:
|
|
83
|
+
raise ToolError(str(e))
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise ToolError(f"Failed to list Slack channels: {str(e)}")
|
|
86
|
+
|
|
87
|
+
@mcp.tool(tags={"slack", "messaging"})
|
|
88
|
+
def send_slack_message(
|
|
89
|
+
message: str = Field(
|
|
90
|
+
...,
|
|
91
|
+
description="Message text to send. Supports Slack mrkdwn formatting.",
|
|
92
|
+
),
|
|
93
|
+
channel: Optional[str] = Field(
|
|
94
|
+
default=None,
|
|
95
|
+
description="Channel name (with or without #) or channel ID. Required.",
|
|
96
|
+
),
|
|
97
|
+
) -> dict:
|
|
98
|
+
"""
|
|
99
|
+
Send a message to a Slack channel.
|
|
100
|
+
|
|
101
|
+
The bot must be a member of the channel to send messages.
|
|
102
|
+
Requires QuickCall authentication with Slack connected.
|
|
103
|
+
|
|
104
|
+
Message formatting (mrkdwn):
|
|
105
|
+
- *bold* for bold text
|
|
106
|
+
- _italic_ for italic text
|
|
107
|
+
- `code` for inline code
|
|
108
|
+
- ```code block``` for code blocks
|
|
109
|
+
- <https://example.com|link text> for links
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
client = _get_client()
|
|
113
|
+
result = client.send_message(text=message, channel=channel)
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
"success": result.ok,
|
|
117
|
+
"channel": result.channel,
|
|
118
|
+
"message_ts": result.ts,
|
|
119
|
+
}
|
|
120
|
+
except ToolError:
|
|
121
|
+
raise
|
|
122
|
+
except SlackAPIError as e:
|
|
123
|
+
raise ToolError(str(e))
|
|
124
|
+
except ValueError as e:
|
|
125
|
+
raise ToolError(str(e))
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise ToolError(f"Failed to send Slack message: {str(e)}")
|
|
128
|
+
|
|
129
|
+
@mcp.tool(tags={"slack", "users"})
|
|
130
|
+
def list_slack_users(
|
|
131
|
+
limit: int = Field(
|
|
132
|
+
default=100,
|
|
133
|
+
description="Maximum number of users to return (default: 100)",
|
|
134
|
+
),
|
|
135
|
+
include_bots: bool = Field(
|
|
136
|
+
default=False,
|
|
137
|
+
description="Include bot users in the list (default: false)",
|
|
138
|
+
),
|
|
139
|
+
) -> dict:
|
|
140
|
+
"""
|
|
141
|
+
List users in the Slack workspace.
|
|
142
|
+
|
|
143
|
+
Returns user names, display names, and email addresses.
|
|
144
|
+
Requires QuickCall authentication with Slack connected.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
client = _get_client()
|
|
148
|
+
users = client.list_users(limit=limit, include_bots=include_bots)
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"count": len(users),
|
|
152
|
+
"users": [user.model_dump() for user in users],
|
|
153
|
+
}
|
|
154
|
+
except ToolError:
|
|
155
|
+
raise
|
|
156
|
+
except SlackAPIError as e:
|
|
157
|
+
raise ToolError(str(e))
|
|
158
|
+
except Exception as e:
|
|
159
|
+
raise ToolError(f"Failed to list Slack users: {str(e)}")
|
|
160
|
+
|
|
161
|
+
@mcp.tool(tags={"slack", "status"})
|
|
162
|
+
def check_slack_connection() -> dict:
|
|
163
|
+
"""
|
|
164
|
+
Check if Slack is connected and working.
|
|
165
|
+
|
|
166
|
+
Tests the Slack bot token by calling auth.test.
|
|
167
|
+
Use this to verify your Slack integration is working.
|
|
168
|
+
"""
|
|
169
|
+
store = get_credential_store()
|
|
170
|
+
|
|
171
|
+
if not store.is_authenticated():
|
|
172
|
+
return {
|
|
173
|
+
"connected": False,
|
|
174
|
+
"error": "Not connected to QuickCall. Run connect_quickcall first.",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
creds = store.get_api_credentials()
|
|
178
|
+
|
|
179
|
+
if not creds:
|
|
180
|
+
return {
|
|
181
|
+
"connected": False,
|
|
182
|
+
"error": "Could not fetch credentials from QuickCall.",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if not creds.slack_connected:
|
|
186
|
+
return {
|
|
187
|
+
"connected": False,
|
|
188
|
+
"error": "Slack not connected. Connect at quickcall.dev/assistant.",
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
client = _get_client()
|
|
193
|
+
status = client.health_check()
|
|
194
|
+
|
|
195
|
+
if status.get("connected"):
|
|
196
|
+
status["team_name"] = creds.slack_team_name
|
|
197
|
+
status["team_id"] = creds.slack_team_id
|
|
198
|
+
return status
|
|
199
|
+
except Exception as e:
|
|
200
|
+
return {
|
|
201
|
+
"connected": False,
|
|
202
|
+
"error": str(e),
|
|
203
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickcall-integrations
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.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
|
+
Requires-Dist: httpx>=0.28.0
|
|
7
8
|
Requires-Dist: pydantic>=2.11.7
|
|
9
|
+
Requires-Dist: pygithub>=2.8.1
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
|
|
10
12
|
<p align="center">
|
|
@@ -17,6 +19,11 @@ Description-Content-Type: text/markdown
|
|
|
17
19
|
<em>Ask about your work, get instant answers. No more context switching.</em>
|
|
18
20
|
</p>
|
|
19
21
|
|
|
22
|
+
<p align="center">
|
|
23
|
+
<a href="https://quickcall.dev"><img src="https://img.shields.io/badge/Web-quickcall.dev-000000?logo=googlechrome&logoColor=white" alt="Web"></a>
|
|
24
|
+
<a href="https://discord.gg/DtnMxuE35v"><img src="https://img.shields.io/badge/Discord-Join%20Us-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
20
27
|
<p align="center">
|
|
21
28
|
<a href="#claude-code">Claude Code</a> |
|
|
22
29
|
<a href="#cursor">Cursor</a> |
|
|
@@ -26,12 +33,12 @@ Description-Content-Type: text/markdown
|
|
|
26
33
|
|
|
27
34
|
---
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
## Current integrations
|
|
37
|
+
|
|
30
38
|
- Git - commits, diffs, code changes
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Slack
|
|
40
|
+
## Coming soon
|
|
41
|
+
|
|
35
42
|
- GitHub PRs & Issues
|
|
36
43
|
|
|
37
44
|
## Install
|
|
@@ -82,6 +89,8 @@ Add to your Cursor MCP config (`~/.cursor/mcp.json` for global, or `.cursor/mcp.
|
|
|
82
89
|
|
|
83
90
|
Then restart Cursor.
|
|
84
91
|
|
|
92
|
+
> Also works with [Antigravity](https://antigravity.dev) and any other IDE that supports MCP servers.
|
|
93
|
+
|
|
85
94
|
## Commands
|
|
86
95
|
|
|
87
96
|
### Claude Code
|
|
@@ -121,3 +130,9 @@ git push origin v0.1.0
|
|
|
121
130
|
```
|
|
122
131
|
|
|
123
132
|
Or trigger manually from GitHub Actions page.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
<p align="center">
|
|
137
|
+
Built with ❤️ by <a href="https://quickcall.dev">QuickCall</a>
|
|
138
|
+
</p>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
mcp_server/__init__.py,sha256=wAVZ0eHQoGovs-66UH9-kRkcv37bprVEUeinyUFS_KI,98
|
|
2
|
+
mcp_server/server.py,sha256=9Ojv5DoQrCeyC6lDD3keh9BuLyNKB6mb6FUslP6z0O8,2839
|
|
3
|
+
mcp_server/api_clients/__init__.py,sha256=kOG5_sxIVpAx_tvf1nq_P0QCkqojAVidRE-wenLS-Wc,207
|
|
4
|
+
mcp_server/api_clients/github_client.py,sha256=Wj326ImYI11eFyItP5HQ4TD7aQ4nC6WAR12ZbneBiAQ,13569
|
|
5
|
+
mcp_server/api_clients/slack_client.py,sha256=Tby3vkPo-yN38Egb6Cj7MQk6Ul3DV4BOp5nVWScR4cw,10424
|
|
6
|
+
mcp_server/auth/__init__.py,sha256=YQpDPH5itIaBuEm0AtwNCHxTX4L5dLutTximVamsItw,552
|
|
7
|
+
mcp_server/auth/credentials.py,sha256=OCPs_4DcQ1zHEBgkcPDNCHVFFO36Xe6_QBx_5Jn2xgk,9379
|
|
8
|
+
mcp_server/auth/device_flow.py,sha256=NXNWHzd-CA4dlhEVCgUhwfpe9TpMKpLSJuyFCh70xKs,8371
|
|
9
|
+
mcp_server/tools/__init__.py,sha256=vIR2ujAaTXm2DgpTsVNz3brI4G34p-Jeg44Qe0uvWc0,405
|
|
10
|
+
mcp_server/tools/auth_tools.py,sha256=wuhEucxeTT08DWT1TCxoYrEa6Jy2MIpOlXBxtCeYEXQ,14586
|
|
11
|
+
mcp_server/tools/git_tools.py,sha256=5cZfngkP1wHNYUvGtLFcMjS7bhrFzxAC_TPz0h3CUB0,7691
|
|
12
|
+
mcp_server/tools/github_tools.py,sha256=GomR88SByAbdi4VHk1vaUNp29hwWEIY6cX1t9QoMDOU,10972
|
|
13
|
+
mcp_server/tools/slack_tools.py,sha256=uXCxnzLfdi5LaM3ayVS5JT7F3MAnI6C0vB7jW0tZfjY,6303
|
|
14
|
+
mcp_server/tools/utility_tools.py,sha256=1WiOpJivu6Ug9OLajm77lzsmFfBPgWHs8e1hNCEX_Aw,3359
|
|
15
|
+
quickcall_integrations-0.1.4.dist-info/METADATA,sha256=MGQRUdJJyCB5SbPPPp3VyF8KXI5ubYOQoJvetKBDphg,3032
|
|
16
|
+
quickcall_integrations-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
+
quickcall_integrations-0.1.4.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
|
|
18
|
+
quickcall_integrations-0.1.4.dist-info/RECORD,,
|
mcp_server/config.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
mcp_server/__init__.py,sha256=wAVZ0eHQoGovs-66UH9-kRkcv37bprVEUeinyUFS_KI,98
|
|
2
|
-
mcp_server/config.py,sha256=ZvvON2pHa7eYvGjP6-SMdm-pkr3-umvtLCtZcV4WnVQ,212
|
|
3
|
-
mcp_server/server.py,sha256=EWrhqcAKrbauscgnaa7kwh7l6ReflLl2Quv0GDiWtgI,945
|
|
4
|
-
mcp_server/tools/__init__.py,sha256=Vqy1qzTxdt90tWFN9ZLEhTCX8aIHFB8TmMR_z70qLtE,42
|
|
5
|
-
mcp_server/tools/git_tools.py,sha256=RZQLx1znOtLI7UF24ZwT-NrbvcGlLsPjm4JUmrQ9bIY,7159
|
|
6
|
-
mcp_server/tools/utility_tools.py,sha256=1WiOpJivu6Ug9OLajm77lzsmFfBPgWHs8e1hNCEX_Aw,3359
|
|
7
|
-
quickcall_integrations-0.1.3.dist-info/METADATA,sha256=d93t0WP-AOSXfDINB-PWkYVjAlVtHRAHXHEX46VVFVY,2463
|
|
8
|
-
quickcall_integrations-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
-
quickcall_integrations-0.1.3.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
|
|
10
|
-
quickcall_integrations-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
{quickcall_integrations-0.1.3.dist-info → quickcall_integrations-0.1.4.dist-info}/entry_points.txt
RENAMED
|
File without changes
|