UNPKG

@nvhien20vn/git-mcp-server

Version:
80 lines (79 loc) 4.33 kB
import { parse as parsePartialJson, Allow as PartialJsonAllow } from 'partial-json'; import { BaseErrorCode, McpError } from '../../types-global/errors.js'; // Import utils from the main barrel file (logger, RequestContext from ../internal/*) import { logger } from '../index.js'; /** * Enum mirroring partial-json's Allow constants for specifying * what types of partial JSON structures are permissible during parsing. * Use bitwise OR to combine options (e.g., Allow.STR | Allow.OBJ). */ export const Allow = PartialJsonAllow; // Regex to find a <think> block at the start, capturing its content and the rest of the string const thinkBlockRegex = /^<think>([\s\S]*?)<\/think>\s*([\s\S]*)$/; /** * Utility class for parsing potentially partial JSON strings. * Wraps the 'partial-json' library to provide a consistent interface * within the atlas-mcp-agent project. * Handles optional <think>...</think> blocks at the beginning of the input. */ class JsonParser { /** * Parses a JSON string, potentially allowing for incomplete structures * and handling optional <think> blocks at the start. * * @param jsonString The JSON string to parse. * @param allowPartial A bitwise OR combination of 'Allow' constants specifying permissible partial types (defaults to Allow.ALL). * @param context Optional RequestContext for error correlation and logging think blocks. * @returns The parsed JavaScript value. * @throws {McpError} Throws an McpError with BaseErrorCode.VALIDATION_ERROR if parsing fails due to malformed JSON. */ parse(jsonString, allowPartial = Allow.ALL, context) { let stringToParse = jsonString; const match = jsonString.match(thinkBlockRegex); if (match) { const thinkContent = match[1].trim(); const restOfString = match[2]; if (thinkContent) { logger.debug('LLM <think> block detected and logged.', { ...context, thinkContent }); } else { logger.debug('Empty LLM <think> block detected.', context); } stringToParse = restOfString; // Parse only the part after </think> } // Trim leading/trailing whitespace which might interfere with JSON parsing, especially if only JSON is left stringToParse = stringToParse.trim(); if (!stringToParse) { // If after removing think block and trimming, the string is empty, it's an error throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'JSON string is empty after removing <think> block.', context); } try { // Ensure the string starts with '{' or '[' if we expect an object or array after stripping <think> // This helps catch cases where only non-JSON text remains. if (!stringToParse.startsWith('{') && !stringToParse.startsWith('[')) { // Check if it might be a simple string value that partial-json could parse // Allow simple strings only if specifically permitted or Allow.ALL is used const allowsString = (allowPartial & Allow.STR) === Allow.STR; if (!allowsString && !stringToParse.startsWith('"')) { // Allow quoted strings if Allow.STR is set throw new Error('Remaining content does not appear to be valid JSON object or array.'); } // If it starts with a quote and strings are allowed, let parsePartialJson handle it } return parsePartialJson(stringToParse, allowPartial); } catch (error) { // Wrap the original error in an McpError for consistent error handling // Include the original error message for better debugging context. logger.error('Failed to parse JSON content.', { ...context, error: error.message, contentAttempted: stringToParse }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to parse JSON: ${error.message}`, { ...context, originalContent: stringToParse, rawError: error instanceof Error ? error.stack : String(error) // Include raw error info }); } } } /** * Singleton instance of the JsonParser utility. */ export const jsonParser = new JsonParser();