es6-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/es6-mcp-server +2 -0
- package/bin/es6-mcp-server.js +2 -0
- package/index.js +212 -0
- package/index.mjs +212 -0
- package/package.json +26 -0
package/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* @op/es6-mcp-server
|
|
4
|
+
*
|
|
5
|
+
* ES 6.x-compatible MCP server for Operative Elasticsearch clusters.
|
|
6
|
+
*
|
|
7
|
+
* Root cause: @elastic/elasticsearch v9 hardcodes `productCheck: 'Elasticsearch'`
|
|
8
|
+
* in the Client constructor, triggering the @elastic/transport ProductCheck
|
|
9
|
+
* middleware. ES 6.x clusters never send the required `X-Elastic-Product:
|
|
10
|
+
* Elasticsearch` response header (introduced in ES 7.14), so every 2xx
|
|
11
|
+
* response throws ProductNotSupportedError.
|
|
12
|
+
*
|
|
13
|
+
* Fix: subclass Transport to override productCheck: null before the middleware
|
|
14
|
+
* registers it, and override vendoredHeaders to plain application/json
|
|
15
|
+
* (ES 6.x rejects `application/vnd.elasticsearch+json; compatible-with=9`
|
|
16
|
+
* with HTTP 406).
|
|
17
|
+
*
|
|
18
|
+
* Tools exposed: list_indices, get_mappings, search, get_shards
|
|
19
|
+
* (same interface as @elastic/mcp-server-elasticsearch v0.3.1)
|
|
20
|
+
*
|
|
21
|
+
* Environment variables:
|
|
22
|
+
* ES_URL — Elasticsearch endpoint (required)
|
|
23
|
+
* ES_API_KEY — API key auth (optional)
|
|
24
|
+
* ES_USERNAME — Basic auth username (optional)
|
|
25
|
+
* ES_PASSWORD — Basic auth password (optional)
|
|
26
|
+
* ES_PATH_PREFIX — URL path prefix (optional)
|
|
27
|
+
* ES_SSL_SKIP_VERIFY — Skip TLS verification, '1' or 'true' (optional)
|
|
28
|
+
*/
|
|
29
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
30
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
31
|
+
import { Client, Transport } from '@elastic/elasticsearch'
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
|
|
34
|
+
class CompatTransport extends Transport {
|
|
35
|
+
constructor (opts) {
|
|
36
|
+
super({
|
|
37
|
+
...opts,
|
|
38
|
+
productCheck: null,
|
|
39
|
+
vendoredHeaders: {
|
|
40
|
+
jsonContentType: 'application/json',
|
|
41
|
+
ndjsonContentType: 'application/x-ndjson',
|
|
42
|
+
accept: 'application/json,text/plain'
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const url = process.env.ES_URL ?? ''
|
|
49
|
+
const apiKey = process.env.ES_API_KEY
|
|
50
|
+
const username = process.env.ES_USERNAME
|
|
51
|
+
const password = process.env.ES_PASSWORD
|
|
52
|
+
const sslSkipVerify = process.env.ES_SSL_SKIP_VERIFY === '1' || process.env.ES_SSL_SKIP_VERIFY === 'true'
|
|
53
|
+
const pathPrefix = process.env.ES_PATH_PREFIX
|
|
54
|
+
|
|
55
|
+
const clientOpts = {
|
|
56
|
+
node: url,
|
|
57
|
+
Transport: CompatTransport,
|
|
58
|
+
tls: sslSkipVerify ? { rejectUnauthorized: false } : {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
clientOpts.auth = { apiKey }
|
|
63
|
+
} else if (username && password) {
|
|
64
|
+
clientOpts.auth = { username, password }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (pathPrefix) {
|
|
68
|
+
const prefix = pathPrefix
|
|
69
|
+
clientOpts.Transport = class extends CompatTransport {
|
|
70
|
+
async request (params, options) {
|
|
71
|
+
return super.request({ ...params, path: prefix + params.path }, options)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const esClient = new Client(clientOpts)
|
|
77
|
+
const server = new McpServer({ name: 'elasticsearch-mcp-compat', version: '1.0.0' })
|
|
78
|
+
|
|
79
|
+
server.tool(
|
|
80
|
+
'list_indices',
|
|
81
|
+
'List all available Elasticsearch indices',
|
|
82
|
+
{ indexPattern: z.string().trim().min(1).describe('Index pattern of Elasticsearch indices to list') },
|
|
83
|
+
async ({ indexPattern }) => {
|
|
84
|
+
try {
|
|
85
|
+
const response = await esClient.cat.indices({ index: indexPattern, format: 'json' })
|
|
86
|
+
const info = response.map(i => ({
|
|
87
|
+
index: i.index,
|
|
88
|
+
health: i.health,
|
|
89
|
+
status: i.status,
|
|
90
|
+
docsCount: i.docsCount ?? i['docs.count']
|
|
91
|
+
}))
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{ type: 'text', text: `Found ${info.length} indices` },
|
|
95
|
+
{ type: 'text', text: JSON.stringify(info, null, 2) }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
server.tool(
|
|
105
|
+
'get_mappings',
|
|
106
|
+
'Get field mappings for a specific Elasticsearch index',
|
|
107
|
+
{ index: z.string().trim().min(1).describe('Name of the Elasticsearch index to get mappings for') },
|
|
108
|
+
async ({ index }) => {
|
|
109
|
+
try {
|
|
110
|
+
const r = await esClient.indices.getMapping({ index })
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{ type: 'text', text: `Mappings for index: ${index}` },
|
|
114
|
+
{ type: 'text', text: `Mappings for index ${index}: ${JSON.stringify(r[index]?.mappings ?? {}, null, 2)}` }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
server.tool(
|
|
124
|
+
'search',
|
|
125
|
+
'Perform an Elasticsearch search with the provided query DSL. Highlights are always enabled.',
|
|
126
|
+
{
|
|
127
|
+
index: z.string().trim().min(1).describe('Name of the Elasticsearch index to search'),
|
|
128
|
+
queryBody: z.record(z.any()).describe('Complete Elasticsearch query DSL object that can include query, size, from, sort, etc.'),
|
|
129
|
+
profile: z.boolean().optional().default(false).describe('Whether to include query profiling information'),
|
|
130
|
+
explain: z.boolean().optional().default(false).describe('Whether to include explanation of how the query was executed')
|
|
131
|
+
},
|
|
132
|
+
async ({ index, queryBody, profile, explain }) => {
|
|
133
|
+
try {
|
|
134
|
+
const mappingResponse = await esClient.indices.getMapping({ index })
|
|
135
|
+
const indexMappings = mappingResponse[index]?.mappings ?? {}
|
|
136
|
+
const searchRequest = { index, ...queryBody, profile, explain }
|
|
137
|
+
|
|
138
|
+
if (indexMappings.properties != null) {
|
|
139
|
+
const textFields = {}
|
|
140
|
+
for (const [fieldName, fieldData] of Object.entries(indexMappings.properties)) {
|
|
141
|
+
if (fieldData.type === 'text' || 'dense_vector' in fieldData) {
|
|
142
|
+
textFields[fieldName] = {}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
searchRequest.highlight = { fields: textFields, pre_tags: ['<em>'], post_tags: ['</em>'] }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await esClient.search(searchRequest)
|
|
149
|
+
const from = queryBody.from ?? 0
|
|
150
|
+
const total = typeof result.hits.total === 'number' ? result.hits.total : (result.hits.total?.value ?? 0)
|
|
151
|
+
const metaFragment = { type: 'text', text: `Total results: ${total}, showing ${result.hits.hits.length} from position ${from}` }
|
|
152
|
+
|
|
153
|
+
const contentFragments = result.hits.hits.map(hit => {
|
|
154
|
+
const highlighted = hit.highlight ?? {}
|
|
155
|
+
const source = hit._source ?? {}
|
|
156
|
+
let content = ''
|
|
157
|
+
for (const [field, fragments] of Object.entries(highlighted)) {
|
|
158
|
+
if (fragments?.length > 0) content += `${field} (highlighted): ${fragments.join(' ... ')}\n`
|
|
159
|
+
}
|
|
160
|
+
for (const [field, value] of Object.entries(source)) {
|
|
161
|
+
if (!(field in highlighted)) content += `${field}: ${JSON.stringify(value)}\n`
|
|
162
|
+
}
|
|
163
|
+
if (explain && hit._explanation) content += `\nExplanation:\n${JSON.stringify(hit._explanation, null, 2)}`
|
|
164
|
+
return { type: 'text', text: content.trim() }
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const aggregationsFragment = result.aggregations != null
|
|
168
|
+
? { type: 'text', text: `Aggregations: ${JSON.stringify(result.aggregations, null, 2)}` }
|
|
169
|
+
: null
|
|
170
|
+
|
|
171
|
+
const profileFragment = (profile && result.profile)
|
|
172
|
+
? { type: 'text', text: `\nQuery Profile:\n${JSON.stringify(result.profile, null, 2)}` }
|
|
173
|
+
: null
|
|
174
|
+
|
|
175
|
+
const fragments = [metaFragment]
|
|
176
|
+
if (aggregationsFragment) fragments.push(aggregationsFragment)
|
|
177
|
+
fragments.push(...contentFragments)
|
|
178
|
+
if (profileFragment) fragments.push(profileFragment)
|
|
179
|
+
|
|
180
|
+
return { content: fragments }
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
server.tool(
|
|
188
|
+
'get_shards',
|
|
189
|
+
'Get shard information for all or specific indices',
|
|
190
|
+
{ index: z.string().optional().describe('Optional index name to get shard information for') },
|
|
191
|
+
async ({ index }) => {
|
|
192
|
+
try {
|
|
193
|
+
const response = await esClient.cat.shards({ index, format: 'json' })
|
|
194
|
+
const shards = response.map(s => ({
|
|
195
|
+
index: s.index, shard: s.shard, prirep: s.prirep,
|
|
196
|
+
state: s.state, docs: s.docs, store: s.store, ip: s.ip, node: s.node
|
|
197
|
+
}))
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{ type: 'text', text: `Found ${shards.length} shards${index != null ? ` for index ${index}` : ''}` },
|
|
201
|
+
{ type: 'text', text: JSON.stringify(shards, null, 2) }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const transport = new StdioServerTransport()
|
|
211
|
+
await server.connect(transport)
|
|
212
|
+
process.on('SIGINT', () => { server.close().finally(() => process.exit(0)) })
|
package/index.mjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* @op/es6-mcp-server
|
|
4
|
+
*
|
|
5
|
+
* ES 6.x-compatible MCP server for Operative Elasticsearch clusters.
|
|
6
|
+
*
|
|
7
|
+
* Root cause: @elastic/elasticsearch v9 hardcodes `productCheck: 'Elasticsearch'`
|
|
8
|
+
* in the Client constructor, triggering the @elastic/transport ProductCheck
|
|
9
|
+
* middleware. ES 6.x clusters never send the required `X-Elastic-Product:
|
|
10
|
+
* Elasticsearch` response header (introduced in ES 7.14), so every 2xx
|
|
11
|
+
* response throws ProductNotSupportedError.
|
|
12
|
+
*
|
|
13
|
+
* Fix: subclass Transport to override productCheck: null before the middleware
|
|
14
|
+
* registers it, and override vendoredHeaders to plain application/json
|
|
15
|
+
* (ES 6.x rejects `application/vnd.elasticsearch+json; compatible-with=9`
|
|
16
|
+
* with HTTP 406).
|
|
17
|
+
*
|
|
18
|
+
* Tools exposed: list_indices, get_mappings, search, get_shards
|
|
19
|
+
* (same interface as @elastic/mcp-server-elasticsearch v0.3.1)
|
|
20
|
+
*
|
|
21
|
+
* Environment variables:
|
|
22
|
+
* ES_URL — Elasticsearch endpoint (required)
|
|
23
|
+
* ES_API_KEY — API key auth (optional)
|
|
24
|
+
* ES_USERNAME — Basic auth username (optional)
|
|
25
|
+
* ES_PASSWORD — Basic auth password (optional)
|
|
26
|
+
* ES_PATH_PREFIX — URL path prefix (optional)
|
|
27
|
+
* ES_SSL_SKIP_VERIFY — Skip TLS verification, '1' or 'true' (optional)
|
|
28
|
+
*/
|
|
29
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
30
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
31
|
+
import { Client, Transport } from '@elastic/elasticsearch'
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
|
|
34
|
+
class CompatTransport extends Transport {
|
|
35
|
+
constructor (opts) {
|
|
36
|
+
super({
|
|
37
|
+
...opts,
|
|
38
|
+
productCheck: null,
|
|
39
|
+
vendoredHeaders: {
|
|
40
|
+
jsonContentType: 'application/json',
|
|
41
|
+
ndjsonContentType: 'application/x-ndjson',
|
|
42
|
+
accept: 'application/json,text/plain'
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const url = process.env.ES_URL ?? ''
|
|
49
|
+
const apiKey = process.env.ES_API_KEY
|
|
50
|
+
const username = process.env.ES_USERNAME
|
|
51
|
+
const password = process.env.ES_PASSWORD
|
|
52
|
+
const sslSkipVerify = process.env.ES_SSL_SKIP_VERIFY === '1' || process.env.ES_SSL_SKIP_VERIFY === 'true'
|
|
53
|
+
const pathPrefix = process.env.ES_PATH_PREFIX
|
|
54
|
+
|
|
55
|
+
const clientOpts = {
|
|
56
|
+
node: url,
|
|
57
|
+
Transport: CompatTransport,
|
|
58
|
+
tls: sslSkipVerify ? { rejectUnauthorized: false } : {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
clientOpts.auth = { apiKey }
|
|
63
|
+
} else if (username && password) {
|
|
64
|
+
clientOpts.auth = { username, password }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (pathPrefix) {
|
|
68
|
+
const prefix = pathPrefix
|
|
69
|
+
clientOpts.Transport = class extends CompatTransport {
|
|
70
|
+
async request (params, options) {
|
|
71
|
+
return super.request({ ...params, path: prefix + params.path }, options)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const esClient = new Client(clientOpts)
|
|
77
|
+
const server = new McpServer({ name: 'elasticsearch-mcp-compat', version: '1.0.0' })
|
|
78
|
+
|
|
79
|
+
server.tool(
|
|
80
|
+
'list_indices',
|
|
81
|
+
'List all available Elasticsearch indices',
|
|
82
|
+
{ indexPattern: z.string().trim().min(1).describe('Index pattern of Elasticsearch indices to list') },
|
|
83
|
+
async ({ indexPattern }) => {
|
|
84
|
+
try {
|
|
85
|
+
const response = await esClient.cat.indices({ index: indexPattern, format: 'json' })
|
|
86
|
+
const info = response.map(i => ({
|
|
87
|
+
index: i.index,
|
|
88
|
+
health: i.health,
|
|
89
|
+
status: i.status,
|
|
90
|
+
docsCount: i.docsCount ?? i['docs.count']
|
|
91
|
+
}))
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{ type: 'text', text: `Found ${info.length} indices` },
|
|
95
|
+
{ type: 'text', text: JSON.stringify(info, null, 2) }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
server.tool(
|
|
105
|
+
'get_mappings',
|
|
106
|
+
'Get field mappings for a specific Elasticsearch index',
|
|
107
|
+
{ index: z.string().trim().min(1).describe('Name of the Elasticsearch index to get mappings for') },
|
|
108
|
+
async ({ index }) => {
|
|
109
|
+
try {
|
|
110
|
+
const r = await esClient.indices.getMapping({ index })
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{ type: 'text', text: `Mappings for index: ${index}` },
|
|
114
|
+
{ type: 'text', text: `Mappings for index ${index}: ${JSON.stringify(r[index]?.mappings ?? {}, null, 2)}` }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
server.tool(
|
|
124
|
+
'search',
|
|
125
|
+
'Perform an Elasticsearch search with the provided query DSL. Highlights are always enabled.',
|
|
126
|
+
{
|
|
127
|
+
index: z.string().trim().min(1).describe('Name of the Elasticsearch index to search'),
|
|
128
|
+
queryBody: z.record(z.any()).describe('Complete Elasticsearch query DSL object that can include query, size, from, sort, etc.'),
|
|
129
|
+
profile: z.boolean().optional().default(false).describe('Whether to include query profiling information'),
|
|
130
|
+
explain: z.boolean().optional().default(false).describe('Whether to include explanation of how the query was executed')
|
|
131
|
+
},
|
|
132
|
+
async ({ index, queryBody, profile, explain }) => {
|
|
133
|
+
try {
|
|
134
|
+
const mappingResponse = await esClient.indices.getMapping({ index })
|
|
135
|
+
const indexMappings = mappingResponse[index]?.mappings ?? {}
|
|
136
|
+
const searchRequest = { index, ...queryBody, profile, explain }
|
|
137
|
+
|
|
138
|
+
if (indexMappings.properties != null) {
|
|
139
|
+
const textFields = {}
|
|
140
|
+
for (const [fieldName, fieldData] of Object.entries(indexMappings.properties)) {
|
|
141
|
+
if (fieldData.type === 'text' || 'dense_vector' in fieldData) {
|
|
142
|
+
textFields[fieldName] = {}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
searchRequest.highlight = { fields: textFields, pre_tags: ['<em>'], post_tags: ['</em>'] }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await esClient.search(searchRequest)
|
|
149
|
+
const from = queryBody.from ?? 0
|
|
150
|
+
const total = typeof result.hits.total === 'number' ? result.hits.total : (result.hits.total?.value ?? 0)
|
|
151
|
+
const metaFragment = { type: 'text', text: `Total results: ${total}, showing ${result.hits.hits.length} from position ${from}` }
|
|
152
|
+
|
|
153
|
+
const contentFragments = result.hits.hits.map(hit => {
|
|
154
|
+
const highlighted = hit.highlight ?? {}
|
|
155
|
+
const source = hit._source ?? {}
|
|
156
|
+
let content = ''
|
|
157
|
+
for (const [field, fragments] of Object.entries(highlighted)) {
|
|
158
|
+
if (fragments?.length > 0) content += `${field} (highlighted): ${fragments.join(' ... ')}\n`
|
|
159
|
+
}
|
|
160
|
+
for (const [field, value] of Object.entries(source)) {
|
|
161
|
+
if (!(field in highlighted)) content += `${field}: ${JSON.stringify(value)}\n`
|
|
162
|
+
}
|
|
163
|
+
if (explain && hit._explanation) content += `\nExplanation:\n${JSON.stringify(hit._explanation, null, 2)}`
|
|
164
|
+
return { type: 'text', text: content.trim() }
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const aggregationsFragment = result.aggregations != null
|
|
168
|
+
? { type: 'text', text: `Aggregations: ${JSON.stringify(result.aggregations, null, 2)}` }
|
|
169
|
+
: null
|
|
170
|
+
|
|
171
|
+
const profileFragment = (profile && result.profile)
|
|
172
|
+
? { type: 'text', text: `\nQuery Profile:\n${JSON.stringify(result.profile, null, 2)}` }
|
|
173
|
+
: null
|
|
174
|
+
|
|
175
|
+
const fragments = [metaFragment]
|
|
176
|
+
if (aggregationsFragment) fragments.push(aggregationsFragment)
|
|
177
|
+
fragments.push(...contentFragments)
|
|
178
|
+
if (profileFragment) fragments.push(profileFragment)
|
|
179
|
+
|
|
180
|
+
return { content: fragments }
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
server.tool(
|
|
188
|
+
'get_shards',
|
|
189
|
+
'Get shard information for all or specific indices',
|
|
190
|
+
{ index: z.string().optional().describe('Optional index name to get shard information for') },
|
|
191
|
+
async ({ index }) => {
|
|
192
|
+
try {
|
|
193
|
+
const response = await esClient.cat.shards({ index, format: 'json' })
|
|
194
|
+
const shards = response.map(s => ({
|
|
195
|
+
index: s.index, shard: s.shard, prirep: s.prirep,
|
|
196
|
+
state: s.state, docs: s.docs, store: s.store, ip: s.ip, node: s.node
|
|
197
|
+
}))
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{ type: 'text', text: `Found ${shards.length} shards${index != null ? ` for index ${index}` : ''}` },
|
|
201
|
+
{ type: 'text', text: JSON.stringify(shards, null, 2) }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }] }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const transport = new StdioServerTransport()
|
|
211
|
+
await server.connect(transport)
|
|
212
|
+
process.on('SIGINT', () => { server.close().finally(() => process.exit(0)) })
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "es6-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ES 6.x-compatible MCP server. Bypasses the @elastic/elasticsearch v9 product-check that fails on Elasticsearch 6.x clusters.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"es6-mcp-server": "bin/es6-mcp-server.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@elastic/elasticsearch": "^9.4.2",
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
13
|
+
"zod": "^3.25.0"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"elasticsearch",
|
|
20
|
+
"mcp",
|
|
21
|
+
"mcp-server",
|
|
22
|
+
"operative"
|
|
23
|
+
],
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"private": false
|
|
26
|
+
}
|