Skip to content

Commit 4666b69

Browse files
committed
add support for SSE
1 parent 68503db commit 4666b69

File tree

4 files changed

+315
-187
lines changed

4 files changed

+315
-187
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ The Elasticsearch MCP Server supports configuration options to connect to your E
7070

7171
| Environment Variable | Description | Required |
7272
|---------------------|-------------|----------|
73+
| `SSE_ADDR` | Enable SSE and set the ADDR | No |
7374
| `ES_URL` | Your Elasticsearch instance URL | Yes |
7475
| `ES_API_KEY` | Elasticsearch API key for authentication | No |
7576
| `ES_USERNAME` | Elasticsearch username for basic authentication | No |

index.ts

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@
55
* SPDX-License-Identifier: Apache-2.0
66
*/
77

8+
import express, { Request, Response } from "express";
9+
import morgan from "morgan";
810
import { z } from "zod";
911
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10-
import { Client, estypes, ClientOptions } from "@elastic/elasticsearch";
12+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
13+
import { Client, ClientOptions, estypes } from "@elastic/elasticsearch";
1114
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1215
import fs from "fs";
1316

1417
// Configuration schema with auth options
1518
const ConfigSchema = z
1619
.object({
20+
sse_addr: z
21+
.string()
22+
.trim()
23+
.optional()
24+
.describe("Address for SSE server (optional)"),
25+
1726
url: z
1827
.string()
1928
.trim()
@@ -65,13 +74,13 @@ const ConfigSchema = z
6574
message:
6675
"Either ES_API_KEY or both ES_USERNAME and ES_PASSWORD must be provided, or no auth for local development",
6776
path: ["username", "password"],
68-
}
77+
},
6978
);
7079

7180
type ElasticsearchConfig = z.infer<typeof ConfigSchema>;
7281

7382
export async function createElasticsearchMcpServer(
74-
config: ElasticsearchConfig
83+
config: ElasticsearchConfig,
7584
) {
7685
const validatedConfig = ConfigSchema.parse(config);
7786
const { url, apiKey, username, password, caCert } = validatedConfig;
@@ -96,7 +105,7 @@ export async function createElasticsearchMcpServer(
96105
console.error(
97106
`Failed to read certificate file: ${
98107
error instanceof Error ? error.message : String(error)
99-
}`
108+
}`,
100109
);
101110
}
102111
}
@@ -140,7 +149,7 @@ export async function createElasticsearchMcpServer(
140149
console.error(
141150
`Failed to list indices: ${
142151
error instanceof Error ? error.message : String(error)
143-
}`
152+
}`,
144153
);
145154
return {
146155
content: [
@@ -153,7 +162,7 @@ export async function createElasticsearchMcpServer(
153162
],
154163
};
155164
}
156-
}
165+
},
157166
);
158167

159168
// Tool 2: Get mappings for an index
@@ -181,19 +190,21 @@ export async function createElasticsearchMcpServer(
181190
},
182191
{
183192
type: "text" as const,
184-
text: `Mappings for index ${index}: ${JSON.stringify(
185-
mappingResponse[index]?.mappings || {},
186-
null,
187-
2
188-
)}`,
193+
text: `Mappings for index ${index}: ${
194+
JSON.stringify(
195+
mappingResponse[index]?.mappings || {},
196+
null,
197+
2,
198+
)
199+
}`,
189200
},
190201
],
191202
};
192203
} catch (error) {
193204
console.error(
194205
`Failed to get mappings: ${
195206
error instanceof Error ? error.message : String(error)
196-
}`
207+
}`,
197208
);
198209
return {
199210
content: [
@@ -206,7 +217,7 @@ export async function createElasticsearchMcpServer(
206217
],
207218
};
208219
}
209-
}
220+
},
210221
);
211222

212223
// Tool 3: Search an index with simplified parameters
@@ -233,10 +244,10 @@ export async function createElasticsearchMcpServer(
233244
},
234245
{
235246
message: "queryBody must be a valid Elasticsearch query DSL object",
236-
}
247+
},
237248
)
238249
.describe(
239-
"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.",
240251
),
241252
},
242253
async ({ index, queryBody }) => {
@@ -257,9 +268,11 @@ export async function createElasticsearchMcpServer(
257268
if (indexMappings.properties) {
258269
const textFields: Record<string, estypes.SearchHighlightField> = {};
259270

260-
for (const [fieldName, fieldData] of Object.entries(
261-
indexMappings.properties
262-
)) {
271+
for (
272+
const [fieldName, fieldData] of Object.entries(
273+
indexMappings.properties,
274+
)
275+
) {
263276
if (fieldData.type === "text" || "dense_vector" in fieldData) {
264277
textFields[fieldName] = {};
265278
}
@@ -285,9 +298,11 @@ export async function createElasticsearchMcpServer(
285298

286299
for (const [field, highlights] of Object.entries(highlightedFields)) {
287300
if (highlights && highlights.length > 0) {
288-
content += `${field} (highlighted): ${highlights.join(
289-
" ... "
290-
)}\n`;
301+
content += `${field} (highlighted): ${
302+
highlights.join(
303+
" ... ",
304+
)
305+
}\n`;
291306
}
292307
}
293308

@@ -319,7 +334,7 @@ export async function createElasticsearchMcpServer(
319334
console.error(
320335
`Search failed: ${
321336
error instanceof Error ? error.message : String(error)
322-
}`
337+
}`,
323338
);
324339
return {
325340
content: [
@@ -332,7 +347,7 @@ export async function createElasticsearchMcpServer(
332347
],
333348
};
334349
}
335-
}
350+
},
336351
);
337352

