UNPKG

survey-mcp-server

Version:

Survey management server handling survey creation, response collection, analysis, and reporting with database access for data management

221 lines 9.77 kB
/** * Survey MCP Server * A low-level server implementation using Model Context Protocol */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourceTemplatesRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { ToolHandler } from "./tools/index.js"; import { config, reloadConfig, loadEnvFile } from "./utils/config.js"; import { logger } from "./utils/logger.js"; import { toolDefinitions } from "./tools/schema.js"; import { Command } from 'commander'; import { typesenseClient } from "./utils/typesense.js"; import { mongoClient } from "./utils/mongodb.js"; import { promptList, handleGetPrompt } from "./prompts/index.js"; import { resourceList, handleReadResource } from "./resources/index.js"; import { fetchCompanyImoNumbers } from "./utils/imoUtils.js"; async function parseCommandLineArgs() { const program = new Command(); program .name('survey-mcp-server') .description('Survey MCP Server with database access for data management') .version('1.0.0') .option('--mongodb-uri <uri>', 'MongoDB connection URI') .option('--mongodb-db-name <name>', 'MongoDB database name') .option('--mongodb-uri-syia-etl <uri>', 'MongoDB connection URI for syia-etl') .option('--db-name-syia-etl <name>', 'MongoDB database name for syia-etl') .option('--mongo-uri-dev-syia-api <uri>', 'MongoDB connection URI for dev-syia-api') .option('--db-name-dev-syia-api <name>', 'MongoDB database name for dev-syia-api') .option('--typesense-host <host>', 'Typesense host') .option('--typesense-port <port>', 'Typesense port') .option('--typesense-api-key <key>', 'Typesense API key') .option('--typesense-protocol <protocol>', 'Typesense protocol') .option('--openai-api-key <key>', 'OpenAI API key') .option('--perplexity-api-key <key>', 'Perplexity API key') .option('--api-token <token>', 'API authentication token') .option('--api-base-url <url>', 'API base URL') .option('--downloads-dir <dir>', 'Downloads directory path') .option('--log-level <level>', 'Log level (debug, info, warn, error)', 'info') .option('--env-file <path>', 'Path to .env file') .option('--company-name <name>', 'Company name for IMO filtering') .allowUnknownOption() .parse(); const options = program.opts(); // Load custom .env file if specified if (options.envFile) { logger.info(`Loading custom .env file: ${options.envFile}`); loadEnvFile(options.envFile); } // Override environment variables with command line arguments if (options.mongodbUri) process.env.MONGODB_URI = options.mongodbUri; if (options.mongodbDbName) process.env.MONGODB_DB_NAME = options.mongodbDbName; if (options.mongodbUriSyiaEtl) process.env.MONGODB_URI_SYIA_ETL = options.mongodbUriSyiaEtl; if (options.dbNameSyiaEtl) process.env.DB_NAME_SYIA_ETL = options.dbNameSyiaEtl; if (options.mongoUriDevSyiaApi) process.env.MONGO_URI_DEV_SYIA_API = options.mongoUriDevSyiaApi; if (options.dbNameDevSyiaApi) process.env.DB_NAME_DEV_SYIA_API = options.dbNameDevSyiaApi; if (options.typesenseHost) process.env.TYPESENSE_HOST = options.typesenseHost; if (options.typesensePort) process.env.TYPESENSE_PORT = options.typesensePort; if (options.typesenseApiKey) process.env.TYPESENSE_API_KEY = options.typesenseApiKey; if (options.typesenseProtocol) process.env.TYPESENSE_PROTOCOL = options.typesenseProtocol; if (options.openaiApiKey) process.env.OPENAI_API_KEY = options.openaiApiKey; if (options.perplexityApiKey) process.env.PERPLEXITY_API_KEY = options.perplexityApiKey; if (options.apiToken) process.env.API_TOKEN = options.apiToken; if (options.apiBaseUrl) process.env.API_BASE_URL = options.apiBaseUrl; if (options.downloadsDir) process.env.DOWNLOADS_DIR = options.downloadsDir; if (options.companyName) process.env.COMPANY_NAME = options.companyName; // Reload config after environment variables have been updated reloadConfig(); return options; } async function main() { // Parse command line arguments first and update environment/config const cliOptions = await parseCommandLineArgs(); // Validate company name configuration if (!config.companyName) { logger.error("Company name is required for IMO filtering. Please provide it via --company-name option or COMPANY_NAME environment variable."); process.exit(1); } logger.info("Starting Survey MCP server...", { version: "1.0.0", logLevel: cliOptions.logLevel || 'info', companyName: config.companyName }); // Initialize MongoDB client after config is properly set up try { logger.info("Initializing MongoDB client..."); await mongoClient.initialize(); logger.info("MongoDB client initialized successfully"); } catch (error) { logger.warn("MongoDB initialization failed:", error.message); logger.warn("MongoDB operations may be limited. Please check your MongoDB configuration."); } // Fetch IMO numbers for the company after MongoDB is initialized try { logger.info(`Fetching IMO numbers for company: ${config.companyName}`); const imoNumbers = await fetchCompanyImoNumbers(config.companyName); logger.info(`Successfully fetched ${imoNumbers.length} IMO numbers for company: ${config.companyName}`); } catch (error) { logger.error("Failed to fetch IMO numbers:", error.message); logger.warn("IMO filtering may be limited. Server will continue with reduced functionality."); // Continue server startup even if IMO fetching fails } // Initialize Typesense client after config is properly set up try { logger.info("Initializing Typesense client..."); await typesenseClient.initialize(); logger.info("Typesense client initialized successfully"); } catch (error) { logger.warn("Typesense initialization failed:", error.message); logger.warn("Typesense operations will be disabled. Please check your Typesense configuration."); } const server = new Server({ name: "survey-mcp-server", version: "1.0.1" }, { capabilities: { resources: { read: true, list: true, templates: true }, tools: { list: true, call: true }, prompts: { list: true, get: true } } }); logger.info("Server initialized successfully", { version: "1.0.10" }); const toolHandler = new ToolHandler(server); // Resource list server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: resourceList }; }); // Resource read server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const content = await handleReadResource(request.params.uri); return { contents: [{ type: "text", text: content }] }; }); // Empty resource templates server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { return { templates: [] }; }); // Tools list server.setRequestHandler(ListToolsRequestSchema, async (request) => { logger.info("Handling ListToolsRequest", { request }); let tools = [...toolDefinitions]; const params = request.params || {}; const category = params.category; const search = params.search; const includeDeprecated = params.include_deprecated; // Apply category filter if provided if (category) { tools = tools.filter(tool => { const toolName = tool.name.toLowerCase(); return toolName.includes(category.toLowerCase()); }); } // Apply search filter if provided if (search) { const searchTerm = search.toLowerCase(); tools = tools.filter(tool => tool.name.toLowerCase().includes(searchTerm) || (tool.description?.toLowerCase() || '').includes(searchTerm)); } // Filter out deprecated tools unless explicitly requested if (!includeDeprecated) { tools = tools.filter(tool => !tool.name.includes('deprecated')); } return { tools }; }); // Tool call server.setRequestHandler(CallToolRequestSchema, async (request) => { const result = await toolHandler.handleCallTool(request.params.name, request.params.arguments || {}); return { content: result }; }); // Prompts list server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: promptList }; }); // Prompt get server.setRequestHandler(GetPromptRequestSchema, async (request) => { return handleGetPrompt(request.params.name, request.params.arguments); }); // Connect to transport and start server const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { logger.error("Fatal error:", error); process.exit(1); }); //# sourceMappingURL=index.js.map