@cyanheads/git-mcp-server 1.2.4 → 2.0.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 +172 -285
- package/dist/config/index.js +69 -0
- package/dist/index.js +135 -0
- package/dist/mcp-server/server.js +572 -0
- package/dist/mcp-server/tools/gitAdd/index.js +7 -0
- package/dist/mcp-server/tools/gitAdd/logic.js +118 -0
- package/dist/mcp-server/tools/gitAdd/registration.js +73 -0
- package/dist/mcp-server/tools/gitBranch/index.js +7 -0
- package/dist/mcp-server/tools/gitBranch/logic.js +180 -0
- package/dist/mcp-server/tools/gitBranch/registration.js +72 -0
- package/dist/mcp-server/tools/gitCheckout/index.js +6 -0
- package/dist/mcp-server/tools/gitCheckout/logic.js +165 -0
- package/dist/mcp-server/tools/gitCheckout/registration.js +78 -0
- package/dist/mcp-server/tools/gitCherryPick/index.js +7 -0
- package/dist/mcp-server/tools/gitCherryPick/logic.js +115 -0
- package/dist/mcp-server/tools/gitCherryPick/registration.js +69 -0
- package/dist/mcp-server/tools/gitClean/index.js +7 -0
- package/dist/mcp-server/tools/gitClean/logic.js +110 -0
- package/dist/mcp-server/tools/gitClean/registration.js +98 -0
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +7 -0
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +35 -0
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +73 -0
- package/dist/mcp-server/tools/gitClone/index.js +7 -0
- package/dist/mcp-server/tools/gitClone/logic.js +136 -0
- package/dist/mcp-server/tools/gitClone/registration.js +44 -0
- package/dist/mcp-server/tools/gitCommit/index.js +7 -0
- package/dist/mcp-server/tools/gitCommit/logic.js +129 -0
- package/dist/mcp-server/tools/gitCommit/registration.js +100 -0
- package/dist/mcp-server/tools/gitDiff/index.js +6 -0
- package/dist/mcp-server/tools/gitDiff/logic.js +114 -0
- package/dist/mcp-server/tools/gitDiff/registration.js +74 -0
- package/dist/mcp-server/tools/gitFetch/index.js +6 -0
- package/dist/mcp-server/tools/gitFetch/logic.js +116 -0
- package/dist/mcp-server/tools/gitFetch/registration.js +71 -0
- package/dist/mcp-server/tools/gitInit/index.js +7 -0
- package/dist/mcp-server/tools/gitInit/logic.js +117 -0
- package/dist/mcp-server/tools/gitInit/registration.js +44 -0
- package/dist/mcp-server/tools/gitLog/index.js +6 -0
- package/dist/mcp-server/tools/gitLog/logic.js +148 -0
- package/dist/mcp-server/tools/gitLog/registration.js +71 -0
- package/dist/mcp-server/tools/gitMerge/index.js +7 -0
- package/dist/mcp-server/tools/gitMerge/logic.js +160 -0
- package/dist/mcp-server/tools/gitMerge/registration.js +77 -0
- package/dist/mcp-server/tools/gitPull/index.js +6 -0
- package/dist/mcp-server/tools/gitPull/logic.js +144 -0
- package/dist/mcp-server/tools/gitPull/registration.js +81 -0
- package/dist/mcp-server/tools/gitPush/index.js +6 -0
- package/dist/mcp-server/tools/gitPush/logic.js +188 -0
- package/dist/mcp-server/tools/gitPush/registration.js +81 -0
- package/dist/mcp-server/tools/gitRebase/index.js +7 -0
- package/dist/mcp-server/tools/gitRebase/logic.js +171 -0
- package/dist/mcp-server/tools/gitRebase/registration.js +72 -0
- package/dist/mcp-server/tools/gitRemote/index.js +7 -0
- package/dist/mcp-server/tools/gitRemote/logic.js +158 -0
- package/dist/mcp-server/tools/gitRemote/registration.js +76 -0
- package/dist/mcp-server/tools/gitReset/index.js +6 -0
- package/dist/mcp-server/tools/gitReset/logic.js +116 -0
- package/dist/mcp-server/tools/gitReset/registration.js +71 -0
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +7 -0
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +91 -0
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +78 -0
- package/dist/mcp-server/tools/gitShow/index.js +7 -0
- package/dist/mcp-server/tools/gitShow/logic.js +99 -0
- package/dist/mcp-server/tools/gitShow/registration.js +83 -0
- package/dist/mcp-server/tools/gitStash/index.js +7 -0
- package/dist/mcp-server/tools/gitStash/logic.js +161 -0
- package/dist/mcp-server/tools/gitStash/registration.js +84 -0
- package/dist/mcp-server/tools/gitStatus/index.js +7 -0
- package/dist/mcp-server/tools/gitStatus/logic.js +215 -0
- package/dist/mcp-server/tools/gitStatus/registration.js +77 -0
- package/dist/mcp-server/tools/gitTag/index.js +7 -0
- package/dist/mcp-server/tools/gitTag/logic.js +142 -0
- package/dist/mcp-server/tools/gitTag/registration.js +84 -0
- package/dist/types-global/errors.js +68 -0
- package/dist/types-global/mcp.js +59 -0
- package/dist/types-global/tool.js +1 -0
- package/dist/utils/errorHandler.js +237 -0
- package/dist/utils/idGenerator.js +148 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/jsonParser.js +78 -0
- package/dist/utils/logger.js +266 -0
- package/dist/utils/rateLimiter.js +177 -0
- package/dist/utils/requestContext.js +49 -0
- package/dist/utils/sanitization.js +371 -0
- package/dist/utils/tokenCounter.js +124 -0
- package/package.json +62 -17
- package/build/index.js +0 -54
- package/build/resources/descriptors.js +0 -77
- package/build/resources/diff.js +0 -241
- package/build/resources/file.js +0 -222
- package/build/resources/history.js +0 -242
- package/build/resources/index.js +0 -99
- package/build/resources/repository.js +0 -286
- package/build/server.js +0 -120
- package/build/services/error-service.js +0 -73
- package/build/services/git-service.js +0 -965
- package/build/tools/advanced.js +0 -526
- package/build/tools/branch.js +0 -296
- package/build/tools/index.js +0 -29
- package/build/tools/remote.js +0 -279
- package/build/tools/repository.js +0 -170
- package/build/tools/workdir.js +0 -445
- package/build/types/git.js +0 -7
- package/build/utils/global-settings.js +0 -64
- package/build/utils/validation.js +0 -108
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { BaseErrorCode, McpError } from '../types-global/errors.js';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
import { sanitizeInputForLogging } from './sanitization.js'; // Updated import
|
|
4
|
+
/**
|
|
5
|
+
* Simple mapper that maps error types to error codes
|
|
6
|
+
*/
|
|
7
|
+
const ERROR_TYPE_MAPPINGS = {
|
|
8
|
+
'SyntaxError': BaseErrorCode.VALIDATION_ERROR,
|
|
9
|
+
'TypeError': BaseErrorCode.VALIDATION_ERROR,
|
|
10
|
+
'ReferenceError': BaseErrorCode.INTERNAL_ERROR,
|
|
11
|
+
'RangeError': BaseErrorCode.VALIDATION_ERROR,
|
|
12
|
+
'URIError': BaseErrorCode.VALIDATION_ERROR,
|
|
13
|
+
'EvalError': BaseErrorCode.INTERNAL_ERROR
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Common error patterns for automatic classification
|
|
17
|
+
*/
|
|
18
|
+
const COMMON_ERROR_PATTERNS = [
|
|
19
|
+
// Authentication related errors
|
|
20
|
+
{ pattern: /auth|unauthorized|unauthenticated|not.*logged.*in|invalid.*token|expired.*token/i, errorCode: BaseErrorCode.UNAUTHORIZED },
|
|
21
|
+
// Permission related errors
|
|
22
|
+
{ pattern: /permission|forbidden|access.*denied|not.*allowed/i, errorCode: BaseErrorCode.FORBIDDEN },
|
|
23
|
+
// Not found errors
|
|
24
|
+
{ pattern: /not.*found|missing|no.*such|doesn't.*exist|couldn't.*find/i, errorCode: BaseErrorCode.NOT_FOUND },
|
|
25
|
+
// Validation errors
|
|
26
|
+
{ pattern: /invalid|validation|malformed|bad request|wrong format/i, errorCode: BaseErrorCode.VALIDATION_ERROR },
|
|
27
|
+
// Conflict errors
|
|
28
|
+
{ pattern: /conflict|already.*exists|duplicate|unique.*constraint/i, errorCode: BaseErrorCode.CONFLICT },
|
|
29
|
+
// Rate limiting
|
|
30
|
+
{ pattern: /rate.*limit|too.*many.*requests|throttled/i, errorCode: BaseErrorCode.RATE_LIMITED },
|
|
31
|
+
// Timeout errors
|
|
32
|
+
{ pattern: /timeout|timed.*out|deadline.*exceeded/i, errorCode: BaseErrorCode.TIMEOUT },
|
|
33
|
+
// External service errors
|
|
34
|
+
{ pattern: /service.*unavailable|bad.*gateway|gateway.*timeout/i, errorCode: BaseErrorCode.SERVICE_UNAVAILABLE }
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Get a readable name for an error
|
|
38
|
+
* @param error Error to get name for
|
|
39
|
+
* @returns User-friendly error name
|
|
40
|
+
*/
|
|
41
|
+
function getErrorName(error) {
|
|
42
|
+
if (error instanceof Error) {
|
|
43
|
+
return error.name || 'Error';
|
|
44
|
+
}
|
|
45
|
+
if (error === null) {
|
|
46
|
+
return 'NullError';
|
|
47
|
+
}
|
|
48
|
+
if (error === undefined) {
|
|
49
|
+
return 'UndefinedError';
|
|
50
|
+
}
|
|
51
|
+
return typeof error === 'object'
|
|
52
|
+
? 'ObjectError'
|
|
53
|
+
: 'UnknownError';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get a message from an error
|
|
57
|
+
* @param error Error to get message from
|
|
58
|
+
* @returns Error message
|
|
59
|
+
*/
|
|
60
|
+
function getErrorMessage(error) {
|
|
61
|
+
if (error instanceof Error) {
|
|
62
|
+
return error.message;
|
|
63
|
+
}
|
|
64
|
+
if (error === null) {
|
|
65
|
+
return 'Null error occurred';
|
|
66
|
+
}
|
|
67
|
+
if (error === undefined) {
|
|
68
|
+
return 'Undefined error occurred';
|
|
69
|
+
}
|
|
70
|
+
return typeof error === 'string'
|
|
71
|
+
? error
|
|
72
|
+
: String(error);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Error handler utility class with various error handling methods
|
|
76
|
+
*/
|
|
77
|
+
export class ErrorHandler {
|
|
78
|
+
/**
|
|
79
|
+
* Determine the appropriate error code for an error based on patterns and type
|
|
80
|
+
* @param error The error to classify
|
|
81
|
+
* @returns The appropriate error code
|
|
82
|
+
*/
|
|
83
|
+
static determineErrorCode(error) {
|
|
84
|
+
// If it's already an McpError, use its code
|
|
85
|
+
if (error instanceof McpError) {
|
|
86
|
+
return error.code;
|
|
87
|
+
}
|
|
88
|
+
const errorName = getErrorName(error);
|
|
89
|
+
const errorMessage = getErrorMessage(error);
|
|
90
|
+
// Check if the error type has a direct mapping
|
|
91
|
+
if (errorName in ERROR_TYPE_MAPPINGS) {
|
|
92
|
+
return ERROR_TYPE_MAPPINGS[errorName];
|
|
93
|
+
}
|
|
94
|
+
// Check for common error patterns
|
|
95
|
+
for (const pattern of COMMON_ERROR_PATTERNS) {
|
|
96
|
+
const regex = pattern.pattern instanceof RegExp
|
|
97
|
+
? pattern.pattern
|
|
98
|
+
: new RegExp(pattern.pattern, 'i');
|
|
99
|
+
if (regex.test(errorMessage) || regex.test(errorName)) {
|
|
100
|
+
return pattern.errorCode;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Default to internal error if no pattern matches
|
|
104
|
+
return BaseErrorCode.INTERNAL_ERROR;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Handle operation errors with consistent logging and transformation
|
|
108
|
+
* @param error The error that occurred
|
|
109
|
+
* @param options Error handling options
|
|
110
|
+
* @returns The transformed error
|
|
111
|
+
*/
|
|
112
|
+
static handleError(error, options) {
|
|
113
|
+
const { context, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false } = options;
|
|
114
|
+
// If it's already an McpError, use it directly but apply additional context
|
|
115
|
+
if (error instanceof McpError) {
|
|
116
|
+
// Add any additional context
|
|
117
|
+
if (context && Object.keys(context).length > 0) {
|
|
118
|
+
error.details = { ...error.details, ...context };
|
|
119
|
+
}
|
|
120
|
+
// Log the error with sanitized input
|
|
121
|
+
logger.error(`Error ${operation}: ${error.message}`, {
|
|
122
|
+
errorCode: error.code,
|
|
123
|
+
requestId: context?.requestId,
|
|
124
|
+
input: input ? sanitizeInputForLogging(input) : undefined,
|
|
125
|
+
stack: includeStack ? error.stack : undefined,
|
|
126
|
+
critical,
|
|
127
|
+
...context
|
|
128
|
+
});
|
|
129
|
+
if (rethrow) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
// Sanitize input for logging
|
|
135
|
+
const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined;
|
|
136
|
+
// Log the error with consistent format
|
|
137
|
+
logger.error(`Error ${operation}`, {
|
|
138
|
+
error: error instanceof Error ? error.message : String(error),
|
|
139
|
+
errorType: getErrorName(error),
|
|
140
|
+
input: sanitizedInput,
|
|
141
|
+
requestId: context?.requestId,
|
|
142
|
+
stack: includeStack && error instanceof Error ? error.stack : undefined,
|
|
143
|
+
critical,
|
|
144
|
+
...context
|
|
145
|
+
});
|
|
146
|
+
// Choose the error code (explicit > determined > default)
|
|
147
|
+
const errorCode = explicitErrorCode ||
|
|
148
|
+
ErrorHandler.determineErrorCode(error) ||
|
|
149
|
+
BaseErrorCode.INTERNAL_ERROR;
|
|
150
|
+
// Transform to appropriate error type
|
|
151
|
+
const transformedError = options.errorMapper
|
|
152
|
+
? options.errorMapper(error)
|
|
153
|
+
: new McpError(errorCode, `Error ${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`, {
|
|
154
|
+
originalError: getErrorName(error),
|
|
155
|
+
...context
|
|
156
|
+
});
|
|
157
|
+
// Rethrow if requested
|
|
158
|
+
if (rethrow) {
|
|
159
|
+
throw transformedError;
|
|
160
|
+
}
|
|
161
|
+
return transformedError;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Map an error to a specific error type based on error message patterns
|
|
165
|
+
* @param error The error to map
|
|
166
|
+
* @param mappings Array of pattern and factory mappings
|
|
167
|
+
* @param defaultFactory Default factory function if no pattern matches
|
|
168
|
+
* @returns The mapped error
|
|
169
|
+
*/
|
|
170
|
+
static mapError(error, mappings, defaultFactory) {
|
|
171
|
+
// If it's already the target type and we have a default factory to check against, return it
|
|
172
|
+
if (defaultFactory && error instanceof Error) {
|
|
173
|
+
const defaultInstance = defaultFactory(error);
|
|
174
|
+
if (error.constructor === defaultInstance.constructor) {
|
|
175
|
+
return error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const errorMessage = getErrorMessage(error);
|
|
179
|
+
// Check each pattern and return the first match
|
|
180
|
+
for (const mapping of mappings) {
|
|
181
|
+
const matches = mapping.pattern instanceof RegExp
|
|
182
|
+
? mapping.pattern.test(errorMessage)
|
|
183
|
+
: errorMessage.includes(mapping.pattern);
|
|
184
|
+
if (matches) {
|
|
185
|
+
return mapping.factory(error, mapping.additionalContext);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Return default or original error
|
|
189
|
+
if (defaultFactory) {
|
|
190
|
+
return defaultFactory(error);
|
|
191
|
+
}
|
|
192
|
+
return error instanceof Error
|
|
193
|
+
? error
|
|
194
|
+
: new Error(String(error));
|
|
195
|
+
}
|
|
196
|
+
// Removed createErrorMapper method for simplification
|
|
197
|
+
/**
|
|
198
|
+
* Format an error for consistent response structure
|
|
199
|
+
* @param error The error to format
|
|
200
|
+
* @returns Formatted error object
|
|
201
|
+
*/
|
|
202
|
+
static formatError(error) {
|
|
203
|
+
if (error instanceof McpError) {
|
|
204
|
+
return {
|
|
205
|
+
code: error.code,
|
|
206
|
+
message: error.message,
|
|
207
|
+
details: error.details || {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (error instanceof Error) {
|
|
211
|
+
return {
|
|
212
|
+
code: ErrorHandler.determineErrorCode(error),
|
|
213
|
+
message: error.message,
|
|
214
|
+
details: { errorType: error.name }
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
code: BaseErrorCode.UNKNOWN_ERROR,
|
|
219
|
+
message: String(error),
|
|
220
|
+
details: { errorType: typeof error }
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Safely execute a function and handle any errors
|
|
225
|
+
* @param fn Function to execute
|
|
226
|
+
* @param options Error handling options
|
|
227
|
+
* @returns The result of the function or error
|
|
228
|
+
*/
|
|
229
|
+
static async tryCatch(fn, options) {
|
|
230
|
+
try {
|
|
231
|
+
return await fn();
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw ErrorHandler.handleError(error, { ...options, rethrow: true });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { BaseErrorCode, McpError } from '../types-global/errors.js';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generic ID Generator class for creating and managing unique identifiers
|
|
6
|
+
*/
|
|
7
|
+
export class IdGenerator {
|
|
8
|
+
// Default charset
|
|
9
|
+
static DEFAULT_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
10
|
+
// Default separator
|
|
11
|
+
static DEFAULT_SEPARATOR = '_';
|
|
12
|
+
// Default random part length
|
|
13
|
+
static DEFAULT_LENGTH = 6;
|
|
14
|
+
// Entity prefixes
|
|
15
|
+
entityPrefixes = {};
|
|
16
|
+
// Reverse mapping for prefix to entity type lookup
|
|
17
|
+
prefixToEntityType = {};
|
|
18
|
+
/**
|
|
19
|
+
* Constructor that accepts entity prefix configuration
|
|
20
|
+
* @param entityPrefixes Map of entity types to their prefixes
|
|
21
|
+
*/
|
|
22
|
+
constructor(entityPrefixes = {}) {
|
|
23
|
+
this.setEntityPrefixes(entityPrefixes);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set or update entity prefixes and rebuild the reverse lookup
|
|
27
|
+
* @param entityPrefixes Map of entity types to their prefixes
|
|
28
|
+
*/
|
|
29
|
+
setEntityPrefixes(entityPrefixes) {
|
|
30
|
+
this.entityPrefixes = { ...entityPrefixes };
|
|
31
|
+
// Rebuild reverse mapping
|
|
32
|
+
this.prefixToEntityType = Object.entries(this.entityPrefixes).reduce((acc, [type, prefix]) => {
|
|
33
|
+
acc[prefix] = type;
|
|
34
|
+
acc[prefix.toLowerCase()] = type;
|
|
35
|
+
return acc;
|
|
36
|
+
}, {});
|
|
37
|
+
logger.debug('Entity prefixes updated', { entityPrefixes: this.entityPrefixes });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get all registered entity prefixes
|
|
41
|
+
* @returns The entity prefix configuration
|
|
42
|
+
*/
|
|
43
|
+
getEntityPrefixes() {
|
|
44
|
+
return { ...this.entityPrefixes };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generates a cryptographically secure random alphanumeric string
|
|
48
|
+
* @param length The length of the random string to generate
|
|
49
|
+
* @param charset Optional custom character set
|
|
50
|
+
* @returns Random alphanumeric string
|
|
51
|
+
*/
|
|
52
|
+
generateRandomString(length = IdGenerator.DEFAULT_LENGTH, charset = IdGenerator.DEFAULT_CHARSET) {
|
|
53
|
+
const bytes = randomBytes(length);
|
|
54
|
+
let result = '';
|
|
55
|
+
for (let i = 0; i < length; i++) {
|
|
56
|
+
result += charset[bytes[i] % charset.length];
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generates a unique ID with an optional prefix
|
|
62
|
+
* @param prefix Optional prefix to add to the ID
|
|
63
|
+
* @param options Optional generation options
|
|
64
|
+
* @returns A unique identifier string
|
|
65
|
+
*/
|
|
66
|
+
generate(prefix, options = {}) {
|
|
67
|
+
const { length = IdGenerator.DEFAULT_LENGTH, separator = IdGenerator.DEFAULT_SEPARATOR, charset = IdGenerator.DEFAULT_CHARSET } = options;
|
|
68
|
+
const randomPart = this.generateRandomString(length, charset);
|
|
69
|
+
return prefix
|
|
70
|
+
? `${prefix}${separator}${randomPart}`
|
|
71
|
+
: randomPart;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generates a custom ID for an entity with format PREFIX_XXXXXX
|
|
75
|
+
* @param entityType The type of entity to generate an ID for
|
|
76
|
+
* @param options Optional generation options
|
|
77
|
+
* @returns A unique identifier string (e.g., "PROJ_A6B3J0")
|
|
78
|
+
* @throws {McpError} If the entity type is not registered
|
|
79
|
+
*/
|
|
80
|
+
generateForEntity(entityType, options = {}) {
|
|
81
|
+
const prefix = this.entityPrefixes[entityType];
|
|
82
|
+
if (!prefix) {
|
|
83
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unknown entity type: ${entityType}`);
|
|
84
|
+
}
|
|
85
|
+
return this.generate(prefix, options);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Validates if a given ID matches the expected format for an entity type
|
|
89
|
+
* @param id The ID to validate
|
|
90
|
+
* @param entityType The expected entity type
|
|
91
|
+
* @param options Optional validation options
|
|
92
|
+
* @returns boolean indicating if the ID is valid
|
|
93
|
+
*/
|
|
94
|
+
isValid(id, entityType, options = {}) {
|
|
95
|
+
const prefix = this.entityPrefixes[entityType];
|
|
96
|
+
const { length = IdGenerator.DEFAULT_LENGTH, separator = IdGenerator.DEFAULT_SEPARATOR } = options;
|
|
97
|
+
if (!prefix) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const pattern = new RegExp(`^${prefix}${separator}[A-Z0-9]{${length}}$`);
|
|
101
|
+
return pattern.test(id);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Strips the prefix from an ID
|
|
105
|
+
* @param id The ID to strip
|
|
106
|
+
* @param separator Optional custom separator
|
|
107
|
+
* @returns The ID without the prefix
|
|
108
|
+
*/
|
|
109
|
+
stripPrefix(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
|
|
110
|
+
return id.split(separator)[1] || id;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Determines the entity type from an ID
|
|
114
|
+
* @param id The ID to get the entity type for
|
|
115
|
+
* @param separator Optional custom separator
|
|
116
|
+
* @returns The entity type
|
|
117
|
+
* @throws {McpError} If the ID format is invalid or entity type is unknown
|
|
118
|
+
*/
|
|
119
|
+
getEntityType(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
|
|
120
|
+
const parts = id.split(separator);
|
|
121
|
+
if (parts.length !== 2 || !parts[0]) {
|
|
122
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid ID format: ${id}. Expected format: PREFIX${separator}XXXXXX`);
|
|
123
|
+
}
|
|
124
|
+
const prefix = parts[0];
|
|
125
|
+
const entityType = this.prefixToEntityType[prefix];
|
|
126
|
+
if (!entityType) {
|
|
127
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unknown entity type prefix: ${prefix}`);
|
|
128
|
+
}
|
|
129
|
+
return entityType;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Normalizes an entity ID to ensure consistent uppercase format
|
|
133
|
+
* @param id The ID to normalize
|
|
134
|
+
* @param separator Optional custom separator
|
|
135
|
+
* @returns The normalized ID in uppercase format
|
|
136
|
+
*/
|
|
137
|
+
normalize(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
|
|
138
|
+
const entityType = this.getEntityType(id, separator);
|
|
139
|
+
const idParts = id.split(separator);
|
|
140
|
+
return `${this.entityPrefixes[entityType]}${separator}${idParts[1].toUpperCase()}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Create and export a default instance with an empty entity prefix configuration
|
|
144
|
+
export const idGenerator = new IdGenerator();
|
|
145
|
+
// For standalone use as a UUID generator
|
|
146
|
+
export const generateUUID = () => {
|
|
147
|
+
return crypto.randomUUID();
|
|
148
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Re-export all utilities using wildcard exports for simplicity
|
|
2
|
+
export * from './requestContext.js';
|
|
3
|
+
export * from './errorHandler.js';
|
|
4
|
+
export * from './idGenerator.js';
|
|
5
|
+
export * from './logger.js';
|
|
6
|
+
export * from './rateLimiter.js';
|
|
7
|
+
export * from './sanitization.js';
|
|
8
|
+
export * from './tokenCounter.js';
|
|
9
|
+
export * from './jsonParser.js';
|
|
10
|
+
// No need for explicit named imports/exports or default export
|
|
11
|
+
// when using wildcard exports for a simple barrel file.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { parse as parsePartialJson, Allow as PartialJsonAllow } from 'partial-json';
|
|
2
|
+
import { BaseErrorCode, McpError } from '../types-global/errors.js';
|
|
3
|
+
import { logger } from './logger.js'; // Import logger
|
|
4
|
+
/**
|
|
5
|
+
* Enum mirroring partial-json's Allow constants for specifying
|
|
6
|
+
* what types of partial JSON structures are permissible during parsing.
|
|
7
|
+
* Use bitwise OR to combine options (e.g., Allow.STR | Allow.OBJ).
|
|
8
|
+
*/
|
|
9
|
+
export const Allow = PartialJsonAllow;
|
|
10
|
+
// Regex to find a <think> block at the start, capturing its content and the rest of the string
|
|
11
|
+
const thinkBlockRegex = /^<think>([\s\S]*?)<\/think>\s*([\s\S]*)$/;
|
|
12
|
+
/**
|
|
13
|
+
* Utility class for parsing potentially partial JSON strings.
|
|
14
|
+
* Wraps the 'partial-json' library to provide a consistent interface
|
|
15
|
+
* within the atlas-mcp-agent project.
|
|
16
|
+
* Handles optional <think>...</think> blocks at the beginning of the input.
|
|
17
|
+
*/
|
|
18
|
+
class JsonParser {
|
|
19
|
+
/**
|
|
20
|
+
* Parses a JSON string, potentially allowing for incomplete structures
|
|
21
|
+
* and handling optional <think> blocks at the start.
|
|
22
|
+
*
|
|
23
|
+
* @param jsonString The JSON string to parse.
|
|
24
|
+
* @param allowPartial A bitwise OR combination of 'Allow' constants specifying permissible partial types (defaults to Allow.ALL).
|
|
25
|
+
* @param context Optional RequestContext for error correlation and logging think blocks.
|
|
26
|
+
* @returns The parsed JavaScript value.
|
|
27
|
+
* @throws {McpError} Throws an McpError with BaseErrorCode.VALIDATION_ERROR if parsing fails due to malformed JSON.
|
|
28
|
+
*/
|
|
29
|
+
parse(jsonString, allowPartial = Allow.ALL, context) {
|
|
30
|
+
let stringToParse = jsonString;
|
|
31
|
+
const match = jsonString.match(thinkBlockRegex);
|
|
32
|
+
if (match) {
|
|
33
|
+
const thinkContent = match[1].trim();
|
|
34
|
+
const restOfString = match[2];
|
|
35
|
+
if (thinkContent) {
|
|
36
|
+
logger.debug('LLM <think> block detected and logged.', { ...context, thinkContent });
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
logger.debug('Empty LLM <think> block detected.', context);
|
|
40
|
+
}
|
|
41
|
+
stringToParse = restOfString; // Parse only the part after </think>
|
|
42
|
+
}
|
|
43
|
+
// Trim leading/trailing whitespace which might interfere with JSON parsing, especially if only JSON is left
|
|
44
|
+
stringToParse = stringToParse.trim();
|
|
45
|
+
if (!stringToParse) {
|
|
46
|
+
// If after removing think block and trimming, the string is empty, it's an error
|
|
47
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'JSON string is empty after removing <think> block.', context);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
// Ensure the string starts with '{' or '[' if we expect an object or array after stripping <think>
|
|
51
|
+
// This helps catch cases where only non-JSON text remains.
|
|
52
|
+
if (!stringToParse.startsWith('{') && !stringToParse.startsWith('[')) {
|
|
53
|
+
// Check if it might be a simple string value that partial-json could parse
|
|
54
|
+
// Allow simple strings only if specifically permitted or Allow.ALL is used
|
|
55
|
+
const allowsString = (allowPartial & Allow.STR) === Allow.STR;
|
|
56
|
+
if (!allowsString && !stringToParse.startsWith('"')) { // Allow quoted strings if Allow.STR is set
|
|
57
|
+
throw new Error('Remaining content does not appear to be valid JSON object or array.');
|
|
58
|
+
}
|
|
59
|
+
// If it starts with a quote and strings are allowed, let parsePartialJson handle it
|
|
60
|
+
}
|
|
61
|
+
return parsePartialJson(stringToParse, allowPartial);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Wrap the original error in an McpError for consistent error handling
|
|
65
|
+
// Include the original error message for better debugging context.
|
|
66
|
+
logger.error('Failed to parse JSON content.', { ...context, error: error.message, contentAttempted: stringToParse });
|
|
67
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to parse JSON: ${error.message}`, {
|
|
68
|
+
...context,
|
|
69
|
+
originalContent: stringToParse,
|
|
70
|
+
rawError: error instanceof Error ? error.stack : String(error) // Include raw error info
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Singleton instance of the JsonParser utility.
|
|
77
|
+
*/
|
|
78
|
+
export const jsonParser = new JsonParser();
|