UNPKG

elasticsearch-mcp

Version:

Secure MCP server for Elasticsearch integration with comprehensive tools and Elastic Cloud support

163 lines 7.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UpdateDocumentTool = void 0; const schemas_js_1 = require("../validation/schemas.js"); const handlers_js_1 = require("../errors/handlers.js"); class UpdateDocumentTool { elasticsearch; logger; constructor(elasticsearch, logger) { this.elasticsearch = elasticsearch; this.logger = logger.child({ tool: 'update-document' }); } async execute(args) { try { const validatedArgs = schemas_js_1.UpdateDocumentArgsSchema.parse(args); this.logger.info('Updating document', { index: validatedArgs.index, id: validatedArgs.id, hasDocument: !!validatedArgs.document, hasScript: !!validatedArgs.script, upsert: validatedArgs.upsert, refresh: validatedArgs.refresh, }); const client = this.elasticsearch.getClient(); // Check if index exists const indexExists = await client.indices.exists({ index: validatedArgs.index, }); if (!indexExists) { throw new handlers_js_1.NotFoundError(`Index '${validatedArgs.index}' does not exist`); } // Prepare update body const updateBody = {}; if (validatedArgs.document) { this.validateDocument(validatedArgs.document); updateBody.doc = validatedArgs.document; if (validatedArgs.upsert) { updateBody.doc_as_upsert = true; } } if (validatedArgs.script) { updateBody.script = this.validateAndPrepareScript(validatedArgs.script); if (validatedArgs.upsert && validatedArgs.document) { updateBody.upsert = validatedArgs.document; } } // Execute update const response = await client.update({ index: validatedArgs.index, id: validatedArgs.id, body: updateBody, refresh: this.normalizeRefreshParameter(validatedArgs.refresh), retry_on_conflict: 3, // Retry on version conflicts }); this.logger.info('Successfully updated document', { id: response._id, index: response._index, version: response._version, result: response.result, }); return { _id: response._id, _index: response._index, _version: response._version, result: response.result, }; } catch (error) { if (error instanceof Error && error.name === 'ZodError') { throw new handlers_js_1.ValidationError('Invalid arguments for update_document', { details: error.message, }); } // Handle Elasticsearch specific errors if (error instanceof Error) { const errorMessage = error.message.toLowerCase(); if (errorMessage.includes('document_missing_exception') || errorMessage.includes('not_found')) { throw new handlers_js_1.NotFoundError(`Document with ID '${args?.id}' not found in index '${args?.index}'`); } if (errorMessage.includes('version_conflict')) { throw new handlers_js_1.ValidationError('Document was modified by another process, please retry'); } } this.logger.error('Failed to update document', {}, error); throw new handlers_js_1.ElasticsearchError('Failed to update document in Elasticsearch', error, { args }); } } validateDocument(document) { // Check if document is empty if (Object.keys(document).length === 0) { throw new handlers_js_1.ValidationError('Update document cannot be empty'); } // Check for invalid field names for (const key of Object.keys(document)) { if (key.startsWith('_')) { throw new handlers_js_1.ValidationError(`Field name '${key}' cannot start with underscore (reserved)`); } if (key.includes('.') && !this.isValidDottedField(key)) { throw new handlers_js_1.ValidationError(`Invalid field name '${key}': improper dot notation`); } } // Validate document size const documentSize = JSON.stringify(document).length; if (documentSize > 100 * 1024 * 1024) { // 100MB limit throw new handlers_js_1.ValidationError('Document size exceeds 100MB limit'); } } validateAndPrepareScript(script) { if (!script.source || script.source.trim().length === 0) { throw new handlers_js_1.ValidationError('Script source cannot be empty'); } // Sanitize script source for security const sanitizedSource = (0, schemas_js_1.sanitizeScriptSource)(script.source); // Validate script parameters if (script.params) { this.validateScriptParams(script.params); } return { source: sanitizedSource, ...(script.params && { params: script.params }), lang: 'painless', // Default to Painless scripting language }; } validateScriptParams(params) { const maxParams = 50; const paramKeys = Object.keys(params); if (paramKeys.length > maxParams) { throw new handlers_js_1.ValidationError(`Too many script parameters (max ${maxParams})`); } for (const [key, value] of Object.entries(params)) { // Check parameter name if (key.length > 128) { throw new handlers_js_1.ValidationError(`Parameter name '${key}' too long (max 128 characters)`); } if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new handlers_js_1.ValidationError(`Invalid parameter name '${key}': must be a valid identifier`); } // Check parameter value size const valueSize = JSON.stringify(value).length; if (valueSize > 1024 * 1024) { // 1MB limit per parameter throw new handlers_js_1.ValidationError(`Parameter '${key}' value exceeds 1MB limit`); } } } isValidDottedField(fieldName) { // Check for valid dot notation (no consecutive dots, no leading/trailing dots) return !/^\.|\.$|\.\./.test(fieldName); } normalizeRefreshParameter(refresh) { if (refresh === undefined || refresh === false || refresh === 'false') { return false; } if (refresh === true || refresh === 'true') { return true; } if (refresh === 'wait_for') { return 'wait_for'; } return false; } } exports.UpdateDocumentTool = UpdateDocumentTool; //# sourceMappingURL=update-document.js.map