#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import dotenv from 'dotenv'; import { registerAuthTools } from './tools/auth.js'; import { registerOrgTools } from './tools/organizations.js'; import { registerAppTools } from './tools/applications.js'; import { registerSystemTools } from './tools/system.js'; import { registerStorageTools } from './tools/storage.js'; import { logger } from './utils/logger.js'; dotenv.config(); const server = new Server( { name: 'saas-platform-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Configuration const config = { apiGateway: process.env.SAAS_API_URL || 'http://localhost', authUrl: process.env.SAAS_AUTH_URL || 'http://localhost/auth', storageUrl: process.env.SAAS_STORAGE_URL || 'http://localhost/storage', }; // Store authentication state let authToken: string | null = null; let currentUser: any = null; // Create authenticated axios instance const createApiClient = (requireAuth = true) => { const client = axios.create({ baseURL: config.apiGateway, headers: { 'Content-Type': 'application/json', ...(requireAuth && authToken ? { Authorization: `Bearer ${authToken}` } : {}), }, }); // Add response interceptor for error handling client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401 && requireAuth) { logger.error('Authentication expired'); authToken = null; currentUser = null; throw new McpError( ErrorCode.Unauthorized, 'Authentication expired. Please login again.' ); } throw error; } ); return client; }; // Export utilities for other modules export { createApiClient, config, authToken, currentUser }; // Authentication helper export const setAuth = (token: string, user: any) => { authToken = token; currentUser = user; logger.info(`Authenticated as user: ${user.email}`); }; export const clearAuth = () => { authToken = null; currentUser = null; logger.info('Authentication cleared'); }; // Register all tool handlers registerAuthTools(server, setAuth, clearAuth); registerOrgTools(server, createApiClient); registerAppTools(server, createApiClient); registerSystemTools(server, config); registerStorageTools(server, config); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Authentication tools { name: 'saas_login', description: 'Login to the SaaS platform', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'User email' }, password: { type: 'string', description: 'User password' }, }, required: ['email', 'password'], }, }, { name: 'saas_logout', description: 'Logout from the SaaS platform', inputSchema: { type: 'object', properties: {}, }, }, { name: 'saas_get_current_user', description: 'Get current authenticated user information', inputSchema: { type: 'object', properties: {}, }, }, { name: 'saas_register_user', description: 'Register a new user account', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'User email' }, password: { type: 'string', description: 'User password (min 6 characters)' }, firstName: { type: 'string', description: 'First name' }, lastName: { type: 'string', description: 'Last name' }, }, required: ['email', 'password'], }, }, // Organization tools { name: 'saas_list_organizations', description: 'List organizations for the current user', inputSchema: { type: 'object', properties: {}, }, }, { name: 'saas_create_organization', description: 'Create a new organization', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Organization name' }, slug: { type: 'string', description: 'Organization slug (unique identifier)' }, description: { type: 'string', description: 'Organization description' }, }, required: ['name', 'slug'], }, }, { name: 'saas_get_organization', description: 'Get organization details', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Organization ID' }, }, required: ['id'], }, }, // Application tools { name: 'saas_list_applications', description: 'List applications in an organization', inputSchema: { type: 'object', properties: { organizationId: { type: 'string', description: 'Organization ID' }, }, required: ['organizationId'], }, }, { name: 'saas_create_application', description: 'Create a new application', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Application name' }, slug: { type: 'string', description: 'Application slug (unique)' }, description: { type: 'string', description: 'Application description' }, organizationId: { type: 'string', description: 'Organization ID' }, repositoryUrl: { type: 'string', description: 'Git repository URL' }, buildCommand: { type: 'string', description: 'Build command' }, startCommand: { type: 'string', description: 'Start command' }, environment: { type: 'object', description: 'Environment variables' }, }, required: ['name', 'slug', 'organizationId'], }, }, { name: 'saas_get_application', description: 'Get application details', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Application ID' }, }, required: ['id'], }, }, { name: 'saas_deploy_application', description: 'Deploy an application', inputSchema: { type: 'object', properties: { applicationId: { type: 'string', description: 'Application ID' }, version: { type: 'string', description: 'Deployment version' }, commitHash: { type: 'string', description: 'Git commit hash' }, }, required: ['applicationId'], }, }, { name: 'saas_get_deployments', description: 'Get deployment history for an application', inputSchema: { type: 'object', properties: { applicationId: { type: 'string', description: 'Application ID' }, }, required: ['applicationId'], }, }, // System tools { name: 'saas_health_check', description: 'Check health of all platform services', inputSchema: { type: 'object', properties: {}, }, }, { name: 'saas_get_platform_stats', description: 'Get platform statistics and usage metrics', inputSchema: { type: 'object', properties: {}, }, }, // Storage tools { name: 'saas_list_files', description: 'List files in storage', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Storage path (default: root)' }, recursive: { type: 'boolean', description: 'List files recursively' }, }, }, }, { name: 'saas_upload_file', description: 'Upload a file to storage', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Destination path' }, content: { type: 'string', description: 'File content (base64 encoded)' }, contentType: { type: 'string', description: 'MIME content type' }, }, required: ['path', 'content'], }, }, { name: 'saas_download_file', description: 'Download a file from storage', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, }, required: ['path'], }, }, { name: 'saas_delete_file', description: 'Delete a file from storage', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, }, required: ['path'], }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'saas_login': return await handleLogin(args); case 'saas_logout': return await handleLogout(); case 'saas_get_current_user': return await handleGetCurrentUser(); case 'saas_register_user': return await handleRegisterUser(args); case 'saas_list_organizations': return await handleListOrganizations(); case 'saas_create_organization': return await handleCreateOrganization(args); case 'saas_get_organization': return await handleGetOrganization(args); case 'saas_list_applications': return await handleListApplications(args); case 'saas_create_application': return await handleCreateApplication(args); case 'saas_get_application': return await handleGetApplication(args); case 'saas_deploy_application': return await handleDeployApplication(args); case 'saas_get_deployments': return await handleGetDeployments(args); case 'saas_health_check': return await handleHealthCheck(); case 'saas_get_platform_stats': return await handleGetPlatformStats(); case 'saas_list_files': return await handleListFiles(args); case 'saas_upload_file': return await handleUploadFile(args); case 'saas_download_file': return await handleDownloadFile(args); case 'saas_delete_file': return await handleDeleteFile(args); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } logger.error(`Tool execution error for ${name}:`, error); throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }); // Tool handlers async function handleLogin(args: { email: string; password: string }) { try { const response = await axios.post(`${config.authUrl}/login`, { email: args.email, password: args.password, }); const { user, accessToken, refreshToken } = response.data; setAuth(accessToken, user); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: `Logged in successfully as ${user.email}`, user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, emailVerified: user.emailVerified, }, }, null, 2), }, ], }; } catch (error: any) { logger.error('Login failed:', error.response?.data || error.message); throw new McpError( ErrorCode.Unauthorized, error.response?.data?.error || 'Login failed' ); } } async function handleLogout() { if (!authToken) { return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'Already logged out', }, null, 2), }, ], }; } try { await axios.post(`${config.authUrl}/logout`, {}, { headers: { Authorization: `Bearer ${authToken}` }, }); } catch (error) { // Continue even if logout request fails } clearAuth(); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'Logged out successfully', }, null, 2), }, ], }; } async function handleGetCurrentUser() { if (!authToken) { throw new McpError(ErrorCode.Unauthorized, 'Not authenticated'); } try { const response = await axios.get(`${config.authUrl}/me`, { headers: { Authorization: `Bearer ${authToken}` }, }); return { content: [ { type: 'text', text: JSON.stringify({ user: response.data, authenticated: true, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.Unauthorized, error.response?.data?.error || 'Failed to get user info' ); } } async function handleRegisterUser(args: { email: string; password: string; firstName?: string; lastName?: string; }) { try { const response = await axios.post(`${config.authUrl}/register`, { email: args.email, password: args.password, firstName: args.firstName, lastName: args.lastName, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'User registered successfully', user: response.data.user, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, error.response?.data?.error || 'Registration failed' ); } } async function handleListOrganizations() { const client = createApiClient(); try { const response = await client.get('/api/organizations'); return { content: [ { type: 'text', text: JSON.stringify({ organizations: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to list organizations' ); } } async function handleCreateOrganization(args: { name: string; slug: string; description?: string; }) { const client = createApiClient(); try { const response = await client.post('/api/organizations', { name: args.name, slug: args.slug, description: args.description, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'Organization created successfully', organization: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, error.response?.data?.error || 'Failed to create organization' ); } } async function handleGetOrganization(args: { id: string }) { const client = createApiClient(); try { const response = await client.get(`/api/organizations/${args.id}`); return { content: [ { type: 'text', text: JSON.stringify({ organization: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.NotFound, error.response?.data?.error || 'Organization not found' ); } } async function handleListApplications(args: { organizationId: string }) { const client = createApiClient(); try { const response = await client.get(`/api/applications?organizationId=${args.organizationId}`); return { content: [ { type: 'text', text: JSON.stringify({ applications: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to list applications' ); } } async function handleCreateApplication(args: { name: string; slug: string; organizationId: string; description?: string; repositoryUrl?: string; buildCommand?: string; startCommand?: string; environment?: any; }) { const client = createApiClient(); try { const response = await client.post('/api/applications', { name: args.name, slug: args.slug, organizationId: args.organizationId, description: args.description, repositoryUrl: args.repositoryUrl, buildCommand: args.buildCommand, startCommand: args.startCommand, environment: args.environment || {}, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'Application created successfully', application: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, error.response?.data?.error || 'Failed to create application' ); } } async function handleGetApplication(args: { id: string }) { const client = createApiClient(); try { const response = await client.get(`/api/applications/${args.id}`); return { content: [ { type: 'text', text: JSON.stringify({ application: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.NotFound, error.response?.data?.error || 'Application not found' ); } } async function handleDeployApplication(args: { applicationId: string; version?: string; commitHash?: string; }) { const client = createApiClient(); try { const response = await client.post('/api/deployments', { applicationId: args.applicationId, version: args.version, commitHash: args.commitHash, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'Deployment started successfully', deployment: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to start deployment' ); } } async function handleGetDeployments(args: { applicationId: string }) { const client = createApiClient(); try { const response = await client.get(`/api/deployments?applicationId=${args.applicationId}`); return { content: [ { type: 'text', text: JSON.stringify({ deployments: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to get deployments' ); } } async function handleHealthCheck() { const services = [ { name: 'API Gateway', url: `${config.apiGateway}/health` }, { name: 'Auth Service', url: `${config.authUrl}/health` }, ]; const results = []; for (const service of services) { try { const response = await axios.get(service.url, { timeout: 5000 }); results.push({ name: service.name, status: 'healthy', response: response.data, timestamp: new Date().toISOString(), }); } catch (error) { results.push({ name: service.name, status: 'unhealthy', error: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date().toISOString(), }); } } return { content: [ { type: 'text', text: JSON.stringify({ platform_health: results, overall_status: results.every(r => r.status === 'healthy') ? 'healthy' : 'degraded', }, null, 2), }, ], }; } async function handleGetPlatformStats() { // This would typically call an admin endpoint or aggregate data // For now, return basic information return { content: [ { type: 'text', text: JSON.stringify({ platform_version: '1.0.0', services: [ 'Authentication', 'API Gateway', 'Database', 'Redis Cache', 'File Storage', 'Real-time WebSocket', 'Monitoring' ], features: [ 'Multi-tenant organizations', 'Application hosting', 'TypeScript support', 'Real-time features', 'File storage', 'User management', 'Deployment management' ], timestamp: new Date().toISOString(), }, null, 2), }, ], }; } async function handleListFiles(args: { path?: string; recursive?: boolean }) { try { const response = await axios.get(`${config.storageUrl}/files`, { params: { path: args.path || '/', recursive: args.recursive || false, }, headers: authToken ? { Authorization: `Bearer ${authToken}` } : {}, }); return { content: [ { type: 'text', text: JSON.stringify({ files: response.data, path: args.path || '/', recursive: args.recursive || false, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to list files' ); } } async function handleUploadFile(args: { path: string; content: string; contentType?: string; }) { try { const response = await axios.post(`${config.storageUrl}/upload`, { path: args.path, content: args.content, contentType: args.contentType || 'application/octet-stream', }, { headers: authToken ? { Authorization: `Bearer ${authToken}` } : {}, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'File uploaded successfully', file: response.data, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, error.response?.data?.error || 'Failed to upload file' ); } } async function handleDownloadFile(args: { path: string }) { try { const response = await axios.get(`${config.storageUrl}/download`, { params: { path: args.path }, headers: authToken ? { Authorization: `Bearer ${authToken}` } : {}, responseType: 'arraybuffer', }); // Convert to base64 for JSON response const base64 = Buffer.from(response.data).toString('base64'); return { content: [ { type: 'text', text: JSON.stringify({ path: args.path, content: base64, contentType: response.headers['content-type'], size: response.data.byteLength, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.NotFound, error.response?.data?.error || 'File not found' ); } } async function handleDeleteFile(args: { path: string }) { try { await axios.delete(`${config.storageUrl}/files`, { data: { path: args.path }, headers: authToken ? { Authorization: `Bearer ${authToken}` } : {}, }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: 'File deleted successfully', path: args.path, }, null, 2), }, ], }; } catch (error: any) { throw new McpError( ErrorCode.NotFound, error.response?.data?.error || 'File not found' ); } } // Start the MCP server async function main() { logger.info('Starting SaaS Platform MCP Server'); const transport = new StdioServerTransport(); await server.connect(transport); logger.info('SaaS Platform MCP Server connected and ready'); } // Handle errors process.on('uncaughtException', (error) => { logger.error('Uncaught exception:', error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled rejection at:', promise, 'reason:', reason); process.exit(1); }); // Start the server main().catch((error) => { logger.error('Failed to start MCP server:', error); process.exit(1); });