haloapi-mcp-tools
Version:
Model Context Protocol (MCP) server for interacting with the HaloPSA API
296 lines (254 loc) • 8.82 kB
JavaScript
/**
* 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.
*/
;
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);