@cyanheads/git-mcp-server 1.2.4
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.
- package/LICENSE +201 -0
- package/README.md +383 -0
- package/build/index.js +54 -0
- package/build/resources/descriptors.js +77 -0
- package/build/resources/diff.js +241 -0
- package/build/resources/file.js +222 -0
- package/build/resources/history.js +242 -0
- package/build/resources/index.js +99 -0
- package/build/resources/repository.js +286 -0
- package/build/server.js +120 -0
- package/build/services/error-service.js +73 -0
- package/build/services/git-service.js +965 -0
- package/build/tools/advanced.js +526 -0
- package/build/tools/branch.js +296 -0
- package/build/tools/index.js +29 -0
- package/build/tools/remote.js +279 -0
- package/build/tools/repository.js +170 -0
- package/build/tools/workdir.js +445 -0
- package/build/types/git.js +7 -0
- package/build/utils/global-settings.js +64 -0
- package/build/utils/validation.js +108 -0
- package/package.json +39 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Git Tools
|
|
3
|
+
* ================
|
|
4
|
+
*
|
|
5
|
+
* MCP tools for advanced Git operations like stashing, tagging, rebasing, etc.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { GitService } from '../services/git-service.js';
|
|
9
|
+
import { PathValidation } from '../utils/validation.js';
|
|
10
|
+
/**
|
|
11
|
+
* Registers advanced Git tools with the MCP server
|
|
12
|
+
*
|
|
13
|
+
* @param server - MCP server instance
|
|
14
|
+
*/
|
|
15
|
+
export function setupAdvancedTools(server) {
|
|
16
|
+
// Create tag
|
|
17
|
+
server.tool("git_tag_create", "Create a new tag in the repository. Tags are references that point to specific commits, useful for marking release points or important commits. Can create lightweight tags or annotated tags with messages.", {
|
|
18
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
19
|
+
name: z.string().min(1, "Tag name is required").describe("Name for the new tag"),
|
|
20
|
+
message: z.string().optional().describe("Optional message for an annotated tag"),
|
|
21
|
+
ref: z.string().optional().describe("Reference (commit, branch) to create the tag at")
|
|
22
|
+
}, async ({ path, name, message, ref }) => {
|
|
23
|
+
try {
|
|
24
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
25
|
+
const gitService = new GitService(normalizedPath);
|
|
26
|
+
// Check if this is a git repository
|
|
27
|
+
const isRepo = await gitService.isGitRepository();
|
|
28
|
+
if (!isRepo) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
33
|
+
}],
|
|
34
|
+
isError: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const result = await gitService.createTag({
|
|
38
|
+
name,
|
|
39
|
+
message,
|
|
40
|
+
ref
|
|
41
|
+
});
|
|
42
|
+
if (!result.resultSuccessful) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
47
|
+
}],
|
|
48
|
+
isError: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `Successfully created ${message ? 'annotated ' : ''}tag '${name}'${ref ? ` at ref '${ref}'` : ''}`
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
63
|
+
}],
|
|
64
|
+
isError: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// List tags
|
|
69
|
+
server.tool("git_tag_list", "List all tags in the repository. Displays all tag names that exist in the repository, which can be used to identify releases or important reference points.", {
|
|
70
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository")
|
|
71
|
+
}, async ({ path }) => {
|
|
72
|
+
try {
|
|
73
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
74
|
+
const gitService = new GitService(normalizedPath);
|
|
75
|
+
// Check if this is a git repository
|
|
76
|
+
const isRepo = await gitService.isGitRepository();
|
|
77
|
+
if (!isRepo) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
82
|
+
}],
|
|
83
|
+
isError: true
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const result = await gitService.listTags();
|
|
87
|
+
if (!result.resultSuccessful) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
92
|
+
}],
|
|
93
|
+
isError: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (result.resultData.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `No tags found in repository at: ${normalizedPath}`
|
|
101
|
+
}]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Format output
|
|
105
|
+
let output = `Tags in repository at: ${normalizedPath}\n\n`;
|
|
106
|
+
result.resultData.forEach(tag => {
|
|
107
|
+
output += `${tag}\n`;
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
content: [{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: output
|
|
113
|
+
}]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
content: [{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
121
|
+
}],
|
|
122
|
+
isError: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Create stash
|
|
127
|
+
server.tool("git_stash_create", "Save uncommitted changes to a stash. Captures the current state of working directory and index and saves it on a stack of stashes, allowing you to switch branches without committing in-progress work.", {
|
|
128
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
129
|
+
message: z.string().optional().describe("Optional description for the stash"),
|
|
130
|
+
includeUntracked: z.boolean().optional().default(false).describe("Whether to include untracked files in the stash")
|
|
131
|
+
}, async ({ path, message, includeUntracked }) => {
|
|
132
|
+
try {
|
|
133
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
134
|
+
const gitService = new GitService(normalizedPath);
|
|
135
|
+
// Check if this is a git repository
|
|
136
|
+
const isRepo = await gitService.isGitRepository();
|
|
137
|
+
if (!isRepo) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
142
|
+
}],
|
|
143
|
+
isError: true
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const result = await gitService.createStash({
|
|
147
|
+
message,
|
|
148
|
+
includeUntracked
|
|
149
|
+
});
|
|
150
|
+
if (!result.resultSuccessful) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
155
|
+
}],
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
content: [{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `Successfully created stash${message ? ` with message: "${message}"` : ''}${includeUntracked ? ' (including untracked files)' : ''}`
|
|
163
|
+
}]
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
171
|
+
}],
|
|
172
|
+
isError: true
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
// List stashes
|
|
177
|
+
server.tool("git_stash_list", "List all stashes in the repository. Shows the stack of stashes that have been created and their descriptions, allowing you to identify the stash you want to apply or pop.", {
|
|
178
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository")
|
|
179
|
+
}, async ({ path }) => {
|
|
180
|
+
try {
|
|
181
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
182
|
+
const gitService = new GitService(normalizedPath);
|
|
183
|
+
// Check if this is a git repository
|
|
184
|
+
const isRepo = await gitService.isGitRepository();
|
|
185
|
+
if (!isRepo) {
|
|
186
|
+
return {
|
|
187
|
+
content: [{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
190
|
+
}],
|
|
191
|
+
isError: true
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const result = await gitService.listStashes();
|
|
195
|
+
if (!result.resultSuccessful) {
|
|
196
|
+
return {
|
|
197
|
+
content: [{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
200
|
+
}],
|
|
201
|
+
isError: true
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (result.resultData.trim() === '') {
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: `No stashes found in repository at: ${normalizedPath}`
|
|
209
|
+
}]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
content: [{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: `Stashes in repository at: ${normalizedPath}\n\n${result.resultData}`
|
|
216
|
+
}]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
content: [{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
224
|
+
}],
|
|
225
|
+
isError: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// Apply stash
|
|
230
|
+
server.tool("git_stash_apply", "Apply stashed changes to the working directory. Applies changes from the specified stash to the current working directory, but keeps the stash in the stash list.", {
|
|
231
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
232
|
+
stashId: z.string().optional().default("stash@{0}").describe("Stash reference to apply (defaults to most recent stash)")
|
|
233
|
+
}, async ({ path, stashId }) => {
|
|
234
|
+
try {
|
|
235
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
236
|
+
const gitService = new GitService(normalizedPath);
|
|
237
|
+
// Check if this is a git repository
|
|
238
|
+
const isRepo = await gitService.isGitRepository();
|
|
239
|
+
if (!isRepo) {
|
|
240
|
+
return {
|
|
241
|
+
content: [{
|
|
242
|
+
type: "text",
|
|
243
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
244
|
+
}],
|
|
245
|
+
isError: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const result = await gitService.applyStash(stashId);
|
|
249
|
+
if (!result.resultSuccessful) {
|
|
250
|
+
return {
|
|
251
|
+
content: [{
|
|
252
|
+
type: "text",
|
|
253
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
254
|
+
}],
|
|
255
|
+
isError: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
content: [{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `Successfully applied stash: ${stashId}`
|
|
262
|
+
}]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
270
|
+
}],
|
|
271
|
+
isError: true
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
// Pop stash
|
|
276
|
+
server.tool("git_stash_pop", "Apply and remove a stash. Applies the specified stash to the working directory and then removes it from the stash stack. Combines the apply and drop operations.", {
|
|
277
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
278
|
+
stashId: z.string().optional().default("stash@{0}").describe("Stash reference to pop (defaults to most recent stash)")
|
|
279
|
+
}, async ({ path, stashId }) => {
|
|
280
|
+
try {
|
|
281
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
282
|
+
const gitService = new GitService(normalizedPath);
|
|
283
|
+
// Check if this is a git repository
|
|
284
|
+
const isRepo = await gitService.isGitRepository();
|
|
285
|
+
if (!isRepo) {
|
|
286
|
+
return {
|
|
287
|
+
content: [{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
290
|
+
}],
|
|
291
|
+
isError: true
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const result = await gitService.popStash(stashId);
|
|
295
|
+
if (!result.resultSuccessful) {
|
|
296
|
+
return {
|
|
297
|
+
content: [{
|
|
298
|
+
type: "text",
|
|
299
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
300
|
+
}],
|
|
301
|
+
isError: true
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
content: [{
|
|
306
|
+
type: "text",
|
|
307
|
+
text: `Successfully popped stash: ${stashId}`
|
|
308
|
+
}]
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
return {
|
|
313
|
+
content: [{
|
|
314
|
+
type: "text",
|
|
315
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
316
|
+
}],
|
|
317
|
+
isError: true
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
// Cherry-pick commits
|
|
322
|
+
server.tool("git_cherry_pick", "Apply changes from specific commits to the current branch. Takes the changes introduced in one or more existing commits and creates new commits with those changes on the current branch.", {
|
|
323
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
324
|
+
commits: z.array(z.string()).min(1, "At least one commit hash is required").describe("Array of commit hashes to cherry-pick")
|
|
325
|
+
}, async ({ path, commits }) => {
|
|
326
|
+
try {
|
|
327
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
328
|
+
const gitService = new GitService(normalizedPath);
|
|
329
|
+
// Check if this is a git repository
|
|
330
|
+
const isRepo = await gitService.isGitRepository();
|
|
331
|
+
if (!isRepo) {
|
|
332
|
+
return {
|
|
333
|
+
content: [{
|
|
334
|
+
type: "text",
|
|
335
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
336
|
+
}],
|
|
337
|
+
isError: true
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const result = await gitService.cherryPick(commits);
|
|
341
|
+
if (!result.resultSuccessful) {
|
|
342
|
+
return {
|
|
343
|
+
content: [{
|
|
344
|
+
type: "text",
|
|
345
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
346
|
+
}],
|
|
347
|
+
isError: true
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
content: [{
|
|
352
|
+
type: "text",
|
|
353
|
+
text: `Successfully cherry-picked ${commits.length} commit${commits.length > 1 ? 's' : ''}`
|
|
354
|
+
}]
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return {
|
|
359
|
+
content: [{
|
|
360
|
+
type: "text",
|
|
361
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
362
|
+
}],
|
|
363
|
+
isError: true
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
// Rebase
|
|
368
|
+
server.tool("git_rebase", "Reapply commits on top of another base commit. Takes all changes that were committed on one branch and replays them on another branch, providing a cleaner project history.", {
|
|
369
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
370
|
+
branch: z.string().min(1, "Branch to rebase onto is required").describe("Branch or reference to rebase onto"),
|
|
371
|
+
interactive: z.boolean().optional().default(false).describe("Whether to use interactive rebase mode")
|
|
372
|
+
}, async ({ path, branch, interactive }) => {
|
|
373
|
+
try {
|
|
374
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
375
|
+
const gitService = new GitService(normalizedPath);
|
|
376
|
+
// Check if this is a git repository
|
|
377
|
+
const isRepo = await gitService.isGitRepository();
|
|
378
|
+
if (!isRepo) {
|
|
379
|
+
return {
|
|
380
|
+
content: [{
|
|
381
|
+
type: "text",
|
|
382
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
383
|
+
}],
|
|
384
|
+
isError: true
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const result = await gitService.rebase(branch, interactive);
|
|
388
|
+
if (!result.resultSuccessful) {
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: "text",
|
|
392
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
393
|
+
}],
|
|
394
|
+
isError: true
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
content: [{
|
|
399
|
+
type: "text",
|
|
400
|
+
text: `Successfully rebased onto '${branch}'${interactive ? ' (interactive)' : ''}`
|
|
401
|
+
}]
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return {
|
|
406
|
+
content: [{
|
|
407
|
+
type: "text",
|
|
408
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
409
|
+
}],
|
|
410
|
+
isError: true
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// Log commits
|
|
415
|
+
server.tool("git_log", "Show commit history. Displays a log of commits in reverse chronological order, optionally limited to a specific file's history or a maximum number of commits.", {
|
|
416
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
417
|
+
maxCount: z.number().positive().optional().default(50).describe("Maximum number of commits to display"),
|
|
418
|
+
file: z.string().optional().describe("Optional file path to show history for a specific file")
|
|
419
|
+
}, async ({ path, maxCount, file }) => {
|
|
420
|
+
try {
|
|
421
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
422
|
+
const gitService = new GitService(normalizedPath);
|
|
423
|
+
// Check if this is a git repository
|
|
424
|
+
const isRepo = await gitService.isGitRepository();
|
|
425
|
+
if (!isRepo) {
|
|
426
|
+
return {
|
|
427
|
+
content: [{
|
|
428
|
+
type: "text",
|
|
429
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
430
|
+
}],
|
|
431
|
+
isError: true
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const result = await gitService.getLog({
|
|
435
|
+
maxCount,
|
|
436
|
+
file
|
|
437
|
+
});
|
|
438
|
+
if (!result.resultSuccessful) {
|
|
439
|
+
return {
|
|
440
|
+
content: [{
|
|
441
|
+
type: "text",
|
|
442
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
443
|
+
}],
|
|
444
|
+
isError: true
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (result.resultData.length === 0) {
|
|
448
|
+
return {
|
|
449
|
+
content: [{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: `No commits found${file ? ` for file '${file}'` : ''}`
|
|
452
|
+
}]
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// Format output
|
|
456
|
+
let output = `Commit history${file ? ` for file '${file}'` : ''} (showing up to ${maxCount} commits)\n\n`;
|
|
457
|
+
result.resultData.forEach(commit => {
|
|
458
|
+
output += `Commit: ${commit.hash}\n`;
|
|
459
|
+
output += `Author: ${commit.author} <${commit.authorEmail}>\n`;
|
|
460
|
+
output += `Date: ${commit.date.toISOString()}\n\n`;
|
|
461
|
+
output += ` ${commit.message}\n\n`;
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
content: [{
|
|
465
|
+
type: "text",
|
|
466
|
+
text: output
|
|
467
|
+
}]
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
return {
|
|
472
|
+
content: [{
|
|
473
|
+
type: "text",
|
|
474
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
475
|
+
}],
|
|
476
|
+
isError: true
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
// Show commit details
|
|
481
|
+
server.tool("git_show", "Show details of a specific commit. Displays the commit message, author, date, and the changes introduced by the commit including the diff.", {
|
|
482
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
483
|
+
commitHash: z.string().min(1, "Commit hash is required").describe("Hash or reference of the commit to display")
|
|
484
|
+
}, async ({ path, commitHash }) => {
|
|
485
|
+
try {
|
|
486
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
487
|
+
const gitService = new GitService(normalizedPath);
|
|
488
|
+
// Check if this is a git repository
|
|
489
|
+
const isRepo = await gitService.isGitRepository();
|
|
490
|
+
if (!isRepo) {
|
|
491
|
+
return {
|
|
492
|
+
content: [{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
495
|
+
}],
|
|
496
|
+
isError: true
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const result = await gitService.showCommit(commitHash);
|
|
500
|
+
if (!result.resultSuccessful) {
|
|
501
|
+
return {
|
|
502
|
+
content: [{
|
|
503
|
+
type: "text",
|
|
504
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
505
|
+
}],
|
|
506
|
+
isError: true
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
content: [{
|
|
511
|
+
type: "text",
|
|
512
|
+
text: result.resultData
|
|
513
|
+
}]
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
return {
|
|
518
|
+
content: [{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
521
|
+
}],
|
|
522
|
+
isError: true
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|