node-server-orchestrator
Version:
CLI tool for orchestrating Node.js development servers (backend, frontend, databases, etc.)
251 lines ⢠10.5 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ProjectServerManager } from './ProjectServerManager.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
class ProjectServerMCPServer {
server;
manager;
constructor() {
this.server = new Server({
name: 'project-server-manager',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.manager = new ProjectServerManager();
this.setupToolHandlers();
this.setupErrorHandlers();
}
setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = [
{
name: 'start_server',
description: 'Start a project development server',
inputSchema: {
type: 'object',
properties: {
server_id: {
type: 'string',
description: 'Server identifier',
},
},
required: ['server_id'],
},
},
{
name: 'stop_server',
description: 'Stop a running server',
inputSchema: {
type: 'object',
properties: {
server_id: {
type: 'string',
description: 'Server identifier',
},
},
required: ['server_id'],
},
},
{
name: 'get_server_status',
description: 'Get status of a server',
inputSchema: {
type: 'object',
properties: {
server_id: {
type: 'string',
description: 'Server identifier',
},
},
required: ['server_id'],
},
},
{
name: 'list_servers',
description: 'List all available servers',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'start_all_servers',
description: 'Start all project servers',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'stop_all_servers',
description: 'Stop all running servers',
inputSchema: {
type: 'object',
properties: {},
},
},
];
return { tools };
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const serverId = args?.server_id;
switch (name) {
case 'start_server':
const startResult = await this.manager.startServer(serverId);
return {
content: [
{
type: 'text',
text: startResult.success
? `ā
${startResult.message}\nš PID: ${startResult.pid}\nš Port: ${startResult.port}`
: `ā ${startResult.message}`
},
],
};
case 'stop_server':
const stopResult = await this.manager.stopServer(serverId);
return {
content: [
{
type: 'text',
text: stopResult.success
? `ā
${stopResult.message}`
: `ā ${stopResult.message}`
},
],
};
case 'get_server_status':
const status = await this.manager.getServerStatus(serverId);
const config = this.manager.getServerConfig(serverId);
if (!config) {
return {
content: [
{
type: 'text',
text: `ā Unknown server: ${serverId}`
},
],
};
}
return {
content: [
{
type: 'text',
text: this.manager.formatStatusMessage(status, config)
},
],
};
case 'list_servers':
const servers = this.manager.listServers();
const serverList = servers.map(id => {
const config = this.manager.getServerConfig(id);
return `${config?.name || id} (${id}) - ${config?.description || 'No description'}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `š Available Servers:\n${serverList}`
},
],
};
case 'start_all_servers':
const allResults = await this.manager.startAllServers();
const resultsText = Object.entries(allResults).map(([id, result]) => {
const config = this.manager.getServerConfig(id);
const status = result.success ? 'ā
' : 'ā';
return `${status} ${config?.name || id}: ${result.message}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `š Starting All Servers:\n${resultsText}`
},
],
};
case 'stop_all_servers':
const stopAllResults = await this.manager.stopAllServers();
const stopAllText = Object.entries(stopAllResults).map(([id, result]) => {
const config = this.manager.getServerConfig(id);
const status = result.success ? 'ā
' : 'ā';
return `${status} ${config?.name || id}: ${result.message}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `š Stopping All Servers:\n${stopAllText}`
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
// Sanitize error messages to prevent information leakage
const sanitizedMessage = this.sanitizeErrorMessage(error.message);
return {
content: [
{
type: 'text',
text: `ā Error: ${sanitizedMessage}`
},
],
isError: true,
};
}
});
}
sanitizeErrorMessage(errorMessage) {
// Remove sensitive information from error messages
const sensitivePatterns = [
/\/home\/[^\s]+/g, // Home directory paths
/\/usr\/[^\s]+/g, // System paths
/\/etc\/[^\s]+/g, // Configuration paths
/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/g, // IP addresses
];
let sanitized = errorMessage;
for (const pattern of sensitivePatterns) {
sanitized = sanitized.replace(pattern, '[REDACTED]');
}
return sanitized;
}
setupErrorHandlers() {
this.server.onerror = (error) => {
console.error('[MCP Server Error]', error);
};
process.on('SIGINT', async () => {
await this.manager.stopAllServers();
process.exit(0);
});
process.on('SIGTERM', async () => {
await this.manager.stopAllServers();
process.exit(0);
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Project Server MCP server running on stdio');
}
}
// Start the server if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new ProjectServerMCPServer();
server.run().catch((error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});
}
export { ProjectServerMCPServer };
//# sourceMappingURL=index.js.map