@cyanheads/git-mcp-server 2.1.0 → 2.1.2

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.
Files changed (97) hide show
  1. package/README.md +8 -11
  2. package/dist/config/index.js +7 -7
  3. package/dist/index.js +35 -21
  4. package/dist/mcp-server/server.js +72 -56
  5. package/dist/mcp-server/tools/gitAdd/index.js +1 -1
  6. package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
  7. package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
  8. package/dist/mcp-server/tools/gitBranch/index.js +1 -1
  9. package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
  10. package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
  11. package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
  12. package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
  13. package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
  14. package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
  15. package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
  16. package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
  17. package/dist/mcp-server/tools/gitClean/index.js +1 -1
  18. package/dist/mcp-server/tools/gitClean/logic.js +93 -41
  19. package/dist/mcp-server/tools/gitClean/registration.js +19 -16
  20. package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
  21. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
  22. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
  23. package/dist/mcp-server/tools/gitClone/index.js +1 -1
  24. package/dist/mcp-server/tools/gitClone/logic.js +89 -30
  25. package/dist/mcp-server/tools/gitClone/registration.js +15 -12
  26. package/dist/mcp-server/tools/gitCommit/index.js +1 -1
  27. package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
  28. package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
  29. package/dist/mcp-server/tools/gitDiff/index.js +1 -1
  30. package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
  31. package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
  32. package/dist/mcp-server/tools/gitFetch/index.js +1 -1
  33. package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
  34. package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
  35. package/dist/mcp-server/tools/gitInit/index.js +1 -1
  36. package/dist/mcp-server/tools/gitInit/logic.js +88 -34
  37. package/dist/mcp-server/tools/gitInit/registration.js +32 -18
  38. package/dist/mcp-server/tools/gitLog/index.js +1 -1
  39. package/dist/mcp-server/tools/gitLog/logic.js +133 -47
  40. package/dist/mcp-server/tools/gitLog/registration.js +16 -14
  41. package/dist/mcp-server/tools/gitMerge/index.js +1 -1
  42. package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
  43. package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
  44. package/dist/mcp-server/tools/gitPull/index.js +1 -1
  45. package/dist/mcp-server/tools/gitPull/logic.js +90 -69
  46. package/dist/mcp-server/tools/gitPull/registration.js +16 -14
  47. package/dist/mcp-server/tools/gitPush/index.js +1 -1
  48. package/dist/mcp-server/tools/gitPush/logic.js +116 -100
  49. package/dist/mcp-server/tools/gitPush/registration.js +16 -14
  50. package/dist/mcp-server/tools/gitRebase/index.js +1 -1
  51. package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
  52. package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
  53. package/dist/mcp-server/tools/gitRemote/index.js +1 -1
  54. package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
  55. package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
  56. package/dist/mcp-server/tools/gitReset/index.js +1 -1
  57. package/dist/mcp-server/tools/gitReset/logic.js +65 -37
  58. package/dist/mcp-server/tools/gitReset/registration.js +14 -12
  59. package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
  60. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
  61. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
  62. package/dist/mcp-server/tools/gitShow/index.js +1 -1
  63. package/dist/mcp-server/tools/gitShow/logic.js +78 -35
  64. package/dist/mcp-server/tools/gitShow/registration.js +17 -12
  65. package/dist/mcp-server/tools/gitStash/index.js +1 -1
  66. package/dist/mcp-server/tools/gitStash/logic.js +143 -58
  67. package/dist/mcp-server/tools/gitStash/registration.js +19 -12
  68. package/dist/mcp-server/tools/gitStatus/index.js +1 -1
  69. package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
  70. package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
  71. package/dist/mcp-server/tools/gitTag/index.js +1 -1
  72. package/dist/mcp-server/tools/gitTag/logic.js +124 -51
  73. package/dist/mcp-server/tools/gitTag/registration.js +14 -11
  74. package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
  75. package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
  76. package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
  77. package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
  78. package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
  79. package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
  80. package/dist/mcp-server/transports/httpTransport.js +187 -79
  81. package/dist/mcp-server/transports/stdioTransport.js +14 -8
  82. package/dist/types-global/errors.js +9 -4
  83. package/dist/utils/index.js +4 -4
  84. package/dist/utils/internal/errorHandler.js +62 -40
  85. package/dist/utils/internal/index.js +3 -3
  86. package/dist/utils/internal/logger.js +97 -54
  87. package/dist/utils/internal/requestContext.js +7 -5
  88. package/dist/utils/metrics/index.js +1 -1
  89. package/dist/utils/metrics/tokenCounter.js +18 -14
  90. package/dist/utils/parsing/dateParser.js +5 -5
  91. package/dist/utils/parsing/index.js +2 -2
  92. package/dist/utils/parsing/jsonParser.js +20 -11
  93. package/dist/utils/security/idGenerator.js +8 -10
  94. package/dist/utils/security/index.js +3 -3
  95. package/dist/utils/security/rateLimiter.js +16 -14
  96. package/dist/utils/security/sanitization.js +139 -82
  97. package/package.json +45 -23
