/**
* M5 Integration Test
*
* Tests the complete MCP server with M5 multi-tool orchestration
* Tests the full stack: MCP server → ConversationManager → Tools → Shared Context
*/
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Start MCP server process
*/
function startServer() {
const serverPath = join(__dirname, 'dist', 'index.js');
const server = spawn('node', [serverPath], {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env }
});
return server;
}
/**
* Send JSON-RPC request to server
*/
function sendRequest(server, method, params) {
return new Promise((resolve, reject) => {
const request = {
jsonrpc: '2.0',
id: Date.now(),
method,
params
};
let responseData = '';
const dataHandler = (data) => {
responseData += data.toString();
// Try to parse complete JSON-RPC response
try {
const lines = responseData.split('\n').filter(l => l.trim());
for (const line of lines) {
const response = JSON.parse(line);
if (response.id === request.id) {
server.stdout.removeListener('data', dataHandler);
resolve(response);
return;
}
}
} catch (e) {
// Not complete JSON yet, wait for more data
}
};
server.stdout.on('data', dataHandler);
// Timeout after 10 seconds
setTimeout(() => {
server.stdout.removeListener('data', dataHandler);
reject(new Error('Request timeout'));
}, 10000);
// Send request
server.stdin.write(JSON.stringify(request) + '\n');
});
}
/**
* Main test suite
*/
async function runIntegrationTests() {
console.error('='.repeat(60));
console.error('M5 INTEGRATION TEST - Full Stack');
console.error('='.repeat(60));
const server = startServer();
// Wait for server to initialize
await new Promise(resolve => setTimeout(resolve, 2000));
let testsPassed = 0;
let testsFailed = 0;
try {
// Test 1: Initialize
console.error('\n[Test 1] Initialize MCP server');
const initResponse = await sendRequest(server, 'initialize', {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: { name: 'test-client', version: '1.0.0' }
});
if (initResponse.result && initResponse.result.serverInfo) {
console.error('✅ Server initialized:', initResponse.result.serverInfo.name);
testsPassed++;
} else {
console.error('❌ Server initialization failed');
testsFailed++;
}
// Test 2: List tools
console.error('\n[Test 2] List available tools');
const listResponse = await sendRequest(server, 'tools/list', {});
if (listResponse.result && listResponse.result.tools) {
const toolNames = listResponse.result.tools.map(t => t.name);
console.error(`✅ Found ${toolNames.length} tools:`, toolNames.join(', '));
if (toolNames.includes('example-tool') && toolNames.includes('data-tool')) {
console.error('✅ Both example-tool and data-tool registered');
testsPassed++;
} else {
console.error('❌ Expected tools not found');
testsFailed++;
}
} else {
console.error('❌ Failed to list tools');
testsFailed++;
}
// Test 3: Call example-tool
console.error('\n[Test 3] Call example-tool (greet action)');
const greetResponse = await sendRequest(server, 'tools/call', {
name: 'example-tool',
arguments: { action: 'greet', conversationId: 'integration-test' }
});
if (greetResponse.result && greetResponse.result.content) {
const text = greetResponse.result.content[0].text;
console.error('✅ Greet response:', text);
if (text.includes('M2 Negotiation Ready')) {
testsPassed++;
} else {
console.error('❌ Unexpected greet response');
testsFailed++;
}
} else {
console.error('❌ Failed to call example-tool');
testsFailed++;
}
// Test 4: Upgrade data-tool permissions
console.error('\n[Test 4] Upgrade data-tool to level 2');
const upgradeResponse = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'upgrade:data-tool:level-2',
conversationId: 'integration-test'
}
});
if (upgradeResponse.result) {
console.error('✅ Permission upgraded');
testsPassed++;
} else {
console.error('❌ Failed to upgrade permission');
testsFailed++;
}
// Test 5: Create resource with data-tool
console.error('\n[Test 5] Create resource with data-tool');
const createResponse = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'create-resource',
name: 'integration-resource',
data: { test: true, value: 42 },
conversationId: 'integration-test'
}
});
if (createResponse.result && createResponse.result.content) {
const text = createResponse.result.content[0].text;
console.error('✅ Resource created:', text);
if (text.includes('created')) {
testsPassed++;
} else {
console.error('❌ Unexpected create response');
testsFailed++;
}
} else {
console.error('❌ Failed to create resource');
testsFailed++;
}
// Test 6: Read resource with data-tool
console.error('\n[Test 6] Read resource with data-tool');
const readResponse = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'read-resource',
name: 'integration-resource',
conversationId: 'integration-test'
}
});
if (readResponse.result && readResponse.result.content) {
const text = readResponse.result.content[0].text;
console.error('✅ Resource read:', text.substring(0, 100) + '...');
if (text.includes('"test": true') && text.includes('"value": 42')) {
console.error('✅ Resource data matches created data');
testsPassed++;
} else {
console.error('❌ Resource data mismatch');
testsFailed++;
}
} else {
console.error('❌ Failed to read resource');
testsFailed++;
}
// Test 7: List resources
console.error('\n[Test 7] List all resources');
const listResourcesResponse = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'list-resources',
conversationId: 'integration-test'
}
});
if (listResourcesResponse.result && listResourcesResponse.result.content) {
const text = listResourcesResponse.result.content[0].text;
console.error('✅ Resources listed');
if (text.includes('integration-resource')) {
console.error('✅ Created resource appears in list');
testsPassed++;
} else {
console.error('❌ Created resource not in list');
testsFailed++;
}
} else {
console.error('❌ Failed to list resources');
testsFailed++;
}
// Test 8: Update resource
console.error('\n[Test 8] Update resource with data-tool');
const updateResponse = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'update-resource',
name: 'integration-resource',
data: { test: true, value: 42, updated: true },
conversationId: 'integration-test'
}
});
if (updateResponse.result && updateResponse.result.content) {
const text = updateResponse.result.content[0].text;
console.error('✅ Resource updated:', text);
if (text.includes('updated')) {
testsPassed++;
} else {
console.error('❌ Unexpected update response');
testsFailed++;
}
} else {
console.error('❌ Failed to update resource');
testsFailed++;
}
// Test 9: Cross-tool coordination (example-tool + data-tool)
console.error('\n[Test 9] Cross-tool coordination');
const tool1Response = await sendRequest(server, 'tools/call', {
name: 'example-tool',
arguments: {
action: 'echo',
conversationId: 'integration-test'
}
});
const tool2Response = await sendRequest(server, 'tools/call', {
name: 'data-tool',
arguments: {
action: 'read-resource',
name: 'integration-resource',
conversationId: 'integration-test'
}
});
if (tool1Response.result && tool2Response.result) {
console.error('✅ Both tools operated on same conversation');
const readText = tool2Response.result.content[0].text;
if (readText.includes('updated')) {
console.error('✅ Resource state persisted across tool calls');
testsPassed++;
} else {
console.error('❌ Resource state not persisted');
testsFailed++;
}
} else {
console.error('❌ Cross-tool coordination failed');
testsFailed++;
}
} catch (error) {
console.error('\n❌ Test error:', error.message);
testsFailed++;
} finally {
// Cleanup
server.kill();
}
// Summary
console.error('\n' + '='.repeat(60));
console.error('TEST SUMMARY');
console.error('='.repeat(60));
console.error(`Passed: ${testsPassed}`);
console.error(`Failed: ${testsFailed}`);
console.error(`Total: ${testsPassed + testsFailed}`);
console.error('='.repeat(60));
if (testsFailed === 0) {
console.error('\n✅ ALL INTEGRATION TESTS PASSED');
process.exit(0);
} else {
console.error('\n❌ SOME TESTS FAILED');
process.exit(1);
}
}
// Run tests
runIntegrationTests().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});