66 */
77
88import express , { Request , Response } from "express" ;
9+ import morgan from "morgan" ;
910import { z } from "zod" ;
1011import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
1112import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ;
12- import { Client , estypes , ClientOptions } from "@elastic/elasticsearch" ;
13+ import { Client , ClientOptions , estypes } from "@elastic/elasticsearch" ;
1314import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
1415import fs from "fs" ;
1516
@@ -73,13 +74,13 @@ const ConfigSchema = z
7374 message :
7475 "Either ES_API_KEY or both ES_USERNAME and ES_PASSWORD must be provided, or no auth for local development" ,
7576 path : [ "username" , "password" ] ,
76- }
77+ } ,
7778 ) ;
7879
7980type ElasticsearchConfig = z . infer < typeof ConfigSchema > ;
8081
8182export async function createElasticsearchMcpServer (
82- config : ElasticsearchConfig
83+ config : ElasticsearchConfig ,
8384) {
8485 const validatedConfig = ConfigSchema . parse ( config ) ;
8586 const { url, apiKey, username, password, caCert } = validatedConfig ;
@@ -104,7 +105,7 @@ export async function createElasticsearchMcpServer(
104105 console . error (
105106 `Failed to read certificate file: ${
106107 error instanceof Error ? error . message : String ( error )
107- } `
108+ } `,
108109 ) ;
109110 }
110111 }
@@ -148,7 +149,7 @@ export async function createElasticsearchMcpServer(
148149 console . error (
149150 `Failed to list indices: ${
150151 error instanceof Error ? error . message : String ( error )
151- } `
152+ } `,
152153 ) ;
153154 return {
154155 content : [
@@ -161,7 +162,7 @@ export async function createElasticsearchMcpServer(
161162 ] ,
162163 } ;
163164 }
164- }
165+ } ,
165166 ) ;
166167
167168 // Tool 2: Get mappings for an index
@@ -189,19 +190,21 @@ export async function createElasticsearchMcpServer(
189190 } ,
190191 {
191192 type : "text" as const ,
192- text : `Mappings for index ${ index } : ${ JSON . stringify (
193- mappingResponse [ index ] ?. mappings || { } ,
194- null ,
195- 2
196- ) } `,
193+ text : `Mappings for index ${ index } : ${
194+ JSON . stringify (
195+ mappingResponse [ index ] ?. mappings || { } ,
196+ null ,
197+ 2 ,
198+ )
199+ } `,
197200 } ,
198201 ] ,
199202 } ;
200203 } catch ( error ) {
201204 console . error (
202205 `Failed to get mappings: ${
203206 error instanceof Error ? error . message : String ( error )
204- } `
207+ } `,
205208 ) ;
206209 return {
207210 content : [
@@ -214,7 +217,7 @@ export async function createElasticsearchMcpServer(
214217 ] ,
215218 } ;
216219 }
217- }
220+ } ,
218221 ) ;
219222
220223 // Tool 3: Search an index with simplified parameters
@@ -241,10 +244,10 @@ export async function createElasticsearchMcpServer(
241244 } ,
242245 {
243246 message : "queryBody must be a valid Elasticsearch query DSL object" ,
244- }
247+ } ,
245248 )
246249 . describe (
247- "Complete Elasticsearch query DSL object that can include query, size, from, sort, etc."
250+ "Complete Elasticsearch query DSL object that can include query, size, from, sort, etc." ,
248251 ) ,
249252 } ,
250253 async ( { index, queryBody } ) => {
@@ -265,9 +268,11 @@ export async function createElasticsearchMcpServer(
265268 if ( indexMappings . properties ) {
266269 const textFields : Record < string , estypes . SearchHighlightField > = { } ;
267270
268- for ( const [ fieldName , fieldData ] of Object . entries (
269- indexMappings . properties
270- ) ) {
271+ for (
272+ const [ fieldName , fieldData ] of Object . entries (
273+ indexMappings . properties ,
274+ )
275+ ) {
271276 if ( fieldData . type === "text" || "dense_vector" in fieldData ) {
272277 textFields [ fieldName ] = { } ;
273278 }
@@ -293,9 +298,11 @@ export async function createElasticsearchMcpServer(
293298
294299 for ( const [ field , highlights ] of Object . entries ( highlightedFields ) ) {
295300 if ( highlights && highlights . length > 0 ) {
296- content += `${ field } (highlighted): ${ highlights . join (
297- " ... "
298- ) } \n`;
301+ content += `${ field } (highlighted): ${
302+ highlights . join (
303+ " ... " ,
304+ )
305+ } \n`;
299306 }
300307 }
301308
@@ -327,7 +334,7 @@ export async function createElasticsearchMcpServer(
327334 console . error (
328335 `Search failed: ${
329336 error instanceof Error ? error . message : String ( error )
330- } `
337+ } `,
331338 ) ;
332339 return {
333340 content : [
@@ -340,7 +347,7 @@ export async function createElasticsearchMcpServer(
340347 ] ,
341348 } ;
342349 }
343- }
350+ } ,
344351 ) ;
345352
346353 // Tool 4: Get shard information
@@ -391,7 +398,7 @@ export async function createElasticsearchMcpServer(
391398 console . error (
392399 `Failed to get shard information: ${
393400 error instanceof Error ? error . message : String ( error )
394- } `
401+ } `,
395402 ) ;
396403 return {
397404 content : [
@@ -404,7 +411,7 @@ export async function createElasticsearchMcpServer(
404411 ] ,
405412 } ;
406413 }
407- }
414+ } ,
408415 ) ;
409416
410417 return server ;
@@ -422,10 +429,13 @@ const config: ElasticsearchConfig = {
422429async function create_sse_server ( server : any ) {
423430 const app = express ( ) ;
424431
425- const transports : { [ sessionId : string ] : SSEServerTransport } = { } ;
432+ // Use morgan to log every request
433+ app . use ( morgan ( "combined" ) ) ;
434+
435+ const transports : { [ sessionId : string ] : SSEServerTransport } = { } ;
426436
427437 app . get ( "/sse" , async ( _ : Request , res : Response ) => {
428- const transport = new SSEServerTransport ( ' /messages' , res ) ;
438+ const transport = new SSEServerTransport ( " /messages" , res ) ;
429439 transports [ transport . sessionId ] = transport ;
430440 res . on ( "close" , ( ) => {
431441 delete transports [ transport . sessionId ] ;
@@ -439,12 +449,20 @@ async function create_sse_server(server: any) {
439449 if ( transport ) {
440450 await transport . handlePostMessage ( req , res ) ;
441451 } else {
442- res . status ( 400 ) . send ( ' No transport found for sessionId' ) ;
452+ res . status ( 400 ) . send ( " No transport found for sessionId" ) ;
443453 }
444454 } ) ;
445455
446- console . info ( "SSE server started on: " , process . env . SSE_ADDR ) ;
447- app . listen ( process . env . SSE_ADDR ) ;
456+ const sseAddr = process . env . SSE_ADDR || "127.0.0.1:3000" ;
457+ const [ host , port ] = sseAddr . split ( ":" ) ;
458+ if ( ! port ) {
459+ console . error ( "Invalid SSE_ADDR format. Expected 'host:port'." ) ;
460+ process . exit ( 1 ) ;
461+ }
462+
463+ app . listen ( Number ( port ) , host , ( ) => {
464+ console . info ( `SSE server started on: ${ host } :${ port } ` ) ;
465+ } ) ;
448466}
449467
450468async function main ( ) {
@@ -467,7 +485,7 @@ async function main() {
467485main ( ) . catch ( ( error ) => {
468486 console . error (
469487 "Server error:" ,
470- error instanceof Error ? error . message : String ( error )
488+ error instanceof Error ? error . message : String ( error ) ,
471489 ) ;
472490 process . exit ( 1 ) ;
473491} ) ;
0 commit comments