esmole

io.github.ed-smartass由 GitHub 账号 ed-smartass 发布v0.2.0更新于 Nov 2, 2026

Elasticsearch 7.x/8.x with connections managed at runtime by the agent, SSH tunnels, readonly.

已验证STDIO仅桌面数据库开发者工具

概览

AI 生成的概览

面向 Elasticsearch 7.x/8.x 的本地 stdio MCP 服务器,让助手在运行时增删改命名集群连接,并进行搜索与查询。

可以做什么
  • 列出、添加、修改、删除并测试命名 Elasticsearch 连接,可存于项目或全局
  • 用查询 DSL、聚合、排序和分页搜索索引
  • 列出索引的健康状态、文档数与存储大小,并读取索引映射
  • 查看集群健康状态
  • 发送任意 Elasticsearch REST 请求,包括 NDJSON 的批量与多搜索请求体
适用场景
适合需要让助手操作一个或多个 Elasticsearch 集群、并自行管理连接设置而无需改客户端配置或重启的场景,包括只能经 SSH 跳板机访问的集群。
运行要求
需要 Node.js 22 或更高版本,通过 npx 或 Docker 在本地运行。启动时无需配置;每个连接需要主机地址,以及 API key 或用户名加密码。SSH 隧道需要跳板机、凭据或 agent,以及 known_hosts 条目或固定的主机密钥指纹。
安装前请注意
连接文件以明文保存凭据,项目文件应排除在 git 之外。在对话中输入的凭据会经过模型上下文。readonly 模式不是安全边界,助手可以将其关闭;如需硬性保证,请使用只读的 Elasticsearch 角色或 API key。克隆仓库中的项目文件会被直接信任,可能把已存凭据重定向到其他主机。打开的 SSH 隧道在本机回环地址上无认证监听。集群错误会原样返回。

安装

在 SourceWeft 中

  1. 打开 控制台中的 esmole,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

[esmole mascot]

esmole-mcp

Elasticsearch 7.x and 8.x for AI agents that manage their own connections: add, edit and remove clusters through MCP tools at runtime, per project, hot-reloaded, through SSH.

[npm version] [npm downloads] [CI] [MIT license] [Glama score]

esmole-mcp is a stdio MCP server for Elasticsearch. Tell your agent "connect to the logs cluster through bastion.example.com, readonly" and it calls add_connection. The connection lands in a per-project .esmole.json or in your global store, and both are re-read on every tool call, so a change made by the agent, by you in an editor, or by another session applies on the next call. No client config edits, no restarts.

  • Elasticsearch 7.x and 8.x, any number of named clusters, API key or basic auth.
  • SSH tunnels with password, key or agent auth; host keys verified against known_hosts or a pinned fingerprint.
  • readonly connections limited to an allowlist of read endpoints, absolute request timeouts, a 10 MB response cap, and results capped at 40 KB so a big aggregation cannot flood the context.

Need PostgreSQL or MySQL? dbmole-mcp is the same design for SQL databases.

Demo

[The agent adds a readonly cluster to the project and searches it]

Install

Requires Node.js 22 or newer. The server needs no configuration to start; connections are added later.

Claude Code

bash
claude mcp add esmole -- npx -y esmole-mcp

Add --scope user to enable it in every project. Or install it as a plugin (Claude Code 2.1.275 or later):

text
/plugin install esmole --marketplace ed-smartass/esmole-mcp

Codex

bash
codex mcp add esmole -- npx -y esmole-mcp

or in ~/.codex/config.toml:

toml
[mcp_servers.esmole]command = "npx"args = ["-y", "esmole-mcp"]

Cursor

[Add esmole to Cursor]

VS Code

[Install in VS Code]

Claude Desktop

Download esmole-mcp-<version>.mcpb from the latest release and open it, or add the JSON below to claude_desktop_config.json. Claude Desktop does not start servers inside a project, so keep connections in the global store (the default scope).

Any other MCP client

json
{    "mcpServers": {        "esmole": { "command": "npx", "args": ["-y", "esmole-mcp"] }    }}

Docker

The image is built from source:

bash
git clone https://github.com/ed-smartass/esmole-mcp.git && cd esmole-mcpdocker build -t esmole-mcp .
json
{    "mcpServers": {        "esmole": {            "command": "docker",            "args": [                "run", "-i", "--rm",                "-v", "esmole-store:/home/node/.config/esmole",                "-v", "/abs/path/to/project:/work",                "-v", "/home/me/.ssh/known_hosts:/home/node/.ssh/known_hosts:ro",                "esmole-mcp"            ]        }    }}

