A local MCP server that exposes Splunk search over the REST API — built for data analysis where exact result counts and full retrieval are non-negotiable.
Splunk's official MCP Server app (installed on the Splunk side) runs searches
with exec_mode=oneshot under a 60-second timeout and silently injects
| head N (default cap 1000 rows). Row counts are unstable, capped results
report only an approximation, and there is no way to retrieve the full set —
unusable for data analysis, and a trigger for agent retry loops.
splunk-mcp runs every search as an asynchronous Splunk job (create →
poll until DONE → read the final resultCount → page through
/results), so:
total_rowsis always the exact final count — never a preview, never an approximation- Large result sets are never cut silently: rows beyond
max_rowsare dropped from the response and counted, next to an exacttotal_rows - Long searches don't time out:
run_queryreturns a SID onwait_timeoutand the job keeps running server-side - No app installation on the Splunk side — a token is all you need
| Tool | Purpose |
|---|---|
run_query |
Run SPL, wait for completion, return exact count + results |
start_query |
Start SPL asynchronously, return the SID immediately |
check_job |
Poll job state / result count by SID |
get_results |
Fetch results of a completed job (offset/count paging) |
cancel_job |
Cancel a running job |
list_indexes |
List visible event indexes with counts and time bounds |
list_sourcetypes |
List sourcetypes for an index/window (via | metadata) |
list_saved_searches |
List saved searches with their SPL and schedule |
run_saved_search |
Dispatch a saved search (alert actions never fire) |
get_usage |
Full tool reference served to the agent |
Results come back in the response, up to max_rows (default 50,000; a call
may set its own, and 0 means no cap). When the cap drops rows, the response
says so with truncated, omitted_rows, and the exact total_rows — the
count is the product, so a capped answer is still an answer about the whole
set. To work through more than one answer can hold, page with get_results
offset/count.
This server does not write results to a file it chose. It cannot know the caller's context window, and an agent runtime that needs a large response on disk already puts it there (gem-agent does this automatically).
Arguments are checked strictly. A call carrying an argument a tool does not
declare fails with invalid_arguments, naming it — unknown field "max_rowz" —
rather than running without it. That matters most for max_rows: a misspelt one
used to fall back to the configured default while reading as though the caller's
cap had been honoured, which is the opposite of the explicit count this server
exists to give. Wrong-typed arguments are refused the same way, and nothing
reaches Splunk before the arguments decode, so a rejected call starts no search
job. Omitting arguments entirely still means "none".
Write/delete commands (delete, collect, mcollect, meventcollect,
outputlookup, outputcsv, sendemail, runshellscript, script) are
rejected by default with a structured unsafe_spl error. Individual commands
can be re-allowed via [server] allow_commands. Splunk-side RBAC remains the
final authority.
Download a pre-built binary from the releases page, or build from source:
git clone https://github.com/nlink-jp/splunk-mcp.git
cd splunk-mcp
make build
# Binary: dist/splunk-mcpOne server instance connects to exactly one Splunk host. For multiple destinations, create one config file per host and register the server multiple times:
{
"mcpServers": {
"splunk-prod": { "command": "splunk-mcp", "args": ["--config", "/path/to/prod.toml"] },
"splunk-dev": { "command": "splunk-mcp", "args": ["--config", "/path/to/dev.toml"] }
}
}Copy config.example.toml to
~/.config/splunk-mcp/config.toml (the default path) and set your values:
[splunk]
host = "https://your-splunk.example.com:8089"
token = "your-token"
# insecure = false # self-signed certs
# prepend = "pipe-only" # auto | pipe-only | off (same as splunk-cli)
[server]
# max_rows = 50000
# job_ttl = "10m"
# allow_commands = []chmod 600 ~/.config/splunk-mcp/config.tomlConfig resolution order: --config flag → $SPLUNK_MCP_CONFIG →
~/.config/splunk-mcp/config.toml → ./config.toml. Connection settings in
the file are overridden by env vars (SPLUNK_HOST, SPLUNK_TOKEN,
SPLUNK_USER, SPLUNK_PASSWORD, SPLUNK_APP) — the same names splunk-cli
uses, so credentials can be shared.
splunk-mcp # serve MCP over stdio (default)
splunk-mcp serve --config /path/to/prod.toml
splunk-mcp --versionTypical agent workflows:
- Discover first —
list_indexes→list_sourcetypesto learn the data landscape before writing SPL. - Quick analysis —
run_querywith SPL; completes withinwait_seconds(default 300) and returns exact counts. - Long-running search —
start_query→ pollcheck_job→get_results. - Large result set — raise
max_rowsif your context can hold it, or page withget_resultsoffset/count. Nothing is ever dropped silently. - Saved searches —
list_saved_searches→run_saved_search(optionally overriding the dispatch time window; alert actions are always suppressed).
- Completed jobs expire after their TTL (Splunk default is a few minutes;
raise with
[server] job_ttl). An expired SID returnsjob_not_found. - Splunk enforces per-role concurrent-search quotas; prefer sequential
start_querybatches over mass parallelism. - Tool errors are structured JSON
{code, message, details}— seeget_usagefor the full error-recovery table.
make test # go test ./... (unit tests, no external deps)
make vet # go vet ./...
make check # vet + test + build
make build # outputs dist/splunk-mcp
make integration-test # start a Splunk container (Podman) and run live E2E tests
make splunk-down # stop and remove the Splunk test containerIntegration tests run the full lifecycle — exact counts, the max_rows cap
and its accounting, async flow — against a real splunk/splunk:9.4 container.
See BUILD.md for details.
MIT — see LICENSE.