UNPKG

suitecrm-mcp-server

Version:

Model Context Protocol server for SuiteCRM integration with natural language SQL reporting

385 lines 16.1 kB
#!/usr/bin/env node "use strict"; /** * Main MCP server for SuiteCRM integration */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const zod_1 = require("zod"); const dotenv_1 = __importDefault(require("dotenv")); const auth_1 = require("./services/auth"); const modules_1 = require("./services/modules"); const query_1 = require("./services/query"); const logger_1 = require("./utils/logger"); // Load environment variables dotenv_1.default.config(); // Environment configuration const config = { SUITECRM_URL: process.env['SUITECRM_URL'] || 'http://localhost/suitecrm', SUITECRM_CLIENT_ID: process.env['SUITECRM_CLIENT_ID'] || '', SUITECRM_CLIENT_SECRET: process.env['SUITECRM_CLIENT_SECRET'] || '', LOG_LEVEL: process.env['LOG_LEVEL'] || 'info', MAX_QUERY_ROWS: parseInt(process.env['MAX_QUERY_ROWS'] || '100', 10), REQUEST_TIMEOUT: parseInt(process.env['REQUEST_TIMEOUT'] || '30000', 10), CACHE_TTL: parseInt(process.env['CACHE_TTL'] || '1800000', 10), RATE_LIMIT_WINDOW: parseInt(process.env['RATE_LIMIT_WINDOW'] || '60000', 10), RATE_LIMIT_MAX_REQUESTS: parseInt(process.env['RATE_LIMIT_MAX_REQUESTS'] || '100', 10) }; // Initialize services const authService = new auth_1.AuthService(config.SUITECRM_URL, config.REQUEST_TIMEOUT); const moduleService = new modules_1.ModuleService(config.SUITECRM_URL, config.REQUEST_TIMEOUT); const queryService = new query_1.QueryService(config.SUITECRM_URL, config.MAX_QUERY_ROWS, config.REQUEST_TIMEOUT); // Set log level logger_1.logger.setLevel(config.LOG_LEVEL); // MCP Server implementation class SuiteCRMCPServer { server; constructor() { this.server = new index_js_1.Server({ name: 'suitecrm-mcp-server', version: '1.0.0', }); this.setupTools(); this.setupErrorHandling(); } setupTools() { // Tool 1: authenticate_crm this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: 'authenticate_crm', description: 'Authenticate with SuiteCRM using client credentials', inputSchema: { type: 'object', properties: { crm_url: { type: 'string', description: 'SuiteCRM instance URL' }, client_id: { type: 'string', description: 'OAuth client ID' }, client_secret: { type: 'string', description: 'OAuth client secret' } }, required: ['crm_url', 'client_id', 'client_secret'] } }, { name: 'get_modules', description: 'Fetch available CRM modules', inputSchema: { type: 'object', properties: { crm_url: { type: 'string', description: 'SuiteCRM instance URL' }, access_token: { type: 'string', description: 'Valid access token' } }, required: ['crm_url', 'access_token'] } }, { name: 'get_module_schema', description: 'Get detailed schema for a specific module', inputSchema: { type: 'object', properties: { crm_url: { type: 'string', description: 'SuiteCRM instance URL' }, access_token: { type: 'string', description: 'Valid access token' }, module_name: { type: 'string', description: 'Name of the module to retrieve schema for' } }, required: ['crm_url', 'access_token', 'module_name'] } }, { name: 'execute_query', description: 'Execute SQL query against CRM database', inputSchema: { type: 'object', properties: { crm_url: { type: 'string', description: 'SuiteCRM instance URL' }, access_token: { type: 'string', description: 'Valid access token' }, sql_query: { type: 'string', description: 'SQL query to execute (SELECT only)' } }, required: ['crm_url', 'access_token', 'sql_query'] } } ] }; }); // Tool 2: authenticate_crm implementation this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'authenticate_crm': { const params = AuthenticateCrmParamsSchema.parse(args); const result = await this.handleAuthenticateCrm(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } case 'get_modules': { const params = GetModulesParamsSchema.parse(args); const result = await this.handleGetModules(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } case 'get_module_schema': { const params = GetModuleSchemaParamsSchema.parse(args); const result = await this.handleGetModuleSchema(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } case 'execute_query': { const params = ExecuteQueryParamsSchema.parse(args); const result = await this.handleExecuteQuery(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger_1.logger.error(`Tool execution failed: ${name}`, { args }, error instanceof Error ? error : new Error(String(error))); throw error; } }); } async handleAuthenticateCrm(params) { try { logger_1.logger.info('Authenticating with SuiteCRM', { crmUrl: params.crm_url }); const authResponse = await authService.authenticate({ client_id: params.client_id, client_secret: params.client_secret }); return { success: true, token: authResponse.access_token, expires_in: authResponse.expires_in }; } catch (error) { logger_1.logger.error('Authentication failed', { crmUrl: params.crm_url }, error instanceof Error ? error : new Error(String(error))); return { success: false, token: '', expires_in: 0, error: error instanceof Error ? error.message : 'Unknown error' }; } } async handleGetModules(params) { try { logger_1.logger.info('Fetching modules', { crmUrl: params.crm_url }); const modules = await moduleService.getModules(params.access_token); return { success: true, modules }; } catch (error) { logger_1.logger.error('Failed to fetch modules', { crmUrl: params.crm_url }, error instanceof Error ? error : new Error(String(error))); return { success: false, modules: [], error: error instanceof Error ? error.message : 'Unknown error' }; } } async handleGetModuleSchema(params) { try { logger_1.logger.info('Fetching module schema', { crmUrl: params.crm_url, module: params.module_name }); const schema = await moduleService.getModuleSchema(params.access_token, params.module_name); return { success: true, schema }; } catch (error) { logger_1.logger.error('Failed to fetch module schema', { crmUrl: params.crm_url, module: params.module_name }, error instanceof Error ? error : new Error(String(error))); return { success: false, schema: { name: params.module_name, label: '', fields: [], relationships: [], indexes: [] }, error: error instanceof Error ? error.message : 'Unknown error' }; } } async handleExecuteQuery(params) { try { logger_1.logger.info('Executing query', { crmUrl: params.crm_url, queryLength: params.sql_query.length }); const response = await queryService.executeQuery(params.access_token, { sql: params.sql_query }); return { success: true, data: response.data, metadata: response.metadata }; } catch (error) { logger_1.logger.error('Query execution failed', { crmUrl: params.crm_url }, error instanceof Error ? error : new Error(String(error))); return { success: false, data: [], metadata: { query_time: 0, rows_returned: 0, columns: [] }, error: error instanceof Error ? error.message : 'Unknown error' }; } } setupErrorHandling() { process.on('uncaughtException', (error) => { logger_1.logger.error('Uncaught exception', {}, error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger_1.logger.error('Unhandled rejection', { promise }, reason instanceof Error ? reason : new Error(String(reason))); process.exit(1); }); process.on('SIGINT', () => { logger_1.logger.info('Received SIGINT, shutting down gracefully'); this.shutdown(); }); process.on('SIGTERM', () => { logger_1.logger.info('Received SIGTERM, shutting down gracefully'); this.shutdown(); }); } async shutdown() { try { authService.destroy(); moduleService.clearCache(); await logger_1.logger.close(); process.exit(0); } catch (error) { logger_1.logger.error('Error during shutdown', {}, error instanceof Error ? error : new Error(String(error))); process.exit(1); } } async run() { try { logger_1.logger.info('Starting SuiteCRM MCP Server', { version: '1.0.0', config: { maxQueryRows: config.MAX_QUERY_ROWS, requestTimeout: config.REQUEST_TIMEOUT, logLevel: config.LOG_LEVEL } }); const transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(transport); logger_1.logger.info('SuiteCRM MCP Server started successfully'); } catch (error) { logger_1.logger.error('Failed to start MCP server', {}, error instanceof Error ? error : new Error(String(error))); process.exit(1); } } } // Validation schemas for tool parameters const AuthenticateCrmParamsSchema = zod_1.z.object({ crm_url: zod_1.z.string().url('Invalid CRM URL'), client_id: zod_1.z.string().min(1, 'Client ID is required'), client_secret: zod_1.z.string().min(1, 'Client secret is required') }); const GetModulesParamsSchema = zod_1.z.object({ crm_url: zod_1.z.string().url('Invalid CRM URL'), access_token: zod_1.z.string().min(1, 'Access token is required') }); const GetModuleSchemaParamsSchema = zod_1.z.object({ crm_url: zod_1.z.string().url('Invalid CRM URL'), access_token: zod_1.z.string().min(1, 'Access token is required'), module_name: zod_1.z.string().min(1, 'Module name is required') }); const ExecuteQueryParamsSchema = zod_1.z.object({ crm_url: zod_1.z.string().url('Invalid CRM URL'), access_token: zod_1.z.string().min(1, 'Access token is required'), sql_query: zod_1.z.string().min(1, 'SQL query is required') }); // Start the server if (require.main === module) { const server = new SuiteCRMCPServer(); server.run().catch((error) => { logger_1.logger.error('Server startup failed', {}, error instanceof Error ? error : new Error(String(error))); process.exit(1); }); } //# sourceMappingURL=index.js.map