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
JavaScript
/**
* 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