6
6
*/
7
7
8
8
import express , { Request , Response } from "express" ;
9
+ import morgan from "morgan" ;
9
10
import { z } from "zod" ;
10
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
11
12
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ;
12
- import { Client , estypes , ClientOptions } from "@elastic/elasticsearch" ;
13
+ import { Client , ClientOptions , estypes } from "@elastic/elasticsearch" ;
13
14
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
14
15
import fs from "fs" ;
15
16
@@ -73,13 +74,13 @@ const ConfigSchema = z
73
74
message :
74
75
"Either ES_API_KEY or both ES_USERNAME and ES_PASSWORD must be provided, or no auth for local development" ,
75
76
path : [ "username" , "password" ] ,
76
- }
77
+ } ,
77
78
) ;
78
79
79
80
type ElasticsearchConfig = z . infer < typeof ConfigSchema > ;
80
81
81
82
export async function createElasticsearchMcpServer (
82
- config : ElasticsearchConfig
83
+ config : ElasticsearchConfig ,
83
84
) {
84
85
const validatedConfig = ConfigSchema . parse ( config ) ;
85
86
const { url, apiKey, username, password, caCert } = validatedConfig ;
@@ -104,7 +105,7 @@ export async function createElasticsearchMcpServer(
104
105
console . error (
105
106
`Failed to read certificate file: ${
106
107
error instanceof Error ? error . message : String ( error )
107
- } `
108
+ } `,
108
109
) ;
109
110
}
110
111
}
@@ -148,7 +149,7 @@ export async function createElasticsearchMcpServer(
148
149
console . error (
149
150
`Failed to list indices: ${
150
151
error instanceof Error ? error . message : String ( error )
151
- } `
152
+ } `,
152
153
) ;
153
154
return {
154
155
content : [
@@ -161,7 +162,7 @@ export async function createElasticsearchMcpServer(
161
162
] ,
162
163
} ;
163
164
}
164
- }
165
+ } ,
165
166
) ;
166
167
167
168
// Tool 2: Get mappings for an index
@@ -189,19 +190,21 @@ export async function createElasticsearchMcpServer(
189
190
} ,
190
191
{
191
192
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
+ } `,
197
200
} ,
198
201
] ,
199
202
} ;
200
203
} catch ( error ) {
201
204
console . error (
202
205
`Failed to get mappings: ${
203
206
error instanceof Error ? error . message : String ( error )
204
- } `
207
+ } `,
205
208
) ;
206
209
return {
207
210
content : [
@@ -214,7 +217,7 @@ export async function createElasticsearchMcpServer(
214
217
] ,
215
218
} ;
216
219
}
217
- }
220
+ } ,
218
221
) ;
219
222
220
223
// Tool 3: Search an index with simplified parameters
@@ -241,10 +244,10 @@ export async function createElasticsearchMcpServer(
241
244
} ,
242
245
{
243
246
message : "queryBody must be a valid Elasticsearch query DSL object" ,
244
- }
247
+ } ,
245
248
)
246
249
. 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." ,
248
251
) ,
249
252
} ,
250
253
async ( { index, queryBody } ) => {
@@ -265,9 +268,11 @@ export async function createElasticsearchMcpServer(
265
268
if ( indexMappings . properties ) {
266
269
const textFields : Record < string , estypes . SearchHighlightField > = { } ;
267
270
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
+ ) {
271
276
if ( fieldData . type === "text" || "dense_vector" in fieldData ) {
272
277
textFields [ fieldName ] = { } ;
273
278
}
@@ -293,9 +298,11 @@ export async function createElasticsearchMcpServer(
293
298
294
299
for ( const [ field , highlights ] of Object . entries ( highlightedFields ) ) {
295
300
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`;
299
306
}
300
307
}
301
308
@@ -327,7 +334,7 @@ export async function createElasticsearchMcpServer(
327
334
console . error (
328
335
`Search failed: ${
329
336
error instanceof Error ? error . message : String ( error )
330
- } `
337
+ } `,
331
338
) ;
332
339
return {
333
340
content : [
@@ -340,7 +347,7 @@ export async function createElasticsearchMcpServer(
340
347
] ,
341
348
} ;
342
349
}
343
- }
350
+ } ,
344
351
) ;
345
352
346
353
// Tool 4: Get shard information
@@ -391,7 +398,7 @@ export async function createElasticsearchMcpServer(
391
398
console . error (
392
399
`Failed to get shard information: ${
393
400
error instanceof Error ? error . message : String ( error )
394
- } `
401
+ } `,
395
402
) ;
396
403
return {
397
404
content : [
@@ -404,7 +411,7 @@ export async function createElasticsearchMcpServer(
404
411
] ,
405
412
} ;
406
413
}
407
- }
414
+ } ,
408
415
) ;
409
416
410
417
return server ;
@@ -422,10 +429,13 @@ const config: ElasticsearchConfig = {
422
429
async function create_sse_server ( server : any ) {
423
430
const app = express ( ) ;
424
431
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 } = { } ;
426
436
427
437
app . get ( "/sse" , async ( _ : Request , res : Response ) => {
428
- const transport = new SSEServerTransport ( ' /messages' , res ) ;
438
+ const transport = new SSEServerTransport ( " /messages" , res ) ;
429
439
transports [ transport . sessionId ] = transport ;
430
440
res . on ( "close" , ( ) => {
431
441
delete transports [ transport . sessionId ] ;
@@ -439,12 +449,20 @@ async function create_sse_server(server: any) {
439
449
if ( transport ) {
440
450
await transport . handlePostMessage ( req , res ) ;
441
451
} else {
442
- res . status ( 400 ) . send ( ' No transport found for sessionId' ) ;
452
+ res . status ( 400 ) . send ( " No transport found for sessionId" ) ;
443
453
}
444
454
} ) ;
445
455
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
+ } ) ;
448
466
}
449
467
450
468
async function main ( ) {
@@ -467,7 +485,7 @@ async function main() {
467
485
main ( ) . catch ( ( error ) => {
468
486
console . error (
469
487
"Server error:" ,
470
- error instanceof Error ? error . message : String ( error )
488
+ error instanceof Error ? error . message : String ( error ) ,
471
489
) ;
472
490
process . exit ( 1 ) ;
473
491
} ) ;
0 commit comments