@@ -1,9 +1,9 @@
1
- import path from 'path';
2
- import sanitizeHtml from 'sanitize-html';
3
- import validator from 'validator';
4
- import { BaseErrorCode, McpError } from '../../types-global/errors.js';
1
+ import path from "path";
2
+ import sanitizeHtml from "sanitize-html";
3
+ import validator from "validator";
4
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
5
5
  // Import utils from the main barrel file (logger from ../internal/logger.js)
6
- import { logger } from '../index.js';
6
+ import { logger } from "../index.js";
7
7
  /**
8
8
  * Sanitization class for handling various input sanitization tasks.
9
9
  * Provides methods to clean and validate strings, HTML, URLs, paths, JSON, and numbers.
@@ -12,22 +12,57 @@ export class Sanitization {
12
12
  static instance;
13
13
  /** Default list of sensitive fields for sanitizing logs */
14
14
  sensitiveFields = [
15
- 'password', 'token', 'secret', 'key', 'apiKey', 'auth',
16
- 'credential', 'jwt', 'ssn', 'credit', 'card', 'cvv', 'authorization'
15
+ "password",
16
+ "token",
17
+ "secret",
18
+ "key",
19
+ "apiKey",
20
+ "auth",
21
+ "credential",
22
+ "jwt",
23
+ "ssn",
24
+ "credit",
25
+ "card",
26
+ "cvv",
27
+ "authorization",
17
28
  ];
18
29
  /** Default sanitize-html configuration */
19
30
  defaultHtmlSanitizeConfig = {
20
31
  allowedTags: [
21
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'ul', 'ol',
22
- 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br',
23
- 'div', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'pre'
32
+ "h1",
33
+ "h2",
34
+ "h3",
35
+ "h4",
36
+ "h5",
37
+ "h6",
38
+ "p",
39
+ "a",
40
+ "ul",
41
+ "ol",
42
+ "li",
43
+ "b",
44
+ "i",
45
+ "strong",
46
+ "em",
47
+ "strike",
48
+ "code",
49
+ "hr",
50
+ "br",
51
+ "div",
52
+ "table",
53
+ "thead",
54
+ "tbody",
55
+ "tr",
56
+ "th",
57
+ "td",
58
+ "pre",
24
59
  ],
25
60
  allowedAttributes: {
26
- 'a': ['href', 'name', 'target'],
27
- 'img': ['src', 'alt', 'title', 'width', 'height'],
28
- '*': ['class', 'id', 'style']
61
+ a: ["href", "name", "target"],
62
+ img: ["src", "alt", "title", "width", "height"],
63
+ "*": ["class", "id", "style"],
29
64
  },
30
- preserveComments: false
65
+ preserveComments: false,
31
66
  };
32
67
  /**
33
68
  * Private constructor to enforce singleton pattern.
@@ -52,7 +87,9 @@ export class Sanitization {
52
87
  */
