UNPKG

haloapi-mcp-tools

Version:

Model Context Protocol (MCP) server for interacting with the HaloPSA API

296 lines (254 loc) 8.82 kB
#!/usr/bin/env node /** * Verify Changes Script * * This script tests the fixes and enhancements implemented in v1.1.0. * It sends various types of requests to the MCP server to verify the functionality. */ 'use strict'; const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const net = require('net'); // Colors for console output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m' }; // Log with colors function log(message, color = colors.reset) { console.log(`${color}${message}${colors.reset}`); } // Print header log(`\n${colors.bright}${colors.blue}HaloPSA MCP Tools v1.1.0 Verification${colors.reset}\n`, colors.blue); log('This script tests the fixes and enhancements implemented in v1.1.0.\n'); // Start MCP server in HTTP mode log(`${colors.magenta}Starting MCP server in HTTP mode...${colors.reset}`, colors.magenta); const serverProcess = spawn('node', [path.join(__dirname, '../standalone-mcp.js')], { env: { ...process.env, TRANSPORT: 'http', PORT: '3001', DEBUG: 'true' } }); // Set timeout for tests const TIMEOUT = 5000; let didExit = false; // Handle server output serverProcess.stdout.on('data', (data) => { console.log(`Server: ${data.toString()}`); }); serverProcess.stderr.on('data', (data) => { console.error(`Server Error: ${data.toString()}`); }); serverProcess.on('close', (code) => { if (!didExit) { log(`\n${colors.red}Server process exited with code ${code}${colors.reset}`, colors.red); process.exit(1); } }); // Give the server time to start setTimeout(async () => { // Use HTTP for testing instead of TCP const http = require('http'); log(`${colors.green}Using HTTP to connect to MCP server${colors.reset}`, colors.green); // Function to send HTTP requests function sendRequest(requestData) { return new Promise((resolve, reject) => { const requestJson = JSON.stringify(requestData); const options = { hostname: 'localhost', port: 3001, path: '/', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(requestJson) } }; const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode !== 200) { reject(new Error(`HTTP Status: ${res.statusCode} ${res.statusMessage}`)); return; } try { const response = JSON.parse(data); resolve(response); } catch (error) { reject(new Error(`Failed to parse response: ${error.message}`)); } }); }); req.on('error', (error) => { reject(error); }); req.write(requestJson); req.end(); }); } // Begin tests try { // Tests to run const tests = [ { name: 'Test 1: Valid tools/list request', request: { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }, expectedStatus: 'success', validate: (response) => { if (!response.result || !response.result.tools) { return { success: false, message: 'Missing result.tools in response' }; } if (!Array.isArray(response.result.tools)) { return { success: false, message: 'result.tools is not an array' }; } return { success: true, message: `Found ${response.result.tools.length} tools` }; } }, { name: 'Test 2: Malformed request missing jsonrpc field', request: { id: 2, method: 'tools/list', params: {} }, expectedStatus: 'success', // Should still succeed due to auto-fixing validate: (response) => { if (response.error) { return { success: false, message: `Got error: ${response.error.message}` }; } return { success: true, message: 'Server successfully handled malformed request' }; } }, { name: 'Test 3: Completely invalid request', request: { garbage: true }, expectedStatus: 'error', validate: (response) => { if (!response.error) { return { success: false, message: 'Missing error in response' }; } return { success: true, message: `Got expected error: ${response.error.message}` }; } }, { name: 'Test 4: Request with extra unrecognized keys', request: { jsonrpc: '2.0', id: 4, method: 'tools/list', params: {}, extra: 'this should be ignored' }, expectedStatus: 'success', validate: (response) => { if (response.error) { return { success: false, message: `Got error: ${response.error.message}` }; } return { success: true, message: 'Server successfully handled request with extra keys' }; } } ]; // Run tests sequentially let results = []; // Function to run tests one by one async function runTests() { for (let i = 0; i < tests.length; i++) { const test = tests[i]; log(`\n${colors.bright}Running ${test.name}${colors.reset}`, colors.bright); log(`Request: ${JSON.stringify(test.request, null, 2)}`); try { // Send the request using HTTP const response = await sendRequest(test.request); log(`Response: ${JSON.stringify(response, null, 2)}`); // Validate the response const status = response.error ? 'error' : 'success'; const statusMatch = status === test.expectedStatus; const validation = test.validate(response); // Store the result results.push({ name: test.name, statusMatch, validation, passed: statusMatch && validation.success }); // Display result if (statusMatch && validation.success) { log(`${colors.green}✓ PASS: ${test.name}${colors.reset}`, colors.green); if (validation.message) { log(` ${validation.message}`); } } else { log(`${colors.red}✗ FAIL: ${test.name}${colors.reset}`, colors.red); if (!statusMatch) { log(` Expected status ${test.expectedStatus} but got ${status}`); } if (!validation.success && validation.message) { log(` ${validation.message}`); } } } catch (error) { log(`${colors.red}Error testing ${test.name}: ${error.message}${colors.reset}`, colors.red); // Store the failed result results.push({ name: test.name, statusMatch: false, validation: { success: false, message: error.message }, passed: false }); } } // Show final results showResults(); } // Show final results function showResults() { log(`\n${colors.bright}Test Results:${colors.reset}`, colors.bright); const passed = results.filter(r => r.passed).length; const failed = results.length - passed; results.forEach((result) => { const icon = result.passed ? '✓' : '✗'; const color = result.passed ? colors.green : colors.red; log(`${color}${icon} ${result.name}${colors.reset}`, color); }); log(`\nSummary: ${colors.green}${passed} passed${colors.reset}, ${failed > 0 ? colors.red : colors.reset}${failed} failed${colors.reset}`, colors.bright); // Clean up didExit = true; serverProcess.kill(); // Exit with appropriate code process.exit(failed > 0 ? 1 : 0); } // Start running tests runTests().catch(error => { log(`${colors.red}Unhandled error running tests: ${error.message}${colors.reset}`, colors.red); serverProcess.kill(); process.exit(1); }); } catch (error) { log(`${colors.red}Error setting up tests: ${error.message}${colors.reset}`, colors.red); serverProcess.kill(); process.exit(1); } }, 2000); // Handle script timeout setTimeout(() => { log(`${colors.red}Test script timed out after ${TIMEOUT}ms${colors.reset}`, colors.red); didExit = true; serverProcess.kill(); process.exit(1); }, TIMEOUT);