mcp-server.ts•10.8 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { SSHService } from './ssh-service.js';
import { VPSInitializer } from '../tools/vps-initializer.js';
import { NginxManager } from '../tools/nginx-manager.js';
import { GitHubCICD } from '../tools/github-cicd.js';
import { logger } from '../utils/logger.js';
import { SSHConfig } from '../types/index.js';
// Zod schemas for tool parameters
const SSHConfigSchema = z.object({
host: z.string().describe('The IP address or hostname of the VPS'),
port: z.number().optional().default(22).describe('SSH port (default: 22)'),
username: z.string().describe('SSH username'),
password: z.string().optional().describe('SSH password (if not using key)'),
privateKeyPath: z.string().optional().describe('Path to private key file'),
passphrase: z.string().optional().describe('Passphrase for private key'),
});
const VPSServicesSchema = z.object({
nodejs: z.boolean().optional().default(false).describe('Install Node.js'),
pm2: z.boolean().optional().default(false).describe('Install PM2'),
rust: z.boolean().optional().default(false).describe('Install Rust'),
nginx: z.boolean().optional().default(false).describe('Install Nginx'),
redis: z.boolean().optional().default(false).describe('Install Redis'),
});
const NginxConfigSchema = z.object({
domain: z.string().describe('Domain name for Nginx configuration'),
port: z.number().describe('Backend port to proxy to'),
ssl: z.boolean().optional().default(true).describe('Enable SSL with Certbot'),
});
const GitHubConfigSchema = z.object({
repoUrl: z.string().describe('GitHub repository URL'),
deployPath: z.string().describe('Deployment path on the server'),
});
export class MCPVPSServer {
private server: Server;
private sshService: SSHService | null = null;
private vpsInitializer: VPSInitializer | null = null;
private nginxManager: NginxManager | null = null;
private githubCICD: GitHubCICD | null = null;
constructor() {
this.server = new Server(
{
name: 'mcp-vps-initialize',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupTools();
}
private setupTools(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'ssh_connect',
description: 'Connect to a VPS via SSH using password or private key',
inputSchema: {
type: 'object',
properties: {
host: { type: 'string', description: 'VPS IP address or hostname' },
port: { type: 'number', description: 'SSH port (default: 22)' },
username: { type: 'string', description: 'SSH username' },
password: { type: 'string', description: 'SSH password (optional)' },
privateKeyPath: { type: 'string', description: 'Private key file path (optional)' },
passphrase: { type: 'string', description: 'Private key passphrase (optional)' },
},
required: ['host', 'username'],
},
},
{
name: 'vps_initialize',
description: 'Initialize a fresh VPS with basic setup and optional services',
inputSchema: {
type: 'object',
properties: {
services: {
type: 'object',
properties: {
nodejs: { type: 'boolean', description: 'Install Node.js' },
pm2: { type: 'boolean', description: 'Install PM2' },
rust: { type: 'boolean', description: 'Install Rust' },
nginx: { type: 'boolean', description: 'Install Nginx' },
redis: { type: 'boolean', description: 'Install Redis' },
},
},
},
required: [],
},
},
{
name: 'nginx_setup',
description: 'Configure Nginx with domain, reverse proxy, and SSL',
inputSchema: {
type: 'object',
properties: {
domain: { type: 'string', description: 'Domain name' },
port: { type: 'number', description: 'Backend port to proxy to' },
ssl: { type: 'boolean', description: 'Enable SSL with Certbot' },
},
required: ['domain', 'port'],
},
},
{
name: 'github_cicd_setup',
description: 'Setup GitHub CI/CD with deploy keys and workflow',
inputSchema: {
type: 'object',
properties: {
repoUrl: { type: 'string', description: 'GitHub repository URL' },
deployPath: { type: 'string', description: 'Deployment path on server' },
},
required: ['repoUrl', 'deployPath'],
},
},
{
name: 'execute_command',
description: 'Execute a command on the connected VPS',
inputSchema: {
type: 'object',
properties: {
command: { type: 'string', description: 'Command to execute' },
},
required: ['command'],
},
},
] satisfies Tool[],
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async request => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'ssh_connect':
return await this.handleSSHConnect(args);
case 'vps_initialize':
return await this.handleVPSInitialize(args);
case 'nginx_setup':
return await this.handleNginxSetup(args);
case 'github_cicd_setup':
return await this.handleGitHubCICDSetup(args);
case 'execute_command':
return await this.handleExecuteCommand(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
logger.error('Tool execution failed', {
tool: name,
error: error instanceof Error ? error.message : 'Unknown error',
});
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
});
}
private async handleSSHConnect(
args: unknown
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const parsedConfig = SSHConfigSchema.parse(args);
const config: SSHConfig = {
host: parsedConfig.host,
port: parsedConfig.port,
username: parsedConfig.username,
password: parsedConfig.password || undefined,
privateKeyPath: parsedConfig.privateKeyPath || undefined,
passphrase: parsedConfig.passphrase || undefined,
};
this.sshService = new SSHService(config);
const connected = await this.sshService.connect();
if (connected) {
// Initialize other services
this.vpsInitializer = new VPSInitializer(this.sshService);
this.nginxManager = new NginxManager(this.sshService);
this.githubCICD = new GitHubCICD(this.sshService);
return {
content: [
{
type: 'text',
text: `Successfully connected to ${config.host} as ${config.username}`,
},
],
};
} else {
throw new Error('Failed to establish SSH connection');
}
}
private async handleVPSInitialize(
args: unknown
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
if (!this.vpsInitializer) {
throw new Error('SSH connection not established. Please connect first.');
}
const services = VPSServicesSchema.parse(args);
const results = await this.vpsInitializer.initializeVPS(services);
const output = results
.map(
result => `${result.service}: ${result.success ? 'Success' : 'Failed'} - ${result.message}`
)
.join('\n');
return {
content: [
{
type: 'text',
text: `VPS Initialization Results:\n${output}`,
},
],
};
}
private async handleNginxSetup(
args: unknown
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
if (!this.nginxManager) {
throw new Error('SSH connection not established. Please connect first.');
}
const config = NginxConfigSchema.parse(args);
const result = await this.nginxManager.setupNginx(config);
return {
content: [
{
type: 'text',
text: result.success
? `Nginx configured successfully for ${config.domain}`
: `Nginx setup failed: ${result.message}`,
},
],
};
}
private async handleGitHubCICDSetup(
args: unknown
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
if (!this.githubCICD) {
throw new Error('SSH connection not established. Please connect first.');
}
const config = GitHubConfigSchema.parse(args);
const result = await this.githubCICD.setupCICD(config);
return {
content: [
{
type: 'text',
text: result.success
? `GitHub CI/CD setup completed. Deploy key and workflow generated.`
: `GitHub CI/CD setup failed: ${result.message}`,
},
],
};
}
private async handleExecuteCommand(
args: unknown
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
if (!this.sshService) {
throw new Error('SSH connection not established. Please connect first.');
}
const { command } = z.object({ command: z.string() }).parse(args);
const result = await this.sshService.executeCommand(command);
return {
content: [
{
type: 'text',
text: `Command: ${command}\nExit Code: ${result.exitCode}\nOutput:\n${result.stdout}\n${result.stderr ? `Error:\n${result.stderr}` : ''}`,
},
],
};
}
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info('MCP VPS Initialize server started');
}
async stop(): Promise<void> {
if (this.sshService) {
await this.sshService.disconnect();
}
logger.info('MCP VPS Initialize server stopped');
}
}