The container runs as node with cwd /work: mount a project there to use its .esmole.json, and the named volume keeps the global store. Mount known_hosts for SSH connections (or pin ssh.hostKey), and any private key you reference by privateKeyPath, at the path the connection names; for ssh.agent: true, forward the agent socket (-v "$SSH_AUTH_SOCK:/ssh-agent" -e SSH_AUTH_SOCK=/ssh-agent). To reach hosts that only your machine can reach, add --add-host=host.docker.internal:host-gateway (Linux) and use host.docker.internal. The image has no git, so the "not ignored by git" check always warns.

A 0.1 setup that still sets ESMOLE_CONNECTIONS, ESMOLE_CONFIG or --config starts fine: the server prints one line on stderr saying they are ignored. See the changelog.

Quick start

Ask the agent, for example:

Add a readonly elasticsearch connection logs to this project: https on 10.0.0.20, API key VnVhQ2ZH…, through ssh as [email protected] with my ssh agent. Then show the five biggest indices.

It calls add_connection with:

json
{    "name": "logs",    "host": "10.0.0.20",    "apiKey": "VnVhQ2ZH…",    "readonly": true,    "ssh": { "host": "bastion.example.com", "user": "deploy", "agent": true },    "scope": "project"}

then test_connection and es_list_indices. Credentials you type in chat pass through the model's context; for production secrets, write the entry into the file yourself.

Connections

Two layers

LayerFileShape
project.esmole.json, found from the server's working directory{ "connections": [ ... ] }
global~/.config/esmole/connections.json, or the path in ESMOLE_STORE[ ... ]
  • Same name in both: the project entry wins. list_connections reports each connection's source (project or global) and, when it hides an entry of the same name in the other layer, shadows.
  • Hot reload. Both files are re-read on every tool call. Edit a file mid-session and the next call uses the new settings; a changed connection gets a fresh client, the old one is closed.
  • Project file discovery. Starting from the server's working directory (Claude Code and Codex start it in the project directory), esmole walks up to the filesystem root and uses the first .esmole.json it finds. In a linked git worktree with no file of its own, it falls back to the main checkout's .esmole.json, so every worktree of a repo shares one file.
  • Both files are plain JSON with mode 0600, written atomically. Writes keep entries they cannot parse verbatim, and refuse to overwrite a file that is not valid JSON.

Managing connections with tools

  • add_connection takes the connection fields plus scope: "global" (default) or "project". With project and no file yet, the file is created at the main checkout root (or the git root, or the working directory outside git). If git does not ignore it, the response warns you; add .esmole.json to .gitignore so credentials are not committed.
  • add_connection fails when the name already exists in the target layer. The same name in the other layer is allowed; the response says which entry now shadows which.
  • update_connection patches the connection where it lives (the winning layer). A null value removes an optional field. An ssh object is merged into the stored one field by field, so { "ssh": { "hostKey": "SHA256:..." } } keeps the stored SSH secrets.
  • remove_connection deletes it from that layer and tells you if that uncovers a same-name entry in the other layer.
  • Credentials are never echoed back: list_connections shows scheme, host, port, the auth kind, verifyTls, readonly, source and whether SSH is used.

Broken and invalid entries

  • An entry that fails validation does not silently disappear: its name resolves to an error (connection 'x' in <file> is invalid: ...) instead of falling through to a same-name entry in the global store, and list_connections lists it as invalid. A name defined twice in one file is an error too. remove_connection can delete an invalid entry.
  • A file that exists but is unreadable, not JSON, or the wrong shape is reported in list_connections (brokenLayers). A broken project file blocks every connection; a broken global store blocks only names the project file does not define. A missing file is just empty.

Connection fields

Unknown fields are rejected. Authentication is required: an apiKey, or both user and password.

FieldTypeNotes
namestring[a-zA-Z0-9_-]+
typeelasticsearchdefault elasticsearch
hoststringas seen from the SSH bastion when ssh is set
portnumberdefault 9200
schemehttp | httpsdefault https
verifyTlsbooleandefault true; false skips certificate verification
apiKeystringAPI key (the base64 id:key form)
user, passwordstringbasic auth
readonlybooleandefault false, see Readonly
queryTimeoutMsintegermilliseconds, default 60000, at most 2147483647; see Limits and timeouts
sshobjectsee SSH tunnels

Example .esmole.json:

json
{    "connections": [        { "name": "local", "host": "localhost", "scheme": "http", "user": "elastic", "password": "changeme" },        {            "name": "logs",            "host": "10.0.0.20",            "apiKey": "VnVhQ2ZHY0JDZGJrUW0t...",            "readonly": true,            "ssh": { "host": "bastion.example.com", "user": "deploy", "agent": true }        }    ]}

