@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.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * History Resources
3
+ * ================
4
+ *
5
+ * MCP resources for exposing Git commit history and related information.
6
+ */
7
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { GitService } from '../services/git-service.js';
9
+ import { PathValidation } from '../utils/validation.js';
10
+ /**
11
+ * Helper function to ensure a variable is treated as a string
12
+ *
13
+ * @param value - The value to convert to string
14
+ * @returns A string representation of the value
15
+ */
16
+ function ensureString(value) {
17
+ return Array.isArray(value) ? value[0] : value;
18
+ }
19
+ /**
20
+ * Registers history resources with the MCP server
21
+ *
22
+ * @param server - MCP server instance
23
+ * @param resourceDescriptors - Resource descriptors for metadata
24
+ */
25
+ export function setupHistoryResources(server, resourceDescriptors) {
26
+ // Commit log resource
27
+ server.resource("commit-log", new ResourceTemplate("git://repo/{repoPath}/log?maxCount={maxCount}&file={file}", { list: undefined }), {
28
+ name: "Commit History",
29
+ description: "Returns the commit history log with author, date, and message details",
30
+ mimeType: "application/json"
31
+ },
32
+ // Returns the commit history for a repository with optional file path filter and commit count limit
33
+ async (uri, variables) => {
34
+ try {
35
+ // Handle variables which might be arrays
36
+ const repoPathStr = ensureString(variables.repoPath);
37
+ const maxCountStr = variables.maxCount ? ensureString(variables.maxCount) : '50';
38
+ const fileStr = variables.file ? ensureString(variables.file) : undefined;
39
+ // Normalize paths
40
+ const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
41
+ // Parse max count
42
+ const maxCount = parseInt(maxCountStr, 10);
43
+ const gitService = new GitService(normalizedRepoPath);
44
+ // Check if the path is a Git repository
45
+ const isRepo = await gitService.isGitRepository();
46
+ if (!isRepo) {
47
+ return {
48
+ contents: [{
49
+ uri: uri.href,
50
+ text: JSON.stringify({
51
+ error: "Not a Git repository",
52
+ repoPath: normalizedRepoPath,
53
+ maxCount: isNaN(maxCount) ? undefined : maxCount,
54
+ file: fileStr
55
+ }, null, 2),
56
+ mimeType: "application/json"
57
+ }]
58
+ };
59
+ }
60
+ // Get commit log
61
+ const logResult = await gitService.getLog({
62
+ maxCount: isNaN(maxCount) ? 50 : maxCount,
63
+ file: fileStr
64
+ });
65
+ if (!logResult.resultSuccessful) {
66
+ return {
67
+ contents: [{
68
+ uri: uri.href,
69
+ text: JSON.stringify({
70
+ error: logResult.resultError.errorMessage,
71
+ repoPath: normalizedRepoPath,
72
+ maxCount: isNaN(maxCount) ? undefined : maxCount,
73
+ file: fileStr
74
+ }, null, 2),
75
+ mimeType: "application/json"
76
+ }]
77
+ };
78
+ }
79
+ return {
80
+ contents: [{
81
+ uri: uri.href,
82
+ text: JSON.stringify({
83
+ repoPath: normalizedRepoPath,
84
+ maxCount: isNaN(maxCount) ? 50 : maxCount,
85
+ file: fileStr,
86
+ commits: logResult.resultData
87
+ }, null, 2),
88
+ mimeType: "application/json"
89
+ }]
90
+ };
91
+ }
92
+ catch (error) {
93
+ return {
94
+ contents: [{
95
+ uri: uri.href,
96
+ text: JSON.stringify({
97
+ error: error instanceof Error ? error.message : String(error),
98
+ repoPath: ensureString(variables.repoPath),
99
+ maxCount: variables.maxCount ? parseInt(ensureString(variables.maxCount), 10) : 50,
100
+ file: variables.file ? ensureString(variables.file) : undefined
101
+ }, null, 2),
102
+ mimeType: "application/json"
103
+ }]
104
+ };
105
+ }
106
+ });
107
+ // File blame resource
108
+ server.resource("file-blame", new ResourceTemplate("git://repo/{repoPath}/blame/{filePath}", { list: undefined }), {
109
+ name: "File Blame",
110
+ description: "Returns line-by-line attribution showing which commit last modified each line",
111
+ mimeType: "text/plain"
112
+ },
113
+ // Returns the blame information showing which commit last modified each line of a file
114
+ async (uri, variables) => {
115
+ try {
116
+ // Handle variables which might be arrays
117
+ const repoPathStr = ensureString(variables.repoPath);
118
+ const filePathStr = ensureString(variables.filePath);
119
+ // Normalize paths
120
+ const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
121
+ const normalizedFilePath = PathValidation.normalizePath(decodeURIComponent(filePathStr));
122
+ const gitService = new GitService(normalizedRepoPath);
123
+ // Check if the path is a Git repository
124
+ const isRepo = await gitService.isGitRepository();
125
+ if (!isRepo) {
126
+ return {
127
+ contents: [{
128
+ uri: uri.href,
129
+ text: JSON.stringify({
130
+ error: "Not a Git repository",
131
+ repoPath: normalizedRepoPath,
132
+ filePath: normalizedFilePath
133
+ }, null, 2),
134
+ mimeType: "application/json"
135
+ }]
136
+ };
137
+ }
138
+ // Get blame information
139
+ const blameResult = await gitService.getBlame(normalizedFilePath);
140
+ if (!blameResult.resultSuccessful) {
141
+ return {
142
+ contents: [{
143
+ uri: uri.href,
144
+ text: JSON.stringify({
145
+ error: blameResult.resultError.errorMessage,
146
+ repoPath: normalizedRepoPath,
147
+ filePath: normalizedFilePath
148
+ }, null, 2),
149
+ mimeType: "application/json"
150
+ }]
151
+ };
152
+ }
153
+ return {
154
+ contents: [{
155
+ uri: uri.href,
156
+ text: blameResult.resultData,
157
+ mimeType: "text/plain"
158
+ }]
159
+ };
160
+ }
161
+ catch (error) {
162
+ return {
163
+ contents: [{
164
+ uri: uri.href,
165
+ text: JSON.stringify({
166
+ error: error instanceof Error ? error.message : String(error),
167
+ repoPath: ensureString(variables.repoPath),
168
+ filePath: ensureString(variables.filePath)
169
+ }, null, 2),
170
+ mimeType: "application/json"
171
+ }]
172
+ };
173
+ }
174
+ });
175
+ // Show commit details
176
+ server.resource("commit-show", new ResourceTemplate("git://repo/{repoPath}/commit/{commitHash}", { list: undefined }), {
177
+ name: "Commit Details",
178
+ description: "Returns detailed information about a specific commit including diff changes",
179
+ mimeType: "text/plain"
180
+ },
181
+ // Returns the detailed information for a specific commit including diff and metadata
182
+ async (uri, variables) => {
183
+ try {
184
+ // Handle variables which might be arrays
185
+ const repoPathStr = ensureString(variables.repoPath);
186
+ const commitHashStr = ensureString(variables.commitHash);
187
+ // Normalize paths
188
+ const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
189
+ const gitService = new GitService(normalizedRepoPath);
190
+ // Check if the path is a Git repository
191
+ const isRepo = await gitService.isGitRepository();
192
+ if (!isRepo) {
193
+ return {
194
+ contents: [{
195
+ uri: uri.href,
196
+ text: JSON.stringify({
197
+ error: "Not a Git repository",
198
+ repoPath: normalizedRepoPath,
199
+ commitHash: commitHashStr
200
+ }, null, 2),
201
+ mimeType: "application/json"
202
+ }]
203
+ };
204
+ }
205
+ // Get commit details
206
+ const commitResult = await gitService.showCommit(commitHashStr);
207
+ if (!commitResult.resultSuccessful) {
208
+ return {
209
+ contents: [{
210
+ uri: uri.href,
211
+ text: JSON.stringify({
212
+ error: commitResult.resultError.errorMessage,
213
+ repoPath: normalizedRepoPath,
214
+ commitHash: commitHashStr
215
+ }, null, 2),
216
+ mimeType: "application/json"
217
+ }]
218
+ };
219
+ }
220
+ return {
221
+ contents: [{
222
+ uri: uri.href,
223
+ text: commitResult.resultData,
224
+ mimeType: "text/plain"
225
+ }]
226
+ };
227
+ }
228
+ catch (error) {
229
+ return {
230
+ contents: [{
231
+ uri: uri.href,
232
+ text: JSON.stringify({
233
+ error: error instanceof Error ? error.message : String(error),
234
+ repoPath: ensureString(variables.repoPath),
235
+ commitHash: ensureString(variables.commitHash)
236
+ }, null, 2),
237
+ mimeType: "application/json"
238
+ }]
239
+ };
240
+ }
241
+ });
242
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Resource Handlers
3
+ * ===============
4
+ *
5
+ * Entry point for all MCP resource implementations.
6
+ * This module registers all resource handlers with the MCP server.
7
+ */
8
+ import { setupRepositoryResources } from './repository.js';
9
+ import { setupFileResources } from './file.js';
10
+ import { setupDiffResources } from './diff.js';
11
+ import { setupHistoryResources } from './history.js';
12
+ import { resourceDescriptors } from './descriptors.js';
13
+ /**
14
+ * Metadata for resource descriptions
15
+ */
16
+ export const resourceMetadata = {
17
+ // Repository resources
18
+ "repository-info": {
19
+ name: "Repository Information",
20
+ description: "Returns basic Git repository information including current branch, status, and reference details",
21
+ mimeType: "application/json"
22
+ },
23
+ "repository-branches": {
24
+ name: "Repository Branches",
25
+ description: "Returns a list of all branches in the repository with current branch indicator",
26
+ mimeType: "application/json"
27
+ },
28
+ "repository-remotes": {
29
+ name: "Repository Remotes",
30
+ description: "Returns a list of all configured remote repositories with their URLs",
31
+ mimeType: "application/json"
32
+ },
33
+ "repository-tags": {
34
+ name: "Repository Tags",
35
+ description: "Returns a list of all tags in the repository with their references",
36
+ mimeType: "application/json"
37
+ },
38
+ // File resources
39
+ "file-at-ref": {
40
+ name: "File Content",
41
+ description: "Returns the content of a specific file at a given Git reference",
42
+ mimeType: "text/plain"
43
+ },
44
+ "directory-listing": {
45
+ name: "Directory Listing",
46
+ description: "Returns a list of files and directories at a specific path and reference",
47
+ mimeType: "application/json"
48
+ },
49
+ // Diff resources
50
+ "diff-refs": {
51
+ name: "Reference Diff",
52
+ description: "Returns a diff between two Git references (commits, branches, tags)",
53
+ mimeType: "text/plain"
54
+ },
55
+ "diff-unstaged": {
56
+ name: "Unstaged Changes Diff",
57
+ description: "Returns a diff of all unstaged changes in the working directory",
58
+ mimeType: "text/plain"
59
+ },
60
+ "diff-staged": {
61
+ name: "Staged Changes Diff",
62
+ description: "Returns a diff of all staged changes in the index",
63
+ mimeType: "text/plain"
64
+ },
65
+ // History resources
66
+ "commit-log": {
67
+ name: "Commit History",
68
+ description: "Returns the commit history log with author, date, and message details",
69
+ mimeType: "application/json"
70
+ },
71
+ "file-blame": {
72
+ name: "File Blame",
73
+ description: "Returns line-by-line attribution showing which commit last modified each line",
74
+ mimeType: "text/plain"
75
+ },
76
+ "commit-show": {
77
+ name: "Commit Details",
78
+ description: "Returns detailed information about a specific commit including diff changes",
79
+ mimeType: "text/plain"
80
+ }
81
+ };
82
+ /**
83
+ * Registers all Git MCP resources with the server
84
+ *
85
+ * @param server - MCP server instance
86
+ */
87
+ export function registerAllResources(server) {
88
+ // Repository info resources (status, branches, remotes, etc.)
89
+ setupRepositoryResources(server, resourceDescriptors);
90
+ // File resources (file content, directory listings)
91
+ setupFileResources(server, resourceDescriptors);
92
+ // Diff resources (changes between refs, unstaged, staged)
93
+ setupDiffResources(server, resourceDescriptors);
94
+ // History resources (log, blame, show commit)
95
+ setupHistoryResources(server, resourceDescriptors);
96
+ // Note: Metadata for resources is defined in the resourceMetadata object,
97
+ // which can be used when registering resources in their respective handlers.
98
+ // The MCP protocol exposes this metadata through the registered resources.
99
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Repository Resources
3
+ * ===================
4
+ *
5
+ * MCP resources for exposing Git repository information.
6
+ */
7
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { GitService } from '../services/git-service.js';
9
+ import { PathValidation } from '../utils/validation.js';
10
+ /**
11
+ * Helper function to ensure a variable is treated as a string
12
+ *
13
+ * @param value - The value to convert to string
14
+ * @returns A string representation of the value
15
+ */
16
+ function ensureString(value) {
17
+ return Array.isArray(value) ? value[0] : value;
18
+ }
19
+ /**
20
+ * Registers repository resources with the MCP server
21
+ *
22
+ * @param server - MCP server instance
23
+ * @param resourceDescriptors - Resource descriptors for metadata
24
+ */
25
+ export function setupRepositoryResources(server, resourceDescriptors) {
26
+ // Repository information resource
27
+ // Repository information resource
28
+ server.resource("repository-info", new ResourceTemplate("git://repo/{repoPath}/info", { list: undefined }), {
29
+ name: "Repository Information",
30
+ description: "Basic Git repository information including current branch, status, and reference details",
31
+ mimeType: "application/json"
32
+ }, async (uri, variables) => {
33
+ try {
34
+ // Handle repoPath which might be an array
35
+ const repoPathStr = ensureString(variables.repoPath);
36
+ const normalizedPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
37
+ const gitService = new GitService(normalizedPath);
38
+ // Check if the path is a Git repository
39
+ const isRepo = await gitService.isGitRepository();
40
+ if (!isRepo) {
41
+ return {
42
+ contents: [{
43
+ uri: uri.href,
44
+ text: JSON.stringify({
45
+ error: "Not a Git repository",
46
+ path: normalizedPath,
47
+ isGitRepository: false
48
+ }, null, 2),
49
+ mimeType: "application/json"
50
+ }]
51
+ };
52
+ }
53
+ // Get repository status
54
+ const statusResult = await gitService.getStatus();
55
+ if (!statusResult.resultSuccessful) {
56
+ return {
57
+ contents: [{
58
+ uri: uri.href,
59
+ text: JSON.stringify({
60
+ error: statusResult.resultError.errorMessage,
61
+ path: normalizedPath,
62
+ isGitRepository: true
63
+ }, null, 2),
64
+ mimeType: "application/json"
65
+ }]
66
+ };
67
+ }
68
+ return {
69
+ contents: [{
70
+ uri: uri.href,
71
+ text: JSON.stringify({
72
+ path: normalizedPath,
73
+ isGitRepository: true,
74
+ status: statusResult.resultData
75
+ }, null, 2),
76
+ mimeType: "application/json"
77
+ }]
78
+ };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ contents: [{
83
+ uri: uri.href,
84
+ text: JSON.stringify({
85
+ error: error instanceof Error ? error.message : String(error),
86
+ path: ensureString(variables.repoPath),
87
+ isGitRepository: false
88
+ }, null, 2),
89
+ mimeType: "application/json"
90
+ }]
91
+ };
92
+ }
93
+ });
94
+ // Repository branches resource
95
+ // Branches resource
96
+ server.resource("repository-branches", new ResourceTemplate("git://repo/{repoPath}/branches", { list: undefined }), {
97
+ name: "Repository Branches",
98
+ description: "List of all branches in the repository with current branch indicator",
99
+ mimeType: "application/json"
100
+ }, async (uri, variables) => {
101
+ try {
102
+ // Handle repoPath which might be an array
103
+ const repoPathStr = ensureString(variables.repoPath);
104
+ const normalizedPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
105
+ const gitService = new GitService(normalizedPath);
106
+ // Check if the path is a Git repository
107
+ const isRepo = await gitService.isGitRepository();
108
+ if (!isRepo) {
109
+ return {
110
+ contents: [{
111
+ uri: uri.href,
112
+ text: JSON.stringify({
113
+ error: "Not a Git repository",
114
+ path: normalizedPath
115
+ }, null, 2),
116
+ mimeType: "application/json"
117
+ }]
118
+ };
119
+ }
120
+ // Get branches
121
+ const branchesResult = await gitService.listBranches(true);
122
+ if (!branchesResult.resultSuccessful) {
123
+ return {
124
+ contents: [{
125
+ uri: uri.href,
126
+ text: JSON.stringify({
127
+ error: branchesResult.resultError.errorMessage,
128
+ path: normalizedPath
129
+ }, null, 2),
130
+ mimeType: "application/json"
131
+ }]
132
+ };
133
+ }
134
+ return {
135
+ contents: [{
136
+ uri: uri.href,
137
+ text: JSON.stringify({
138
+ path: normalizedPath,
139
+ branches: branchesResult.resultData
140
+ }, null, 2),
141
+ mimeType: "application/json"
142
+ }]
143
+ };
144
+ }
145
+ catch (error) {
146
+ return {
147
+ contents: [{
148
+ uri: uri.href,
149
+ text: JSON.stringify({
150
+ error: error instanceof Error ? error.message : String(error),
151
+ path: ensureString(variables.repoPath)
152
+ }, null, 2),
153
+ mimeType: "application/json"
154
+ }]
155
+ };
156
+ }
157
+ });
158
+ // Repository remotes resource
159
+ // Remotes resource
160
+ server.resource("repository-remotes", new ResourceTemplate("git://repo/{repoPath}/remotes", { list: undefined }), {
161
+ name: "Repository Remotes",
162
+ description: "List of all configured remote repositories with their URLs",
163
+ mimeType: "application/json"
164
+ }, async (uri, variables) => {
165
+ try {
166
+ // Handle repoPath which might be an array
167
+ const repoPathStr = ensureString(variables.repoPath);
168
+ const normalizedPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
169
+ const gitService = new GitService(normalizedPath);
170
+ // Check if the path is a Git repository
171
+ const isRepo = await gitService.isGitRepository();
172
+ if (!isRepo) {
173
+ return {
174
+ contents: [{
175
+ uri: uri.href,
176
+ text: JSON.stringify({
177
+ error: "Not a Git repository",
178
+ path: normalizedPath
179
+ }, null, 2),
180
+ mimeType: "application/json"
181
+ }]
182
+ };
183
+ }
184
+ // Get remotes
185
+ const remotesResult = await gitService.listRemotes();
186
+ if (!remotesResult.resultSuccessful) {
187
+ return {
188
+ contents: [{
189
+ uri: uri.href,
190
+ text: JSON.stringify({
191
+ error: remotesResult.resultError.errorMessage,
192
+ path: normalizedPath
193
+ }, null, 2),
194
+ mimeType: "application/json"
195
+ }]
196
+ };
197
+ }
198
+ return {
199
+ contents: [{
200
+ uri: uri.href,
201
+ text: JSON.stringify({
202
+ path: normalizedPath,
203
+ remotes: remotesResult.resultData
204
+ }, null, 2),
205
+ mimeType: "application/json"
206
+ }]
207
+ };
208
+ }
209
+ catch (error) {
210
+ return {
211
+ contents: [{
212
+ uri: uri.href,
213
+ text: JSON.stringify({
214
+ error: error instanceof Error ? error.message : String(error),
215
+ path: ensureString(variables.repoPath)
216
+ }, null, 2),
217
+ mimeType: "application/json"
218
+ }]
219
+ };
220
+ }
221
+ });
222
+ // Repository tags resource
223
+ // Tags resource
224
+ server.resource("repository-tags", new ResourceTemplate("git://repo/{repoPath}/tags", { list: undefined }), {
225
+ name: "Repository Tags",
226
+ description: "List of all tags in the repository with their references",
227
+ mimeType: "application/json"
228
+ }, async (uri, variables) => {
229
+ try {
230
+ // Handle repoPath which might be an array
231
+ const repoPathStr = ensureString(variables.repoPath);
232
+ const normalizedPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
233
+ const gitService = new GitService(normalizedPath);
234
+ // Check if the path is a Git repository
235
+ const isRepo = await gitService.isGitRepository();
236
+ if (!isRepo) {
237
+ return {
238
+ contents: [{
239
+ uri: uri.href,
240
+ text: JSON.stringify({
241
+ error: "Not a Git repository",
242
+ path: normalizedPath
243
+ }, null, 2),
244
+ mimeType: "application/json"
245
+ }]
246
+ };
247
+ }
248
+ // Get tags
249
+ const tagsResult = await gitService.listTags();
250
+ if (!tagsResult.resultSuccessful) {
251
+ return {
252
+ contents: [{
253
+ uri: uri.href,
254
+ text: JSON.stringify({
255
+ error: tagsResult.resultError.errorMessage,
256
+ path: normalizedPath
257
+ }, null, 2),
258
+ mimeType: "application/json"
259
+ }]
260
+ };
261
+ }
262
+ return {
263
+ contents: [{
264
+ uri: uri.href,
265
+ text: JSON.stringify({
266
+ path: normalizedPath,
267
+ tags: tagsResult.resultData
268
+ }, null, 2),
269
+ mimeType: "application/json"
270
+ }]
271
+ };
272
+ }
273
+ catch (error) {
274
+ return {
275
+ contents: [{
276
+ uri: uri.href,
277
+ text: JSON.stringify({
278
+ error: error instanceof Error ? error.message : String(error),
279
+ path: ensureString(variables.repoPath)
280
+ }, null, 2),
281
+ mimeType: "application/json"
282
+ }]
283
+ };
284
+ }
285
+ });
286
+ }