| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968 |
- #!/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);
- });
|