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,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication tools for QuickCall MCP.
|
|
3
|
+
|
|
4
|
+
Provides tools for users to connect, check status, and disconnect
|
|
5
|
+
their QuickCall account from the CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
import webbrowser
|
|
11
|
+
from typing import Dict, Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from fastmcp import FastMCP
|
|
15
|
+
|
|
16
|
+
from mcp_server.auth import (
|
|
17
|
+
get_credential_store,
|
|
18
|
+
is_authenticated,
|
|
19
|
+
DeviceFlowAuth,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# QuickCall API URL - configurable for local dev
|
|
25
|
+
QUICKCALL_API_URL = os.getenv("QUICKCALL_API_URL", "https://api.quickcall.dev")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_auth_tools(mcp: FastMCP):
|
|
29
|
+
"""Register authentication tools with the MCP server."""
|
|
30
|
+
|
|
31
|
+
@mcp.tool(tags={"auth", "quickcall"})
|
|
32
|
+
def connect_quickcall() -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Connect your CLI to QuickCall.
|
|
35
|
+
|
|
36
|
+
This starts the OAuth device flow authentication:
|
|
37
|
+
1. Opens your browser to quickcall.dev
|
|
38
|
+
2. You sign in with Google
|
|
39
|
+
3. Your CLI is linked to your account
|
|
40
|
+
|
|
41
|
+
After connecting, you can use GitHub and Slack tools
|
|
42
|
+
with your configured integrations from quickcall.dev.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Authentication result with status and instructions
|
|
46
|
+
"""
|
|
47
|
+
store = get_credential_store()
|
|
48
|
+
|
|
49
|
+
# Check if already authenticated
|
|
50
|
+
if store.is_authenticated():
|
|
51
|
+
status = store.get_status()
|
|
52
|
+
return {
|
|
53
|
+
"status": "already_connected",
|
|
54
|
+
"message": "You're already connected to QuickCall!",
|
|
55
|
+
"user": status.get("username") or status.get("email"),
|
|
56
|
+
"github_connected": status.get("github", {}).get("connected", False),
|
|
57
|
+
"slack_connected": status.get("slack", {}).get("connected", False),
|
|
58
|
+
"hint": "Use check_quickcall_status for details or disconnect_quickcall to logout.",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Start device flow
|
|
62
|
+
auth = DeviceFlowAuth()
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Initialize flow
|
|
66
|
+
device_code, user_code, verification_url, expires_in, interval = (
|
|
67
|
+
auth.init_flow()
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Build URL with code
|
|
71
|
+
auth_url = f"{verification_url}?code={user_code}"
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"status": "pending",
|
|
75
|
+
"message": "Authentication started! Complete the following steps:",
|
|
76
|
+
"code": user_code,
|
|
77
|
+
"url": auth_url,
|
|
78
|
+
"instructions": [
|
|
79
|
+
f"1. Open this URL in your browser: {auth_url}",
|
|
80
|
+
"2. Sign in with Google",
|
|
81
|
+
"3. Your CLI will be connected automatically",
|
|
82
|
+
],
|
|
83
|
+
"expires_in_minutes": expires_in // 60,
|
|
84
|
+
"hint": "The browser should open automatically. If not, copy the URL above.",
|
|
85
|
+
"_device_code": device_code,
|
|
86
|
+
"_interval": interval,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.error(f"Failed to start authentication: {e}")
|
|
91
|
+
return {
|
|
92
|
+
"status": "error",
|
|
93
|
+
"message": f"Failed to start authentication: {e}",
|
|
94
|
+
"hint": "Check your internet connection and try again.",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@mcp.tool(tags={"auth", "quickcall"})
|
|
98
|
+
def check_quickcall_status() -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Check your QuickCall connection status.
|
|
101
|
+
|
|
102
|
+
Shows:
|
|
103
|
+
- Whether you're connected
|
|
104
|
+
- Your account info
|
|
105
|
+
- GitHub connection status
|
|
106
|
+
- Slack connection status
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Current authentication and integration status
|
|
110
|
+
"""
|
|
111
|
+
store = get_credential_store()
|
|
112
|
+
|
|
113
|
+
if not store.is_authenticated():
|
|
114
|
+
return {
|
|
115
|
+
"connected": False,
|
|
116
|
+
"message": "Not connected to QuickCall",
|
|
117
|
+
"hint": "Use connect_quickcall to authenticate.",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
status = store.get_status()
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"connected": True,
|
|
124
|
+
"user": {
|
|
125
|
+
"id": status.get("user_id"),
|
|
126
|
+
"email": status.get("email"),
|
|
127
|
+
"username": status.get("username"),
|
|
128
|
+
},
|
|
129
|
+
"authenticated_at": status.get("authenticated_at"),
|
|
130
|
+
"integrations": {
|
|
131
|
+
"github": {
|
|
132
|
+
"connected": status.get("github", {}).get("connected", False),
|
|
133
|
+
"username": status.get("github", {}).get("username"),
|
|
134
|
+
},
|
|
135
|
+
"slack": {
|
|
136
|
+
"connected": status.get("slack", {}).get("connected", False),
|
|
137
|
+
"team_name": status.get("slack", {}).get("team_name"),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
"credentials_file": status.get("credentials_file"),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@mcp.tool(tags={"auth", "quickcall"})
|
|
144
|
+
def disconnect_quickcall() -> Dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Disconnect your CLI from QuickCall.
|
|
147
|
+
|
|
148
|
+
This removes your local credentials. You'll need to
|
|
149
|
+
run connect_quickcall again to use GitHub and Slack tools.
|
|
150
|
+
|
|
151
|
+
Note: This doesn't revoke the device from your QuickCall
|
|
152
|
+
account. To fully revoke access, visit quickcall.dev/settings.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Disconnection result
|
|
156
|
+
"""
|
|
157
|
+
store = get_credential_store()
|
|
158
|
+
|
|
159
|
+
if not store.is_authenticated():
|
|
160
|
+
return {
|
|
161
|
+
"status": "not_connected",
|
|
162
|
+
"message": "You're not connected to QuickCall.",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Get user info before clearing
|
|
167
|
+
status = store.get_status()
|
|
168
|
+
user = (
|
|
169
|
+
status.get("username") or status.get("email") or status.get("user_id")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Clear credentials
|
|
173
|
+
store.clear()
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"status": "disconnected",
|
|
177
|
+
"message": f"Disconnected from QuickCall ({user})",
|
|
178
|
+
"hint": "To fully revoke access, visit quickcall.dev/settings. Use connect_quickcall to reconnect.",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Failed to disconnect: {e}")
|
|
183
|
+
return {
|
|
184
|
+
"status": "error",
|
|
185
|
+
"message": f"Failed to disconnect: {e}",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@mcp.tool(tags={"auth", "quickcall"})
|
|
189
|
+
def complete_quickcall_auth(
|
|
190
|
+
device_code: str, timeout_seconds: int = 300
|
|
191
|
+
) -> Dict[str, Any]:
|
|
192
|
+
"""
|
|
193
|
+
Complete QuickCall authentication after browser sign-in.
|
|
194
|
+
|
|
195
|
+
This polls for the authentication result after you've signed
|
|
196
|
+
in via the browser. Usually called automatically after connect_quickcall.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
device_code: The device code from connect_quickcall
|
|
200
|
+
timeout_seconds: How long to wait for authentication (default: 5 minutes)
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Authentication result
|
|
204
|
+
"""
|
|
205
|
+
auth = DeviceFlowAuth()
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
credentials = auth.poll_for_completion(
|
|
209
|
+
device_code=device_code,
|
|
210
|
+
timeout=timeout_seconds,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if credentials:
|
|
214
|
+
return {
|
|
215
|
+
"status": "success",
|
|
216
|
+
"message": "Successfully connected to QuickCall!",
|
|
217
|
+
"user_id": credentials.user_id,
|
|
218
|
+
"email": credentials.email,
|
|
219
|
+
"hint": "You can now use GitHub and Slack tools. Run check_quickcall_status to see your integrations.",
|
|
220
|
+
}
|
|
221
|
+
else:
|
|
222
|
+
return {
|
|
223
|
+
"status": "failed",
|
|
224
|
+
"message": "Authentication failed or timed out.",
|
|
225
|
+
"hint": "Try running connect_quickcall again.",
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"Authentication error: {e}")
|
|
230
|
+
return {
|
|
231
|
+
"status": "error",
|
|
232
|
+
"message": f"Authentication error: {e}",
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@mcp.tool(tags={"auth", "github", "quickcall"})
|
|
236
|
+
def connect_github(open_browser: bool = True) -> Dict[str, Any]:
|
|
237
|
+
"""
|
|
238
|
+
Connect GitHub to your QuickCall account.
|
|
239
|
+
|
|
240
|
+
This opens your browser to install the QuickCall GitHub App.
|
|
241
|
+
After installation, you'll be able to use GitHub tools like
|
|
242
|
+
list_repos, create_issue, etc.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
open_browser: Automatically open the install URL in browser (default: True)
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Install URL and instructions
|
|
249
|
+
"""
|
|
250
|
+
store = get_credential_store()
|
|
251
|
+
|
|
252
|
+
if not store.is_authenticated():
|
|
253
|
+
return {
|
|
254
|
+
"status": "error",
|
|
255
|
+
"message": "Not connected to QuickCall",
|
|
256
|
+
"hint": "Run connect_quickcall first to authenticate.",
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
stored = store.get_stored_credentials()
|
|
260
|
+
if not stored:
|
|
261
|
+
return {
|
|
262
|
+
"status": "error",
|
|
263
|
+
"message": "No stored credentials found",
|
|
264
|
+
"hint": "Run connect_quickcall to authenticate.",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
with httpx.Client(timeout=30.0) as client:
|
|
269
|
+
response = client.get(
|
|
270
|
+
f"{QUICKCALL_API_URL}/api/cli/github/install-url",
|
|
271
|
+
headers={"Authorization": f"Bearer {stored.device_token}"},
|
|
272
|
+
)
|
|
273
|
+
response.raise_for_status()
|
|
274
|
+
data = response.json()
|
|
275
|
+
|
|
276
|
+
if data.get("already_connected"):
|
|
277
|
+
return {
|
|
278
|
+
"status": "already_connected",
|
|
279
|
+
"message": f"GitHub is already connected (username: {data.get('username')})",
|
|
280
|
+
"hint": "You can use GitHub tools like list_repos, create_issue, etc.",
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
install_url = data.get("install_url")
|
|
284
|
+
|
|
285
|
+
if open_browser and install_url:
|
|
286
|
+
try:
|
|
287
|
+
webbrowser.open(install_url)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.warning(f"Failed to open browser: {e}")
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"status": "pending",
|
|
293
|
+
"message": "Please complete GitHub App installation in your browser.",
|
|
294
|
+
"install_url": install_url,
|
|
295
|
+
"instructions": [
|
|
296
|
+
f"1. Open this URL: {install_url}",
|
|
297
|
+
"2. Select the organization/account to install",
|
|
298
|
+
"3. Choose which repositories to grant access",
|
|
299
|
+
"4. Click 'Install'",
|
|
300
|
+
],
|
|
301
|
+
"hint": "After installation, run check_quickcall_status to verify.",
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
except httpx.HTTPStatusError as e:
|
|
305
|
+
if e.response.status_code == 401:
|
|
306
|
+
return {
|
|
307
|
+
"status": "error",
|
|
308
|
+
"message": "Session expired. Please reconnect.",
|
|
309
|
+
"hint": "Run disconnect_quickcall then connect_quickcall again.",
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
"status": "error",
|
|
313
|
+
"message": f"API error: {e.response.status_code}",
|
|
314
|
+
}
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Failed to get GitHub install URL: {e}")
|
|
317
|
+
return {
|
|
318
|
+
"status": "error",
|
|
319
|
+
"message": f"Failed to connect GitHub: {e}",
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@mcp.tool(tags={"auth", "slack", "quickcall"})
|
|
323
|
+
def connect_slack(open_browser: bool = True) -> Dict[str, Any]:
|
|
324
|
+
"""
|
|
325
|
+
Connect Slack to your QuickCall account.
|
|
326
|
+
|
|
327
|
+
This opens your browser to authorize the QuickCall Slack App.
|
|
328
|
+
After authorization, you'll be able to use Slack tools like
|
|
329
|
+
list_channels, send_message, etc.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
open_browser: Automatically open the install URL in browser (default: True)
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Install URL and instructions
|
|
336
|
+
"""
|
|
337
|
+
store = get_credential_store()
|
|
338
|
+
|
|
339
|
+
if not store.is_authenticated():
|
|
340
|
+
return {
|
|
341
|
+
"status": "error",
|
|
342
|
+
"message": "Not connected to QuickCall",
|
|
343
|
+
"hint": "Run connect_quickcall first to authenticate.",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
stored = store.get_stored_credentials()
|
|
347
|
+
if not stored:
|
|
348
|
+
return {
|
|
349
|
+
"status": "error",
|
|
350
|
+
"message": "No stored credentials found",
|
|
351
|
+
"hint": "Run connect_quickcall to authenticate.",
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
with httpx.Client(timeout=30.0) as client:
|
|
356
|
+
response = client.get(
|
|
357
|
+
f"{QUICKCALL_API_URL}/api/cli/slack/install-url",
|
|
358
|
+
headers={"Authorization": f"Bearer {stored.device_token}"},
|
|
359
|
+
)
|
|
360
|
+
response.raise_for_status()
|
|
361
|
+
data = response.json()
|
|
362
|
+
|
|
363
|
+
if data.get("already_connected"):
|
|
364
|
+
return {
|
|
365
|
+
"status": "already_connected",
|
|
366
|
+
"message": f"Slack is already connected (workspace: {data.get('team_name')})",
|
|
367
|
+
"hint": "You can use Slack tools like list_channels, send_message, etc.",
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
install_url = data.get("install_url")
|
|
371
|
+
|
|
372
|
+
if open_browser and install_url:
|
|
373
|
+
try:
|
|
374
|
+
webbrowser.open(install_url)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
logger.warning(f"Failed to open browser: {e}")
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"status": "pending",
|
|
380
|
+
"message": "Please complete Slack authorization in your browser.",
|
|
381
|
+
"install_url": install_url,
|
|
382
|
+
"instructions": [
|
|
383
|
+
f"1. Open this URL: {install_url}",
|
|
384
|
+
"2. Select the Slack workspace",
|
|
385
|
+
"3. Review permissions and click 'Allow'",
|
|
386
|
+
],
|
|
387
|
+
"hint": "After authorization, run check_quickcall_status to verify.",
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
except httpx.HTTPStatusError as e:
|
|
391
|
+
if e.response.status_code == 401:
|
|
392
|
+
return {
|
|
393
|
+
"status": "error",
|
|
394
|
+
"message": "Session expired. Please reconnect.",
|
|
395
|
+
"hint": "Run disconnect_quickcall then connect_quickcall again.",
|
|
396
|
+
}
|
|
397
|
+
if e.response.status_code == 503:
|
|
398
|
+
return {
|
|
399
|
+
"status": "error",
|
|
400
|
+
"message": "Slack integration is not configured on the server.",
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
"status": "error",
|
|
404
|
+
"message": f"API error: {e.response.status_code}",
|
|
405
|
+
}
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.error(f"Failed to get Slack install URL: {e}")
|
|
408
|
+
return {
|
|
409
|
+
"status": "error",
|
|
410
|
+
"message": f"Failed to connect Slack: {e}",
|
|
411
|
+
}
|
mcp_server/tools/git_tools.py
CHANGED
|
@@ -100,11 +100,15 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
100
100
|
"""
|
|
101
101
|
try:
|
|
102
102
|
repo_info = _get_repo_info(path)
|
|
103
|
-
repo_name =
|
|
103
|
+
repo_name = (
|
|
104
|
+
f"{repo_info['owner']}/{repo_info['repo']}"
|
|
105
|
+
if repo_info["owner"]
|
|
106
|
+
else repo_info["root"]
|
|
107
|
+
)
|
|
104
108
|
|
|
105
109
|
result = {
|
|
106
110
|
"repository": repo_name,
|
|
107
|
-
"branch": repo_info[
|
|
111
|
+
"branch": repo_info["branch"],
|
|
108
112
|
"period": f"Last {days} days",
|
|
109
113
|
}
|
|
110
114
|
|
|
@@ -123,18 +127,25 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
123
127
|
continue
|
|
124
128
|
parts = line.split("|", 3)
|
|
125
129
|
if len(parts) >= 4:
|
|
126
|
-
commits.append(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
commits.append(
|
|
131
|
+
{
|
|
132
|
+
"sha": parts[0][:7],
|
|
133
|
+
"author": parts[1],
|
|
134
|
+
"date": parts[2],
|
|
135
|
+
"message": parts[3],
|
|
136
|
+
}
|
|
137
|
+
)
|
|
132
138
|
|
|
133
139
|
result["commits"] = commits
|
|
134
140
|
result["commit_count"] = len(commits)
|
|
135
141
|
|
|
136
142
|
if not commits:
|
|
137
|
-
result["diff"] = {
|
|
143
|
+
result["diff"] = {
|
|
144
|
+
"files_changed": 0,
|
|
145
|
+
"additions": 0,
|
|
146
|
+
"deletions": 0,
|
|
147
|
+
"patch": "",
|
|
148
|
+
}
|
|
138
149
|
return result
|
|
139
150
|
|
|
140
151
|
# Get total diff between oldest and newest commit
|
|
@@ -143,7 +154,9 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
143
154
|
|
|
144
155
|
try:
|
|
145
156
|
# Get stats
|
|
146
|
-
numstat = _run_git(
|
|
157
|
+
numstat = _run_git(
|
|
158
|
+
["diff", "--numstat", f"{oldest_sha}^", newest_sha], path
|
|
159
|
+
)
|
|
147
160
|
|
|
148
161
|
files = []
|
|
149
162
|
total_add = 0
|
|
@@ -156,11 +169,13 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
156
169
|
if len(parts) >= 3:
|
|
157
170
|
adds = int(parts[0]) if parts[0] != "-" else 0
|
|
158
171
|
dels = int(parts[1]) if parts[1] != "-" else 0
|
|
159
|
-
files.append(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
172
|
+
files.append(
|
|
173
|
+
{
|
|
174
|
+
"file": parts[2],
|
|
175
|
+
"additions": adds,
|
|
176
|
+
"deletions": dels,
|
|
177
|
+
}
|
|
178
|
+
)
|
|
164
179
|
total_add += adds
|
|
165
180
|
total_del += dels
|
|
166
181
|
|
|
@@ -169,7 +184,9 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
169
184
|
|
|
170
185
|
# Truncate if too large
|
|
171
186
|
if len(diff_patch) > 50000:
|
|
172
|
-
diff_patch =
|
|
187
|
+
diff_patch = (
|
|
188
|
+
diff_patch[:50000] + "\n\n... (truncated, diff too large)"
|
|
189
|
+
)
|
|
173
190
|
|
|
174
191
|
result["diff"] = {
|
|
175
192
|
"files_changed": len(files),
|
|
@@ -179,7 +196,12 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
179
196
|
"patch": diff_patch,
|
|
180
197
|
}
|
|
181
198
|
except ToolError:
|
|
182
|
-
result["diff"] = {
|
|
199
|
+
result["diff"] = {
|
|
200
|
+
"files_changed": 0,
|
|
201
|
+
"additions": 0,
|
|
202
|
+
"deletions": 0,
|
|
203
|
+
"patch": "",
|
|
204
|
+
}
|
|
183
205
|
|
|
184
206
|
# Uncommitted changes
|
|
185
207
|
staged = _run_git(["diff", "--cached", "--name-only"], path)
|
|
@@ -192,7 +214,9 @@ def create_git_tools(mcp: FastMCP) -> None:
|
|
|
192
214
|
# Get uncommitted diff patch too
|
|
193
215
|
uncommitted_patch = _run_git(["diff", "HEAD"], path)
|
|
194
216
|
if len(uncommitted_patch) > 20000:
|
|
195
|
-
uncommitted_patch =
|
|
217
|
+
uncommitted_patch = (
|
|
218
|
+
uncommitted_patch[:20000] + "\n\n... (truncated)"
|
|
219
|
+
)
|
|
196
220
|
|
|
197
221
|
result["uncommitted"] = {
|
|
198
222
|
"staged": staged_list,
|