SSH tunnels

ssh fieldNotes
host, portbastion; port defaults to 22
userSSH user
passwordpassword auth
privateKey / privateKeyPathkey contents, or a path (~ is expanded)
passphrasefor an encrypted key
agenttrue to use the agent at SSH_AUTH_SOCK
hostKeypinned host key fingerprint, SHA256:... (OpenSSH format)
json
{ "host": "bastion.example.com", "user": "deploy", "privateKeyPath": "~/.ssh/id_ed25519" }{ "host": "bastion.example.com", "user": "deploy", "password": "…" }{ "host": "bastion.example.com", "port": 2222, "user": "deploy", "agent": true }{ "host": "bastion.example.com", "user": "deploy", "agent": true, "hostKey": "SHA256:EXAMPLEexampleEXAMPLEexampleEXAMPLEexample1" }

Host keys are verified. With hostKey set, the bastion's key must match it (trailing = padding is accepted) and known_hosts is not consulted. Otherwise the key must match an entry for the host in ~/.ssh/known_hosts (host on port 22, [host]:port otherwise):

  • Plain and hashed host names match. Wildcard patterns never match, and a line with a negated wildcard (!*.example.com) is ignored.
  • A key listed under @revoked is rejected for every host, whatever the line's host pattern. @cert-authority lines and unknown markers are skipped.

There is no trust-on-first-use: an unknown key fails the connection with an error that shows the presented fingerprint. Check it against a trusted source, then either add the host to known_hosts or pin it:

bash
ssh-keyscan -p 22 bastion.example.com 2>/dev/null | ssh-keygen -lf -# 256 SHA256:EXAMPLEexampleEXAMPLEexampleEXAMPLEexample1 bastion.example.com (ED25519)

and ask the agent to update_connection with ssh.hostKey set to that SHA256:... value.

Tunnels send keepalives every 15 s and are rebuilt after 3 missed replies. Not supported: ProxyJump and ~/.ssh/config, SSH certificates. With scheme: "https" through a tunnel, the certificate is checked against host, not the tunnel's local address.

Readonly

