263 lines
9.2 KiB
TypeScript
263 lines
9.2 KiB
TypeScript
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
|
|
|
|
export class ValidationTools implements ToolExecutor {
|
|
getTools(): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: 'validate_json_params',
|
|
description: 'Validate and fix JSON parameters before sending to other tools',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
jsonString: {
|
|
type: 'string',
|
|
description: 'JSON string to validate and fix'
|
|
},
|
|
expectedSchema: {
|
|
type: 'object',
|
|
description: 'Expected parameter schema (optional)'
|
|
}
|
|
},
|
|
required: ['jsonString']
|
|
}
|
|
},
|
|
{
|
|
name: 'safe_string_value',
|
|
description: 'Create a safe string value that won\'t cause JSON parsing issues',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
value: {
|
|
type: 'string',
|
|
description: 'String value to make safe'
|
|
}
|
|
},
|
|
required: ['value']
|
|
}
|
|
},
|
|
{
|
|
name: 'format_mcp_request',
|
|
description: 'Format a complete MCP request with proper JSON escaping',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
toolName: {
|
|
type: 'string',
|
|
description: 'Tool name to call'
|
|
},
|
|
arguments: {
|
|
type: 'object',
|
|
description: 'Tool arguments'
|
|
}
|
|
},
|
|
required: ['toolName', 'arguments']
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
|
switch (toolName) {
|
|
case 'validate_json_params':
|
|
return await this.validateJsonParams(args.jsonString, args.expectedSchema);
|
|
case 'safe_string_value':
|
|
return await this.createSafeStringValue(args.value);
|
|
case 'format_mcp_request':
|
|
return await this.formatMcpRequest(args.toolName, args.arguments);
|
|
default:
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
}
|
|
|
|
private async validateJsonParams(jsonString: string, expectedSchema?: any): Promise<ToolResponse> {
|
|
try {
|
|
// First try to parse as-is
|
|
let parsed;
|
|
try {
|
|
parsed = JSON.parse(jsonString);
|
|
} catch (error: any) {
|
|
// Try to fix common issues
|
|
const fixed = this.fixJsonString(jsonString);
|
|
try {
|
|
parsed = JSON.parse(fixed);
|
|
} catch (secondError) {
|
|
return {
|
|
success: false,
|
|
error: `Cannot fix JSON: ${error.message}`,
|
|
data: {
|
|
originalJson: jsonString,
|
|
fixedAttempt: fixed,
|
|
suggestions: this.getJsonFixSuggestions(jsonString)
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Validate against schema if provided
|
|
if (expectedSchema) {
|
|
const validation = this.validateAgainstSchema(parsed, expectedSchema);
|
|
if (!validation.valid) {
|
|
return {
|
|
success: false,
|
|
error: 'Schema validation failed',
|
|
data: {
|
|
parsedJson: parsed,
|
|
validationErrors: validation.errors,
|
|
suggestions: validation.suggestions
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
parsedJson: parsed,
|
|
fixedJson: JSON.stringify(parsed, null, 2),
|
|
isValid: true
|
|
}
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
private async createSafeStringValue(value: string): Promise<ToolResponse> {
|
|
const safeValue = this.escapJsonString(value);
|
|
return {
|
|
success: true,
|
|
data: {
|
|
originalValue: value,
|
|
safeValue: safeValue,
|
|
jsonReady: JSON.stringify(safeValue),
|
|
usage: `Use "${safeValue}" in your JSON parameters`
|
|
}
|
|
};
|
|
}
|
|
|
|
private async formatMcpRequest(toolName: string, toolArgs: any): Promise<ToolResponse> {
|
|
try {
|
|
const mcpRequest = {
|
|
jsonrpc: '2.0',
|
|
id: Date.now(),
|
|
method: 'tools/call',
|
|
params: {
|
|
name: toolName,
|
|
arguments: toolArgs
|
|
}
|
|
};
|
|
|
|
const formattedJson = JSON.stringify(mcpRequest, null, 2);
|
|
const compactJson = JSON.stringify(mcpRequest);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
request: mcpRequest,
|
|
formattedJson: formattedJson,
|
|
compactJson: compactJson,
|
|
curlCommand: this.generateCurlCommand(compactJson)
|
|
}
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
error: `Failed to format MCP request: ${error.message}`
|
|
};
|
|
}
|
|
}
|
|
|
|
private fixJsonString(jsonStr: string): string {
|
|
let fixed = jsonStr;
|
|
|
|
// Fix common escape character issues
|
|
fixed = fixed
|
|
// Fix unescaped quotes in string values
|
|
.replace(/(\{[^}]*"[^"]*":\s*")([^"]*")([^"]*")([^}]*\})/g, (match, prefix, content, suffix, end) => {
|
|
const escapedContent = content.replace(/"/g, '\\"');
|
|
return prefix + escapedContent + suffix + end;
|
|
})
|
|
// Fix unescaped backslashes
|
|
.replace(/([^\\])\\([^"\\\/bfnrtu])/g, '$1\\\\$2')
|
|
// Fix trailing commas
|
|
.replace(/,(\s*[}\]])/g, '$1')
|
|
// Fix control characters
|
|
.replace(/\n/g, '\\n')
|
|
.replace(/\r/g, '\\r')
|
|
.replace(/\t/g, '\\t')
|
|
// Fix single quotes to double quotes
|
|
.replace(/'/g, '"');
|
|
|
|
return fixed;
|
|
}
|
|
|
|
private escapJsonString(str: string): string {
|
|
return str
|
|
.replace(/\\/g, '\\\\') // Escape backslashes first
|
|
.replace(/"/g, '\\"') // Escape quotes
|
|
.replace(/\n/g, '\\n') // Escape newlines
|
|
.replace(/\r/g, '\\r') // Escape carriage returns
|
|
.replace(/\t/g, '\\t') // Escape tabs
|
|
.replace(/\f/g, '\\f') // Escape form feeds
|
|
.replace(/\b/g, '\\b'); // Escape backspaces
|
|
}
|
|
|
|
private validateAgainstSchema(data: any, schema: any): { valid: boolean; errors: string[]; suggestions: string[] } {
|
|
const errors: string[] = [];
|
|
const suggestions: string[] = [];
|
|
|
|
// Basic type checking
|
|
if (schema.type) {
|
|
const actualType = Array.isArray(data) ? 'array' : typeof data;
|
|
if (actualType !== schema.type) {
|
|
errors.push(`Expected type ${schema.type}, got ${actualType}`);
|
|
suggestions.push(`Convert value to ${schema.type}`);
|
|
}
|
|
}
|
|
|
|
// Required fields checking
|
|
if (schema.required && Array.isArray(schema.required)) {
|
|
for (const field of schema.required) {
|
|
if (!(field in data)) {
|
|
errors.push(`Missing required field: ${field}`);
|
|
suggestions.push(`Add required field "${field}"`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
suggestions
|
|
};
|
|
}
|
|
|
|
private getJsonFixSuggestions(jsonStr: string): string[] {
|
|
const suggestions: string[] = [];
|
|
|
|
if (jsonStr.includes('\\"')) {
|
|
suggestions.push('Check for improperly escaped quotes');
|
|
}
|
|
if (jsonStr.includes("'")) {
|
|
suggestions.push('Replace single quotes with double quotes');
|
|
}
|
|
if (jsonStr.includes('\n') || jsonStr.includes('\t')) {
|
|
suggestions.push('Escape newlines and tabs properly');
|
|
}
|
|
if (jsonStr.match(/,\s*[}\]]/)) {
|
|
suggestions.push('Remove trailing commas');
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
private generateCurlCommand(jsonStr: string): string {
|
|
const escapedJson = jsonStr.replace(/'/g, "'\"'\"'");
|
|
return `curl -X POST http://127.0.0.1:8585/mcp \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '${escapedJson}'`;
|
|
}
|
|
} |