Skip to main content
Glama

Foundry VTT MCP Bridge

by adambdooley
validate-manifest.js•6.32 kB
#!/usr/bin/env node /** * Foundry VTT Module Manifest Validator * Validates module.json against Foundry VTT requirements */ const fs = require('fs'); const path = require('path'); const manifestPath = path.join(__dirname, 'packages', 'foundry-module', 'module.json'); console.log('šŸ” Validating Foundry Module Manifest...\n'); // Required fields according to Foundry VTT documentation const requiredFields = [ 'id', 'title', 'description', 'version', 'compatibility', 'authors' ]; const recommendedFields = [ 'url', 'bugs', 'changelog', 'readme', 'license', 'manifest', 'download' ]; const unsupportedFields = [ 'keywords' // Not supported in Foundry VTT manifests ]; try { // Read and parse manifest const manifestContent = fs.readFileSync(manifestPath, 'utf8'); const manifest = JSON.parse(manifestContent); console.log(`šŸ“‹ Module: ${manifest.title} (${manifest.id})`); console.log(`šŸ“¦ Version: ${manifest.version}`); console.log(`šŸŽÆ Compatibility: ${manifest.compatibility.minimum}-${manifest.compatibility.maximum}\n`); let errors = []; let warnings = []; // Check required fields console.log('āœ… Required Fields:'); requiredFields.forEach(field => { if (manifest[field] === undefined) { errors.push(`Missing required field: ${field}`); console.log(` āŒ ${field}: MISSING`); } else { console.log(` āœ… ${field}: OK`); } }); console.log('\nšŸ“‹ Recommended Fields:'); recommendedFields.forEach(field => { if (manifest[field] === undefined) { warnings.push(`Missing recommended field: ${field}`); console.log(` āš ļø ${field}: MISSING`); } else { console.log(` āœ… ${field}: OK`); } }); // Validate specific field formats console.log('\nšŸ” Field Validation:'); // ID validation if (manifest.id && !/^[a-z0-9-]+$/.test(manifest.id)) { errors.push('ID should only contain lowercase letters, numbers, and hyphens'); console.log(' āŒ id: Invalid format (should be lowercase, alphanumeric, hyphens only)'); } else { console.log(' āœ… id: Valid format'); } // Version validation if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) { warnings.push('Version should follow semantic versioning (x.y.z)'); console.log(' āš ļø version: Should follow semantic versioning'); } else { console.log(' āœ… version: Valid format'); } // Compatibility validation if (manifest.compatibility) { const { minimum, verified, maximum } = manifest.compatibility; if (!minimum || !verified) { errors.push('Compatibility must include minimum and verified versions'); console.log(' āŒ compatibility: Missing minimum or verified versions'); } else { console.log(' āœ… compatibility: Valid'); } } // URL validation const urlFields = ['url', 'bugs', 'changelog', 'readme', 'license', 'manifest', 'download']; urlFields.forEach(field => { if (manifest[field] && !manifest[field].startsWith('http')) { warnings.push(`${field} should be a valid HTTP/HTTPS URL`); console.log(` āš ļø ${field}: Should be HTTP/HTTPS URL`); } else if (manifest[field]) { console.log(` āœ… ${field}: Valid URL`); } }); // File existence validation console.log('\nšŸ“ File Existence:'); if (manifest.esmodules) { manifest.esmodules.forEach(file => { const filePath = path.join(__dirname, 'packages', 'foundry-module', file); if (fs.existsSync(filePath)) { console.log(` āœ… ${file}: EXISTS`); } else { errors.push(`Missing esmodule file: ${file}`); console.log(` āŒ ${file}: MISSING`); } }); } if (manifest.styles) { manifest.styles.forEach(file => { const filePath = path.join(__dirname, 'packages', 'foundry-module', file); if (fs.existsSync(filePath)) { console.log(` āœ… ${file}: EXISTS`); } else { errors.push(`Missing style file: ${file}`); console.log(` āŒ ${file}: MISSING`); } }); } if (manifest.languages) { manifest.languages.forEach(lang => { const filePath = path.join(__dirname, 'packages', 'foundry-module', lang.path); if (fs.existsSync(filePath)) { console.log(` āœ… ${lang.path}: EXISTS`); } else { errors.push(`Missing language file: ${lang.path}`); console.log(` āŒ ${lang.path}: MISSING`); } }); } // Check for unsupported fields console.log('\n🚫 Unsupported Fields:'); const manifestKeys = Object.keys(manifest); let hasUnsupported = false; unsupportedFields.forEach(field => { if (manifestKeys.includes(field)) { errors.push(`Unsupported field (will cause Foundry warnings): ${field}`); console.log(` āŒ ${field}: UNSUPPORTED (remove this field)`); hasUnsupported = true; } }); if (!hasUnsupported) { console.log(' āœ… No unsupported fields detected'); } // Summary console.log('\nšŸ“Š Validation Summary:'); console.log(` āœ… Errors: ${errors.length}`); console.log(` āš ļø Warnings: ${warnings.length}`); if (errors.length > 0) { console.log('\nāŒ ERRORS:'); errors.forEach(error => console.log(` • ${error}`)); } if (warnings.length > 0) { console.log('\nāš ļø WARNINGS:'); warnings.forEach(warning => console.log(` • ${warning}`)); } if (errors.length === 0) { console.log('\nšŸŽ‰ Manifest validation PASSED! Ready for Foundry VTT.'); } else { console.log('\nšŸ’„ Manifest validation FAILED! Fix errors before release.'); process.exit(1); } } catch (error) { console.error('āŒ Failed to validate manifest:', error.message); process.exit(1); }

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/adambdooley/foundry-vtt-mcp'

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