readonly: true protects against accidental writes. Every tool, es_request included, checks method and path against an allowlist before anything is sent:

  • GET and HEAD: allowed.
  • POST: only _search, _msearch, _count, _field_caps and _pit (optionally under one index or pattern, e.g. /logs-*/_search), and /_search/scroll.
  • DELETE: only /_search/scroll and /_pit (closing read sessions).
  • Everything else is refused, as is any path containing .., %2e, %2f, %5c, \ or #, whatever the method. So on a readonly connection es_request cannot use date-math index names with / rounding, such as <logs-{now/d}>; writable connections are not affected.
  • Index and pattern arguments of the helper tools are validated too (no /, \, ?, #, %, whitespace, ..); comma lists, wildcards, -exclusions and cluster:index work. Date-math names need %-encoding, so they only work through es_request.
  • Because every GET passes, GET-only maintenance endpoints such as GET /_flush pass too. They change no documents, but they are not reads.

It is not a security boundary: the agent can switch it off with update_connection, by design. For a hard guarantee, use an Elasticsearch role or API key with read privileges only.

Tools

ToolWhat it does
list_connectionsconnections with their source, shadowed names, invalid entries and broken files
add_connectionadd a connection to the project file or the global store
update_connectionpatch a connection where it lives; null removes a field
remove_connectionremove a connection; reports a same-name entry it uncovers
test_connectionconnect end to end (including the tunnel) and report cluster version and latency
es_requestany method, path, body and query params; readonly-guarded; a string body for NDJSON (_bulk, _msearch)
es_searchquery, aggs, size, source (sent as _source), sort, from, search_after, track_total_hits, highlight, script_fields on an index or pattern; a whole body nested into query is lifted, with a note
es_list_indicesindices matching a pattern (default *) with health, doc count and store size
es_get_mappingmapping flattened to field: type; raw: true for the original
es_cluster_healthcluster health

es_search size defaults to 10 and is capped at 1000, also when it comes from a nested body; with aggs and no query it is 0. Tool arguments are checked strictly: a wrong key is rejected with a hint, e.g. unknown key '_source' — did you mean 'source'?.

A 4xx or 5xx from the cluster, es_request included, comes back as a tool error: HTTP <status> <root_cause[0].type>: <reason> on the first line, then the trimmed response body. test_connection fails on any non-2xx answer (e.g. 401 from a secured cluster).

The ten tool definitions cost about 2.1k tokens of context (tools/list is about 8,200 characters; measured with npm run smoke, at roughly 4 characters per token).

Limits and timeouts

  • Every result is at most 40 KB of JSON. Strings longer than 2 KB are cut and marked …[+N bytes]; if the body still does not fit, its longest array (hits, buckets) is halved until it does. The result says so in truncated and stays valid JSON. Ask for less with size and source on es_search, or filter_path on es_request.
  • Responses larger than 10 MB are aborted with an error ("narrow it with size/_source"), never returned partially.
  • queryTimeoutMs (default 60 s) is an absolute deadline per request, from connecting to reading the last byte of the body.

How it compares

esmole-mcpelastic/mcp-server-elasticsearchcr7258/elasticsearch-mcp-server
Statusmaintaineddeprecated; Elastic points to the Agent Builder MCP endpoint (Elasticsearch 9.2+)maintained
Elasticsearch 7.x✓—✓
Elasticsearch 8.x✓✓✓
OpenSearch—✗✓
SSH tunnel built in✓✗✗
Agent adds and edits connections through MCP tools✓✗✗

✓ documented, ✗ not offered, — not mentioned or not tested. Checked October 2026; corrections welcome.

Security

Connection files hold plaintext credentials (mode 0600). Keep .esmole.json out of git. Cluster errors are passed to the client verbatim.

Whoever can change a connection, through update_connection or by editing a connection file, can redirect its credentials. Changing host keeps the stored password or API key, and changing ssh.host keeps the stored SSH password or key, so the next call sends them to the new host. Treat write access to the connection tools and files like access to the credentials.

A project's .esmole.json is trusted as is: in a cloned or untrusted repository it can add connections or shadow a global one of the same name and send your queries to another host. Discovery walks up to /, so a file in a parent directory (such as ~) applies to every project below it. Check list_connections before working in an unfamiliar repository.

Each open SSH tunnel listens on 127.0.0.1 at a random port without authentication: while it is open, any local process, including other users on a shared host, can connect through it to the target host. The cluster still asks for its own credentials.

See SECURITY.md for the threat model and how to report a vulnerability.

Development

bash
npm cinpm run lint        # Biomenpm run typecheck   # tscnpm run test:unit   # unit tests, mocked IOnpm run test:int    # integration tests against Elasticsearch 7.17 and 8.x (needs Docker)npm run smoke       # build, npm pack, install the tarball in a temp dir, boot, tools/list

The shared connection core is @dbmole/core, developed in the dbmole-mcp repo and bundled into this package at build time. See CONTRIBUTING.md.

License

MIT

来源:README.md,提交 1968561

工具

10

list_connections

List Elasticsearch connections with the layer each comes from: project (.esmole.json, found by walking up from the project dir) or global (user store). Also shows invalid entries and broken files. Secrets are omitted.

无需输入参数。

add_connection

Add a named Elasticsearch connection. scope "global" (default) saves it to the user store; "project" saves it to .esmole.json in the project root. Auth via user/password or apiKey; optional ssh tunnel, readonly flag, scheme and verifyTls.

输入参数sshhost必填name必填porttypeuserscopeapiKeyschemepasswordreadonlyverifyTls另有 1 个

update_connection

Patch an ES connection by name in the layer it lives in (project or global). Set a field to null to remove it (e.g. "ssh": null); an ssh object is merged field by field, e.g. { "ssh": { "hostKey": "SHA256:..." } }. queryTimeoutMs sets the per-request deadline (null restores the 60 s default).

输入参数name必填patch必填

remove_connection

Remove an ES connection by name from the layer it lives in (invalid entries too) and drop its cached client/tunnel.

输入参数name必填

test_connection

Verify an ES connection works end to end (including ssh tunnel). Reports server version and latency.

输入参数name必填

es_request

Generic Elasticsearch REST passthrough: any method, any path. Returns {status, body}. A 4xx/5xx answer is returned as an error: "HTTP <status> <type>: <reason>" plus the trimmed body. Use a string body for NDJSON (_bulk, _msearch). On readonly connections only reads pass: GET, HEAD, POST [/<index>]/_search|_msearch|_count|_field_caps|_pit, POST /_search/scroll, DELETE /_pit and /_search/scroll.

输入参数bodypath必填method必填paramsconnection必填

es_list_indices

List indices matching a pattern with health, doc count and store size. Wraps GET /_cat/indices.

输入参数patternconnection必填

es_get_mapping

Return an index mapping as a flat field:type list (compact, token-cheap). Pass raw=true for the nested ES JSON.

输入参数rawindex必填connection必填

es_cluster_health

Cluster health summary (status, node and shard counts). Wraps GET /_cluster/health.

输入参数connection必填

版本历史

1
  1. v0.2.0最新Oct 26, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分