@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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch Tools
|
|
3
|
+
* ===========
|
|
4
|
+
*
|
|
5
|
+
* MCP tools for Git branch operations.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { GitService } from '../services/git-service.js';
|
|
9
|
+
import { PathValidation } from '../utils/validation.js';
|
|
10
|
+
/**
|
|
11
|
+
* Registers branch tools with the MCP server
|
|
12
|
+
*
|
|
13
|
+
* @param server - MCP server instance
|
|
14
|
+
*/
|
|
15
|
+
export function setupBranchTools(server) {
|
|
16
|
+
// List branches
|
|
17
|
+
server.tool("git_branch_list", "List branches in a repository. Displays both local and optionally remote branches, clearly marking the current branch.", {
|
|
18
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
19
|
+
all: z.boolean().optional().default(false).describe("Whether to include remote branches in the list")
|
|
20
|
+
}, async ({ path, all }) => {
|
|
21
|
+
try {
|
|
22
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
23
|
+
const gitService = new GitService(normalizedPath);
|
|
24
|
+
// Check if this is a git repository
|
|
25
|
+
const isRepo = await gitService.isGitRepository();
|
|
26
|
+
if (!isRepo) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
31
|
+
}],
|
|
32
|
+
isError: true
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const result = await gitService.listBranches(all);
|
|
36
|
+
if (!result.resultSuccessful) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
41
|
+
}],
|
|
42
|
+
isError: true
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// The listBranches result now contains the current branch
|
|
46
|
+
const branchSummary = result.resultData;
|
|
47
|
+
const currentBranch = branchSummary.current;
|
|
48
|
+
const allBranches = branchSummary.all; // Get all branch names from the summary
|
|
49
|
+
if (allBranches.length === 0) { // Check the length of the derived array
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: `No branches found in repository at: ${normalizedPath}`
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Format output
|
|
58
|
+
let output = `Branches in repository at: ${normalizedPath}\n\n`;
|
|
59
|
+
allBranches.forEach(branch => {
|
|
60
|
+
// Clean up potential remote prefixes like 'remotes/origin/' for display if needed
|
|
61
|
+
const displayBranch = branch.replace(/^remotes\/[^\/]+\//, '');
|
|
62
|
+
if (displayBranch === currentBranch) {
|
|
63
|
+
output += `* ${displayBranch} (current)\n`;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
output += ` ${displayBranch}\n`;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
content: [{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: output
|
|
73
|
+
}]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
content: [{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
81
|
+
}],
|
|
82
|
+
isError: true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Create branch
|
|
87
|
+
server.tool("git_branch_create", "Create a new branch. Creates a new branch at the specified reference point (commit or branch) and optionally checks it out.", {
|
|
88
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
89
|
+
name: z.string().min(1, "Branch name is required").describe("Name of the new branch to create"),
|
|
90
|
+
startPoint: z.string().optional().describe("Reference (commit, branch) to create the branch from"),
|
|
91
|
+
checkout: z.boolean().optional().default(false).describe("Whether to checkout the newly created branch")
|
|
92
|
+
}, async ({ path, name, startPoint, checkout }) => {
|
|
93
|
+
try {
|
|
94
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
95
|
+
const gitService = new GitService(normalizedPath);
|
|
96
|
+
// Check if this is a git repository
|
|
97
|
+
const isRepo = await gitService.isGitRepository();
|
|
98
|
+
if (!isRepo) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
103
|
+
}],
|
|
104
|
+
isError: true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const result = await gitService.createBranch({
|
|
108
|
+
name,
|
|
109
|
+
startPoint,
|
|
110
|
+
checkout
|
|
111
|
+
});
|
|
112
|
+
if (!result.resultSuccessful) {
|
|
113
|
+
return {
|
|
114
|
+
content: [{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
117
|
+
}],
|
|
118
|
+
isError: true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: `Successfully created branch '${name}'${checkout ? ' and checked it out' : ''}`
|
|
125
|
+
}]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
content: [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
133
|
+
}],
|
|
134
|
+
isError: true
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// Checkout branch
|
|
139
|
+
server.tool("git_checkout", "Checkout a branch, tag, or commit. Switches the working directory to the specified target and updates HEAD to point to it. Can optionally create a new branch.", {
|
|
140
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
141
|
+
target: z.string().min(1, "Branch or commit to checkout is required").describe("Branch name, tag, or commit hash to checkout"),
|
|
142
|
+
createBranch: z.boolean().optional().default(false).describe("Whether to create a new branch with the specified name")
|
|
143
|
+
}, async ({ path, target, createBranch }) => {
|
|
144
|
+
try {
|
|
145
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
146
|
+
const gitService = new GitService(normalizedPath);
|
|
147
|
+
// Check if this is a git repository
|
|
148
|
+
const isRepo = await gitService.isGitRepository();
|
|
149
|
+
if (!isRepo) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
154
|
+
}],
|
|
155
|
+
isError: true
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const result = await gitService.checkout(target, createBranch);
|
|
159
|
+
if (!result.resultSuccessful) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
164
|
+
}],
|
|
165
|
+
isError: true
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
content: [{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: `Successfully checked out '${target}'${createBranch ? ' (new branch)' : ''}`
|
|
172
|
+
}]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{
|
|
178
|
+
type: "text",
|
|
179
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
180
|
+
}],
|
|
181
|
+
isError: true
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
// Delete branch
|
|
186
|
+
server.tool("git_branch_delete", "Delete a branch. Removes the specified branch from the repository. By default, only fully merged branches can be deleted unless force is set to true.", {
|
|
187
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
188
|
+
branch: z.string().min(1, "Branch name is required").describe("Name of the branch to delete"),
|
|
189
|
+
force: z.boolean().optional().default(false).describe("Force deletion even if branch is not fully merged")
|
|
190
|
+
}, async ({ path, branch, force }) => {
|
|
191
|
+
try {
|
|
192
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
193
|
+
const gitService = new GitService(normalizedPath);
|
|
194
|
+
// Check if this is a git repository
|
|
195
|
+
const isRepo = await gitService.isGitRepository();
|
|
196
|
+
if (!isRepo) {
|
|
197
|
+
return {
|
|
198
|
+
content: [{
|
|
199
|
+
type: "text",
|
|
200
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
201
|
+
}],
|
|
202
|
+
isError: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const result = await gitService.deleteBranch(branch, force);
|
|
206
|
+
if (!result.resultSuccessful) {
|
|
207
|
+
return {
|
|
208
|
+
content: [{
|
|
209
|
+
type: "text",
|
|
210
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
211
|
+
}],
|
|
212
|
+
isError: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
content: [{
|
|
217
|
+
type: "text",
|
|
218
|
+
text: `Successfully deleted branch '${branch}'`
|
|
219
|
+
}]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text",
|
|
226
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
227
|
+
}],
|
|
228
|
+
isError: true
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
// Merge branch
|
|
233
|
+
server.tool("git_merge", "Merge a branch into the current branch. Combines changes from the specified branch into the current branch with configurable merge strategies.", {
|
|
234
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
235
|
+
branch: z.string().min(1, "Branch to merge is required").describe("Name of the branch to merge into the current branch"),
|
|
236
|
+
message: z.string().optional().describe("Custom commit message for the merge commit"),
|
|
237
|
+
fastForwardOnly: z.boolean().optional().default(false).describe("Only allow fast-forward merges (fail if not possible)"),
|
|
238
|
+
noFastForward: z.boolean().optional().default(false).describe("Create a merge commit even when fast-forward is possible")
|
|
239
|
+
}, async ({ path, branch, message, fastForwardOnly, noFastForward }) => {
|
|
240
|
+
try {
|
|
241
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
242
|
+
const gitService = new GitService(normalizedPath);
|
|
243
|
+
// Check if this is a git repository
|
|
244
|
+
const isRepo = await gitService.isGitRepository();
|
|
245
|
+
if (!isRepo) {
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
250
|
+
}],
|
|
251
|
+
isError: true
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// Can't have both fastForwardOnly and noFastForward
|
|
255
|
+
if (fastForwardOnly && noFastForward) {
|
|
256
|
+
return {
|
|
257
|
+
content: [{
|
|
258
|
+
type: "text",
|
|
259
|
+
text: `Error: Cannot specify both fastForwardOnly and noFastForward`
|
|
260
|
+
}],
|
|
261
|
+
isError: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const result = await gitService.merge({
|
|
265
|
+
branch,
|
|
266
|
+
message,
|
|
267
|
+
fastForwardOnly,
|
|
268
|
+
noFastForward
|
|
269
|
+
});
|
|
270
|
+
if (!result.resultSuccessful) {
|
|
271
|
+
return {
|
|
272
|
+
content: [{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
275
|
+
}],
|
|
276
|
+
isError: true
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
content: [{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: `Successfully merged branch '${branch}' into current branch`
|
|
283
|
+
}]
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
return {
|
|
288
|
+
content: [{
|
|
289
|
+
type: "text",
|
|
290
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
291
|
+
}],
|
|
292
|
+
isError: true
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Handlers
|
|
3
|
+
* ================
|
|
4
|
+
*
|
|
5
|
+
* Entry point for all MCP tool implementations.
|
|
6
|
+
* This module registers all tool handlers with the MCP server.
|
|
7
|
+
*/
|
|
8
|
+
import { setupRepositoryTools } from './repository.js';
|
|
9
|
+
import { setupBranchTools } from './branch.js';
|
|
10
|
+
import { setupWorkdirTools } from './workdir.js';
|
|
11
|
+
import { setupRemoteTools } from './remote.js';
|
|
12
|
+
import { setupAdvancedTools } from './advanced.js';
|
|
13
|
+
/**
|
|
14
|
+
* Registers all Git MCP tools with the server
|
|
15
|
+
*
|
|
16
|
+
* @param server - MCP server instance
|
|
17
|
+
*/
|
|
18
|
+
export function registerAllTools(server) {
|
|
19
|
+
// Repository operations (init, clone, status)
|
|
20
|
+
setupRepositoryTools(server);
|
|
21
|
+
// Branch operations (create, checkout, merge, etc.)
|
|
22
|
+
setupBranchTools(server);
|
|
23
|
+
// Working directory operations (stage, unstage, commit, etc.)
|
|
24
|
+
setupWorkdirTools(server);
|
|
25
|
+
// Remote operations (add, fetch, pull, push, etc.)
|
|
26
|
+
setupRemoteTools(server);
|
|
27
|
+
// Advanced operations (stash, cherry-pick, rebase, etc.)
|
|
28
|
+
setupAdvancedTools(server);
|
|
29
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Tools
|
|
3
|
+
* ===========
|
|
4
|
+
*
|
|
5
|
+
* MCP tools for Git remote operations.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { GitService } from '../services/git-service.js';
|
|
9
|
+
import { PathValidation } from '../utils/validation.js';
|
|
10
|
+
/**
|
|
11
|
+
* Registers remote operation tools with the MCP server
|
|
12
|
+
*
|
|
13
|
+
* @param server - MCP server instance
|
|
14
|
+
*/
|
|
15
|
+
export function setupRemoteTools(server) {
|
|
16
|
+
// Add a remote
|
|
17
|
+
server.tool("git_remote_add", "Add a new remote repository reference. Creates a connection to a remote repository with a name and URL, allowing fetching and pushing changes to and from that repository.", {
|
|
18
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
19
|
+
name: z.string().min(1, "Remote name is required").describe("Name for the remote repository (e.g., 'origin')"),
|
|
20
|
+
url: z.string().url("Invalid URL format").describe("URL of the remote repository")
|
|
21
|
+
}, async ({ path, name, url }) => {
|
|
22
|
+
try {
|
|
23
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
24
|
+
const gitService = new GitService(normalizedPath);
|
|
25
|
+
// Check if this is a git repository
|
|
26
|
+
const isRepo = await gitService.isGitRepository();
|
|
27
|
+
if (!isRepo) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
32
|
+
}],
|
|
33
|
+
isError: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const result = await gitService.addRemote({
|
|
37
|
+
name,
|
|
38
|
+
url
|
|
39
|
+
});
|
|
40
|
+
if (!result.resultSuccessful) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
45
|
+
}],
|
|
46
|
+
isError: true
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: [{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `Successfully added remote '${name}' with URL '${url}'`
|
|
53
|
+
}]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
61
|
+
}],
|
|
62
|
+
isError: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// List remotes
|
|
67
|
+
server.tool("git_remote_list", "List all configured remote repositories. Displays the names and URLs of all remotes associated with the repository, showing both fetch and push URLs.", {
|
|
68
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository")
|
|
69
|
+
}, async ({ path }) => {
|
|
70
|
+
try {
|
|
71
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
72
|
+
const gitService = new GitService(normalizedPath);
|
|
73
|
+
// Check if this is a git repository
|
|
74
|
+
const isRepo = await gitService.isGitRepository();
|
|
75
|
+
if (!isRepo) {
|
|
76
|
+
return {
|
|
77
|
+
content: [{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
80
|
+
}],
|
|
81
|
+
isError: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const result = await gitService.listRemotes();
|
|
85
|
+
if (!result.resultSuccessful) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
90
|
+
}],
|
|
91
|
+
isError: true
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (result.resultData.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `No remotes found in repository at: ${normalizedPath}`
|
|
99
|
+
}]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Format output
|
|
103
|
+
let output = `Remotes in repository at: ${normalizedPath}\n\n`;
|
|
104
|
+
result.resultData.forEach(remote => {
|
|
105
|
+
output += `${remote.name}\n`;
|
|
106
|
+
output += ` fetch: ${remote.refs.fetch}\n`;
|
|
107
|
+
output += ` push: ${remote.refs.push}\n\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
|
+
// Fetch from remote
|
|
127
|
+
server.tool("git_fetch", "Fetch changes from a remote repository. Downloads objects and refs from a remote repository without merging them into local branches.", {
|
|
128
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
129
|
+
remote: z.string().optional().default("origin").describe("Name of the remote to fetch from (defaults to 'origin')"),
|
|
130
|
+
branch: z.string().optional().describe("Specific branch to fetch (fetches all branches if omitted)")
|
|
131
|
+
}, async ({ path, remote, branch }) => {
|
|
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.fetch(remote, branch);
|
|
147
|
+
if (!result.resultSuccessful) {
|
|
148
|
+
return {
|
|
149
|
+
content: [{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
152
|
+
}],
|
|
153
|
+
isError: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
content: [{
|
|
158
|
+
type: "text",
|
|
159
|
+
text: `Successfully fetched from remote '${remote}'${branch ? ` branch '${branch}'` : ''}`
|
|
160
|
+
}]
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
content: [{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
168
|
+
}],
|
|
169
|
+
isError: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// Pull from remote
|
|
174
|
+
server.tool("git_pull", "Pull changes from a remote repository. Fetches from a remote repository and integrates changes into the current branch, either by merging or rebasing.", {
|
|
175
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
176
|
+
remote: z.string().optional().describe("Name of the remote to pull from (defaults to origin)"),
|
|
177
|
+
branch: z.string().optional().describe("Branch to pull from (defaults to current tracking branch)"),
|
|
178
|
+
rebase: z.boolean().optional().default(false).describe("Whether to use rebase instead of merge when pulling")
|
|
179
|
+
}, async ({ path, remote, branch, rebase }) => {
|
|
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.pull({
|
|
195
|
+
remote,
|
|
196
|
+
branch,
|
|
197
|
+
rebase
|
|
198
|
+
});
|
|
199
|
+
if (!result.resultSuccessful) {
|
|
200
|
+
return {
|
|
201
|
+
content: [{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
204
|
+
}],
|
|
205
|
+
isError: true
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: `Successfully pulled changes${remote ? ` from remote '${remote}'` : ''}${branch ? ` branch '${branch}'` : ''}${rebase ? ' with rebase' : ''}`
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
content: [{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
220
|
+
}],
|
|
221
|
+
isError: true
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
// Push to remote
|
|
226
|
+
server.tool("git_push", "Push local changes to a remote repository. Uploads local branch commits to the remote repository, updating remote references.", {
|
|
227
|
+
path: z.string().min(1, "Repository path is required").describe("Path to the Git repository"),
|
|
228
|
+
remote: z.string().optional().default("origin").describe("Name of the remote to push to (defaults to 'origin')"),
|
|
229
|
+
branch: z.string().optional().describe("Branch to push (defaults to current branch)"),
|
|
230
|
+
force: z.boolean().optional().default(false).describe("Force push changes, overwriting remote history"),
|
|
231
|
+
setUpstream: z.boolean().optional().default(false).describe("Set upstream tracking for the branch being pushed")
|
|
232
|
+
}, async ({ path, remote, branch, force, setUpstream }) => {
|
|
233
|
+
try {
|
|
234
|
+
const normalizedPath = PathValidation.normalizePath(path);
|
|
235
|
+
const gitService = new GitService(normalizedPath);
|
|
236
|
+
// Check if this is a git repository
|
|
237
|
+
const isRepo = await gitService.isGitRepository();
|
|
238
|
+
if (!isRepo) {
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: `Error: Not a Git repository: ${normalizedPath}`
|
|
243
|
+
}],
|
|
244
|
+
isError: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const result = await gitService.push({
|
|
248
|
+
remote,
|
|
249
|
+
branch,
|
|
250
|
+
force,
|
|
251
|
+
setUpstream
|
|
252
|
+
});
|
|
253
|
+
if (!result.resultSuccessful) {
|
|
254
|
+
return {
|
|
255
|
+
content: [{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: `Error: ${result.resultError.errorMessage}`
|
|
258
|
+
}],
|
|
259
|
+
isError: true
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
content: [{
|
|
264
|
+
type: "text",
|
|
265
|
+
text: `Successfully pushed changes to remote '${remote}'${branch ? ` branch '${branch}'` : ''}${force ? ' (force push)' : ''}${setUpstream ? ' (set upstream)' : ''}`
|
|
266
|
+
}]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{
|
|
272
|
+
type: "text",
|
|
273
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
274
|
+
}],
|
|
275
|
+
isError: true
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|