search
Query Elasticsearch using DSL to retrieve, highlight, and script fields for specified indices, enabling precise data extraction and analysis.
Instructions
Perform an Elasticsearch search with the provided query DSL, highlighting, and script fields
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| index | Yes | Name of the Elasticsearch index to search | |
| queryBody | Yes | Complete Elasticsearch query DSL object (can include query, size, from, sort, etc.) | |
| scriptFields | No | Script fields to evaluate and include in the response |
Implementation Reference
- src/index.ts:230-323 (handler)The main handler function for the 'search' MCP tool. It enhances the query body with script_fields if provided, performs the search using ElasticsearchService, processes the results including metadata, aggregations, highlights, script fields, and source data, and formats them into MCP-compliant text content fragments.async ({ index, queryBody, scriptFields }, extra) => { try { // Add script_fields to the query body if provided const enhancedQueryBody = { ...queryBody }; if (scriptFields && Object.keys(scriptFields).length > 0) { enhancedQueryBody.script_fields = scriptFields; } const result = await esService.search(index, enhancedQueryBody); // Extract the 'from' parameter from queryBody, defaulting to 0 if not provided const from = queryBody.from ?? 0; const contentFragments: TextContent[] = []; // Add metadata about the search results contentFragments.push({ type: "text", text: `Total results: ${ typeof result.hits.total === "number" ? result.hits.total : result.hits.total?.value ?? 0 }, showing ${result.hits.hits.length} from position ${from}`, }); // Add aggregation results if present if (result.aggregations) { contentFragments.push({ type: "text", text: `Aggregations: ${JSON.stringify( result.aggregations, null, 2 )}`, }); } // Process and add individual hit results result.hits.hits.forEach((hit: any) => { const highlightedFields = hit.highlight ?? {}; const sourceData = hit._source ?? {}; const scriptFieldsData = hit.fields ?? {}; let content = `Document ID: ${hit._id}\nScore: ${hit._score}\n\n`; // Add script fields results for (const [field, value] of Object.entries(scriptFieldsData)) { content += `${field} (script): ${JSON.stringify(value)}\n`; } // Add highlighted fields for (const [field, highlights] of Object.entries(highlightedFields)) { if (Array.isArray(highlights) && highlights.length > 0) { content += `${field} (highlighted): ${( highlights as string[] ).join(" ... ")}\n`; } } // Add source fields that weren't highlighted for (const [field, value] of Object.entries(sourceData)) { if (!(field in highlightedFields)) { content += `${field}: ${JSON.stringify(value)}\n`; } } contentFragments.push({ type: "text", text: content.trim(), }); }); const response: ResponseContent = { content: contentFragments, }; return response; } catch (error) { console.error( `Search failed: ${ error instanceof Error ? error.message : String(error) }` ); return { content: [ { type: "text", text: `Error: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } }
- src/index.ts:195-229 (schema)Zod schema defining the input parameters for the 'search' tool: required 'index' string, 'queryBody' as arbitrary record for Elasticsearch DSL, and optional 'scriptFields' for computed fields with script definitions.index: z .string() .trim() .min(1, "Index name is required") .describe("Name of the Elasticsearch index to search"), queryBody: z .record(z.any()) .describe( "Complete Elasticsearch query DSL object (can include query, size, from, sort, etc.)" ), scriptFields: z .record( z.object({ script: z.object({ source: z .string() .min(1, "Script source is required") .describe("Painless script source code"), params: z .record(z.any()) .optional() .describe("Optional parameters for the script"), lang: z .string() .optional() .default("painless") .describe("Script language (defaults to painless)"), }) }) ) .optional() .describe("Script fields to evaluate and include in the response"), },
- src/index.ts:191-324 (registration)Registration of the 'search' tool on the MCP server using server.tool(), including name, description, input schema, and handler function.server.tool( "search", "Perform an Elasticsearch search with the provided query DSL, highlighting, and script fields", { index: z .string() .trim() .min(1, "Index name is required") .describe("Name of the Elasticsearch index to search"), queryBody: z .record(z.any()) .describe( "Complete Elasticsearch query DSL object (can include query, size, from, sort, etc.)" ), scriptFields: z .record( z.object({ script: z.object({ source: z .string() .min(1, "Script source is required") .describe("Painless script source code"), params: z .record(z.any()) .optional() .describe("Optional parameters for the script"), lang: z .string() .optional() .default("painless") .describe("Script language (defaults to painless)"), }) }) ) .optional() .describe("Script fields to evaluate and include in the response"), }, async ({ index, queryBody, scriptFields }, extra) => { try { // Add script_fields to the query body if provided const enhancedQueryBody = { ...queryBody }; if (scriptFields && Object.keys(scriptFields).length > 0) { enhancedQueryBody.script_fields = scriptFields; } const result = await esService.search(index, enhancedQueryBody); // Extract the 'from' parameter from queryBody, defaulting to 0 if not provided const from = queryBody.from ?? 0; const contentFragments: TextContent[] = []; // Add metadata about the search results contentFragments.push({ type: "text", text: `Total results: ${ typeof result.hits.total === "number" ? result.hits.total : result.hits.total?.value ?? 0 }, showing ${result.hits.hits.length} from position ${from}`, }); // Add aggregation results if present if (result.aggregations) { contentFragments.push({ type: "text", text: `Aggregations: ${JSON.stringify( result.aggregations, null, 2 )}`, }); } // Process and add individual hit results result.hits.hits.forEach((hit: any) => { const highlightedFields = hit.highlight ?? {}; const sourceData = hit._source ?? {}; const scriptFieldsData = hit.fields ?? {}; let content = `Document ID: ${hit._id}\nScore: ${hit._score}\n\n`; // Add script fields results for (const [field, value] of Object.entries(scriptFieldsData)) { content += `${field} (script): ${JSON.stringify(value)}\n`; } // Add highlighted fields for (const [field, highlights] of Object.entries(highlightedFields)) { if (Array.isArray(highlights) && highlights.length > 0) { content += `${field} (highlighted): ${( highlights as string[] ).join(" ... ")}\n`; } } // Add source fields that weren't highlighted for (const [field, value] of Object.entries(sourceData)) { if (!(field in highlightedFields)) { content += `${field}: ${JSON.stringify(value)}\n`; } } contentFragments.push({ type: "text", text: content.trim(), }); }); const response: ResponseContent = { content: contentFragments, }; return response; } catch (error) { console.error( `Search failed: ${ error instanceof Error ? error.message : String(error) }` ); return { content: [ { type: "text", text: `Error: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } );
- Supporting helper method in ElasticsearchService class that wraps the Elasticsearch client's search method, spreading the queryBody into the search parameters.async search(index: string, queryBody: any): Promise<any> { return await this.client.search({ index, ...queryBody, }); }