- Complete MCP server for Incus container management - 10 tools for instance lifecycle operations (create, start, stop, etc.) - 2 resources for instance and remote server data - Support for multiple Incus remotes with TLS authentication - TypeScript implementation with comprehensive error handling - Test suite for validation and integration testing - MCP configuration for Claude Code integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
164 lines
4.6 KiB
JavaScript
164 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawn } from 'child_process';
|
|
|
|
async function testResources() {
|
|
console.log('🔍 Testing MCP Resources...\n');
|
|
|
|
// Test reading instances list resource
|
|
console.log('1. Testing incus://instances/list resource...');
|
|
const instancesResourceMessage = {
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
method: 'resources/read',
|
|
params: {
|
|
uri: 'incus://instances/list'
|
|
}
|
|
};
|
|
|
|
try {
|
|
const result = await sendMCPRequest(instancesResourceMessage);
|
|
console.log(` ✅ Resource read successful`);
|
|
if (result.contents && result.contents[0]) {
|
|
const content = result.contents[0];
|
|
console.log(` URI: ${content.uri}`);
|
|
console.log(` MIME Type: ${content.mimeType}`);
|
|
console.log(` Content length: ${content.text.length} chars`);
|
|
|
|
// Try to parse as JSON
|
|
try {
|
|
const instances = JSON.parse(content.text);
|
|
console.log(` Found ${instances.length} instances in JSON format`);
|
|
} catch (e) {
|
|
console.log(` Content is not JSON: ${e.message}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log(` ❌ Error: ${error.message}`);
|
|
}
|
|
|
|
// Test reading remotes list resource
|
|
console.log('\n2. Testing incus://remotes/list resource...');
|
|
const remotesResourceMessage = {
|
|
jsonrpc: '2.0',
|
|
id: 2,
|
|
method: 'resources/read',
|
|
params: {
|
|
uri: 'incus://remotes/list'
|
|
}
|
|
};
|
|
|
|
try {
|
|
const result = await sendMCPRequest(remotesResourceMessage);
|
|
console.log(` ✅ Resource read successful`);
|
|
if (result.contents && result.contents[0]) {
|
|
const content = result.contents[0];
|
|
console.log(` URI: ${content.uri}`);
|
|
console.log(` MIME Type: ${content.mimeType}`);
|
|
console.log(` Content length: ${content.text.length} chars`);
|
|
|
|
// Try to parse as JSON
|
|
try {
|
|
const remotes = JSON.parse(content.text);
|
|
const remoteNames = Object.keys(remotes);
|
|
console.log(` Found ${remoteNames.length} remotes: ${remoteNames.join(', ')}`);
|
|
} catch (e) {
|
|
console.log(` Content is not JSON: ${e.message}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log(` ❌ Error: ${error.message}`);
|
|
}
|
|
|
|
// Test reading unknown resource
|
|
console.log('\n3. Testing unknown resource...');
|
|
const unknownResourceMessage = {
|
|
jsonrpc: '2.0',
|
|
id: 3,
|
|
method: 'resources/read',
|
|
params: {
|
|
uri: 'incus://unknown/resource'
|
|
}
|
|
};
|
|
|
|
try {
|
|
const result = await sendMCPRequest(unknownResourceMessage);
|
|
console.log(` ❌ Unexpected success: ${JSON.stringify(result)}`);
|
|
} catch (error) {
|
|
console.log(` ✅ Expected error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function sendMCPRequest(message) {
|
|
return new Promise((resolve, reject) => {
|
|
const serverProcess = spawn('node', ['build/index.js'], {
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
});
|
|
|
|
let response = '';
|
|
let error = '';
|
|
|
|
serverProcess.stdout.on('data', (data) => {
|
|
response += data.toString();
|
|
});
|
|
|
|
serverProcess.stderr.on('data', (data) => {
|
|
error += data.toString();
|
|
});
|
|
|
|
serverProcess.on('close', (code) => {
|
|
if (code !== 0) {
|
|
reject(new Error(`Server exited with code ${code}: ${error}`));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Parse JSON-RPC response
|
|
const lines = response.trim().split('\n');
|
|
let jsonResponse = null;
|
|
|
|
for (const line of lines) {
|
|
if (line.trim()) {
|
|
try {
|
|
const parsed = JSON.parse(line);
|
|
if (parsed.id === message.id) {
|
|
jsonResponse = parsed;
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
// Skip non-JSON lines
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jsonResponse) {
|
|
if (jsonResponse.error) {
|
|
reject(new Error(jsonResponse.error.message || 'Unknown error'));
|
|
} else {
|
|
resolve(jsonResponse.result);
|
|
}
|
|
} else {
|
|
reject(new Error(`No valid response found in: ${response}`));
|
|
}
|
|
} catch (e) {
|
|
reject(new Error(`Failed to parse response: ${e.message}\nResponse: ${response}`));
|
|
}
|
|
});
|
|
|
|
serverProcess.on('error', (err) => {
|
|
reject(new Error(`Failed to start server: ${err.message}`));
|
|
});
|
|
|
|
// Send the JSON-RPC message
|
|
serverProcess.stdin.write(JSON.stringify(message) + '\n');
|
|
serverProcess.stdin.end();
|
|
});
|
|
}
|
|
|
|
// Run resource tests
|
|
testResources().then(() => {
|
|
console.log('\n✅ All resource tests completed!');
|
|
}).catch((error) => {
|
|
console.error('\n❌ Resource test suite failed:', error);
|
|
process.exit(1);
|
|
}); |