verify-registry-submission.ts•12 kB
#!/usr/bin/env tsx
/**
* Registry submission verification script
* Tests all installation methods and validates registry entry functionality
*/
import { execSync, spawn } from 'child_process';
import { readFileSync, existsSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
interface VerificationResult {
success: boolean;
message: string;
details?: string;
}
/**
* Safely extracts error message from unknown error type.
* @param error - The error to extract message from.
* @returns The error message string.
*/
function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
/**
* Executes a shell command and returns the output.
* @param command - The command to execute.
* @param options - Execution options.
* @returns The command output as a string.
*/
function executeCommand(command: string, options: { silent?: boolean; cwd?: string; timeout?: number } = {}): string {
try {
const result = execSync(command, {
encoding: 'utf-8',
stdio: options.silent ? 'pipe' : 'inherit',
cwd: options.cwd || process.cwd(),
timeout: options.timeout || 30000
});
return typeof result === 'string' ? result : '';
} catch (error: any) {
throw new Error(`Command failed: ${command}\n${error.message}`);
}
}
/**
* Verifies NPM package publication and metadata.
* @returns Promise resolving to verification result.
*/
async function verifyNpmPackage(): Promise<VerificationResult> {
console.log('🔍 Verifying NPM package publication...');
try {
const packageJson = JSON.parse(readFileSync('package.json', 'utf-8'));
const packageName = packageJson.name;
const packageVersion = packageJson.version;
const mcpName = packageJson.mcpName;
// Check if package exists on NPM
const npmInfo = executeCommand(`npm view ${packageName}@${packageVersion} --json`, { silent: true });
const npmData = JSON.parse(npmInfo);
// Verify version matches
if (npmData.version !== packageVersion) {
return {
success: false,
message: 'NPM package version mismatch',
details: `Expected ${packageVersion}, found ${npmData.version}`
};
}
// Verify mcpName field exists
if (npmData.mcpName !== mcpName) {
return {
success: false,
message: 'NPM package missing or incorrect mcpName field',
details: `Expected ${mcpName}, found ${npmData.mcpName || 'undefined'}`
};
}
// Verify required fields
const requiredFields = ['name', 'version', 'description', 'main', 'bin'];
for (const field of requiredFields) {
if (!npmData[field]) {
return {
success: false,
message: `NPM package missing required field: ${field}`
};
}
}
console.log(` ✅ Package ${packageName}@${packageVersion} verified on NPM`);
console.log(` ✅ MCP name: ${mcpName}`);
return {
success: true,
message: 'NPM package verification successful'
};
} catch (error) {
return {
success: false,
message: 'NPM package verification failed',
details: getErrorMessage(error)
};
}
}
/**
* Verifies NPM package installation readiness.
* @returns Promise resolving to verification result.
*/
async function verifyInstallationReadiness(): Promise<VerificationResult> {
console.log('📦 Verifying NPM package installation readiness...');
try {
const packageJson = JSON.parse(readFileSync('package.json', 'utf-8'));
// Verify binary configuration
if (!packageJson.bin || !packageJson.bin['fmp-mcp']) {
return {
success: false,
message: 'NPM package missing binary configuration'
};
}
// Verify main entry point
if (!packageJson.main) {
return {
success: false,
message: 'NPM package missing main entry point'
};
}
// Verify files array includes dist
if (!packageJson.files || !packageJson.files.includes('dist')) {
return {
success: false,
message: 'NPM package files array missing dist directory'
};
}
// Verify built files exist
if (!existsSync('dist/index.js')) {
return {
success: false,
message: 'Built JavaScript files not found in dist directory'
};
}
console.log(' ✅ Binary configuration: fmp-mcp -> dist/index.js');
console.log(' ✅ Main entry point: dist/index.js');
console.log(' ✅ Distribution files present');
console.log(' ✅ Package ready for installation via NPM/NPX');
return {
success: true,
message: 'NPM package installation readiness verified'
};
} catch (error) {
return {
success: false,
message: 'Installation readiness verification failed',
details: getErrorMessage(error)
};
}
}
/**
* Validates server.json schema and content.
* @returns Promise resolving to verification result.
*/
async function validateServerJson(): Promise<VerificationResult> {
console.log('📋 Validating server.json schema and content...');
try {
if (!existsSync('server.json')) {
return {
success: false,
message: 'server.json file not found'
};
}
const serverConfig = JSON.parse(readFileSync('server.json', 'utf-8'));
const packageJson = JSON.parse(readFileSync('package.json', 'utf-8'));
// Validate required fields
const requiredFields = ['name', 'description', 'version', 'packages'];
for (const field of requiredFields) {
if (!serverConfig[field]) {
return {
success: false,
message: `server.json missing required field: ${field}`
};
}
}
// Validate version consistency
if (serverConfig.version !== packageJson.version) {
return {
success: false,
message: 'Version mismatch between server.json and package.json',
details: `server.json: ${serverConfig.version}, package.json: ${packageJson.version}`
};
}
// Validate package configuration
if (!Array.isArray(serverConfig.packages) || serverConfig.packages.length === 0) {
return {
success: false,
message: 'server.json packages array is empty or invalid'
};
}
const npmPackage = serverConfig.packages.find((pkg: any) => pkg.registry_type === 'npm');
if (!npmPackage) {
return {
success: false,
message: 'server.json missing NPM package configuration'
};
}
// Validate NPM package fields
const requiredPackageFields = ['identifier', 'version', 'transport'];
for (const field of requiredPackageFields) {
if (!npmPackage[field]) {
return {
success: false,
message: `server.json NPM package missing required field: ${field}`
};
}
}
console.log(' ✅ server.json schema validation passed');
console.log(` ✅ MCP name: ${serverConfig.name}`);
console.log(` ✅ Version: ${serverConfig.version}`);
console.log(` ✅ NPM package: ${npmPackage.identifier}@${npmPackage.version}`);
return {
success: true,
message: 'server.json validation successful'
};
} catch (error) {
return {
success: false,
message: 'server.json validation failed',
details: getErrorMessage(error)
};
}
}
/**
* Tests HTTP transport functionality.
* @returns Promise resolving to verification result.
*/
async function testHttpTransport(): Promise<VerificationResult> {
console.log('🌐 Testing HTTP transport functionality...');
try {
const serverConfig = JSON.parse(readFileSync('server.json', 'utf-8'));
const npmPackage = serverConfig.packages.find((pkg: any) => pkg.registry_type === 'npm');
if (!npmPackage?.transport?.url) {
return {
success: false,
message: 'No HTTP transport URL found in server.json'
};
}
const transportUrl = npmPackage.transport.url;
console.log(` Testing transport URL: ${transportUrl}`);
// Test HTTP endpoint availability (basic connectivity)
try {
executeCommand(`curl -s -o /dev/null -w "%{http_code}" "${transportUrl}"`, {
silent: true,
timeout: 10000
});
console.log(' ✅ HTTP transport endpoint is accessible');
} catch (error) {
console.log(' ⚠️ HTTP transport endpoint test skipped (curl not available or endpoint not responding)');
}
return {
success: true,
message: 'HTTP transport configuration validated'
};
} catch (error) {
return {
success: false,
message: 'HTTP transport test failed',
details: getErrorMessage(error)
};
}
}
/**
* Runs all verification tests.
*/
async function runAllVerifications(): Promise<void> {
console.log('🚀 Starting registry submission verification...\n');
const tests = [
{ name: 'NPM Package Verification', test: verifyNpmPackage },
{ name: 'server.json Validation', test: validateServerJson },
{ name: 'Installation Readiness Check', test: verifyInstallationReadiness },
{ name: 'HTTP Transport Test', test: testHttpTransport }
];
const results: Array<{ name: string; result: VerificationResult }> = [];
for (const { name, test } of tests) {
console.log(`\n📋 Running: ${name}`);
try {
const result = await test();
results.push({ name, result });
if (result.success) {
console.log(`✅ ${name}: ${result.message}`);
} else {
console.log(`❌ ${name}: ${result.message}`);
if (result.details) {
console.log(` Details: ${result.details}`);
}
}
} catch (error) {
const failedResult: VerificationResult = {
success: false,
message: 'Test execution failed',
details: getErrorMessage(error)
};
results.push({ name, result: failedResult });
console.log(`❌ ${name}: ${failedResult.message}`);
console.log(` Details: ${failedResult.details}`);
}
}
// Summary
console.log('\n📊 Verification Summary:');
console.log('=' .repeat(50));
const passed = results.filter(r => r.result.success).length;
const total = results.length;
results.forEach(({ name, result }) => {
const status = result.success ? '✅ PASS' : '❌ FAIL';
console.log(`${status} ${name}`);
});
console.log('=' .repeat(50));
console.log(`Total: ${passed}/${total} tests passed`);
if (passed === total) {
console.log('\n🎉 All verifications passed! Registry submission is successful.');
console.log('\n📋 Registry Entry Details:');
const packageJson = JSON.parse(readFileSync('package.json', 'utf-8'));
const serverConfig = JSON.parse(readFileSync('server.json', 'utf-8'));
console.log(` MCP Name: ${serverConfig.name}`);
console.log(` NPM Package: ${packageJson.name}@${packageJson.version}`);
console.log(` Description: ${serverConfig.description}`);
console.log(` Repository: ${packageJson.repository?.url || 'N/A'}`);
console.log('\n📦 Installation Methods Verified:');
console.log(' • Global NPM: npm install -g financial-modeling-prep-mcp-server');
console.log(' • Local NPM: npm install financial-modeling-prep-mcp-server');
console.log(' • NPX: npx financial-modeling-prep-mcp-server');
} else {
console.log('\n❌ Some verifications failed. Please review the errors above.');
process.exit(1);
}
}
// Run verifications if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
runAllVerifications().catch((error) => {
console.error('❌ Verification script failed:', getErrorMessage(error));
process.exit(1);
});
}
export { runAllVerifications };