Docker image
Create these files to run HTTP with PostgreSQL catalogs. Users sign in with your OpenID Connect provider, so no gateway is needed.
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.yamlkibana.mcp.config.mjs
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 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:
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.
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.
docker compose up -d --buildCheck health:
curl -s http://127.0.0.1:3000/healthzCheck that unauthenticated calls are challenged:
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:
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 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.