@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
319 lines • 13.3 kB
JavaScript
/**
* Validate Persona Tool - Validate a specific persona's structure and configuration with detailed reporting
*/
import { validatePersona, } from "../../../../persona/validator.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { stat } from "fs/promises";
// Define the response schema using Zod
const validatePersonaResponseSchema = z.object({
success: z.boolean(),
result: z.object({
isValid: z.boolean(),
errors: z.array(z.object({
type: z.enum(["schema", "business", "tool-resolution", "mcp-config"]),
field: z.string().optional(),
message: z.string(),
suggestion: z.string().optional(),
severity: z.enum(["error", "warning"]),
})),
warnings: z.array(z.object({
type: z.enum(["schema", "business", "tool-resolution", "mcp-config"]),
field: z.string().optional(),
message: z.string(),
suggestion: z.string().optional(),
severity: z.enum(["error", "warning"]),
})),
}),
personaPath: z.string(),
summary: z.object({
totalErrors: z.number(),
totalWarnings: z.number(),
validationLayers: z.array(z.string()),
pathType: z.enum(["file", "directory"]),
}),
error: z.string().optional(),
});
export const validatePersonaDefinition = {
name: "validate-persona",
description: "Validate a specific persona's structure and configuration with comprehensive reporting. Performs multi-layer validation including YAML syntax, schema validation, business rules, tool resolution, and MCP config validation. Supports both persona folder paths and individual persona.yaml/yml file paths. Returns detailed validation results with actionable suggestions for fixing any issues found.",
inputSchema: {
type: "object",
properties: {
personaPath: {
type: "string",
description: "Path to the persona folder or persona.yaml/yml file to validate",
},
includeWarnings: {
type: "boolean",
description: "Whether to include warnings in the validation result (default: true)",
default: true,
},
checkToolAvailability: {
type: "boolean",
description: "Whether to perform tool availability validation (default: true)",
default: true,
},
validateMcpConfig: {
type: "boolean",
description: "Whether to validate MCP config files if present (default: true)",
default: true,
},
},
required: ["personaPath"],
additionalProperties: false,
},
outputSchema: zodToJsonSchema(validatePersonaResponseSchema),
annotations: {
title: "Validate Persona",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
};
/**
* Determine if the given path is a file or directory
* @param path Path to check
* @returns "file" | "directory" | "not-found"
*/
async function getPathType(path) {
try {
const stats = await stat(path);
return stats.isDirectory() ? "directory" : "file";
}
catch {
return "not-found";
}
}
/**
* Create validation summary with error categorization
* @param result ValidationResult
* @param pathType Type of path validated
* @returns Summary object
*/
function createValidationSummary(result, pathType) {
return {
totalErrors: result.errors.length,
totalWarnings: result.warnings.length,
validationLayers: [
"YAML Syntax & Schema",
"Business Rules",
"Tool Resolution",
"MCP Configuration",
],
pathType: pathType, // Filter out "not-found"
};
}
/**
* Generate actionable suggestions based on validation results
* @param result ValidationResult
* @param personaPath Original path provided
* @returns Array of suggestion strings
*/
function generateActionableSuggestions(result, personaPath) {
const suggestions = [];
if (!result.isValid) {
suggestions.push(`Fix the ${result.errors.length} error(s) found in "${personaPath}"`);
// Group suggestions by error type
const errorTypes = [...new Set(result.errors.map((e) => e.type))];
for (const errorType of errorTypes) {
switch (errorType) {
case "schema":
suggestions.push("Check YAML syntax and ensure all required fields are present");
break;
case "business":
suggestions.push("Review business rules: persona name should match folder name, default toolset should exist");
break;
case "tool-resolution":
suggestions.push("Verify that all tool IDs reference existing tools from connected MCP servers");
break;
case "mcp-config":
suggestions.push("Check mcp.json file format and server configuration validity");
break;
}
}
}
if (result.warnings.length > 0) {
suggestions.push(`Consider addressing ${result.warnings.length} warning(s) to improve persona quality`);
}
return suggestions;
}
export const createValidatePersonaModule = (deps) => {
return {
toolName: "validate-persona",
definition: validatePersonaDefinition,
handler: async (args) => {
try {
const { personaPath, includeWarnings = true, checkToolAvailability = true, validateMcpConfig = true, } = args || {};
if (!personaPath || typeof personaPath !== "string") {
const errorResponse = {
success: false,
result: {
isValid: false,
errors: [
{
type: "schema",
message: "personaPath parameter is required and must be a string",
suggestion: "Provide a valid path to a persona folder or persona.yaml/yml file",
severity: "error",
},
],
warnings: [],
},
personaPath: personaPath || "",
summary: {
totalErrors: 1,
totalWarnings: 0,
validationLayers: [
"YAML Syntax & Schema",
"Business Rules",
"Tool Resolution",
"MCP Configuration",
],
pathType: "file",
},
error: "Invalid personaPath parameter",
};
return {
content: [
{
type: "text",
text: JSON.stringify(errorResponse),
},
],
structuredContent: errorResponse,
isError: true,
};
}
// Check if path exists and determine type
const pathType = await getPathType(personaPath);
if (pathType === "not-found") {
const errorResponse = {
success: false,
result: {
isValid: false,
errors: [
{
type: "schema",
message: `Path "${personaPath}" does not exist`,
suggestion: "Check the path and ensure the persona folder or file exists",
severity: "error",
},
],
warnings: [],
},
personaPath,
summary: {
totalErrors: 1,
totalWarnings: 0,
validationLayers: [
"YAML Syntax & Schema",
"Business Rules",
"Tool Resolution",
"MCP Configuration",
],
pathType: "file",
},
error: `File or directory not found: ${personaPath}`,
};
return {
content: [
{
type: "text",
text: JSON.stringify(errorResponse),
},
],
structuredContent: errorResponse,
isError: true,
};
}
// Set up validation options
const validationOptions = {
includeWarnings,
checkToolAvailability,
validateMcpConfig,
stopOnFirstError: false,
};
// Perform validation using PersonaValidator
const validationResult = await validatePersona(personaPath, deps.discoveryEngine, validationOptions);
// Generate actionable suggestions
const suggestions = generateActionableSuggestions(validationResult, personaPath);
// Add suggestions to errors and warnings if they don't already have them
const enrichedErrors = validationResult.errors.map((error) => ({
...error,
suggestion: error.suggestion ||
suggestions.find((s) => s.includes(error.type)) ||
"Check the persona configuration and documentation",
}));
const enrichedWarnings = validationResult.warnings.map((warning) => ({
...warning,
suggestion: warning.suggestion ||
suggestions.find((s) => s.includes(warning.type)) ||
"Consider improving the persona configuration",
}));
const response = {
success: true,
result: {
isValid: validationResult.isValid,
errors: enrichedErrors,
warnings: enrichedWarnings,
},
personaPath,
summary: createValidationSummary(validationResult, pathType),
};
return {
content: [
{
type: "text",
text: JSON.stringify(response),
},
],
structuredContent: response,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const errorResponse = {
success: false,
result: {
isValid: false,
errors: [
{
type: "schema",
message: `Validation failed: ${errorMessage}`,
suggestion: "Check that the path is accessible and the persona configuration is valid",
severity: "error",
},
],
warnings: [],
},
personaPath: args?.personaPath || "",
summary: {
totalErrors: 1,
totalWarnings: 0,
validationLayers: [
"YAML Syntax & Schema",
"Business Rules",
"Tool Resolution",
"MCP Configuration",
],
pathType: "file",
},
error: `Failed to validate persona: ${errorMessage}`,
};
return {
content: [
{
type: "text",
text: JSON.stringify(errorResponse),
},
],
structuredContent: errorResponse,
isError: true,
};
}
},
};
};
//# sourceMappingURL=validate-persona.js.map