elasticsearch-mcp
Version:
Secure MCP server for Elasticsearch integration with comprehensive tools and Elastic Cloud support
135 lines • 5.72 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InsertDataTool = void 0;
const schemas_js_1 = require("../validation/schemas.js");
const handlers_js_1 = require("../errors/handlers.js");
class InsertDataTool {
elasticsearch;
logger;
constructor(elasticsearch, logger) {
this.elasticsearch = elasticsearch;
this.logger = logger.child({ tool: 'insert-data' });
}
async execute(args) {
try {
const validatedArgs = schemas_js_1.InsertDataArgsSchema.parse(args);
this.logger.info('Inserting document', {
index: validatedArgs.index,
hasId: !!validatedArgs.id,
documentKeys: Object.keys(validatedArgs.document),
refresh: validatedArgs.refresh,
});
const client = this.elasticsearch.getClient();
// Validate document content
this.validateDocument(validatedArgs.document);
// Check if index exists
const indexExists = await client.indices.exists({
index: validatedArgs.index,
});
if (!indexExists) {
this.logger.warn('Index does not exist, it will be created automatically', {
index: validatedArgs.index,
});
}
// Prepare the request
const request = {
index: validatedArgs.index,
body: validatedArgs.document,
refresh: this.normalizeRefreshParameter(validatedArgs.refresh),
};
let response;
if (validatedArgs.id) {
// Use index API for specific ID
request.id = validatedArgs.id;
response = await client.index(request);
}
else {
// Use create API to auto-generate ID
response = await client.index(request);
}
this.logger.info('Successfully inserted 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 insert_data', {
details: error.message,
});
}
this.logger.error('Failed to insert document', {}, error);
throw new handlers_js_1.ElasticsearchError('Failed to insert document into Elasticsearch', error, { args });
}
}
validateDocument(document) {
// Check if document is empty
if (Object.keys(document).length === 0) {
throw new handlers_js_1.ValidationError('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`);
}
if (key.length > 256) {
throw new handlers_js_1.ValidationError(`Field name '${key}' exceeds maximum length of 256 characters`);
}
}
// Validate document size (rough estimate)
const documentSize = JSON.stringify(document).length;
if (documentSize > 100 * 1024 * 1024) { // 100MB limit
throw new handlers_js_1.ValidationError('Document size exceeds 100MB limit');
}
// Recursively validate nested objects
this.validateNestedObject(document, 0);
}
isValidDottedField(fieldName) {
// Check for valid dot notation (no consecutive dots, no leading/trailing dots)
return !/^\.|\.$|\.\./.test(fieldName);
}
validateNestedObject(obj, depth) {
const MAX_NESTING_DEPTH = 20;
if (depth > MAX_NESTING_DEPTH) {
throw new handlers_js_1.ValidationError(`Document nesting exceeds maximum depth of ${MAX_NESTING_DEPTH}`);
}
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
this.validateNestedObject(value, depth + 1);
}
// Check for extremely large arrays
if (Array.isArray(value) && value.length > 10000) {
throw new handlers_js_1.ValidationError(`Array field '${key}' contains too many elements (max 10,000)`);
}
// Check for very long strings
if (typeof value === 'string' && value.length > 1024 * 1024) { // 1MB
throw new handlers_js_1.ValidationError(`String field '${key}' exceeds maximum length of 1MB`);
}
}
}
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.InsertDataTool = InsertDataTool;
//# sourceMappingURL=insert-data.js.map