53
88
  setSensitiveFields(fields) {
54
89
  this.sensitiveFields = [...new Set([...this.sensitiveFields, ...fields])]; // Ensure uniqueness
55
- logger.debug('Updated sensitive fields list', { count: this.sensitiveFields.length });
90
+ logger.debug("Updated sensitive fields list", {
91
+ count: this.sensitiveFields.length,
92
+ });
56
93
  }
57
94
  /**
58
95
  * Get the current list of sensitive fields used for log sanitization.
@@ -70,15 +107,15 @@ export class Sanitization {
70
107
  */
71
108
  sanitizeHtml(input, config) {
72
109
  if (!input)
73
- return '';
110
+ return "";
74
111
  const effectiveConfig = { ...this.defaultHtmlSanitizeConfig, ...config };
75
112
  const options = {
76
113
  allowedTags: effectiveConfig.allowedTags,
77
114
  allowedAttributes: effectiveConfig.allowedAttributes,
78
- transformTags: effectiveConfig.transformTags
115
+ transformTags: effectiveConfig.transformTags,
79
116
  };
80
117
  if (effectiveConfig.preserveComments) {
81
- options.allowedTags = [...(options.allowedTags || []), '!--'];
118
+ options.allowedTags = [...(options.allowedTags || []), "!--"];
82
119
  }
83
120
  return sanitizeHtml(input, options);
84
121
  }
