pms-analysis-reports-mcp-server
Version:
PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction
372 lines • 13.9 kB
JavaScript
import { MongoClient } from 'mongodb';
import { logger } from "../utils/logger.js";
import { BaseService } from "./base-service.js";
import { SanitizationMiddleware } from "../middleware/sanitization-middleware.js";
export class DatabaseService extends BaseService {
constructor(dbConfig) {
super(dbConfig);
this.dbConfig = dbConfig;
this.isConnected = false;
}
/**
* Connect to database
*/
async connect() {
return this.executeWithResilience(async () => {
if (this.isConnected && this.client) {
return true;
}
logger.info('Connecting to database', {
dbName: this.dbConfig.dbName,
uri: this.sanitizeUri(this.dbConfig.uri)
});
this.client = new MongoClient(this.dbConfig.uri, {
maxPoolSize: this.dbConfig.connectionOptions?.maxPoolSize || 10,
minPoolSize: this.dbConfig.connectionOptions?.minPoolSize || 2,
maxIdleTimeMS: this.dbConfig.connectionOptions?.maxIdleTimeMS || 30000,
serverSelectionTimeoutMS: this.dbConfig.connectionOptions?.serverSelectionTimeoutMS || 5000
});
await this.client.connect();
this.db = this.client.db(this.dbConfig.dbName);
this.isConnected = true;
logger.info('Database connected successfully', {
dbName: this.dbConfig.dbName
});
return true;
}, { toolName: 'database_connect' });
}
/**
* Disconnect from database
*/
async disconnect() {
return this.executeWithResilience(async () => {
if (this.client && this.isConnected) {
await this.client.close();
this.isConnected = false;
logger.info('Database disconnected');
}
return true;
}, { toolName: 'database_disconnect' });
}
/**
* Find documents in collection
*/
async find(collectionName, query, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedQuery = this.sanitizeQuery(query, options);
const findOptions = this.buildFindOptions(options);
logger.debug('Executing find query', {
collection: collectionName,
query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery),
options: SanitizationMiddleware.sanitizeForLogging(findOptions)
});
const cursor = collection.find(sanitizedQuery, findOptions);
const results = await cursor.toArray();
logger.debug('Find query completed', {
collection: collectionName,
resultCount: results.length
});
return results;
}, {
toolName: 'database_find',
arguments: { collection: collectionName, query }
});
}
/**
* Find one document in collection
*/
async findOne(collectionName, query, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedQuery = this.sanitizeQuery(query, options);
const findOptions = this.buildFindOptions(options);
logger.debug('Executing findOne query', {
collection: collectionName,
query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery)
});
const result = await collection.findOne(sanitizedQuery, findOptions);
logger.debug('FindOne query completed', {
collection: collectionName,
found: !!result
});
return result;
}, {
toolName: 'database_find_one',
arguments: { collection: collectionName, query }
});
}
/**
* Aggregate documents in collection
*/
async aggregate(collectionName, pipeline, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedPipeline = this.sanitizeAggregationPipeline(pipeline, options);
logger.debug('Executing aggregation', {
collection: collectionName,
pipelineLength: sanitizedPipeline.length,
pipeline: SanitizationMiddleware.sanitizeForLogging(sanitizedPipeline)
});
const cursor = collection.aggregate(sanitizedPipeline);
const results = await cursor.toArray();
logger.debug('Aggregation completed', {
collection: collectionName,
resultCount: results.length
});
return results;
}, {
toolName: 'database_aggregate',
arguments: { collection: collectionName, pipelineLength: pipeline.length }
});
}
/**
* Count documents in collection
*/
async count(collectionName, query = {}, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedQuery = this.sanitizeQuery(query, options);
logger.debug('Executing count query', {
collection: collectionName,
query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery)
});
const count = await collection.countDocuments(sanitizedQuery);
logger.debug('Count query completed', {
collection: collectionName,
count
});
return count;
}, {
toolName: 'database_count',
arguments: { collection: collectionName, query }
});
}
/**
* Insert document into collection
*/
async insertOne(collectionName, document) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedDocument = SanitizationMiddleware.sanitizeToolResponse(document);
logger.debug('Executing insertOne', {
collection: collectionName
});
const result = await collection.insertOne(sanitizedDocument);
logger.debug('InsertOne completed', {
collection: collectionName,
insertedId: result.insertedId.toString()
});
return result.insertedId.toString();
}, {
toolName: 'database_insert_one',
arguments: { collection: collectionName }
});
}
/**
* Update document in collection
*/
async updateOne(collectionName, filter, update, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedFilter = this.sanitizeQuery(filter, options);
const sanitizedUpdate = this.sanitizeUpdate(update);
logger.debug('Executing updateOne', {
collection: collectionName,
filter: SanitizationMiddleware.sanitizeForLogging(sanitizedFilter)
});
const result = await collection.updateOne(sanitizedFilter, sanitizedUpdate);
logger.debug('UpdateOne completed', {
collection: collectionName,
matchedCount: result.matchedCount,
modifiedCount: result.modifiedCount
});
return {
matchedCount: result.matchedCount,
modifiedCount: result.modifiedCount
};
}, {
toolName: 'database_update_one',
arguments: { collection: collectionName, filter }
});
}
/**
* Delete document from collection
*/
async deleteOne(collectionName, filter, options = {}) {
return this.executeWithResilience(async () => {
await this.ensureConnected();
const collection = this.getCollection(collectionName);
const sanitizedFilter = this.sanitizeQuery(filter, options);
logger.debug('Executing deleteOne', {
collection: collectionName,
filter: SanitizationMiddleware.sanitizeForLogging(sanitizedFilter)
});
const result = await collection.deleteOne(sanitizedFilter);
logger.debug('DeleteOne completed', {
collection: collectionName,
deletedCount: result.deletedCount
});
return result.deletedCount;
}, {
toolName: 'database_delete_one',
arguments: { collection: collectionName, filter }
});
}
/**
* Ensure database connection
*/
async ensureConnected() {
if (!this.isConnected) {
const result = await this.connect();
if (!result.success) {
throw new Error('Failed to connect to database');
}
}
}
/**
* Get collection instance
*/
getCollection(name) {
if (!this.db) {
throw new Error('Database not connected');
}
return this.db.collection(name);
}
/**
* Sanitize MongoDB query
*/
sanitizeQuery(query, options) {
if (options.sanitize === false) {
return query;
}
return SanitizationMiddleware.sanitizeMongoQuery(query, {
allowedFields: options.allowedFields,
maxStringLength: 1000,
allowObjectIds: true
});
}
/**
* Sanitize update operations
*/
sanitizeUpdate(update) {
// Ensure update operations are safe
const sanitized = SanitizationMiddleware.sanitizeMongoQuery(update, {
maxStringLength: 1000,
allowObjectIds: true
});
// Remove dangerous update operators
const dangerousOps = ['$where', '$eval', '$function'];
for (const op of dangerousOps) {
delete sanitized[op];
}
return sanitized;
}
/**
* Sanitize aggregation pipeline
*/
sanitizeAggregationPipeline(pipeline, options) {
if (options.sanitize === false) {
return pipeline;
}
const maxLength = options.maxPipelineLength || 10;
const allowedStages = options.allowedStages || [
'$match', '$group', '$sort', '$limit', '$skip', '$project', '$lookup', '$unwind'
];
if (pipeline.length > maxLength) {
throw new Error(`Aggregation pipeline too long (max ${maxLength} stages)`);
}
return pipeline.map((stage, index) => {
if (typeof stage !== 'object' || stage === null) {
throw new Error(`Invalid pipeline stage at index ${index}`);
}
const stageKeys = Object.keys(stage);
if (stageKeys.length !== 1) {
throw new Error(`Pipeline stage must have exactly one operator at index ${index}`);
}
const operator = stageKeys[0];
if (!allowedStages.includes(operator)) {
throw new Error(`Disallowed pipeline operator: ${operator}`);
}
return SanitizationMiddleware.sanitizeMongoQuery(stage, {
maxStringLength: 1000,
allowObjectIds: true
});
});
}
/**
* Build find options
*/
buildFindOptions(options) {
const findOptions = {};
if (options.limit !== undefined) {
findOptions.limit = Math.min(Math.max(1, options.limit), 1000); // Cap at 1000
}
if (options.skip !== undefined) {
findOptions.skip = Math.max(0, options.skip);
}
if (options.sort) {
findOptions.sort = options.sort;
}
return findOptions;
}
/**
* Sanitize URI for logging
*/
sanitizeUri(uri) {
return uri.replace(/\/\/[^@]*@/, '//***@');
}
/**
* Health check implementation
*/
async healthCheck() {
try {
await this.ensureConnected();
if (!this.db) {
return false;
}
// Test with a simple ping operation
await this.db.admin().ping();
return true;
}
catch (error) {
logger.error('Database health check failed', {
error: SanitizationMiddleware.sanitizeErrorMessage(error)
});
return false;
}
}
/**
* Get database statistics
*/
async getStats() {
return this.executeWithResilience(async () => {
await this.ensureConnected();
if (!this.db) {
throw new Error('Database not connected');
}
const stats = await this.db.stats();
return SanitizationMiddleware.sanitizeToolResponse(stats);
}, { toolName: 'database_stats' });
}
/**
* List collections
*/
async listCollections() {
return this.executeWithResilience(async () => {
await this.ensureConnected();
if (!this.db) {
throw new Error('Database not connected');
}
const collections = await this.db.listCollections().toArray();
return collections.map(col => col.name);
}, { toolName: 'database_list_collections' });
}
}
//# sourceMappingURL=database-service.js.map