338353
// Tool 4: Get shard information
@@ -383,7 +398,7 @@ export async function createElasticsearchMcpServer(
383398
console.error(
384399
`Failed to get shard information: ${
385400
error instanceof Error ? error.message : String(error)
386-
}`
401+
}`,
387402
);
388403
return {
389404
content: [
@@ -396,36 +411,81 @@ export async function createElasticsearchMcpServer(
396411
],
397412
};
398413
}
399-
}
414+
},
400415
);
401416

402417
return server;
403418
}
404419

405420
const config: ElasticsearchConfig = {
421+
sse_addr: process.env.SSE_ADDR || "",
406422
url: process.env.ES_URL || "",
407423
apiKey: process.env.ES_API_KEY || "",
408424
username: process.env.ES_USERNAME || "",
409425
password: process.env.ES_PASSWORD || "",
410426
caCert: process.env.ES_CA_CERT || "",
411427
};
412428

429+
async function create_sse_server(server: any) {
430+
const app = express();
431+
432+
// Use morgan to log every request
433+
app.use(morgan("combined"));
434+
435+
const transports: { [sessionId: string]: SSEServerTransport } = {};
436+
437+
app.get("/sse", async (_: Request, res: Response) => {
438+
const transport = new SSEServerTransport("/messages", res);
439+
transports[transport.sessionId] = transport;
440+
res.on("close", () => {
441+
delete transports[transport.sessionId];
442+
});
443+
await server.connect(transport);
444+
});
445+
446+
app.post("/messages", async (req: Request, res: Response) => {
447+
const sessionId = req.query.sessionId as string;
448+
const transport = transports[sessionId];
449+
if (transport) {
450+
await transport.handlePostMessage(req, res);
451+
} else {
452+
res.status(400).send("No transport found for sessionId");
453+
}
454+
});
455+
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+
});
466+
}
467+
413468
async function main() {
414-
const transport = new StdioServerTransport();
469+
console.info("Starting Elasticsearch MCP server...");
415470
const server = await createElasticsearchMcpServer(config);
416471

417-
await server.connect(transport);
472+
if (process.env.SSE_ADDR) {
473+
await create_sse_server(server);
474+
} else {
475+
const transport = new StdioServerTransport();
476+
await server.connect(transport);
418477

419-
process.on("SIGINT", async () => {
420-
await server.close();
421-
process.exit(0);
422-
});
478+
process.on("SIGINT", async () => {
479+
await server.close();
480+
process.exit(0);
481+
});
482+
}
423483
}
424484

425485
main().catch((error) => {
426486
console.error(
427487
"Server error:",
428-
error instanceof Error ? error.message : String(error)
488+
error instanceof Error ? error.message : String(error),
429489
);
430490
process.exit(1);
431491
});

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@
3838
},
3939
"dependencies": {
4040
"@elastic/elasticsearch": "^8.17.1",
41-
"@modelcontextprotocol/sdk": "1.9.0"
41+
"@modelcontextprotocol/sdk": "1.9.0",
42+
"@types/express": "^5.0.1",
43+
"@types/morgan": "^1.9.9",
44+
"express": "^5.1.0",
45+
"morgan": "^1.10.0"
4246
},
4347
"engines": {
4448
"node": ">=18"

0 commit comments

Comments
 (0)