--- url: /kibana-mcp-server/guide/what-is-it.md description: What Kibana MCP Server is, why it exists and what it deliberately does not do. --- # What is Kibana MCP Server? Read-only MCP server for agent-driven log investigation against Kibana-backed search endpoints protected with basic auth. ## Start an investigation 1. Run `bootstrap`: verify only the connection, save credentials, create an empty managed catalog, and register Codex without config-file edits. Supply all connection values for prompt-free use. For hand-authored catalogs, guided `setup` imports your explicit selection and saves machine profiles. 2. Call `discover` for configured logical sources and field hints. If empty, ask the user for an exact index or pattern and wait; `configure_index` validates and saves only that selection. 3. Call `describe_fields` for a source's effective field capabilities before filtering. 4. Use `query` over an absolute time window: `hits`, `count`, `histogram`, `terms`, `stats`, or `grouped_top_hits`. Use `filter` when the exact field name is known. ## Why it exists Agents use a small operator-selected source catalog, field capabilities, and typed results over the Model Context Protocol—no UI scraping or cluster-wide index listing. ## Boundaries * **Read-only by design.** The server searches and describes. It never writes to Elasticsearch or Kibana. * **No index guessing.** Never guess, list, or scan indexes before a person names the exact index or pattern. * **Schema-aware features depend on the deployment.** When Kibana does not expose field metadata, the server falls back to sampling fields through search, which is a heuristic. ## Where to go next * [Getting started](./getting-started): install and investigate. * [HTTP transport](../deployment/http): share a server across callers. * [Tool reference](../reference/tools/query): inputs and outputs. --- --- url: /kibana-mcp-server/guide/getting-started.md description: >- Requirements, prompt-free bootstrap, agent skill installation and use in Codex. --- # Getting started ## Requirements * Node.js 22+ * A Kibana-compatible read endpoint reachable with basic auth * No index for bootstrap. Select the exact index or pattern later with `configure_index`; hand-authored catalogs remain an advanced option. ## Installation ### Prompt-free connection bootstrap 1. Provision the connection through a trusted environment, then run: Never pass passwords as command-line arguments. Use `KIBANA_PASSWORD`, `--password-env NAME`, or `--password-stdin`. ```bash export KIBANA_BASE_URL='https://kibana.example.com' export KIBANA_USERNAME='elastic' export KIBANA_PASSWORD='use-a-secret-provider' npx -y @havesomecode/kibana-mcp-server bootstrap \ --client codex ``` Expected: no prompts with these values supplied; a verified connection, secure credentials, an empty managed catalog on first install, and registration of the exact running package transport. No direct Codex config edits or index listing, scanning, inference, or configuration. 2. Start the server without `KIBANA_BASE_URL`, `KIBANA_USERNAME`, and `KIBANA_PASSWORD` to use the saved profile. Direct startup requires an explicit `KIBANA_SOURCE_CATALOG_PATH` or configuration-file store; no implicit checkout catalog. 3. Reconnect the client and call `discover`. If empty, ask the user for the exact Kibana index or pattern and wait. Never guess, list, or scan indexes. Call `configure_index` only with their answer; it validates and saves only that index and accepts no credentials. Reruns preserve explicitly configured sources. Bootstrap refuses to replace hand-authored or modified catalogs without `--replace`. Use that flag only with operator approval: it creates an empty managed catalog. ### Install the agent skill Run: ```bash npx skills add Havesomecode/kibana-mcp-server \ --skill kibana-log-investigation \ --agent codex \ --global \ --yes ``` Expected: the skill at `skills/kibana-log-investigation/SKILL.md` teaches bootstrap instead of manual MCP config edits. ## Use in Codex Use package bootstrap above for Codex. Use the repo plugin below only from a cloned checkout. ### Quick path 1. Clone the repository. 2. Check Node.js 22+ (pinned in `.node-version`). If missing, ask Codex to use `ensure-node-runtime`. 3. From the repo root, run: ```bash npm install npm run build ``` ### Install the built plugin 1. Open the cloned repo in Codex. 2. Install `Kibana Log Investigation` from the repo marketplace in Codex's plugin directory. If the model cannot install it, click in the UI yourself, then let the agent continue. 3. Run `node dist/src/bin/kibana-mcp-server.js bootstrap --client none`. This verifies the connection and creates an empty catalog on first install; the plugin already registers the server. 4. Reconnect the MCP process and check startup status. On failure, inspect sanitized stderr; rebuild and reinstall a missing runtime. After startup succeeds, restart once if tools need refreshing. 5. In a fresh thread, call `discover`. If empty, ask for the exact index or pattern and wait; only then call `configure_index`. Repo-scoped plugin files: * `.agents/plugins/marketplace.json` * `plugins/kibana-log-investigation/.codex-plugin/plugin.json` * `plugins/kibana-log-investigation/.mcp.json` * `plugins/kibana-log-investigation/mcp-launcher.mjs` * `plugins/kibana-log-investigation/dist/` (generated self-contained runtime and notices) ### Minimal handoff prompt Send this prompt with the repo link: ```text Install the `kibana-log-investigation` skill from Havesomecode/kibana-mcp-server, then run its connection-only bootstrap with the provisioned KIBANA_BASE_URL, KIBANA_USERNAME, and KIBANA_PASSWORD values. Do not edit Codex config files, import the example catalog, or inspect indexes. After install, ask me which exact index or pattern to configure before calling configure_index. ``` --- --- url: /kibana-mcp-server/guide/investigating.md description: >- The investigation workflow step by step: configure a source, discover, describe fields, then query. --- # Investigating with an agent Ask the agent to discover a source, inspect its fields, then query. Configure a source only if none exists. ## 1. Configure a source, once For a fresh profile, call `discover` first. If empty, ask for the exact Kibana index or pattern and wait. Never guess, list, or scan indexes. Call [`configure_index`](../reference/tools/configure-index) only with the user's answer; this example assumes they selected `app-logs-*`: ```json { "index": "app-logs-*", "source_name": "Application logs", "source_id": "app-logs" } ``` Expected: fields checked, a time field selected, one probe search, and a saved source for later conversations. ## 2. Discover the sources Call [`discover`](../reference/tools/discover): ```json { "query": "application" } ``` Expected: this profile's logical sources, time fields, and field hints. ## 3. Inspect the fields Before filtering, call [`describe_fields`](../reference/tools/describe-fields): ```json { "source_id": "app-logs", "query": "status" } ``` Use the returned preferred exact field for matching, such as `event.keyword` for `event`. ## 4. Query Call [`query`](../reference/tools/query) over an absolute time window. Count first: ```json { "source_ids": ["app-logs"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T13:00:00Z", "text": "timeout", "mode": "count" } ``` Group to locate the errors: ```json { "source_ids": ["app-logs"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T13:00:00Z", "text": "timeout", "mode": "terms", "group_by": "service.name" } ``` Read hits for the service that stands out: ```json { "source_ids": ["app-logs"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T13:00:00Z", "filters": [{ "field": "service.name", "value": "checkout" }], "mode": "hits", "limit": 50 } ``` For a known exact field, use [`filter`](../reference/tools/filter) without alias resolution. ## Query modes | Mode | Returns | | --- | --- | | `hits` | Matching documents, sorted, with a cursor for the next page | | `count` | Number of matches per source | | `histogram` | Matches over time, needs `histogram_interval` | | `terms` | Top values of a field, needs `group_by` | | `stats` | Count, min, max, average and percentiles of a numeric field, needs `stats_field` | | `grouped_top_hits` | The first hits of each group, needs `group_by` | ## When something fails Look up stable failure codes such as `KIBANA_TIMEOUT` or `KIBANA_AUTHENTICATION` in [Errors](../reference/errors). --- --- url: /kibana-mcp-server/guide/profiles.md description: Saved machine profiles and how to work with several Kibana environments. --- # Profiles and environments Run `npx -y @havesomecode/kibana-mcp-server bootstrap --client codex`. Expected: `/api/status` verified without index access, an empty managed catalog on first install, saved profile metadata and secure credentials. Conflicting MCP registrations are rejected, not overwritten. Call `discover`; if empty, ask for the exact index or pattern and wait. Never guess, list, or scan indexes. Use `configure_index` only for the user's selected value. For hand-authored catalogs, run `npm run setup` or `node dist/src/bin/kibana-mcp-server.js setup`. Supply an explicit catalog path; type `example` only to opt into the bundled example. Setup imports the catalog into machine-local state, independent of the checkout. Metadata lives in the machine-level app config directory. Credentials use macOS Keychain, Windows Credential Manager, or Linux Secret Service when available. Never put passwords in command arguments. For direct startup, supply environment credentials and an explicit `KIBANA_SOURCE_CATALOG_PATH`, or a store in a [configuration file](./configuration-file). No implicit checkout catalog; no MCP tool accepts credentials. Without a saved or explicit connection: `No saved Kibana connection is available. Run connection bootstrap first.` ## Machine profiles Remove `KIBANA_BASE_URL`, `KIBANA_USERNAME`, and `KIBANA_PASSWORD` from the server environment to use saved profiles. All three together select direct startup and require an explicit catalog path or store. The default saved profile loads automatically. To select another, set `KIBANA_PROFILE=`: ```bash KIBANA_PROFILE=staging npm run dev ``` ## Multiple environments 1. Save each environment with `bootstrap --profile --client codex`, or guided setup for hand-authored catalogs. Use `--no-default` to keep the current default. 2. Set `KIBANA_PROFILE` in extra MCP entries; no per-thread connection values or catalog paths are needed: ```bash KIBANA_PROFILE=staging npm run dev KIBANA_PROFILE=prod npm run dev ``` For direct startup instead, keep these distinct per environment: * `KIBANA_BASE_URL` * `KIBANA_USERNAME` * `KIBANA_PASSWORD` * `KIBANA_SOURCE_CATALOG_PATH` Use separate catalog paths to avoid mixing persisted sources. `configure_index` does not overwrite operator-managed, hand-authored catalogs. --- --- url: /kibana-mcp-server/guide/configuration-file.md description: >- Configure the server with a kibana.mcp.config.ts file: connection, catalog store and HTTP options. --- # Configuration file 1. Create `kibana.mcp.config.ts`: **Use only trusted files:** configuration executes as code. `.ts` needs Node.js 22.18+; `.mjs` works on any supported Node.js version. ```ts // kibana.mcp.config.ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/redis"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.REDIS_URL, keyPrefix: "kibana-mcp:production:" }, http: { port: 3000, profileHeader: "x-kibana-profile", authToken: process.env.MCP_TOKEN }, }); ``` 2. Run with the explicit file (stdio or HTTP): ```bash kibana-mcp-server serve --http --config ./kibana.mcp.config.ts ``` Matching `KIBANA_*` variables or `serve` flags override `url`, `user`, `password`, `timeoutMs`, `profile` and `http.*`. `store` holds one catalog per profile; `storeOptions` goes to its factory. Without a store, direct startup requires `KIBANA_SOURCE_CATALOG_PATH`; saved profiles supply their own path. No implicit checkout catalog. Select the file with `--config` or `KIBANA_MCP_CONFIG`; it is never discovered from the working directory. ## Next steps * [Configuration file reference](../reference/config-file) lists every option. * [Catalog stores](../deployment/catalog-stores) explains where source catalogs can live. --- --- url: /kibana-mcp-server/deployment/stdio.md description: 'The default stdio transport: what it serves and when to move to HTTP.' --- # stdio transport ```bash npx -y @havesomecode/kibana-mcp-server serve ``` stdio is the default: the MCP client starts a child process over standard input/output, serving **one global configuration** to one caller. Supply a connection before startup. Otherwise it fails with `No saved Kibana connection is available. Run connection bootstrap first.` ## What it serves * After [guided setup or bootstrap](../guide/getting-started), the default saved profile. * With `--profile ` or `KIBANA_PROFILE`, that saved profile. * With an [environment connection](../reference/environment), the explicit `KIBANA_SOURCE_CATALOG_PATH` catalog. No checkout file is selected implicitly. * With a [configuration file](../guide/configuration-file) that declares a store, the profile named by `profile`, `--profile` or `KIBANA_PROFILE`, falling back to `default`. ## Running from a checkout ```bash KIBANA_BASE_URL=https://kibana.example.com \ KIBANA_USERNAME=elastic \ KIBANA_PASSWORD=secret \ KIBANA_SOURCE_CATALOG_PATH=config/sources.json \ npm run dev ``` After guided setup, omit the environment values: ```bash npm run dev ``` ## When to move to HTTP Use one [HTTP server](./http) behind a multi-user gateway with per-user catalogs. stdio needs one process per caller. --- --- url: /kibana-mcp-server/deployment/http.md description: Run one shared server over Streamable HTTP with a source catalog per caller. --- # HTTP transport Start a shared Streamable HTTP server behind an authenticated MCP gateway: ```bash KIBANA_BASE_URL=https://kibana.example.com \ KIBANA_USERNAME=service-account \ KIBANA_PASSWORD=... \ KIBANA_SOURCE_CATALOG_PATH='/var/lib/kibana-mcp/catalogs/{profile}.json' \ KIBANA_HTTP_AUTH_TOKEN=... \ kibana-mcp-server serve --http --port 3000 --profile-header x-kibana-profile ``` Only the gateway should reach the server. The profile header is trusted as sent: the server authenticates the gateway, not end users. Non-loopback listening requires `KIBANA_HTTP_AUTH_TOKEN` or `KIBANA_HTTP_OIDC_ISSUER`. **No gateway?** Authenticate end users with your OpenID Connect provider instead: see [OpenID Connect authentication](./oidc). Each caller then gets the catalog named after the issuer and a claim of their access token. Without `--http`, the default is stdio with one global configuration. ## Select each caller's catalog One process serves all callers; each tool call uses the configured profile header: * `{profile}` in `KIBANA_SOURCE_CATALOG_PATH`: separate catalog files, shared environment connection. * Configuration-file [`store`](./catalog-stores): separate store entries per profile. * Without bootstrap environment variables: selects a saved machine profile by **exact name**, not generated id. CLI selection still accepts case-insensitive names and ids. * Without `--profile-header`: startup requires an explicit default (`--profile`, `KIBANA_PROFILE` or configuration-file `profile`); all callers use it. Over stdio, per-profile catalogs fall back to `default`. * HTTP profile names are exact: `Alice` and `alice` are distinct. Missing catalogs start empty; fill them with `configure_index`. Profile names become filesystem-safe path/store keys: plain lowercase slugs stay unchanged; other names get a digest suffix. Literal names ending in reserved `-<12 hex digits>` are hashed too, preventing impersonation of generated keys. The server never adopts another identity's catalog. ## HTTP options * `--host` / `KIBANA_HTTP_HOST`, default `127.0.0.1` * `--port` / `KIBANA_HTTP_PORT`, default `3000` * `--path` / `KIBANA_HTTP_PATH`, default `/mcp` * `--profile-header` / `KIBANA_HTTP_PROFILE_HEADER`, no default * `KIBANA_HTTP_AUTH_TOKEN`, shared secret expected as `Authorization: Bearer ` `GET /healthz` answers `200` without authentication. Without a token or OpenID Connect, MCP requests require a loopback `Host` and, if present, loopback `Origin`. Authenticated gateways may forward their own host/origin headers. ## Legacy sessions 1. Send `initialize` to receive an `Mcp-Session-Id`. 2. Echo it on subsequent requests, including cancellation notifications. 3. Send `DELETE` with it when finished. Sessions stay bound to the original profile: * Maximum: 128 per process; a full registry returns `503`. * Idle expiry: 30 minutes, reclaimed on the next legacy request. * Retirement: after 1,024 requests, bounding SDK cancellation metadata and closing remaining session exchanges. * Expired, exhausted, unknown or mismatched session: `404`; reinitialize. The `2025-06-18` initialization flow supports session-based GET/SSE streams and authenticated DELETE. Disconnecting GET releases only the stream, not its session. Handshake-free legacy calls remain supported; cross-request cancellation requires a session. ## Modern requests For stateless `2026-07-28` tool calls, send the per-request `_meta` envelope and SDK-required `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers. Disconnecting an in-flight modern request cancels backend work; backend deadlines still bound tool calls. Authentication and profile selection are checked on **every request**, legacy or modern. --- --- url: /kibana-mcp-server/deployment/oidc.md description: >- Protect the HTTP transport with your OpenID Connect provider: each user signs in, and gets the catalog named by their token. --- # OpenID Connect authentication Over HTTP, the server can authenticate each user with your OpenID Connect provider (Keycloak, Entra ID, Okta, Auth0…) instead of trusting a gateway. It then acts as an OAuth 2.1 resource server: MCP clients discover the provider, sign the user in, and send an access token on every request. The server never sees passwords and never issues tokens. Client registration, sign-in and consent stay with the provider. ## How it works 1. An MCP client calls the endpoint without a token. The server answers `401` with a `WWW-Authenticate` challenge pointing to its [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) metadata, `/.well-known/oauth-protected-resource`. 2. The metadata names your provider as the authorization server. The client signs the user in there and asks for a token bound to the endpoint ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707) `resource`). 3. The client retries with `Authorization: Bearer `. The server verifies the token and reads the caller's profile from one of its claims. Each user gets their own source catalog, and nobody can choose another one: the profile comes from the token, never from a header. ## Configure In the [configuration file](../reference/config-file): ```js import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/postgres"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.DATABASE_URL }, http: { host: "0.0.0.0", oidc: { issuer: "https://sso.example.com/realms/acme", resource: "https://kibana-mcp.example.com/mcp", requiredScopes: ["kibana-mcp"], }, }, }); ``` Or with environment variables only: ```bash KIBANA_HTTP_HOST=0.0.0.0 \ KIBANA_HTTP_OIDC_ISSUER=https://sso.example.com/realms/acme \ KIBANA_HTTP_OIDC_RESOURCE=https://kibana-mcp.example.com/mcp \ KIBANA_HTTP_OIDC_REQUIRED_SCOPES=kibana-mcp \ kibana-mcp-server serve --http --config ./kibana.mcp.config.mjs ``` | Option | Environment variable | Description | | --- | --- | --- | | `issuer` | `KIBANA_HTTP_OIDC_ISSUER` | Issuer identifier, exactly as published in its discovery document. Enables OpenID Connect. | | `resource` | `KIBANA_HTTP_OIDC_RESOURCE` | Public URL of the MCP endpoint, as clients reach it. Required. | | `audience` | `KIBANA_HTTP_OIDC_AUDIENCE` | Expected token audience. Defaults to `resource`. | | `mode` | `KIBANA_HTTP_OIDC_MODE` | `offline` or `introspection`. Defaults to `introspection` when `clientId` is set, `offline` otherwise. | | `clientId`, `clientSecret` | `KIBANA_HTTP_OIDC_CLIENT_ID`, `KIBANA_HTTP_OIDC_CLIENT_SECRET` | Confidential client of this server at the provider, used for introspection. | | `requiredScopes` | `KIBANA_HTTP_OIDC_REQUIRED_SCOPES` | Scopes every token must carry. Separate them with spaces in the variable: a comma belongs to the scope name. | | `scopesSupported` | `KIBANA_HTTP_OIDC_SCOPES_SUPPORTED` | Scopes advertised to clients in the metadata. | | `profileClaim` | `KIBANA_HTTP_OIDC_PROFILE_CLAIM` | Claim naming the caller at the issuer. Defaults to `sub`. | | `allowInsecureRequests` | `KIBANA_HTTP_OIDC_ALLOW_INSECURE_REQUESTS` | Allow a non-HTTPS issuer or resource. Development only. | ::: warning `resource` is the public URL `resource` is never derived from the incoming request, whose `Host` header the caller controls. Behind a reverse proxy, use the external HTTPS URL. Its path must be the HTTP path of the server (`/mcp` by default): clients call that URL and look up its metadata at `/.well-known/oauth-protected-resource` followed by that path, so a proxy must forward both without rewriting the path. ::: At startup, the server refuses: * OpenID Connect combined with `KIBANA_HTTP_AUTH_TOKEN` or a profile header; * a configuration without named profiles (a `store`, saved profiles, or the `{profile}` placeholder in `KIBANA_SOURCE_CATALOG_PATH`), since every user needs their own catalog; * a non-HTTPS issuer or resource outside the loopback interface; * a `resource` whose path differs from the HTTP path of the server; * an issuer whose discovery document cannot be read, or names another issuer; * in the offline mode, an issuer whose JWKS cannot be read or is malformed. ## Verify tokens | Mode | How | Use it when | | --- | --- | --- | | `offline` | The JWT access token is checked locally with the provider's JWKS: signature, issuer, audience, expiration. No call to the provider per request. | The provider issues RFC 9068 JWT access tokens. | | `introspection` | The provider is asked about each token ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)). Answers are cached up to 60 seconds, never past the token expiration. Refused tokens are cached too, in a separate, smaller cache: tokens made up by callers cannot evict accepted ones. The server does not limit request rates: do it at your reverse proxy. | Tokens are opaque, or revocation must apply quickly. | In the offline mode the JWKS is loaded at startup, then refreshed by `oauth4webapi` every 5 minutes, or sooner when a token names an unknown key. The JWKS and introspection endpoints are discovered from `/.well-known/openid-configuration`, then `/.well-known/oauth-authorization-server`. The provider metadata is read once, at startup: restart the server after the provider changes its endpoints. Verification relies on [`oauth4webapi`](https://github.com/panva/oauth4webapi). ::: warning Access token format in offline mode The offline mode accepts only [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068) JWT access tokens: the JWT header must carry `typ: at+jwt`, and the payload the `iss`, `sub`, `aud`, `exp`, `iat`, `jti` and `client_id` claims. Many providers issue plain JWTs by default (`typ: JWT`, `azp` instead of `client_id`), which are refused with `401`. If yours does, enable RFC 9068 access tokens on the provider, or use the introspection mode. ::: ::: warning Token audience Every token must name the endpoint in its audience: the `aud` claim, or the `aud` field of the introspection answer, must contain `resource` (or `audience` when set). A token without it is refused with `401`, including when the provider returns no audience at all. * **Keycloak**: add an audience mapper (*Audience*, *Included Custom Audience* set to the `resource` URL) to a client scope granted to MCP clients. * **Entra ID**: tokens carry the application ID URI of the API (`api://…`) as audience; set `audience` to that URI. ::: ## Choose the profile claim The caller's profile is the issuer followed by `#` and the value of `profileClaim`, for example `https://sso.example.com/realms/acme#alice`. A token without that claim is refused with `401`. The issuer prefix keeps identities apart: the same `sub` or username at another provider is another person, and a catalog created through a gateway's profile header (`alice`) is never handed to an OIDC user. Changing `issuer` therefore starts every user with a new catalog. * `sub` (default) is stable and unique, but opaque: catalogs are named after identifiers like `f81d4fae-7dec-…`. * `preferred_username` or `email` give readable catalog names. Use them only if users cannot change them on the provider, or a user could take over another one's catalog. Profile names are turned into safe keys before they reach a store, as with the [profile header](./http). ## Set up the provider The provider must: * publish its metadata over HTTPS, with an `issuer` identical to the configured one; * let MCP clients register, through Dynamic Client Registration or Client ID Metadata Documents depending on what your clients support, or offer them a pre-registered public client; * issue tokens for the endpoint audience (see the warning above); * for the offline mode, issue RFC 9068 access tokens and publish a `jwks_uri`; * for the introspection mode, publish an `introspection_endpoint` and give the server a confidential client. Grant the scope listed in `requiredScopes` only to the users allowed to query Kibana: every authenticated caller reads what the Kibana service account can read. See the [security model](./security). ## Check the setup ```bash # 1. an unauthenticated call is challenged curl -i https://kibana-mcp.example.com/mcp -X POST \ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # HTTP/1.1 401 Unauthorized # WWW-Authenticate: Bearer error="invalid_token", ..., resource_metadata="https://kibana-mcp.example.com/.well-known/oauth-protected-resource/mcp" ``` ```bash # 2. the metadata names the provider curl -s https://kibana-mcp.example.com/.well-known/oauth-protected-resource/mcp # {"resource":"https://kibana-mcp.example.com/mcp","authorization_servers":["https://sso.example.com/realms/acme"],...} ``` ```bash # 3. a token issued for the endpoint is accepted curl -s https://kibana-mcp.example.com/mcp -X POST \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` | Status | Meaning | | --- | --- | | `401` | Missing, expired or invalid token, wrong audience, wrong format in offline mode, or no profile claim. The client must sign in again. | | `403` | Valid token without one of `requiredScopes`. | | `500` | The provider (JWKS or introspection) could not be reached. Not a token issue. | --- --- url: /kibana-mcp-server/deployment/security.md description: >- What the HTTP transport trusts, the safeguards built in and how credentials are handled. --- # Security model Restrict HTTP access to an authenticated gateway. Bind to loopback or a private network; **overwrite the profile header** at the gateway, never forward a client-supplied value. ## Gateway trust With a shared secret, the server authenticates **the gateway**, not the end user. | Element | Role | Trusted because | | --- | --- | --- | | `Authorization: Bearer ` | Proves the request comes from your gateway | It matches `KIBANA_HTTP_AUTH_TOKEN` | | Profile header | Names the caller | The gateway set it after authenticating the caller | The header is trusted as sent. Anyone with port access and the shared secret can act as **any profile**. ## OpenID Connect trust With [OpenID Connect](./oidc), the server authenticates **the end user**. | Element | Role | Trusted because | | --- | --- | --- | | `Authorization: Bearer ` | Proves who the caller is | Signature, issuer, audience and expiration check out, or the provider says it is active | | Profile claim of the token | Names the caller | The provider issued it; the profile is `#` | A caller cannot pick another profile: no profile header can be configured alongside OpenID Connect. ## Safeguards built in * Non-loopback listening requires `KIBANA_HTTP_AUTH_TOKEN` or `KIBANA_HTTP_OIDC_ISSUER`. * With OpenID Connect, the token audience must name this endpoint, and no profile header is accepted. * HTTP startup requires a profile header or explicitly named default profile. With a configured header, requests missing it get `401`. * Profile path/key values contain lowercase letters, digits and dashes; other names get a digest suffix. `../../etc/passwd` becomes `etc-passwd-`. * Profile names are exact: `Alice` and `alice` have separate catalogs. * **MCP tools never accept connection credentials.** Supply saved platform credentials or explicit environment/configuration values at startup. `configure_index` changes only the caller’s managed source catalog. ## Kibana credentials * Saved profiles use macOS Keychain, Windows Credential Manager or Linux Secret Service. * Environment/configuration credentials live wherever you put them. In configuration files, read secrets from the environment; do not hard-code them. * Prefer environment variables to secret flags: other machine users can see arguments. ## What a caller can do Scope the shared service account to indexes you will expose. All shared-server callers can read what it can read: profiles isolate **catalogs**, not underlying data. Protect the gateway endpoint with a scope reserved for the intended audience. `configure_index` can add any index pattern the service account can read to the caller's catalog. If too broad, exclude it at the gateway and manage catalogs yourself. ## Read-only The server sends only search and field-metadata requests; it never writes to Elasticsearch or Kibana. ## Reporting a vulnerability See `SECURITY.md` in the repository. --- --- url: /kibana-mcp-server/deployment/catalog-stores.md description: >- Where source catalogs can live: files, Redis, PostgreSQL, MongoDB, SQLite or a custom store. --- # Catalog stores Declare a store in the [configuration file](../guide/configuration-file): ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/postgres"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.DATABASE_URL }, }); ``` Catalogs list the logical sources a profile can query. They default to files; a store keeps one entry per profile for shared processes and persistence across redeploys. ## Bundled stores Import only the store you use; only its driver loads: | Import | Driver to install | `storeOptions` | | --- | --- | --- | | `@havesomecode/kibana-mcp-server/stores/redis` | `redis` | `url`, `keyPrefix`, `client` | | `@havesomecode/kibana-mcp-server/stores/ioredis` | `ioredis` | `url`, `keyPrefix`, `client` (standalone, Sentinel or Cluster) | | `@havesomecode/kibana-mcp-server/stores/postgres` | `pg` | `url`, `table`, `createTable`, `client` | | `@havesomecode/kibana-mcp-server/stores/mongodb` | `mongodb` | `url`, `database`, `collectionName`, `collection` | | `@havesomecode/kibana-mcp-server/stores/sqlite` | none, uses `node:sqlite` | `path`, `table`, `database` | * Install the required driver next to the server; drivers are optional peer dependencies. * `url` defaults to `KIBANA_STORE_URL`, then to the usual variable of the backend (`REDIS_URL`, `DATABASE_URL`, `MONGODB_URI` or `MONGO_URL`). For SQLite, `path` defaults to `KIBANA_STORE_URL`. * Defaults: key prefix `kibana-mcp:catalog:`, table and collection `kibana_mcp_catalogs`. The SQL stores create their table unless `createTable` is `false`. * Pass `client`, `collection` or `database` to reuse your connection. The store never closes it. * Stores replace catalogs only if the stored content still matches what was read; concurrent writers cannot overwrite each other. * With Redis, use an instance with persistence and a `noeviction` policy: catalogs are data, not a cache. Every tool call rereads the store-backed catalog; processes sharing a store serve the same sources. ## Choosing a store | Situation | Store | | --- | --- | | One host, no extra service to run | [SQLite](./stores/sqlite) | | Several replicas, a database already in place | [PostgreSQL](./stores/postgres) or [MongoDB](./stores/mongodb) | | Several replicas, Redis already in place and persistent | [Redis](./stores/redis) | | Anything else | [Write your own](./stores/custom) | --- --- url: /kibana-mcp-server/deployment/stores/redis.md description: >- Keep source catalogs in Redis with the redis or ioredis client: options and durability requirements. --- # Redis store ## 1. Install a driver Both entry points behave identically; choose your client: | Entry point | Driver | | --- | --- | | `@havesomecode/kibana-mcp-server/stores/redis` | [`redis`](https://www.npmjs.com/package/redis) | | `@havesomecode/kibana-mcp-server/stores/ioredis` | [`ioredis`](https://www.npmjs.com/package/ioredis) | ```bash npm install redis ``` ## 2. Configure the store Redis keeps one key per profile, without expiry: ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/redis"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.REDIS_URL, keyPrefix: "kibana-mcp:production:" }, }); ``` ## Options | Option | Default | Description | | --- | --- | --- | | `url` | `KIBANA_STORE_URL`, then `REDIS_URL` | `redis://` or `rediss://` URL | | `keyPrefix` | `kibana-mcp:catalog:` | Prefix of the catalog keys | | `client` | none | A client you configured yourself, used instead of `url`. The store never closes it. | Use `client` with `ioredis` to reach Sentinel or Cluster deployments: ```ts import { Redis } from "ioredis"; import store from "@havesomecode/kibana-mcp-server/stores/ioredis"; const client = new Redis({ sentinels: [{ host: "sentinel-1", port: 26379 }], name: "mymaster" }); export default defineKibanaMcpConfig({ /* … */ store, storeOptions: { client } }); ``` ## Redis must be durable Use durable Redis; catalogs are data, not cache: * Enable disk persistence (AOF or RDB). * Set `noeviction`: full instances must refuse writes, not drop catalogs. Cache instances usually have opposite settings. If unsure, use a dedicated instance or database. ## How writes are protected A Lua script sets the key only if its content still matches what was read. Shared server processes cannot overwrite each other's changes. --- --- url: /kibana-mcp-server/deployment/stores/postgres.md description: >- Keep source catalogs in PostgreSQL: options, table definition and required privileges. --- # PostgreSQL store ## 1. Install the driver ```bash npm install pg ``` ## 2. Configure the store PostgreSQL keeps one row per profile: ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/postgres"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.DATABASE_URL, table: "ops.kibana_catalogs" }, }); ``` ## Options | Option | Default | Description | | --- | --- | --- | | `url` | `KIBANA_STORE_URL`, then `DATABASE_URL` | `postgres://` or `postgresql://` URL | | `table` | `kibana_mcp_catalogs` | Table name, optionally schema-qualified. Letters, digits and underscores only. | | `createTable` | `true` | Set to `false` when your own migrations create the table | | `client` | none | A `pg` Pool or Client you configured yourself. The store never closes it. | ## Table Created on startup unless `createTable` is `false`: ```sql CREATE TABLE IF NOT EXISTS kibana_mcp_catalogs ( profile_key TEXT PRIMARY KEY, content TEXT NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ``` Grant the database user `SELECT`, `INSERT` and `UPDATE` on the table, plus schema `CREATE` if the store creates it. ## How writes are protected New catalogs use `ON CONFLICT DO NOTHING`; existing ones update only `WHERE content = `. No changed row reports a conflict. --- --- url: /kibana-mcp-server/deployment/stores/mongodb.md description: >- Keep source catalogs in MongoDB: options, document shape and replica set connections. --- # MongoDB store ## 1. Install the driver ```bash npm install mongodb ``` ## 2. Configure the store MongoDB keeps one document per profile: ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/mongodb"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.MONGODB_URI, database: "ops" }, }); ``` ## Options | Option | Default | Description | | --- | --- | --- | | `url` | `KIBANA_STORE_URL`, then `MONGODB_URI`, then `MONGO_URL` | `mongodb://` or `mongodb+srv://` URL | | `database` | the database of the URL | Database name | | `collectionName` | `kibana_mcp_catalogs` | Collection name | | `collection` | none | A collection you obtained yourself, used instead of `url`. The store never closes its client. | ::: warning Replica sets The `url` option does not yet accept multi-host strings such as `mongodb://db1,db2,db3/logs?replicaSet=rs0`. Use `mongodb+srv://`, or create a client and pass its `collection`: ```ts import { MongoClient } from "mongodb"; const client = await new MongoClient(process.env.MONGODB_URI).connect(); const collection = client.db("ops").collection("kibana_mcp_catalogs"); export default defineKibanaMcpConfig({ /* … */ store, storeOptions: { collection } }); ``` ::: ## Documents ```json { "_id": "", "content": "", "updatedAt": "" } ``` ## How writes are protected New catalogs use insert; duplicate keys report a conflict. Existing catalogs update only when previous content matches; no matching document reports a conflict. --- --- url: /kibana-mcp-server/deployment/stores/sqlite.md description: >- Keep source catalogs in a local SQLite file with the built-in node:sqlite module. --- # SQLite store Configure a local SQLite file; no extra package is needed (`node:sqlite` is built into Node.js): ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/sqlite"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { path: "/var/lib/kibana-mcp/catalogs.sqlite" }, }); ``` SQLite keeps one row per profile. ## Options | Option | Default | Description | | --- | --- | --- | | `path` | `KIBANA_STORE_URL`, with or without a `sqlite:` prefix | Database file, or `:memory:`. Its directory is created if needed. | | `table` | `kibana_mcp_catalogs` | Table name. Letters, digits and underscores only. | | `database` | none | An open `DatabaseSync` you created yourself. The store never closes it. | ## When to use it Use SQLite for one server or a few same-host processes sharing the file. WAL mode keeps readers from blocking. For replicas on different hosts, use [PostgreSQL](./postgres), [MongoDB](./mongodb) or [Redis](./redis). ## Limits * Calls are synchronous. Waiting for another process's write lock can pause the server for up to five seconds. Writes occur only during source configuration, so this is rare. * Put the file on a persistent volume. In containers, mount that volume at its directory. --- --- url: /kibana-mcp-server/deployment/stores/custom.md description: Implement the CatalogStore contract to keep source catalogs in any backend. --- # Custom store ## 1. Implement the store Implement `read` and `replace` for your backend: a database, object bucket or internal service. ```ts import { CatalogConflictError, type CatalogStore } from "@havesomecode/kibana-mcp-server/config"; export default function myStore(options: { table: string }): CatalogStore { return { // serialized catalog of the profile, or undefined when it has none yet read: async (profileKey) => db.get(options.table, profileKey), // write only if the stored content is still `expected`, else throw CatalogConflictError replace: async (profileKey, expected, next, signal) => { signal?.throwIfAborted(); if (!(await db.compareAndSet(options.table, profileKey, expected, next))) { throw new CatalogConflictError(profileKey); } }, describe: (profileKey) => `db://${options.table}/${profileKey}`, close: () => db.close(), }; } ``` ### Contract | Method | Required | Role | | --- | --- | --- | | `read(profileKey)` | yes | Returns the serialized catalog of the profile, or `undefined` when it has none yet. | | `replace(profileKey, expected, next, signal?)` | yes | Writes `next` only if the stored content is still `expected` (`undefined` when there was none). Throws `CatalogConflictError` otherwise. Checks the optional `AbortSignal` before issuing a write. | | `describe(profileKey)` | no | Human-readable location reported by tools. Must not expose credentials. | | `close()` | no | Releases connections when the server stops. | The server derives `profileKey` from the profile name using only lowercase letters, digits and dashes: safe for file names, keys and primary keys. Check the optional cancellation signal after any wait and before issuing a write. Cancellation never rolls back an already-dispatched write. ### Require compare-and-set `configure_index` reads, validates against Kibana, then writes. Concurrent callers/processes must fail with a conflict if content changed, not erase another writer's source. Retry the operation after a conflict. ## 2. Configure the store Pass the store and its required options to the configuration file: ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import myStore from "./my-store.js"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store: myStore({ table: "kibana_catalogs" }), }); ``` --- --- url: /kibana-mcp-server/integrations.md description: Complete setups for running Kibana MCP Server as a shared service. --- # Integration examples Choose a shared-service setup: | Example | What it shows | | --- | --- | | [Behind an MCP gateway](./gateway) | Authenticate callers, overwrite the profile header with verified identity, send the shared secret. | | [Ts.ED gateway](./tsed) | Use OIDC and the verified token subject as the profile. | | [Docker image](./docker) | Run HTTP with a persistent catalog store. | All use [HTTP transport](../deployment/http) and [catalog stores](../deployment/catalog-stores). --- --- url: /kibana-mcp-server/integrations/gateway.md description: >- What an MCP gateway or reverse proxy must do in front of the HTTP transport, with an nginx example. --- # Behind an MCP gateway Put HTTP behind an MCP/API gateway or reverse proxy. The server does not authenticate end users; the gateway must. ## The contract For every forwarded request: 1. **Authenticate the caller:** OIDC, API key, or mutual TLS. 2. **Set the profile header** from a stable verified identity, typically token `sub`. It selects the caller's catalog. 3. **Overwrite, never forward, that header.** Client-supplied values could expose another caller's catalog. 4. **Send the shared secret** as `Authorization: Bearer ` to authenticate the gateway to the server. ``` caller ──(OIDC token)──▶ gateway ──(Authorization: Bearer X-Kibana-Profile: )──▶ kibana-mcp-server ──▶ Kibana ``` ## Server side Set a profile header, or name one default profile. Non-loopback HTTP requires `KIBANA_HTTP_AUTH_TOKEN`; otherwise startup is refused. ```bash KIBANA_HTTP_AUTH_TOKEN=change-me \ kibana-mcp-server serve --http \ --config ./kibana.mcp.config.ts \ --profile-header x-kibana-profile ``` ## Example: nginx with an auth subrequest With nginx and an OAuth2 proxy, authenticate and overwrite headers: ```nginx location /mcp/kibana { auth_request /oauth2/auth; auth_request_set $caller $upstream_http_x_auth_request_user; proxy_set_header X-Kibana-Profile $caller; # always overwritten proxy_set_header Authorization "Bearer change-me"; # shared secret proxy_pass http://127.0.0.1:3000/mcp; proxy_buffering off; # responses may be streamed } ``` ## One server per Kibana Run one process per Kibana instance. Give each its own port and store prefix or table, then route from the gateway. ## Callers without an identity Reject identities without a stable identifier, or map them to a named profile at the gateway. An empty profile header (for example, a token without `sub`) returns `401`. --- --- url: /kibana-mcp-server/integrations/tsed.md description: >- Expose Kibana MCP Server through a Ts.ED gateway protected by OIDC, with one catalog per user. --- # Ts.ED gateway Use [Ts.ED](https://tsed.dev/docs/mcp.html) to expose OAuth-protected MCP endpoints: one Kibana server per environment, one catalog per user. ## Run the Kibana MCP server 1. Create one config per Kibana instance: ```ts // kibana.production.config.ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/redis"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_PRODUCTION_BASE_URL, user: process.env.KIBANA_USERNAME, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.REDIS_URL, keyPrefix: "kibana-mcp:production:" }, http: { port: 3101, profileHeader: "x-kibana-profile", authToken: process.env.KIBANA_MCP_TOKEN, }, }); ``` 2. Start production: ```bash kibana-mcp-server serve --http --config ./kibana.production.config.ts ``` 3. For staging, use its own URL, port `3102`, and prefix `kibana-mcp:staging:`. ## Declare the gateway Set the profile from the verified token subject. Ts.ED interpolates `${OAUTH_SUB}`; keep placeholders as plain strings, not template literals. Overwrite client-supplied profile headers; never trust them. ```ts import type { PlatformMcpSettings } from "@tsed/platform-mcp"; export function kibanaGateway(name: string, path: string, upstreamUrl: string): PlatformMcpSettings { return { name, path, auth: { issuer: process.env.OIDC_ISSUER, resource: new URL(path, process.env.PORTAL_BASE_URL).toString(), mode: "introspection", clientId: process.env.OIDC_CLIENT_ID, clientSecret: process.env.OIDC_CLIENT_SECRET, requiredScopes: ["api_admin"], }, upstream: { type: "http", url: upstreamUrl, headers: { Authorization: `Bearer ${process.env.KIBANA_MCP_TOKEN}`, "X-Kibana-Profile": "${OAUTH_SUB}", }, }, }; } export const mcpsConfig: PlatformMcpSettings[] = [ kibanaGateway("kibana", "/mcp/kibana/production", "http://127.0.0.1:3101/mcp"), kibanaGateway("kibana_staging", "/mcp/kibana/staging", "http://127.0.0.1:3102/mcp"), ]; ``` ## What each user gets Expected: a catalog per token subject; queries use the shared Kibana service account. New catalogs are empty. Call `discover`, ask for the exact index or pattern, and wait before `configure_index`. Never guess, list, or scan indexes. ## Things to check * Reject tokens without a string `sub`, or assign a subject in Ts.ED. `${OAUTH_SUB}` is otherwise empty and the server returns `401`; check `client_credentials` and custom pre-authentication results. * Use `http` for one MCP process shared across users. A `stdio` upstream starts one process per token. --- --- url: /kibana-mcp-server/integrations/docker.md description: >- A Docker image and compose file that run the server over HTTP, protected by OpenID Connect, with catalogs in PostgreSQL. --- # Docker image Create these files to run HTTP with PostgreSQL catalogs. Users sign in with your OpenID Connect provider, so no gateway is needed. ::: warning Version Use a package version with HTTP transport, configuration files, and catalog stores. Releases predating this documentation do not include them. ::: ## Files ``` kibana-mcp/ Dockerfile kibana.mcp.config.mjs compose.yaml ``` ### `kibana.mcp.config.mjs` ```js import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; import store from "@havesomecode/kibana-mcp-server/stores/postgres"; export default defineKibanaMcpConfig({ url: process.env.KIBANA_URL, user: process.env.KIBANA_USER, password: process.env.KIBANA_PASSWORD, store, storeOptions: { url: process.env.DATABASE_URL }, http: { host: "0.0.0.0", port: 3000, oidc: { issuer: process.env.OIDC_ISSUER, resource: process.env.MCP_PUBLIC_URL, requiredScopes: ["kibana-mcp"], }, }, }); ``` Each user gets their own catalog, named after the issuer and the `sub` claim of their token. See [OpenID Connect authentication](../deployment/oidc) for the other options and the provider setup. Use `.mjs` on any supported Node.js version; `.ts` needs Node.js 22.18+. ### `Dockerfile` Install the server and driver beside the config file so imports resolve: ```dockerfile FROM node:22-alpine WORKDIR /app RUN npm init -y >/dev/null \ && npm install --omit=dev @havesomecode/kibana-mcp-server pg COPY kibana.mcp.config.mjs ./ USER node EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1 CMD ["npx", "kibana-mcp-server", "serve", "--http", "--config", "kibana.mcp.config.mjs"] ``` ### `compose.yaml` **Terminate TLS in front of the container:** `MCP_PUBLIC_URL` is the HTTPS URL clients use, and its path must be the server path (`/mcp`). This file binds to host loopback for your reverse proxy. **Keep catalogs persistent:** the `catalogs` volume below outlives the container. For file or SQLite stores, mount a volume at the catalog or database path. ```yaml services: kibana-mcp: build: . ports: - "127.0.0.1:3000:3000" environment: KIBANA_URL: https://kibana.example.com KIBANA_USER: service-account KIBANA_PASSWORD: ${KIBANA_PASSWORD} OIDC_ISSUER: https://sso.example.com/realms/acme MCP_PUBLIC_URL: https://kibana-mcp.example.com/mcp DATABASE_URL: postgres://kibana_mcp:${POSTGRES_PASSWORD}@postgres:5432/kibana_mcp depends_on: postgres: condition: service_healthy postgres: image: postgres:17 environment: POSTGRES_USER: kibana_mcp POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: kibana_mcp volumes: - catalogs:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U kibana_mcp"] interval: 5s retries: 10 volumes: catalogs: ``` ## Run and check Supply `KIBANA_PASSWORD` and `POSTGRES_PASSWORD` through your secret provider. Listening on `0.0.0.0` requires authentication: the server refuses non-loopback startup without OpenID Connect or a shared secret. The provider must be reachable at startup: the server reads its discovery document, and in offline mode its JWKS, before listening. ```bash docker compose up -d --build ``` Check health: ```bash curl -s http://127.0.0.1:3000/healthz ``` Check that unauthenticated calls are challenged: ```bash curl -si http://127.0.0.1:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Expected: `401` with a `WWW-Authenticate` header pointing to `https://kibana-mcp.example.com/.well-known/oauth-protected-resource/mcp`. An MCP client follows it, signs the user in, and retries with an access token: ```bash curl -s http://127.0.0.1:3000/mcp \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Expected: the MCP tool list, not proof of index access. ## Notes **Behind a gateway instead:** replace `oidc` with `profileHeader: "x-kibana-profile"` and `authToken: process.env.KIBANA_MCP_TOKEN`, and let only the [gateway](./gateway) reach the port. **SQLite instead of PostgreSQL:** import `stores/sqlite`, set `storeOptions: { path: "/data/catalogs.sqlite" }`, drop the `pg` install, and mount a volume at `/data`. --- --- url: /kibana-mcp-server/ai.md description: >- How Kibana MCP Server supports AI agents - LLM-friendly documentation, agent plugins for Claude Code and Codex, and a skill installable with skills.sh. --- # Built for agents Choose how to give your agent the server, workflow, or documentation: | | What you get | Where | | --- | --- | --- | | **Documentation for LLMs** | This whole site as plain Markdown, following the `llms.txt` standard | [LLM-friendly documentation](./llms) | | **Agent plugins** | The MCP server and the investigation skill, installed together from a plugin marketplace | [Agent plugins and skills](./agent-plugins) | | **A skill for any agent** | The investigation workflow, installable with [skills.sh](https://skills.sh/) | [Agent plugins and skills](./agent-plugins#skills-sh-any-agent) | ## What the skill teaches The `kibana-log-investigation` skill follows this sequence: 1. Bootstrap with the agent's client mode. Do not hand-edit configuration files or put passwords in command arguments. 2. Call `discover`. If empty, ask the user for the exact Kibana index or pattern and wait. Never guess, enumerate, or scan indexes. 3. Call `configure_index` only with the user's value. 4. Inspect `describe_fields` before filtering. Query an absolute time window. ## Pointing an agent at this documentation Send your agent this prompt: ```text Read https://havesomecode.github.io/kibana-mcp-server/llms.txt and use it to answer questions about Kibana MCP Server. ``` For one page, add `.md` to its URL. --- --- url: /kibana-mcp-server/ai/llms.md description: >- The documentation is published as plain Markdown for language models - llms.txt, llms-full.txt and a Markdown version of every page. --- # LLM-friendly documentation Use the plain Markdown docs below. They follow the [`llms.txt`](https://llmstxt.org/) standard and match the HTML pages. ## What is published | File | Content | Use it when | | --- | --- | --- | | [`/llms.txt`](https://havesomecode.github.io/kibana-mcp-server/llms.txt) | An index: every page with its title, description and link to its Markdown version | The agent should pick the pages it needs | | [`/llms-full.txt`](https://havesomecode.github.io/kibana-mcp-server/llms-full.txt) | The whole documentation in a single file | You want to load everything into the context at once | | `.md` | The Markdown version of one page | You need one precise page | For the `query` reference, fetch: ```text https://havesomecode.github.io/kibana-mcp-server/reference/tools/query.md ``` ## Copy a page as Markdown Click **Copy as Markdown** or **Download** under the page title. If your agent cannot browse, paste the result into its conversation. ## In an agent or an IDE Send your agent this prompt: ```text Use https://havesomecode.github.io/kibana-mcp-server/llms.txt as the documentation of Kibana MCP Server. ``` Add that line to `AGENTS.md` or `CLAUDE.md` to reuse it each session. ## How it is generated [`vitepress-plugin-llms`](https://github.com/okineadev/vitepress-plugin-llms) builds these files from the same Markdown sources as the HTML pages. --- --- url: /kibana-mcp-server/ai/agent-plugins.md description: >- Install the Kibana log investigation plugin for Claude Code or Codex, or the skill with skills.sh, and connect any MCP client to the server. --- # Agent plugins and skills Choose your agent below. Plugins install the `kibana-log-investigation` skill and register the MCP server; skills.sh installs only the skill. | Channel | Skill | MCP server | Agents | | --- | --- | --- | --- | | [Claude Code plugin marketplace](#claude-code) | Yes | Yes | Claude Code (CLI, desktop, IDE extensions) | | [Codex](#codex) | Yes | Yes | Codex CLI and the Codex app | | [skills.sh](#skills-sh-any-agent) | Yes | Manual | Cursor, Gemini CLI, Windsurf and others | Then [Connect to Kibana](#connect-to-kibana). Registration alone does not save a connection. ## Claude Code Run: ```bash claude plugin marketplace add Havesomecode/kibana-mcp-server claude plugin install kibana-log-investigation@kibana-mcp-server ``` Or run inside Claude Code: ```text /plugin marketplace add Havesomecode/kibana-mcp-server /plugin install kibana-log-investigation@kibana-mcp-server ``` For team installation, add this to the project's `.claude/settings.json`: ```json { "extraKnownMarketplaces": { "kibana-mcp-server": { "source": { "source": "github", "repo": "Havesomecode/kibana-mcp-server" } } }, "enabledPlugins": { "kibana-log-investigation@kibana-mcp-server": true } } ``` Expected: the plugin registers `npx -y @havesomecode/kibana-mcp-server serve`. Claude loads the skill for matching tasks. ## Codex 1. Run connection bootstrap. Supply credentials through a trusted environment or its secret prompt, never password arguments: ```bash npx -y @havesomecode/kibana-mcp-server bootstrap --client codex ``` 2. Install the skill: ```bash npx skills add Havesomecode/kibana-mcp-server --skill kibana-log-investigation --agent codex --global --yes ``` ### Codex plugin marketplace For a cloned checkout, use `.agents/plugins/marketplace.json`: 1. Run `npm install` and `npm run build` before installing the plugin. The build creates `plugins/kibana-log-investigation/dist/`; the installed bundle needs neither the checkout nor its `node_modules`. 2. Check Codex's MCP startup status before restarting a discussion. On failure, inspect sanitized stderr; rebuild and reinstall a missing runtime. `npm run verify:entrypoint` checks a copied plugin with a synthetic connection, not native Codex startup or a real Kibana backend. See [Getting started](../guide/getting-started#use-in-codex). ### Version-matched handoff 1. Resolve the branch, tag, or package installation to its full commit SHA: the **resolved installation commit**. 2. Hand off this immutable README URL: ```text https://raw.githubusercontent.com/Havesomecode/kibana-mcp-server//README.md ``` 3. Read `INSTALL.md` and the skills from that same commit. Branches and tags are resolution inputs, not final URLs. Do not infer a Git tag from an npm version; use the exact package's `gitHead`. In a maintained checkout, run `node scripts/readme-handoff.mjs ` to verify the commit-pinned README before handoff. ## skills.sh (any agent) Run the [`skills`](https://skills.sh/) CLI: ```bash # pick the agents interactively npx skills add Havesomecode/kibana-mcp-server --skill kibana-log-investigation # install for specific agents, without prompts npx skills add Havesomecode/kibana-mcp-server --skill kibana-log-investigation -a claude-code -a cursor -y ``` Add `-g` to install the skill globally instead of in the current project. ::: warning skills.sh installs the skill only. Declare the MCP server yourself with the snippet below. ::: ## Declare the MCP server manually With Node.js 22+, add the stdio command to your MCP client: ::: code-group ```json [.mcp.json (Claude Code, VS Code)] { "mcpServers": { "kibana-log-investigation": { "command": "npx", "args": ["-y", "@havesomecode/kibana-mcp-server", "serve"] } } } ``` ```json [Cursor (.cursor/mcp.json)] { "mcpServers": { "kibana-log-investigation": { "command": "npx", "args": ["-y", "@havesomecode/kibana-mcp-server", "serve"] } } } ``` ```toml [Codex (~/.codex/config.toml)] [mcp_servers.kibana-log-investigation] command = "npx" args = ["-y", "@havesomecode/kibana-mcp-server", "serve"] ``` ::: For a shared server [over HTTP](../deployment/http), declare the gateway URL instead. ## Connect to Kibana 1. Provision credentials through a secret provider, then save the connection once per machine. `--client none` avoids duplicate registration: ```bash export KIBANA_BASE_URL='https://kibana.example.com' export KIBANA_USERNAME='elastic' export KIBANA_PASSWORD='use-a-secret-provider' npx -y @havesomecode/kibana-mcp-server bootstrap --client none ``` Expected: a verified connection, credentials in the OS secret store, and an empty source catalog. Bootstrap does not inspect indexes. 2. Remove direct connection variables from the MCP server environment to use the saved profile. Reconnect the client or start a new session. 3. Call `discover`. If the catalog is empty, ask for the exact index or pattern and wait. Never guess, list, or scan indexes; call `configure_index` only with the user's answer. Without a saved connection, startup fails: `No saved Kibana connection is available. Run connection bootstrap first.` Direct environment startup requires an explicit `KIBANA_SOURCE_CATALOG_PATH` or configuration-file store; no implicit checkout catalog. For agent-run bootstrap, use `--client codex` in Codex; use `--client none` where a plugin or manual configuration already registers the server. --- --- url: /kibana-mcp-server/reference/cli.md description: >- Commands and options of the kibana-mcp-server command line: serve, bootstrap and setup. --- # Command line ```bash npx -y @havesomecode/kibana-mcp-server ``` Without a command, the server starts over stdio. ## `serve` Start the MCP server; stdio is the default. | Option | Description | | --- | --- | | `--profile ` | Profile to serve. Same as `KIBANA_PROFILE`. | | `--config ` | [Configuration file](./config-file). Same as `KIBANA_MCP_CONFIG`. | | `--http` | Serve over Streamable HTTP instead of stdio | | `--host ` | HTTP listen host, default `127.0.0.1` | | `--port ` | HTTP listen port, default `3000` | | `--path ` | HTTP endpoint path, default `/mcp` | | `--profile-header ` | HTTP header carrying the profile of the caller | `--host`, `--port`, `--path` and `--profile-header` require `--http`. The shared secret has no flag on purpose: set `KIBANA_HTTP_AUTH_TOKEN`. ## `bootstrap` Bootstrap the connection only, even without options: verify Kibana, create an empty managed catalog, save credentials and optionally register a client. **Never inspects or configures indexes.** Supply flags or `KIBANA_BASE_URL`, `KIBANA_USERNAME` and `KIBANA_PASSWORD` for prompt-free operation; otherwise only missing connection values are prompted. | Option | Description | | --- | --- | | `--profile ` | Saved profile name | | `--url ` | Kibana base URL | | `--username ` | Kibana basic-auth username | | `--password-stdin` | Read the password from stdin | | `--password-env ` | Read the password from an environment variable | | `--timeout ` | Kibana request timeout | | `--client ` | Client registration mode | | `--package ` | Package specifier used for client registration | | `--mcp-name ` | MCP registration name | | `--replace` | Replace an existing catalog | | `--no-default` | Do not make this profile the default | ## `setup` Choose the setup flow: * **Bare `setup`:** guided setup for explicitly selected hand-authored catalogs, even with connection environment variables set. * **Any explicit option from the `bootstrap` table:** connection-only bootstrap, with the same options. Prompts only for missing connection values; never inspects or configures indexes. ```bash npx -y @havesomecode/kibana-mcp-server setup --profile prod --client none ``` ## Connection-option rules * Flags override environment fallbacks. * Passwords: stdin, environment variable or secret prompt only; never a password argument. * `--password-stdin` and `--password-env` are mutually exclusive. * `--timeout` must be an integer from 1 to 120000 milliseconds. --- --- url: /kibana-mcp-server/reference/environment.md description: Every KIBANA_* environment variable read by the server. --- # Environment variables Set environment variables to override [configuration-file](./config-file) defaults. ## Connection * `KIBANA_BASE_URL` * `KIBANA_USERNAME` * `KIBANA_PASSWORD` * `KIBANA_PROFILE` optional, selects a saved non-default machine profile * `KIBANA_TIMEOUT_MS` optional, default `10000` * `KIBANA_SOURCE_CATALOG_PATH` required for direct environment-based startup unless a configuration-file store supplies the catalog; saved profiles carry their own path. No implicit default. ### Deadlines and cancellation `KIBANA_TIMEOUT_MS` covers each Kibana HTTP request end to end: queue wait, connection and response-body read. * Per configured client: eight concurrent requests, 32 queued. Excess work fails immediately with `KIBANA_OVERLOADED`. * Cancelled MCP calls abort queued/in-flight backend work. * Shared schema discovery: cancellation stops only that caller's wait while others remain; the last caller's cancellation aborts the shared backend request. Backend failures use stable codes: `KIBANA_TIMEOUT`, `KIBANA_CANCELLED`, `KIBANA_OVERLOADED`, `KIBANA_AUTHENTICATION`, `KIBANA_DNS`, `KIBANA_TLS`, `KIBANA_CONNECTION`, `KIBANA_RESPONSE` and [others](./errors). Failed backend tool results expose diagnostics in `_meta.kibana_request_error`. ### Choose the base URL Set `KIBANA_BASE_URL` to the base prefix; the server joins it with configured backend/schema paths. Use: * `https://kibana.example.com` * `https://gateway.example.com/logs` when Kibana is reverse-proxied under `/logs` Do not use: * `https://kibana.example.com/internal/search/es` * `https://gateway.example.com/logs/internal/search/es` Sources already carry paths such as `/internal/search/es`; a full API path here usually produces a bad combined URL and `404`. ### Select startup mode * **Direct environment:** supply all three connection values plus explicit `KIBANA_SOURCE_CATALOG_PATH` or a [configuration-file store](../deployment/catalog-stores). Only the named catalog is read; the checkout is never searched. Use `config/sources.example.json` only as a deliberate hand-authored template, never automatic setup input. * **Saved profile:** unset connection variables; optionally select `KIBANA_PROFILE`. Credentials and catalog path come from saved state. Without a saved connection, startup fails with `No saved Kibana connection is available. Run connection bootstrap first.` ## Configuration file * `KIBANA_MCP_CONFIG` path of a [configuration file](../guide/configuration-file), same as `serve --config` ## HTTP transport * `KIBANA_HTTP_HOST` listen host, default `127.0.0.1` * `KIBANA_HTTP_PORT` listen port, default `3000` * `KIBANA_HTTP_PATH` endpoint path, default `/mcp` * `KIBANA_HTTP_PROFILE_HEADER` header carrying the profile of the caller, no default * `KIBANA_HTTP_AUTH_TOKEN` shared secret expected as `Authorization: Bearer ` * `KIBANA_HTTP_OIDC_ISSUER` OpenID Connect issuer protecting the endpoint, instead of the shared secret * `KIBANA_HTTP_OIDC_RESOURCE` public URL of the MCP endpoint, required with the issuer * `KIBANA_HTTP_OIDC_AUDIENCE` expected token audience, default the resource * `KIBANA_HTTP_OIDC_MODE` `offline` or `introspection` * `KIBANA_HTTP_OIDC_CLIENT_ID`, `KIBANA_HTTP_OIDC_CLIENT_SECRET` client used for introspection * `KIBANA_HTTP_OIDC_REQUIRED_SCOPES` scopes every token must carry, separated by spaces * `KIBANA_HTTP_OIDC_SCOPES_SUPPORTED` scopes advertised to clients * `KIBANA_HTTP_OIDC_PROFILE_CLAIM` claim naming the caller at the issuer, default `sub`; the profile is `#` * `KIBANA_HTTP_OIDC_ALLOW_INSECURE_REQUESTS` `true` allows a non-HTTPS issuer or resource, for development only See [HTTP transport](../deployment/http) and [OpenID Connect authentication](../deployment/oidc). ## Catalog stores * `KIBANA_STORE_URL` connection URL of the bundled stores when `storeOptions.url` is not set * `REDIS_URL`, `DATABASE_URL`, `MONGODB_URI`, `MONGO_URL` are read next, by the matching store See [Catalog stores](../deployment/catalog-stores). ## Machine state * `KIBANA_STATE_DIR` directory holding saved profiles and their generated catalogs. Defaults to the configuration directory of the operating system. --- --- url: /kibana-mcp-server/reference/config-file.md description: Every option of the configuration file, its precedence and its constraints. --- # Configuration file reference Load a file explicitly with `serve --config ` or `KIBANA_MCP_CONFIG`. It executes as code; working-directory discovery is disabled. ```ts import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config"; export default defineKibanaMcpConfig({ // … }); ``` Default-export an object or a function (sync or async) returning it. `defineKibanaMcpConfig` provides types only. ## Options | Option | Type | Overridden by | Description | | --- | --- | --- | --- | | `url` | `string` | `KIBANA_BASE_URL` | Kibana base URL | | `user` | `string` | `KIBANA_USERNAME` | Kibana user | | `password` | `string` | `KIBANA_PASSWORD` | Kibana password | | `timeoutMs` | `number` | `KIBANA_TIMEOUT_MS` | Backend deadline in milliseconds | | `profile` | `string` | `--profile`, `KIBANA_PROFILE` | Profile served when the transport names none | | `store` | store or factory | | Where source catalogs are kept. Without it, catalogs are files. | | `storeOptions` | any | | Handed to the `store` factory | | `http.host` | `string` | `--host`, `KIBANA_HTTP_HOST` | Listen host | | `http.port` | `number` | `--port`, `KIBANA_HTTP_PORT` | Listen port | | `http.path` | `string` | `--path`, `KIBANA_HTTP_PATH` | Endpoint path | | `http.profileHeader` | `string` | `--profile-header`, `KIBANA_HTTP_PROFILE_HEADER` | Header carrying the profile of the caller | | `http.authToken` | `string` | `KIBANA_HTTP_AUTH_TOKEN` | Shared secret expected as a bearer token | | `http.oidc.issuer` | `string` | `KIBANA_HTTP_OIDC_ISSUER` | OpenID Connect issuer protecting the endpoint | | `http.oidc.resource` | `string` | `KIBANA_HTTP_OIDC_RESOURCE` | Public URL of the MCP endpoint, required with `issuer` | | `http.oidc.audience` | `string` | `KIBANA_HTTP_OIDC_AUDIENCE` | Expected token audience, default `resource` | | `http.oidc.mode` | `"offline"` | `"introspection"` | `KIBANA_HTTP_OIDC_MODE` | Token verification, default `introspection` with a client id, `offline` otherwise | | `http.oidc.clientId` | `string` | `KIBANA_HTTP_OIDC_CLIENT_ID` | Client used for introspection | | `http.oidc.clientSecret` | `string` | `KIBANA_HTTP_OIDC_CLIENT_SECRET` | Secret of that client | | `http.oidc.requiredScopes` | `string[]` | `KIBANA_HTTP_OIDC_REQUIRED_SCOPES` | Scopes every token must carry | | `http.oidc.scopesSupported` | `string[]` | `KIBANA_HTTP_OIDC_SCOPES_SUPPORTED` | Scopes advertised to clients | | `http.oidc.profileClaim` | `string` | `KIBANA_HTTP_OIDC_PROFILE_CLAIM` | Claim naming the caller at the issuer, default `sub`; the profile is `#` | | `http.oidc.allowInsecureRequests` | `boolean` | `KIBANA_HTTP_OIDC_ALLOW_INSECURE_REQUESTS` | Allow a non-HTTPS issuer or resource. Development only. | ## Precedence Command-line flag → environment variable → configuration file → built-in default. ## Constraints * `store` requires `url`, `user` and `password`, from the file or from the environment. * `store` cannot be combined with `KIBANA_SOURCE_CATALOG_PATH`. * Without `store`, direct connections require explicit `KIBANA_SOURCE_CATALOG_PATH`; saved profiles use their saved path. No implicit catalog path. * `http.oidc` excludes `http.authToken` and `http.profileHeader`, requires named profiles, and its `resource` path must be `http.path`. * A `.ts` file needs Node.js 22.18 or later. A `.mjs` file works on every supported version. * The package must resolve from the file's directory for its import to work. ## Types ```ts import type { CatalogStore } from "@havesomecode/kibana-mcp-server/config"; import { CatalogConflictError } from "@havesomecode/kibana-mcp-server/config"; ``` See [Custom store](../deployment/stores/custom) for the `CatalogStore` contract. --- --- url: /kibana-mcp-server/reference/source-catalog.md description: The format of a source definition, the supported backends and schema backends. --- # Source catalog Use `configure_index` to maintain a profile's JSON source catalog. Hand-authored catalogs remain supported for advanced setups. ## Source definition Define each logical source with: * stable `id` * human-readable `name` * `timeField` * `tags` and optional `description` * `fieldHints` so agents can form exact-match filters * `defaultTextFields` for free-text search * `evidenceFields` to elevate likely report-worthy fields * `backend` describing how the source is queried * optional `schema` describing how field introspection is performed for `describe_fields` and other schema-dependent features ## Search backends * `kibana_internal_search_es` * POSTs to a Kibana endpoint like `/internal/search/es` * wraps the query body inside `{ "params": { "index": ..., "body": ... } }` * adds `kbn-xsrf` * `elasticsearch_search` * POSTs the compiled Elasticsearch body directly to the configured path ## Schema backends * `kibana_data_views_fields`: tries wildcard field-metadata endpoints, preferring `/internal/data_views/_fields_for_wildcard`. * `kibana_index_patterns_fields`: tries legacy `/api/index_patterns/_fields_for_wildcard` and related fallbacks. * `elasticsearch_field_caps` * GETs an Elasticsearch field capabilities endpoint such as `//_field_caps?fields=*` * uses `schema.path` when provided or derives the default from `schema.index` Both Kibana schema backends add `pattern=...` from `schema.index` or `backend.index`. If all known metadata endpoints return `404`, they sample fields through the configured search backend. Some deployments expose only search transport; sampling is heuristic, not authoritative, but often recovers `.keyword` promotion and object-path discovery. Direct `elasticsearch_field_caps` failures return without that fallback. ## Application log example ```json { "id": "app_logs", "name": "Application Logs", "timeField": "@timestamp", "backend": { "kind": "kibana_internal_search_es", "path": "/internal/search/es", "index": "app-logs-*" }, "schema": { "kind": "kibana_data_views_fields", "index": "app-logs-*" } } ``` Omit `schema.path` here: try known Kibana metadata endpoints, then sample through search transport if all return `404`. --- --- url: /kibana-mcp-server/reference/tools/configure-index.md description: >- Reference of the configure_index tool: configure one index or pattern as a logical source. --- # `configure_index` Configure **exactly the index or pattern explicitly named by the user**. Never guess, list or scan candidates; this tool accepts no credentials. Input: ```json { "index": "app-logs-*", "source_name": "Application logs" } ``` The tool inspects fields, runs one bounded validation search for that index only, then persists the generated logical source in the managed catalog. Use optional `source_id`, `time_field` and `replace` for explicit collision or schema handling. --- --- url: /kibana-mcp-server/reference/tools/discover.md description: >- Reference of the discover tool: list the logical sources of the current profile. --- # `discover` List the current profile's configured logical sources and field hints. Input: ```json { "query": "application", "limit": 10 } ``` Output includes: * source id * source name * time field * tags * configured field hints --- --- url: /kibana-mcp-server/reference/tools/describe-fields.md description: >- Reference of the describe_fields tool: inspect the effective fields of a source. --- # `describe_fields` Inspect one source's effective fields for safe exact matching, grouping or sorting. Requires a configured schema backend; missing configuration fails clearly, not by guessing. When direct schema endpoints are unavailable, fields are inferred from sample hits through the configured search backend. Input: ```json { "source_id": "workflow_metrics", "query": "event", "limit": 50 } ``` Output includes: * field name * field type * `searchable` * `aggregatable` * available subfields * `preferred_exact_field` when a safer exact-match variant exists, such as `.keyword` --- --- url: /kibana-mcp-server/reference/tools/query.md description: 'Reference of the query tool: search sources over a time window in six modes.' --- # `query` Search one or more sources over an absolute time window. Input: ```json { "source_ids": ["app_logs", "workflow_metrics"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T12:15:00Z", "text": "SERVICE_RELOAD", "filters": [ { "field": "region", "value": "us-east-1" }, { "field": "traceId", "value": "trace-12345" } ], "nested_filters": [ { "path": "steps", "field": "name", "value": "CACHE_REFRESH" } ], "extract_nested": true, "mode": "hits", "sort_by": "total_duration_ms", "sort": "desc", "limit": 50 } ``` ## Modes | Mode | Required fields | Optional fields | | --- | --- | --- | | `hits` | | `sort_by` (sort on a chosen field) | | `count` | | | | `histogram` | `histogram_interval` | | | `terms` | `group_by` | | | `stats` | `stats_field` | `group_by` | | `grouped_top_hits` | `group_by`, `sort_by` | `top_hits_size` | ## Field resolution * Exact filters and grouped bucket fields use field-hint aliases and, with metadata, schema-aware exact-field preference. * Rewriting `event` to safer exact field `event.keyword` returns an advisory. * Without metadata, queries use the requested field and return `schema_unavailable`; exact-safe resolution is not claimed. * Nested filters require working `schema` metadata; otherwise they fail early with a configuration error, not a backend 404. Every response includes `query_echo`: sources, bounds, filters and mode actually used. ## Cursor pagination For large hit sets, pass `cursor`. Supported only for **single-source `hits`** queries: ```json { "source_ids": ["workflow_metrics"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T12:15:00Z", "mode": "hits", "limit": 100, "cursor": "REPLACE_ME_NEXT_CURSOR" } ``` ## `stats` example ```json { "source_ids": ["workflow_metrics"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T12:15:00Z", "mode": "stats", "stats_field": "total_duration_ms", "group_by": "trace_id", "limit": 25 } ``` ## `grouped_top_hits` example ```json { "source_ids": ["workflow_metrics"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T12:15:00Z", "mode": "grouped_top_hits", "group_by": "trace_id", "sort_by": "total_duration_ms", "top_hits_size": 1, "limit": 25 } ``` --- --- url: /kibana-mcp-server/reference/tools/filter.md description: 'Reference of the filter tool: exact-field filtering without alias resolution.' --- # `filter` Filter a known backend field directly, without field-hint alias resolution. Input: ```json { "source_ids": ["workflow_metrics"], "start_time": "2026-04-02T12:00:00Z", "end_time": "2026-04-02T12:15:00Z", "field": "eventName.keyword", "value": "WORKFLOW_RELOAD_STATS", "nested_filters": [ { "path": "steps", "field": "steps.name.keyword", "value": "CACHE_REFRESH" } ], "extract_nested": true, "mode": "hits", "sort_by": "total_duration_ms", "limit": 50 } ``` * Returns the same structured output envelope as `query`, without field-hint alias rewrites. * With schema metadata, a logical text field such as `event` may still promote to preferred exact field `event.keyword`. An exact backend field such as `event.keyword` is used as-is. * `sort_by` supports `hits` and `grouped_top_hits`. --- --- url: /kibana-mcp-server/reference/errors.md description: Kibana request error codes, catalog errors and HTTP transport statuses. --- # Errors ## Kibana request errors Read `_meta.kibana_request_error` on failed Kibana tool calls: `code`, `phase` (`queue`, `request` or `response`), `source_id`, `timeout_ms`, plus `status` or `cause_code` when available. | Code | Meaning | | --- | --- | | `KIBANA_TIMEOUT` | The call exceeded its backend deadline (`KIBANA_TIMEOUT_MS`) | | `KIBANA_CANCELLED` | The MCP client cancelled the tool call | | `KIBANA_OVERLOADED` | Too many requests are already running and queued for this connection | | `KIBANA_AUTHENTICATION` | Kibana rejected the credentials | | `KIBANA_HTTP` | Kibana answered with another error status | | `KIBANA_RESPONSE` | The response could not be read or parsed | | `KIBANA_DNS` | The host name could not be resolved | | `KIBANA_TLS` | The TLS handshake failed | | `KIBANA_CONNECTION` | The connection was refused or reset | | `KIBANA_NETWORK` | Another network failure | ## Concurrency limits Each Kibana connection allows eight concurrent requests and thirty-two queued; excess calls fail immediately with `KIBANA_OVERLOADED`. Profiles sharing a connection share its budget. ## Startup errors | Message or condition | Action | | --- | --- | | `No saved Kibana connection is available. Run connection bootstrap first.` | Run connection-only `bootstrap --client codex` (or `--client none` for an already registered server), then reconnect the client. | | `KIBANA_SOURCE_CATALOG_PATH is required for an environment connection.` | Name the catalog explicitly, declare a store, or unset the connection variables to use a saved profile. | | `Incomplete Kibana bootstrap environment. Provide KIBANA_BASE_URL, KIBANA_USERNAME, and KIBANA_PASSWORD together.` | Supply all three together for direct startup, or unset them to use saved credentials. | ## Catalog errors | Message | Cause | | --- | --- | | `Source catalog '…' changed concurrently. Retry the operation.` | Another caller changed the catalog while `configure_index` was validating. Retry. | | `configure_index can update only a managed generated catalog.` | The catalog was written or edited by hand. The server will not overwrite it. | | `Source catalog '…' does not exist although sources are configured.` | The loaded catalog was removed while the server was running. Restore the configured file before retrying. | | `Source id '…' already refers to a different index.` | Pass `replace: true` after the user approved the replacement. | ## HTTP transport | Status | Cause | | --- | --- | | `401` | Missing or wrong bearer token, or missing profile header | | `400` | Invalid profile header value | | `404` | Unknown path | | `413` | Request body larger than 4 MB | --- --- url: /kibana-mcp-server/contributing/development.md description: Set up a checkout, run the checks and build the documentation site. --- # Development ```bash git clone https://github.com/Havesomecode/kibana-mcp-server cd kibana-mcp-server npm install ``` ## Checks ```bash npm run lint npm run format:check npm run check:types npm test npm run verify ``` `npm run verify` runs everything CI runs: type check, lint, format check, tests, build, and the release and package contracts. ## Documentation site ```bash npm run docs:dev npm run docs:build ``` The site lives in `website/` and is published to GitHub Pages from `master`. ## Conventions * Commits follow [Conventional Commits](https://www.conventionalcommits.org/); releases are cut by semantic-release. * New dependencies must be at least 20 days old, the same cooldown Dependabot applies. * See `CONTRIBUTING.md` and `SECURITY.md` in the repository for contribution and disclosure rules. --- --- url: /kibana-mcp-server/contributing/architecture.md description: >- Source layout, the path of a tool call, the profile registry and catalog stores. --- # Architecture ## Source layout | Folder | Content | | --- | --- | | `src/bin/` | The executable and CLI wiring | | `src/commands/` | One Commander command per file: `serve`, `setup`, `bootstrap` | | `src/config/` | Environment parsing, configuration file loading, startup resolution | | `src/kibana/` | Kibana client, schema and source catalogs | | `src/profiles/` | Profile runtimes, saved profiles, secrets, state lock | | `src/query/` | Query compilation, cursors, result normalization | | `src/setup/` | Bootstrap, guided setup, client registration | | `src/stores/` | The catalog store contract and its implementations | | `src/tools/` | One MCP tool per file | | `src/transports/` | stdio and HTTP transports | | `src/server.ts` | Builds the MCP server and registers the tools | ## A tool call 1. The transport receives the request. Over HTTP it validates the shared secret and reads the profile header. 2. `createApplication` builds an MCP server. Over HTTP there is one per request; state lives outside it. 3. The tool handler asks the **profile registry** for the runtime of the profile. A runtime holds the configuration, the source catalog, the Kibana client and the schema cache. 4. The runtime is bound to the call through `AsyncLocalStorage`, so deeper code reads it without receiving it as an argument. 5. The tool runs against that runtime only. ## Profile registry * Runtimes are created on demand and cached, with a least-recently-used limit. * Profiles sharing a Kibana connection share one client, hence one concurrency budget. * A store-backed catalog is read again on every call. The parsed catalog and the schema cache are kept while its content is unchanged. ## Catalog stores Every catalog write goes through one contract: read the content, compute the next one, replace it only if it is unchanged. Files, Redis, PostgreSQL, MongoDB and SQLite all implement it. See [Custom store](../deployment/stores/custom). ## Startup modes | Configuration | Result | | --- | --- | | Connection plus a store, or `{profile}` in the catalog path | One connection, one catalog per profile | | Connection plus a plain catalog path | One connection, one catalog | | Saved profiles | One connection and one catalog per saved profile | | Connection without a catalog path or store | Startup fails; an explicit catalog is required | | Nothing | Startup fails: `No saved Kibana connection is available. Run connection bootstrap first.` |