@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.
- package/README.md +8 -11
- package/dist/config/index.js +7 -7
- package/dist/index.js +35 -21
- package/dist/mcp-server/server.js +72 -56
- package/dist/mcp-server/tools/gitAdd/index.js +1 -1
- package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
- package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
- package/dist/mcp-server/tools/gitBranch/index.js +1 -1
- package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
- package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
- package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
- package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
- package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
- package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
- package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
- package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
- package/dist/mcp-server/tools/gitClean/index.js +1 -1
- package/dist/mcp-server/tools/gitClean/logic.js +93 -41
- package/dist/mcp-server/tools/gitClean/registration.js +19 -16
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
- package/dist/mcp-server/tools/gitClone/index.js +1 -1
- package/dist/mcp-server/tools/gitClone/logic.js +89 -30
- package/dist/mcp-server/tools/gitClone/registration.js +15 -12
- package/dist/mcp-server/tools/gitCommit/index.js +1 -1
- package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
- package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
- package/dist/mcp-server/tools/gitDiff/index.js +1 -1
- package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
- package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
- package/dist/mcp-server/tools/gitFetch/index.js +1 -1
- package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
- package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
- package/dist/mcp-server/tools/gitInit/index.js +1 -1
- package/dist/mcp-server/tools/gitInit/logic.js +88 -34
- package/dist/mcp-server/tools/gitInit/registration.js +32 -18
- package/dist/mcp-server/tools/gitLog/index.js +1 -1
- package/dist/mcp-server/tools/gitLog/logic.js +133 -47
- package/dist/mcp-server/tools/gitLog/registration.js +16 -14
- package/dist/mcp-server/tools/gitMerge/index.js +1 -1
- package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
- package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
- package/dist/mcp-server/tools/gitPull/index.js +1 -1
- package/dist/mcp-server/tools/gitPull/logic.js +90 -69
- package/dist/mcp-server/tools/gitPull/registration.js +16 -14
- package/dist/mcp-server/tools/gitPush/index.js +1 -1
- package/dist/mcp-server/tools/gitPush/logic.js +116 -100
- package/dist/mcp-server/tools/gitPush/registration.js +16 -14
- package/dist/mcp-server/tools/gitRebase/index.js +1 -1
- package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
- package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
- package/dist/mcp-server/tools/gitRemote/index.js +1 -1
- package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
- package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
- package/dist/mcp-server/tools/gitReset/index.js +1 -1
- package/dist/mcp-server/tools/gitReset/logic.js +65 -37
- package/dist/mcp-server/tools/gitReset/registration.js +14 -12
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
- package/dist/mcp-server/tools/gitShow/index.js +1 -1
- package/dist/mcp-server/tools/gitShow/logic.js +78 -35
- package/dist/mcp-server/tools/gitShow/registration.js +17 -12
- package/dist/mcp-server/tools/gitStash/index.js +1 -1
- package/dist/mcp-server/tools/gitStash/logic.js +143 -58
- package/dist/mcp-server/tools/gitStash/registration.js +19 -12
- package/dist/mcp-server/tools/gitStatus/index.js +1 -1
- package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
- package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
- package/dist/mcp-server/tools/gitTag/index.js +1 -1
- package/dist/mcp-server/tools/gitTag/logic.js +124 -51
- package/dist/mcp-server/tools/gitTag/registration.js +14 -11
- package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
- package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
- package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
- package/dist/mcp-server/transports/httpTransport.js +187 -79
- package/dist/mcp-server/transports/stdioTransport.js +14 -8
- package/dist/types-global/errors.js +9 -4
- package/dist/utils/index.js +4 -4
- package/dist/utils/internal/errorHandler.js +62 -40
- package/dist/utils/internal/index.js +3 -3
- package/dist/utils/internal/logger.js +97 -54
- package/dist/utils/internal/requestContext.js +7 -5
- package/dist/utils/metrics/index.js +1 -1
- package/dist/utils/metrics/tokenCounter.js +18 -14
- package/dist/utils/parsing/dateParser.js +5 -5
- package/dist/utils/parsing/index.js +2 -2
- package/dist/utils/parsing/jsonParser.js +20 -11
- package/dist/utils/security/idGenerator.js +8 -10
- package/dist/utils/security/index.js +3 -3
- package/dist/utils/security/rateLimiter.js +16 -14
- package/dist/utils/security/sanitization.js +139 -82
- package/package.json +45 -23
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import sanitizeHtml from
|
|
3
|
-
import validator from
|
|
4
|
-
import { BaseErrorCode, McpError } from
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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(
|
|
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
|
|
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
|
|
144
|
+
case "attribute":
|
|
108
145
|
return sanitizeHtml(input, { allowedTags: [], allowedAttributes: {} });
|
|
109
|
-
case
|
|
110
|
-
if (!validator.isURL(input, {
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
116
|
-
logger.error(
|
|
117
|
-
|
|
118
|
-
|
|
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 = [
|
|
175
|
+
sanitizeUrl(input, allowedProtocols = ["http", "https"]) {
|
|
132
176
|
try {
|
|
133
|
-
if (!validator.isURL(input, {
|
|
134
|
-
|
|
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(
|
|
138
|
-
|
|
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 :
|
|
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 !==
|
|
167
|
-
throw new Error(
|
|
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(
|
|
172
|
-
throw new Error(
|
|
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) &&
|
|
190
|
-
|
|
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 =
|
|
245
|
+
finalSanitizedPath =
|
|
246
|
+
finalSanitizedPath === "" ? "." : finalSanitizedPath;
|
|
196
247
|
}
|
|
197
|
-
else {
|
|
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(
|
|
258
|
+
throw new Error("Absolute path encountered when not allowed and not rooted");
|
|
207
259
|
}
|
|
208
260
|
}
|
|
209
|
-
else {
|
|
210
|
-
|
|
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(
|
|
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(
|
|
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 :
|
|
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 !==
|
|
249
|
-
throw new Error(
|
|
300
|
+
if (typeof input !== "string") {
|
|
301
|
+
throw new Error("Invalid input: expected a JSON string");
|
|
250
302
|
}
|
|
251
|
-
if (maxSize !== undefined && Buffer.byteLength(input,
|
|
252
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `JSON exceeds maximum allowed size of ${maxSize} bytes`, { size: Buffer.byteLength(input,
|
|
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 :
|
|
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 ===
|
|
328
|
+
if (typeof input === "string") {
|
|
277
329
|
if (!validator.isNumeric(input.trim())) {
|
|
278
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR,
|
|
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 ===
|
|
334
|
+
else if (typeof input === "number") {
|
|
283
335
|
value = input;
|
|
284
336
|
}
|
|
285
337
|
else {
|
|
286
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR,
|
|
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,
|
|
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(
|
|
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 !==
|
|
370
|
+
if (!input || typeof input !== "object") {
|
|
314
371
|
return input;
|
|
315
372
|
}
|
|
316
|
-
const clonedInput = typeof structuredClone ===
|
|
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(
|
|
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
|
|
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 !==
|
|
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 ===
|
|
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] =
|
|
414
|
+
obj[key] = "[REDACTED]";
|
|
358
415
|
}
|
|
359
|
-
else if (value && typeof value ===
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.12.
|
|
38
|
+
"@modelcontextprotocol/inspector": "^0.14.1",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.12.3",
|
|
42
40
|
"@types/jsonwebtoken": "^9.0.9",
|
|
43
|
-
"@types/node": "^
|
|
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
|
|
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.
|
|
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.
|
|
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
|
}
|