Skip to main content
Glama
AlyssonM

HiveAuth MCP Server

by AlyssonM
validatePresentationDefinition.ts10.6 kB
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { validateAndSanitizeInput, createValidationErrorResult } from '../utils/validation.js'; import { TOOL_SCHEMAS, type ValidatePresentationDefinitionInput } from '../schemas/toolSchemas.js'; /** * Validate a presentation definition against DIF Presentation Exchange v2.0 specification * Performs comprehensive validation including schema compliance and best practices */ export async function validatePresentationDefinition(args: unknown): Promise<CallToolResult> { const validation = validateAndSanitizeInput( TOOL_SCHEMAS.validate_presentation_definition, args, 'validate_presentation_definition' ); if (!validation.success) { return createValidationErrorResult(validation.error!); } const data = validation.data!; const startTime = Date.now(); try { const validationIssues: string[] = []; const warnings: string[] = []; const recommendations: string[] = []; const { presentationDefinition, strict, checkSchemas } = data; // Basic structure validation (already validated by Zod, but we'll add business logic checks) // 1. ID validation if (!presentationDefinition.id || presentationDefinition.id.trim().length === 0) { validationIssues.push('Presentation definition ID is required and cannot be empty'); } else if (!/^[a-zA-Z0-9_-]+$/.test(presentationDefinition.id)) { warnings.push('ID should only contain alphanumeric characters, underscores, and hyphens for maximum compatibility'); } // 2. Input descriptors validation if (!presentationDefinition.input_descriptors || presentationDefinition.input_descriptors.length === 0) { validationIssues.push('At least one input descriptor is required'); } else { // Check for duplicate input descriptor IDs const descriptorIds = presentationDefinition.input_descriptors.map(desc => desc.id); const duplicateIds = descriptorIds.filter((id, index) => descriptorIds.indexOf(id) !== index); if (duplicateIds.length > 0) { validationIssues.push(`Duplicate input descriptor IDs found: ${duplicateIds.join(', ')}`); } // Validate each input descriptor presentationDefinition.input_descriptors.forEach((descriptor, index) => { const prefix = `Input Descriptor ${index + 1} (${descriptor.id})`; // ID validation if (!descriptor.id || descriptor.id.trim().length === 0) { validationIssues.push(`${prefix}: ID is required and cannot be empty`); } // Purpose/Name recommendation if (!descriptor.purpose && !descriptor.name) { recommendations.push(`${prefix}: Consider adding a 'purpose' or 'name' for better user experience`); } // Constraints validation if (descriptor.constraints) { if (descriptor.constraints.fields && descriptor.constraints.fields.length > 0) { descriptor.constraints.fields.forEach((field, fieldIndex) => { const fieldPrefix = `${prefix}, Field ${fieldIndex + 1}`; // Path validation if (!field.path || field.path.length === 0) { validationIssues.push(`${fieldPrefix}: At least one path is required`); } else { // JSONPath validation field.path.forEach((path, pathIndex) => { if (!path.startsWith('$.')) { warnings.push(`${fieldPrefix}, Path ${pathIndex + 1}: JSONPath should start with '$.' for clarity`); } }); } // Filter validation if (field.filter) { const filterKeys = Object.keys(field.filter); const validFilterKeys = ['type', 'const', 'contains', 'pattern', 'format', 'minimum', 'maximum', 'minLength', 'maxLength']; const invalidKeys = filterKeys.filter(key => !validFilterKeys.includes(key)); if (invalidKeys.length > 0) { warnings.push(`${fieldPrefix}: Unrecognized filter keys: ${invalidKeys.join(', ')}`); } } // Purpose recommendation if (!field.purpose) { recommendations.push(`${fieldPrefix}: Consider adding a 'purpose' to explain why this field is required`); } }); } else if (strict) { warnings.push(`${prefix}: No field constraints specified - this may accept any credential type`); } // Status validation if (descriptor.constraints.statuses) { const statuses = descriptor.constraints.statuses; Object.keys(statuses).forEach(statusType => { if (!['active', 'suspended', 'revoked'].includes(statusType)) { warnings.push(`${prefix}: Unrecognized status type '${statusType}'`); } }); } } else if (strict) { warnings.push(`${prefix}: No constraints specified - this may be overly permissive`); } // Schema validation if (descriptor.schema && descriptor.schema.length > 0) { descriptor.schema.forEach((schema, schemaIndex) => { const schemaPrefix = `${prefix}, Schema ${schemaIndex + 1}`; try { new URL(schema.uri); } catch (error) { validationIssues.push(`${schemaPrefix}: Invalid schema URI '${schema.uri}'`); } if (checkSchemas) { // Note: In a real implementation, you would fetch and validate the schema recommendations.push(`${schemaPrefix}: Schema validation not implemented - would validate ${schema.uri}`); } }); } }); } // 3. Submission requirements validation (if present) if (presentationDefinition.submission_requirements && presentationDefinition.submission_requirements.length > 0) { recommendations.push('Submission requirements detected - ensure they are properly structured according to DIF PEX v2.0 spec'); } // 4. Overall recommendations if (!presentationDefinition.purpose) { recommendations.push('Consider adding a top-level purpose to explain the intent of this presentation request'); } if (!presentationDefinition.name) { recommendations.push('Consider adding a name for better user identification of this presentation request'); } const processingTime = Date.now() - startTime; const isValid = validationIssues.length === 0; const hasWarnings = warnings.length > 0; const hasRecommendations = recommendations.length > 0; return { content: [ { type: 'text', text: `${isValid ? '✅' : '❌'} **Presentation Definition Validation ${isValid ? 'Passed' : 'Failed'}** **Validation Summary:** • **Status:** ${isValid ? 'Valid' : 'Invalid'} • **Processing Time:** ${processingTime}ms • **Strict Mode:** ${strict ? 'Enabled' : 'Disabled'} • **Schema Checking:** ${checkSchemas ? 'Enabled' : 'Disabled'} **📊 Results Overview:** • **Errors:** ${validationIssues.length} • **Warnings:** ${warnings.length} • **Recommendations:** ${recommendations.length} • **Input Descriptors:** ${presentationDefinition.input_descriptors?.length || 0} **🎯 Presentation Definition Details:** • **ID:** ${presentationDefinition.id} • **Name:** ${presentationDefinition.name || 'Not specified'} • **Purpose:** ${presentationDefinition.purpose || 'Not specified'} ${validationIssues.length > 0 ? `**❌ Validation Errors:** ${validationIssues.map((issue, index) => `${index + 1}. ${issue}`).join('\n')} ` : ''}${hasWarnings ? `**⚠️ Warnings:** ${warnings.map((warning, index) => `${index + 1}. ${warning}`).join('\n')} ` : ''}${hasRecommendations ? `**💡 Recommendations:** ${recommendations.map((rec, index) => `${index + 1}. ${rec}`).join('\n')} ` : ''}**📋 Input Descriptor Analysis:** ${presentationDefinition.input_descriptors?.map((desc, index) => { const fieldCount = desc.constraints?.fields?.length || 0; const hasConstraints = !!desc.constraints; const hasSchemas = desc.schema && desc.schema.length > 0; return `• **${desc.id}** ${desc.name ? `(${desc.name})` : ''} - Purpose: ${desc.purpose || 'Not specified'} - Fields: ${fieldCount} constraint${fieldCount !== 1 ? 's' : ''} - Schemas: ${hasSchemas ? desc.schema!.length : 0} referenced - Status: ${hasConstraints ? 'Has constraints' : 'No constraints'}`; }).join('\n') || '(No input descriptors)'} **🔍 DIF PEX v2.0 Compliance:** • **Structure:** ${isValid ? 'Compliant' : 'Non-compliant'} • **Required Fields:** ${presentationDefinition.id && presentationDefinition.input_descriptors ? 'Present' : 'Missing'} • **Best Practices:** ${hasWarnings ? 'Some issues found' : 'Following best practices'} ${isValid ? '🎉 **This presentation definition is valid and ready for use in credential requests.**' : '🔧 **Please address the validation errors before using this presentation definition.**'} ${hasRecommendations || hasWarnings ? '\n💡 **Next Steps:** Consider implementing the recommendations above for better interoperability and user experience.' : ''} ` } ] }; } catch (error: any) { const processingTime = Date.now() - startTime; return { content: [ { type: 'text', text: `❌ **Presentation Definition Validation Error** **Error:** ${error.message} **Processing Time:** ${processingTime}ms **Tool:** validate_presentation_definition **📋 Input Summary:** • **Definition ID:** ${data.presentationDefinition.id} • **Input Descriptors:** ${data.presentationDefinition.input_descriptors?.length || 0} • **Strict Mode:** ${data.strict} **🔧 Troubleshooting:** 1. **Definition Structure:** Ensure the presentation definition follows DIF PEX v2.0 format 2. **Required Fields:** Verify all required fields (id, input_descriptors) are present 3. **Field Constraints:** Check that constraint fields use valid JSONPath expressions 4. **Schema References:** Ensure any referenced schemas are accessible if checkSchemas is enabled **📚 Reference:** • DIF Presentation Exchange v2.0: https://identity.foundation/presentation-exchange/spec/v2.0.0/ • JSONPath specification: https://tools.ietf.org/rfc/rfc9535.txt For detailed validation requirements, consult the DIF PEX v2.0 specification.` } ], isError: true }; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AlyssonM/hiveauth-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server