@@ -95,27 +132,34 @@ export class Sanitization {
95
132
  */
96
133
  sanitizeString(input, options = {}) {
97
134
  if (!input)
98
- return '';
135
+ return "";
99
136
  switch (options.context) {
100
- case 'html':
137
+ case "html":
101
138
  return this.sanitizeHtml(input, {
102
139
  allowedTags: options.allowedTags,
103
- allowedAttributes: options.allowedAttributes ?
104
- this.convertAttributesFormat(options.allowedAttributes) :
105
- undefined
140
+ allowedAttributes: options.allowedAttributes
141
+ ? this.convertAttributesFormat(options.allowedAttributes)
142
+ : undefined,
106
143
  });
107
- case 'attribute':
144
+ case "attribute":
108
145
  return sanitizeHtml(input, { allowedTags: [], allowedAttributes: {} });
109
- case 'url':
110
- if (!validator.isURL(input, { protocols: ['http', 'https'], require_protocol: true })) {
111
- logger.warning('Invalid URL detected during string sanitization', { input });
112
- return '';
146
+ case "url":
147
+ if (!validator.isURL(input, {
148
+ protocols: ["http", "https"],
149
+ require_protocol: true,
150
+ })) {
151
+ logger.warning("Invalid URL detected during string sanitization", {
152
+ input,
153
+ });
154
+ return "";
113
155
  }
114
156
  return validator.trim(input);
115
- case 'javascript':
116
- logger.error('Attempted JavaScript sanitization via sanitizeString', { input: input.substring(0, 50) });
117
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'JavaScript sanitization not supported through string sanitizer');
118
- case 'text':
157
+ case "javascript":
158
+ logger.error("Attempted JavaScript sanitization via sanitizeString", {
159
+ input: input.substring(0, 50),
160
+ });
161
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "JavaScript sanitization not supported through string sanitizer");
162
+ case "text":
119
163
  default:
120
164
  return sanitizeHtml(input, { allowedTags: [], allowedAttributes: {} });
121
165
  }
@@ -128,19 +172,23 @@ export class Sanitization {
128
172
  * @returns {string} Sanitized URL.
129
173
  * @throws {McpError} If URL is invalid or uses a disallowed protocol.
130
174
  */
131
- sanitizeUrl(input, allowedProtocols = ['http', 'https']) {
175
+ sanitizeUrl(input, allowedProtocols = ["http", "https"]) {
132
176
  try {
133
- if (!validator.isURL(input, { protocols: allowedProtocols, require_protocol: true })) {
134
- throw new Error('Invalid URL format or protocol');
177
+ if (!validator.isURL(input, {
178
+ protocols: allowedProtocols,
179
+ require_protocol: true,
180
+ })) {
181
+ throw new Error("Invalid URL format or protocol");
135
182
  }
136
183
  const lowerInput = input.toLowerCase().trim();
137
- if (lowerInput.startsWith('javascript:')) { // Double-check against javascript:
138
- throw new Error('JavaScript protocol not allowed');
184
+ if (lowerInput.startsWith("javascript:")) {
185
+ // Double-check against javascript:
186
+ throw new Error("JavaScript protocol not allowed");
139
187
  }
140
188
  return validator.trim(input);
141
189
  }
142
190
  catch (error) {
143
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : 'Invalid URL format', { input });
191
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : "Invalid URL format", { input });
144
192
  }
145
193
  }
146
194
  /**
@@ -156,28 +204,29 @@ export class Sanitization {
156
204
  sanitizePath(input, options = {}) {
157
205
  const originalInput = input;
158
206
  const effectiveOptions = {
207
+ // Ensure all options have defaults
159
208
  toPosix: options.toPosix ?? false,
160
209
  allowAbsolute: options.allowAbsolute ?? false,
161
- rootDir: options.rootDir
210
+ rootDir: options.rootDir,
162
211
  };
163
212
  let wasAbsoluteInitially = false;
164
213
  let convertedToRelative = false;
165
214
  try {
166
- if (!input || typeof input !== 'string') {
167
- throw new Error('Invalid path input: must be a non-empty string');
215
+ if (!input || typeof input !== "string") {
216
+ throw new Error("Invalid path input: must be a non-empty string");
168
217
  }
169
218
  let normalized = path.normalize(input);
170
219
  wasAbsoluteInitially = path.isAbsolute(normalized);
171
- if (normalized.includes('\0')) {
172
- throw new Error('Path contains null byte');
220
+ if (normalized.includes("\0")) {
221
+ throw new Error("Path contains null byte");
173
222
  }
174
223
  if (effectiveOptions.toPosix) {
175
- normalized = normalized.replace(/\\/g, '/');
224
+ normalized = normalized.replace(/\\/g, "/");
176
225
  }
177
226
  if (!effectiveOptions.allowAbsolute && path.isAbsolute(normalized)) {
178
227
  // Original path was absolute, but absolute paths are not allowed.
179
228
  // Convert to relative by stripping leading slash or drive letter.
180
- normalized = normalized.replace(/^(?:[A-Za-z]:)?[/\\]+/, '');
229
+ normalized = normalized.replace(/^(?:[A-Za-z]:)?[/\\]+/, "");
181
230
  convertedToRelative = true;
182
231
  }
183
232
  let finalSanitizedPath;
@@ -186,15 +235,18 @@ export class Sanitization {
186
235
  // If 'normalized' is absolute (and allowed), path.resolve uses it as the base.
187
236
  // If 'normalized' is relative, it's resolved against 'rootDirResolved'.
188
237
  const fullPath = path.resolve(rootDirResolved, normalized);
189
- if (!fullPath.startsWith(rootDirResolved + path.sep) && fullPath !== rootDirResolved) {
190
- throw new Error('Path traversal detected (escapes rootDir)');
238
+ if (!fullPath.startsWith(rootDirResolved + path.sep) &&
239
+ fullPath !== rootDirResolved) {
240
+ throw new Error("Path traversal detected (escapes rootDir)");
191
241
  }
192
242
  // Path is within rootDir, return it relative to rootDir.
193
243
  finalSanitizedPath = path.relative(rootDirResolved, fullPath);
194
244
  // Ensure empty string result from path.relative (if fullPath equals rootDirResolved) becomes '.'
195
- finalSanitizedPath = finalSanitizedPath === '' ? '.' : finalSanitizedPath;
245
+ finalSanitizedPath =
246
+ finalSanitizedPath === "" ? "." : finalSanitizedPath;
196
247
  }
197
- else { // No rootDir specified
248
+ else {
249
+ // No rootDir specified
198
250
  if (path.isAbsolute(normalized)) {
199
251
  if (effectiveOptions.allowAbsolute) {
200
252
  // Absolute path is allowed and no rootDir to constrain it.
@@ -203,15 +255,16 @@ export class Sanitization {
203
255
  else {
204
256
  // Should not happen if logic above is correct (already made relative or was originally relative)
205
257
  // but as a safeguard:
206
- throw new Error('Absolute path encountered when not allowed and not rooted');
258
+ throw new Error("Absolute path encountered when not allowed and not rooted");
207
259
  }
208
260
  }
209
- else { // Path is relative and no rootDir
210
- if (normalized.includes('..')) {
261
+ else {
262
+ // Path is relative and no rootDir
263
+ if (normalized.includes("..")) {
211
264
  const resolvedPath = path.resolve(normalized); // Resolves relative to CWD
212
- const currentWorkingDir = path.resolve('.');
265
+ const currentWorkingDir = path.resolve(".");
213
266
  if (!resolvedPath.startsWith(currentWorkingDir)) {
214
- throw new Error('Relative path traversal detected (escapes CWD)');
267
+ throw new Error("Relative path traversal detected (escapes CWD)");
215
268
  }
216
269
  }
217
270
  finalSanitizedPath = normalized;
@@ -222,17 +275,16 @@ export class Sanitization {
222
275
  originalInput,
223
276
  wasAbsolute: wasAbsoluteInitially,
224
277
  convertedToRelative,
225
- optionsUsed: effectiveOptions
278
+ optionsUsed: effectiveOptions,
226
279
  };
227
280
  }
228
281
  catch (error) {
229
- logger.warning('Path sanitization error', {
282
+ logger.warning("Path sanitization error", {
230
283
  input: originalInput,
231
284
  options: effectiveOptions,
232
- error: error instanceof Error ? error.message : String(error)
285
+ error: error instanceof Error ? error.message : String(error),
233
286
  });
234
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : 'Invalid or unsafe path', { input: originalInput } // Provide original input in error details
235
- );
287
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : "Invalid or unsafe path", { input: originalInput });
236
288
  }
237
289
  }
238
290
  /**
@@ -245,21 +297,21 @@ export class Sanitization {
245
297
  */
246
298
  sanitizeJson(input, maxSize) {
247
299
  try {
248
- if (typeof input !== 'string') {
249
- throw new Error('Invalid input: expected a JSON string');
300
+ if (typeof input !== "string") {
301
+ throw new Error("Invalid input: expected a JSON string");
250
302
  }
251
- if (maxSize !== undefined && Buffer.byteLength(input, 'utf8') > maxSize) {
252
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `JSON exceeds maximum allowed size of ${maxSize} bytes`, { size: Buffer.byteLength(input, 'utf8'), maxSize });
303
+ if (maxSize !== undefined && Buffer.byteLength(input, "utf8") > maxSize) {
304
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `JSON exceeds maximum allowed size of ${maxSize} bytes`, { size: Buffer.byteLength(input, "utf8"), maxSize });
253
305
  }
254
306
  const parsed = JSON.parse(input);
255
307
  // Optional: Add recursive sanitization of parsed object values if needed
256
- // this.sanitizeObjectRecursively(parsed);
308
+ // this.sanitizeObjectRecursively(parsed);
257
309
  return parsed;
258
310
  }
259
311
  catch (error) {
260
312
  if (error instanceof McpError)
261
313
  throw error;
262
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : 'Invalid JSON format', { input: input.length > 100 ? `${input.substring(0, 100)}...` : input });
314
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, error instanceof Error ? error.message : "Invalid JSON format", { input: input.length > 100 ? `${input.substring(0, 100)}...` : input });
263
315
  }
264
316
  }
265
317
  /**
@@ -273,20 +325,20 @@ export class Sanitization {
273
325
  */
274
326
  sanitizeNumber(input, min, max) {
275
327
  let value;
276
- if (typeof input === 'string') {
328
+ if (typeof input === "string") {
277
329
  if (!validator.isNumeric(input.trim())) {
278
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid number format', { input });
330
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid number format", { input });
279
331
  }
280
332
  value = parseFloat(input.trim());
281
333
  }
282
- else if (typeof input === 'number') {
334
+ else if (typeof input === "number") {
283
335
  value = input;
284
336
  }
285
337
  else {
286
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid input type: expected number or string', { input: String(input) });
338
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid input type: expected number or string", { input: String(input) });
287
339
  }
288
340
  if (isNaN(value) || !isFinite(value)) {
289
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid number value (NaN or Infinity)', { input });
341
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid number value (NaN or Infinity)", { input });
290
342
  }
291
343
  let clamped = false;
292
344
  if (min !== undefined && value < min) {
@@ -298,7 +350,12 @@ export class Sanitization {
298
350
  clamped = true;
299
351
  }
300
352
  if (clamped) {
301
- logger.debug('Number clamped to range', { input, min, max, finalValue: value });
353
+ logger.debug("Number clamped to range", {
354
+ input,
355
+ min,
356
+ max,
357
+ finalValue: value,
358
+ });
302
359
  }
303
360
  return value;
304
361
  }
@@ -310,20 +367,20 @@ export class Sanitization {
310
367
  */
311
368
  sanitizeForLogging(input) {
312
369
  try {
313
- if (!input || typeof input !== 'object') {
370
+ if (!input || typeof input !== "object") {
314
371
  return input;
315
372
  }
316
- const clonedInput = typeof structuredClone === 'function'
373
+ const clonedInput = typeof structuredClone === "function"
317
374
  ? structuredClone(input)
318
375
  : JSON.parse(JSON.stringify(input)); // Fallback for older Node versions
319
376
  this.redactSensitiveFields(clonedInput);
320
377
  return clonedInput;
321
378
  }
322
379
  catch (error) {
323
- logger.error('Error during log sanitization', {
324
- error: error instanceof Error ? error.message : String(error)
380
+ logger.error("Error during log sanitization", {
381
+ error: error instanceof Error ? error.message : String(error),
325
382
  });
326
- return '[Log Sanitization Failed]';
383
+ return "[Log Sanitization Failed]";
327
384
  }
328
385
  }
329
386
  /**
@@ -338,12 +395,12 @@ export class Sanitization {
338
395
  * @param {unknown} obj - The object or array to redact.
339
396
  */
340
397
  redactSensitiveFields(obj) {
341
- if (!obj || typeof obj !== 'object') {
398
+ if (!obj || typeof obj !== "object") {
342
399
  return;
343
400
  }
344
401
  if (Array.isArray(obj)) {
345
- obj.forEach(item => {
346
- if (item && typeof item === 'object') {
402
+ obj.forEach((item) => {
403
+ if (item && typeof item === "object") {
347
404
  this.redactSensitiveFields(item);
348
405
  }
349
406
  });
@@ -352,11 +409,11 @@ export class Sanitization {
352
409
  for (const key in obj) {
353
410
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
354
411
  const value = obj[key];
355
- const isSensitive = this.sensitiveFields.some(field => key.toLowerCase().includes(field.toLowerCase()));
412
+ const isSensitive = this.sensitiveFields.some((field) => key.toLowerCase().includes(field.toLowerCase()));
356
413
  if (isSensitive) {
357
- obj[key] = '[REDACTED]';
414
+ obj[key] = "[REDACTED]";
358
415
  }
359
- else if (value && typeof value === 'object') {
416
+ else if (value && typeof value === "object") {
360
417
  this.redactSensitiveFields(value);
361
418
  }
362
419
  }
package/package.json CHANGED
@@ -1,54 +1,55 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management, and more, via the MCP standard. STDIO & HTTP.",
5
+ "main": "dist/index.js",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "bin": {
10
+ "git-mcp-server": "dist/index.js"
11
+ },
12
+ "exports": "./dist/index.js",
13
+ "types": "dist/index.d.ts",
5
14
  "type": "module",
6
- "license": "Apache-2.0",
7
- "author": "Casey Hand @cyanheads",
8
15
  "repository": {
9
16
  "type": "git",
10
17
  "url": "git+https://github.com/cyanheads/git-mcp-server.git"
11
18
  },
12
- "homepage": "https://github.com/cyanheads/git-mcp-server#readme",
13
19
  "bugs": {
14
20
  "url": "https://github.com/cyanheads/git-mcp-server/issues"
15
21
  },
16
- "engines": {
17
- "node": ">=18.0.0"
18
- },
19
- "bin": {
20
- "git-mcp-server": "dist/index.js"
21
- },
22
- "files": [
23
- "dist"
24
- ],
22
+ "homepage": "https://github.com/cyanheads/git-mcp-server#readme",
25
23
  "scripts": {
26
24
  "build": "tsc && node --loader ts-node/esm scripts/make-executable.ts dist/index.js",
27
25
  "start": "node dist/index.js",
28
26
  "start:stdio": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=stdio node dist/index.js",
29
27
  "start:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http node dist/index.js",
30
28
  "rebuild": "ts-node --esm scripts/clean.ts && npm run build",
29
+ "docs:generate": "typedoc --tsconfig ./tsconfig.typedoc.json",
31
30
  "tree": "ts-node --esm scripts/tree.ts",
31
+ "fetch-spec": "ts-node --esm scripts/fetch-openapi-spec.ts",
32
+ "format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
32
33
  "inspector": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server",
33
34
  "inspector:http": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server-http",
34
35
  "clean": "ts-node --esm scripts/clean.ts"
35
36
  },
36
- "publishConfig": {
37
- "access": "public"
38
- },
39
37
  "dependencies": {
40
- "@modelcontextprotocol/inspector": "^0.13.0",
41
- "@modelcontextprotocol/sdk": "^1.12.1",
38
+ "@modelcontextprotocol/inspector": "^0.14.1",
39
+ "@modelcontextprotocol/sdk": "^1.12.3",
42
40
  "@types/jsonwebtoken": "^9.0.9",
43
- "@types/node": "^22.15.29",
41
+ "@types/node": "^24.0.1",
44
42
  "@types/sanitize-html": "^2.16.0",
45
43
  "@types/validator": "^13.15.1",
44
+ "chalk": "^5.4.1",
46
45
  "chrono-node": "2.8.0",
46
+ "cli-table3": "^0.6.5",
47
47
  "dotenv": "^16.5.0",
48
48
  "express": "^5.1.0",
49
49
  "ignore": "^7.0.5",
50
+ "jose": "^6.0.11",
50
51
  "jsonwebtoken": "^9.0.2",
51
- "openai": "^5.0.2",
52
+ "openai": "^5.3.0",
52
53
  "partial-json": "^0.1.7",
53
54
  "sanitize-html": "^2.17.0",
54
55
  "tiktoken": "^1.0.21",
@@ -58,7 +59,7 @@
58
59
  "winston": "^3.17.0",
59
60
  "winston-daily-rotate-file": "^5.0.0",
60
61
  "yargs": "^18.0.0",
61
- "zod": "^3.25.49"
62
+ "zod": "^3.25.64"
62
63
  },
63
64
  "keywords": [
64
65
  "typescript",
@@ -81,7 +82,6 @@
81
82
  "diff",
82
83
  "fetch",
83
84
  "log",
84
- "llm-tools",
85
85
  "merge",
86
86
  "pull",
87
87
  "push",
@@ -95,7 +95,29 @@
95
95
  "ai-agent",
96
96
  "automation"
97
97
  ],
98
+ "author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/git-mcp-server#readme)",
99
+ "license": "Apache-2.0",
100
+ "funding": [
101
+ {
102
+ "type": "github",
103
+ "url": "https://github.com/sponsors/cyanheads"
104
+ },
105
+ {
106
+ "type": "buy_me_a_coffee",
107
+ "url": "https://www.buymeacoffee.com/cyanheads"
108
+ }
109
+ ],
110
+ "engines": {
111
+ "node": ">=20.0.0"
112
+ },
98
113
  "devDependencies": {
99
- "@types/express": "^5.0.2"
114
+ "@types/express": "^5.0.3",
115
+ "@types/js-yaml": "^4.0.9",
116
+ "js-yaml": "^4.1.0",
117
+ "prettier": "^3.5.3",
118
+ "typedoc": "^0.28.5"
119
+ },
120
+ "publishConfig": {
121
+ "access": "public"
100
122
  }
101
123
  }