A safety-first command-line tool and Model Context Protocol (MCP) server for operating Elastic Security as code on self-managed stacks, Elastic Cloud Hosted deployments, and Elastic Cloud Serverless projects. Keep detection rules, exception lists, and value lists in Git, triage alerts and cases, move data views, dashboards, and Fleet policies between stacks, and query Elasticsearch. It is a sibling to splunkctl and shares its operating contracts: dry-run-by-default changes, named profiles, stable output, and typed errors.
This is a personal, unofficial project, not affiliated with or endorsed by
Elastic. Expect breaking changes in minor releases until 1.0.0.
Examples run elkctl, a short alias for the same binary as elasticctl.
Every install method puts both names on PATH. The project, the crate on
crates.io, and the GitHub repository stay named elasticctl.
From crates.io, with the stable Rust toolchain:
cargo install elasticctlThis installs both binaries. Not every release reaches crates.io, so this can be a version behind. GitHub Releases always carry the newest version.
Or use a prebuilt binary. Each
release ships
archives for Linux (glibc and musl), macOS (Intel and Apple Silicon), and
Windows, each containing both binaries. The installer scripts put them on
PATH:
# macOS and Linux
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/dannyota/elasticctl/releases/latest/download/elasticctl-installer.sh \
| sh# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/dannyota/elasticctl/releases/latest/download/elasticctl-installer.ps1 | iex"Create a profile from ELASTICCTL_* environment variables;
.env.example documents them. Use an Elasticsearch API key
created inside Kibana, scoped to the project or deployment. An
organization-level Cloud key is not supported: Hosted refuses it, and MCP
planning refuses it on every flavor, though Serverless 9.7.0 accepts it for
CLI rule changes.
export ELASTICCTL_KIBANA_URL=https://YOUR-PROJECT.kb.YOUR-REGION.aws.elastic.cloud
export ELASTICCTL_API_KEY=...
elkctl config init --from-env
elkctl doctor # checks reachability, key scope, and rule accessKeep rules in a directory and review drift before pushing:
elkctl state pull --dir state # writes rules/, exceptions/, and value-lists/
# edit rules, or add new ones
elkctl state diff --dir state # field-level drift, no changes made
elkctl state push --dir state # preview only
elkctl state push --dir state --yesEvery remote change previews first and applies only with --yes. state push
never deletes a remote rule, exception list, or value list. See
Getting Started and Rules as Code.
A state directory that holds value-lists/ needs elasticctl 0.10 or later to
push. 0.9.4 refuses it; 0.9.0 to 0.9.3 would push its exceptions without its
value lists.
| Area | Supports | Guide |
|---|---|---|
| Rules as code | Pull, diff, and push rules, their exception lists, and the value lists those use, with change-evidence reports | Rules as Code |
| Rules | List, get, validate, enable, disable, delete, export, import, preview; prebuilt status and install | Rules |
| Exceptions | List, get, validate, export, import, and delete exception lists with their items | Exceptions |
| Value lists | List, get, validate, export, import, and delete value lists with exact value reconciliation; bootstrap a space | Value Lists |
| Search | ES|QL and Query DSL peeks and full exports | Search |
| Alerts and cases | Alert status, tags, and assignees; case create, comment, attach, close, reopen, delete | Alerts and Cases |
| Data views and dashboards | Portable files, the default data view, reference-safe delete, Saved Objects bundles | Data Views and Dashboards |
| Fleet | Agent and integration policies as portable files | Fleet |
| MCP | Twenty-four read-only tools, among them the task tools alert_context, rule_health, detection_coverage, and rule_readiness; two opt-in query tools; opt-in rule enable, disable, and disabled create, alert status and tags, and case create, comment, and status, through stored plans a person confirms |
MCP, MCP Mutations |
| Stack | Profiles, doctor, and info |
Configuration |
elkctl mcp serve serves read-only tools over stdio for one profile and space
chosen at startup:
elkctl --profile analyst --space default mcp serveThe default catalog has twenty-four tools. Four task tools answer questions
that span several objects: alert_context triages one alert, rule_health
checks whether a rule runs and alerts, detection_coverage counts rules by
MITRE ATT&CK technique, tactic, index pattern, or integration, and
rule_readiness checks whether a rule has data and integrations. Twenty
inspection tools read stack diagnostics, rules, exception lists, alerts,
cases, data views, dashboards, and Fleet policies; rules_list also filters
by ATT&CK technique or tactic. --allow-query-tools adds synchronous
ES|QL and Query DSL tools. By default no tool changes the stack, and no tool
selects local files or changes the target. See MCP for the client
stanza, limits, and tested clients.
--allow-mutations <CLASSES> adds tools that change Elastic. A planner tool
reads the stack and stores a plan; plan_apply takes only the plan id and
asks the person at the client to confirm it in a form. No tool argument
confirms a change, and a client without form elicitation cannot apply: the
plan names the CLI command to run instead. The profile needs es_url, and
an organization-level Cloud key is refused. The four classes:
rules-toggleenables or disables named rules. A rule without an API key that is enabled this way then runs with the server's credential; a rule that has a key keeps it.alerts-triagesets the status or tags of alerts named by id. It cannot close alerts by query or assign them.casescreates a case, comments on one, or closes and reopens cases whose alert sync (syncAlerts) is off. It cannot set assignees.rules-createcreates one rule, always disabled; it cannot create an enabled rule. The rule has no API key until it is first enabled, when it takes the key of the enabling credential.
No change is exactly reversible. A second call does not restore an alert's
workflow user and time or the tags it had before, and nothing in elasticctl
removes a posted comment; see MCP Mutation Kinds. For a client
that uses an mcpServers object, this placeholder stanza starts such a server:
{
"mcpServers": {
"elasticctl": {
"command": "elkctl",
"args": ["--profile", "analyst", "--space", "default", "mcp", "serve",
"--allow-mutations", "rules-toggle"]
}
}
}The server can check that an answer came through the form, not that a person
gave it: a client hook can accept for the person, and a model with a shell can
run elkctl ... --yes without MCP. Rule names and case titles in the form are
data from the stack and cannot change its fixed lines. A model can plan and
ask again after a decline; each plan asks at most three times, and at most
eight plans are open. A change made between the last check and the write is
not seen. Claude Code shows only the form's first three lines, so read the
plan with plan_get before you accept; Codex CLI's form has no decline
button, so leave the value false instead.
See MCP Mutations for the flow, the audit trail, and the
limits of this consent model.
The server also offers five resources, four resource templates, and four
prompts. Their lists are fixed: listing them sends no Elastic request and
needs no credential. Reading a resource such as elasticctl://stack/info runs
one read-only tool with fixed arguments and returns that tool's JSON, under the
same call limit and --timeout. A prompt such as triage_alert reads nothing;
it returns fixed text that asks the model to call one task tool. In Claude
Code, with the server registered as elasticctl, type @ to pick a resource
or write @elasticctl:elasticctl://stack/info, and type / to run a prompt
such as /mcp__elasticctl__triage_alert <alert_id>. See Claude Code's
resources and prompts pages, and
MCP for the full lists.
- Stable output: a table by default, in a terminal and in a script alike.
--jsonor--format json|yaml|csv|jsonlfor machine output, and--fieldsto keep only named fields. - Typed errors: a failure prints one JSON object on stderr with a stable
kind, such aspermissionornot_found. - Exit codes:
0success,1error,2usage error. - Dry run by default: every remote change previews and names the profile,
host, and space;
--yesapplies it. - Named profiles: keep development, UAT, and production stacks apart.
- Discovery:
elkctl commandsprints the command tree as JSON.
elkctl --profile uat rules list --source custom --jsonSee the CLI reference for output, errors, and selectors, and Security for credential handling and the dry-run guard.
- Getting Started: install, create a profile, first commands.
- Configuration: profiles, environment variables, spaces, API keys.
- CLI reference: command tree, output formats, errors, exit codes.
- Guides: Rules as Code, Rules, Exceptions, Value Lists, Search, Alerts and Cases, Data Views and Dashboards, Fleet, MCP, MCP Mutations, MCP Mutation Kinds.
- Security: the dry-run guard, credentials, and MCP reach.
- Limitations: Elastic behaviors the tool works around.
- Changelog: changes by version.
- Design: scope, architecture, and measured API behavior.
See CONTRIBUTING.md for setup, the checks a pull request must pass, and the rules review holds changes to. Report security problems privately through SECURITY.md. Maintainers cut releases with docs/releasing.md.
Licensed under Apache 2.0.