@havesomecode/kibana-mcp-server 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,6 @@ export declare function buildGeneratedSource(rawIndexes: string[], sourceName?:
45
45
  export declare function chooseGeneratedSourceFields(fields: SourceFieldDescriptor[], explicitTimeField?: string): GeneratedSourceFields;
46
46
  export declare function runBootstrap(rawOptions: BootstrapOptions, dependencies?: BootstrapDependencies): Promise<BootstrapResult>;
47
47
  export declare function resolveCurrentPackageSpecifier(): Promise<string>;
48
- export declare function createGeneratedCatalog(sourceOrSources: SourceDefinition | SourceDefinition[]): ReturnType<typeof sourceCatalogSchema.parse>;
48
+ export declare function createGeneratedCatalog(sources: SourceDefinition[]): ReturnType<typeof sourceCatalogSchema.parse>;
49
49
  export declare function parseGeneratedCatalog(content: string): ReturnType<typeof sourceCatalogSchema.parse> | undefined;
50
50
  export {};
@@ -264,8 +264,7 @@ export async function resolveCurrentPackageSpecifier() {
264
264
  }
265
265
  throw new Error("Could not resolve the running package version for deterministic registration.");
266
266
  }
267
- export function createGeneratedCatalog(sourceOrSources) {
268
- const sources = Array.isArray(sourceOrSources) ? sourceOrSources : [sourceOrSources];
267
+ export function createGeneratedCatalog(sources) {
269
268
  const parsedSources = sources.map((source) => sourceDefinitionSchema.parse(source));
270
269
  return sourceCatalogSchema.parse({
271
270
  generatedBy: {
@@ -27,8 +27,7 @@ export async function executeConfigureIndex(inputValue, options) {
27
27
  // only happens if the catalog is still the one that was read, so Kibana is never called under a lock.
28
28
  const catalog = options.config.catalog ?? fileCatalogOf(options.config, options.stateRoot);
29
29
  const existingContent = await catalog.read();
30
- // Sources without a catalog at the persisted location were loaded from somewhere else, such as
31
- // the static fallback catalog: starting from an empty catalog would silently drop them.
30
+ // A disappeared catalog must not silently discard sources already loaded into memory.
32
31
  if (existingContent === undefined && options.config.sources.length > 0) {
33
32
  throw new Error(`Source catalog '${catalog.location}' does not exist although sources are configured. Refusing to replace them with a new managed catalog.`);
34
33
  }
@@ -41,7 +41,7 @@ async function listen(resolved, startupConfiguration) {
41
41
  throw new Error("A profile header requires named profiles: use saved profiles, a store in the configuration file, or the {profile} placeholder in KIBANA_SOURCE_CATALOG_PATH.");
42
42
  }
43
43
  if (!resolved.profileHeader && !registry.hasDefault) {
44
- throw new Error("The HTTP transport needs a profile header, or a default profile named with --profile or KIBANA_PROFILE to serve every caller the same catalog. The legacy 'configure' tool is available over stdio only.");
44
+ throw new Error("The HTTP transport needs a profile header, or a default profile named with --profile or KIBANA_PROFILE to serve every caller the same catalog.");
45
45
  }
46
46
  // Modern and handshake-free legacy calls stay per-request.
47
47
  const handler = createMcpHandler((context) => {
@@ -2,8 +2,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
2
2
  import { DEFAULT_PROFILE_NAME, loadStartupConfiguration } from "../config/startup.js";
3
3
  import { createApplication } from "../server.js";
4
4
  export async function startMcpServer(envInput = process.env) {
5
- // stdio serves one global configuration: the default profile, or the legacy 'configure' flow.
6
- // It has a single caller, so per-profile catalogs fall back to one well-known profile.
5
+ // stdio has one caller and serves the configured default profile.
7
6
  const { startupConfiguration } = await loadStartupConfiguration(envInput);
8
7
  let server;
9
8
  let closing;
@@ -60,7 +60,6 @@ export interface ResolvedAppConfig extends AppConfig {
60
60
  /** Set when the catalog of the profile lives in a catalog store rather than at a plain path. */
61
61
  catalog?: CatalogHandle;
62
62
  sourceCatalogPath?: string;
63
- sourceCatalogOrigin?: "environment" | "profile" | "runtime_default" | "static_default";
64
63
  }
65
64
  export interface SavedProfile {
66
65
  id: string;
@@ -15,9 +15,11 @@ This matrix distinguishes guaranteed support from best-effort behavior. Each cla
15
15
  | Fully autonomous plugin install by prompting alone across Codex model variants | Best-effort | Field reports show model-dependent behavior | Manual install from the local marketplace is the supported fallback when the agent cannot complete the UI step itself. |
16
16
  | `kibana_internal_search_es` backend | Guaranteed | Verified in tests | Primary search transport. |
17
17
  | `elasticsearch_search` backend | Guaranteed | Verified in tests | Direct Elasticsearch-compatible search path. |
18
- | Schema metadata endpoints | Best-effort | Known 404s in some deployments | `describe_fields` may fail if endpoints are blocked. |
18
+ | Schema metadata endpoints | Best-effort | Known 404s in some deployments | The server can sample fields through the configured search backend on `404`; this fallback is heuristic and `describe_fields` can still fail. |
19
19
  | Nested query features | Best-effort | Depends on schema availability | Requires schema metadata for validation. |
20
20
  | Connection-only package bootstrap | Guaranteed | Verified through CLI, rollback, packlist, and release-contract tests | Use `npx -y @havesomecode/kibana-mcp-server bootstrap --client codex`; configure an index only after the user names it. |
21
21
  | Agent Skill installation | Guaranteed after publication | Repository discovery is verified with the `skills` CLI; skills.sh indexing follows the public branch | Install `kibana-log-investigation` from `Havesomecode/kibana-mcp-server`. |
22
22
 
23
+ Saved profiles carry an explicit catalog path. Direct connection startup requires `KIBANA_SOURCE_CATALOG_PATH` or a configuration-file catalog store; no checkout catalog is loaded implicitly. Without a saved connection, startup fails with `No saved Kibana connection is available. Run connection bootstrap first.` Guided `setup` remains supported for hand-authored catalogs.
24
+
23
25
  Upgrade a row from best-effort to guaranteed only after verifying it in CI or in a reproducible environment test and documenting the evidence here.
@@ -66,7 +66,7 @@ The release artifact must include:
66
66
  The release artifact must exclude:
67
67
 
68
68
  - tests and fixtures
69
- - local operator config (`config/sources.json`, `config/sources.runtime.json`)
69
+ - local operator catalogs and saved machine profiles
70
70
  - development-only scripts and tooling that do not affect runtime execution
71
71
 
72
72
  ## Versioning
@@ -10,8 +10,11 @@ updated: 2026-04-09
10
10
 
11
11
  - CI is green on `master`.
12
12
  - `npm run verify` passes locally.
13
+ - Build output must correspond to current source: `npm run build` replaces root `dist/`, and every packed `dist/src/**/*.js` or `dist/src/**/*.d.ts` must have its corresponding `src/**/*.ts` file. `npm run verify:packlist` rejects orphaned outputs from removed modules.
13
14
  - The compatibility matrix is up to date (`docs/project/compatibility-matrix.md`).
14
15
  - Any new env or config requirements are documented in `README.md` and `INSTALL.md`.
16
+ - Startup requires a connection and an explicit catalog path or store; saved profiles use their stored path. Docs must not advertise a credential-taking MCP setup tool or implicit checkout catalog.
17
+ - Connection-only bootstrap remains the default install flow; guided setup remains supported for explicitly selected hand-authored catalogs.
15
18
  - The hosted homepage still matches the supported install paths and support posture.
16
19
  - The npm scope and package name (`@havesomecode/kibana-mcp-server`) are owned by the maintainers.
17
20
  - Maintainers understand that released versions are tracked in npm, tags, and GitHub Releases, not by the `package.json` version committed in git.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Support Policy
3
3
  status: active
4
- updated: 2026-04-09
4
+ updated: 2026-10-14
5
5
  ---
6
6
 
7
7
  # Support Policy
@@ -15,8 +15,14 @@ This MCP is designed for operators and AI agents that need read-only access to K
15
15
  - Node.js 22+ runtime
16
16
  - Repo-local Codex plugin install
17
17
  - `kibana_internal_search_es` and `elasticsearch_search` backends
18
- - Guided machine setup with saved profiles and imported source catalogs
19
- - Runtime configuration via `configure` plus persisted non-secret source catalogs
18
+ - Connection-only bootstrap with saved profiles and platform credentials
19
+ - Guided machine setup for explicitly selected hand-authored source catalogs
20
+ - Startup with a saved profile, or a complete explicit connection plus `KIBANA_SOURCE_CATALOG_PATH` or a configuration-file catalog store
21
+ - Explicit user-approved source configuration through `configure_index`; no MCP tool accepts connection credentials
22
+
23
+ ## Connection Setup Boundary
24
+
25
+ Connection setup is an operator step, not an MCP capability. The credential-taking `configure` tool and unconfigured startup are intentionally retired in favor of `bootstrap` plus `configure_index`. Direct environment startup must select a catalog path or store explicitly; the server no longer searches the working directory for one. This does not change the catalog JSON schema or delete existing catalog or profile files.
20
26
 
21
27
  ## Best-Effort Support
22
28
 
@@ -27,14 +33,16 @@ This MCP is designed for operators and AI agents that need read-only access to K
27
33
 
28
34
  Best-effort means we will investigate issues but cannot guarantee behavior across all deployments.
29
35
 
36
+ Startup without a saved connection fails with `No saved Kibana connection is available. Run connection bootstrap first.` Run connection-only bootstrap before reconnecting the MCP client. For saved-profile use, leave direct connection variables unset in the server environment.
37
+
30
38
  ## Known Environment Constraints
31
39
 
32
40
  - Some Codex model variants are less reliable at completing repo-local plugin installation without a manual Codex UI step. The supported fallback is to install the plugin manually from the local marketplace, then continue with MCP configuration.
33
- - Some Kibana deployments proxy or block schema metadata endpoints. In those cases, schema-aware features may be unavailable.
41
+ - Some Kibana deployments proxy or block schema metadata endpoints. When all known Kibana data-view or index-pattern metadata endpoints return `404`, the server can sample fields through the configured search backend. Direct Elasticsearch field-capability errors do not use this fallback. Sampling is heuristic, not authoritative; schema-dependent features can still be unavailable.
34
42
  - `KIBANA_BASE_URL` must be the Kibana base prefix, not a full search endpoint such as `/internal/search/es`.
35
43
  - Linux secure storage depends on a Secret Service-compatible keyring being available and unlocked.
36
- - If you run staging and production side by side, prefer saved machine profiles plus `KIBANA_PROFILE` selection. If you stay on env bootstrap, keep `KIBANA_SOURCE_CATALOG_PATH` distinct per entry so runtime-persisted sources do not overwrite each other.
37
- - Credentials are not persisted to repo-local files or tracked config. Guided setup stores them in the platform credential store.
44
+ - If you run staging and production side by side, prefer saved machine profiles plus `KIBANA_PROFILE` selection. For direct environment startup, name `KIBANA_SOURCE_CATALOG_PATH` explicitly and keep it distinct per entry so managed sources from different environments do not mix.
45
+ - Credentials are not persisted to repo-local files or tracked config. Bootstrap and guided setup store them in the platform credential store.
38
46
 
39
47
  ## Security Reporting
40
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@havesomecode/kibana-mcp-server",
3
- "version": "1.4.1",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "description": "Read-only MCP server for Kibana-backed log investigation and schema-aware querying",
6
6
  "homepage": "https://havesomecode.github.io/kibana-mcp-server/",
@@ -62,7 +62,7 @@
62
62
  "node": ">=22.0.0"
63
63
  },
64
64
  "scripts": {
65
- "build": "tsc -p tsconfig.build.json && node scripts/build-plugin.mjs",
65
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node scripts/build-plugin.mjs",
66
66
  "check:types": "tsc --noEmit -p tsconfig.json",
67
67
  "lint": "biome lint .",
68
68
  "format": "biome format . --write",
@@ -28758,16 +28758,8 @@ function isMissingFileError3(error48) {
28758
28758
  }
28759
28759
 
28760
28760
  // src/config/environment.ts
28761
- var DEFAULT_RUNTIME_SOURCE_CATALOG_PATH = "config/sources.runtime.json";
28762
- var DEFAULT_STATIC_SOURCE_CATALOG_PATH = "config/sources.json";
28763
28761
  var PROFILE_NAME_ENV = "KIBANA_PROFILE";
28764
28762
  var PROFILE_PLACEHOLDER = "{profile}";
28765
- var NoSavedProfileError = class extends Error {
28766
- constructor(message) {
28767
- super(message);
28768
- this.name = "NoSavedProfileError";
28769
- }
28770
- };
28771
28763
  var bootstrapEnvSchema = external_exports.object({
28772
28764
  KIBANA_BASE_URL: external_exports.url(),
28773
28765
  KIBANA_USERNAME: external_exports.string().min(1),
@@ -28858,28 +28850,19 @@ function buildKibanaConfig(env) {
28858
28850
  timeoutMs: env.KIBANA_TIMEOUT_MS ?? 1e4
28859
28851
  };
28860
28852
  }
28861
- function resolveSourceCatalogPath(envInput = process.env) {
28862
- const explicitPath = envInput.KIBANA_SOURCE_CATALOG_PATH?.trim();
28863
- if (explicitPath) {
28864
- return explicitPath;
28865
- }
28866
- return DEFAULT_RUNTIME_SOURCE_CATALOG_PATH;
28867
- }
28868
28853
  async function loadConfigFromEnvironment(envInput = process.env, dependencies = {}) {
28869
28854
  const overrides = startupOverrideSchema.parse(envInput);
28870
28855
  const readFileImpl = dependencies.readFile ?? readFile4;
28871
28856
  const kibana = readBootstrapConnection(envInput);
28872
28857
  if (kibana) {
28873
- const preferredPath = resolveSourceCatalogPath(envInput);
28874
- const sourceCatalog2 = await loadSourceCatalog(
28875
- preferredPath === DEFAULT_RUNTIME_SOURCE_CATALOG_PATH ? [preferredPath, DEFAULT_STATIC_SOURCE_CATALOG_PATH] : [preferredPath],
28876
- readFileImpl
28877
- );
28858
+ const sourceCatalogPath2 = overrides.KIBANA_SOURCE_CATALOG_PATH?.trim();
28859
+ if (!sourceCatalogPath2) {
28860
+ throw new Error("KIBANA_SOURCE_CATALOG_PATH is required for an environment connection.");
28861
+ }
28878
28862
  return {
28879
28863
  kibana,
28880
- sources: sourceCatalogSchema.parse(sourceCatalog2.value).sources,
28881
- sourceCatalogPath: preferredPath,
28882
- sourceCatalogOrigin: sourceCatalog2.loadedFrom === DEFAULT_STATIC_SOURCE_CATALOG_PATH ? "static_default" : preferredPath === DEFAULT_RUNTIME_SOURCE_CATALOG_PATH ? "runtime_default" : "environment"
28864
+ sources: await loadSourceCatalog(sourceCatalogPath2, readFileImpl),
28865
+ sourceCatalogPath: sourceCatalogPath2
28883
28866
  };
28884
28867
  }
28885
28868
  const profileStore = dependencies.profileStore ?? new ProfileStore();
@@ -28889,11 +28872,8 @@ async function loadConfigFromEnvironment(envInput = process.env, dependencies =
28889
28872
  exactName: dependencies.exactProfileName
28890
28873
  }) : await profileStore.getDefaultProfile();
28891
28874
  if (!profile) {
28892
- if (!requestedProfileName) {
28893
- throw new NoSavedProfileError("No saved Kibana profile is available.");
28894
- }
28895
28875
  throw new Error(
28896
- requestedProfileName ? `Saved Kibana profile '${requestedProfileName}' was not found.` : "No saved Kibana profile is available."
28876
+ requestedProfileName ? `Saved Kibana profile '${requestedProfileName}' was not found.` : "No saved Kibana connection is available. Run connection bootstrap first."
28897
28877
  );
28898
28878
  }
28899
28879
  const savedSecret = await secretStore.load(profile.id).catch((error48) => {
@@ -28905,7 +28885,6 @@ async function loadConfigFromEnvironment(envInput = process.env, dependencies =
28905
28885
  throw error48;
28906
28886
  });
28907
28887
  const sourceCatalogPath = overrides.KIBANA_SOURCE_CATALOG_PATH?.trim() || profile.sourceCatalogPath;
28908
- const sourceCatalog = await loadSourceCatalog([sourceCatalogPath], readFileImpl);
28909
28888
  return {
28910
28889
  kibana: {
28911
28890
  baseUrl: profile.baseUrl.replace(/\/+$/, ""),
@@ -28913,10 +28892,9 @@ async function loadConfigFromEnvironment(envInput = process.env, dependencies =
28913
28892
  password: savedSecret.password,
28914
28893
  timeoutMs: parseTimeoutOverride(overrides.KIBANA_TIMEOUT_MS) ?? profile.timeoutMs
28915
28894
  },
28916
- sources: sourceCatalogSchema.parse(sourceCatalog.value).sources,
28895
+ sources: await loadSourceCatalog(sourceCatalogPath, readFileImpl),
28917
28896
  profileName: profile.name,
28918
- sourceCatalogPath,
28919
- sourceCatalogOrigin: overrides.KIBANA_SOURCE_CATALOG_PATH?.trim() ? "environment" : "profile"
28897
+ sourceCatalogPath
28920
28898
  };
28921
28899
  }
28922
28900
  function readBootstrapConnection(envInput = process.env) {
@@ -28928,15 +28906,6 @@ function readBootstrapConnection(envInput = process.env) {
28928
28906
  }
28929
28907
  return hasBootstrapConfig(overrides) ? buildKibanaConfig(bootstrapEnvSchema.parse(overrides)) : void 0;
28930
28908
  }
28931
- async function persistSourceCatalog(sources, options = {}) {
28932
- const sourceCatalogPath = options.sourceCatalogPath ?? resolveSourceCatalogPath(options.envInput);
28933
- await writeTextAtomically(
28934
- sourceCatalogPath,
28935
- `${JSON.stringify(options.catalog ?? { sources }, null, 2)}
28936
- `
28937
- );
28938
- return sourceCatalogPath;
28939
- }
28940
28909
  function hasBootstrapConfig(envInput) {
28941
28910
  return Boolean(
28942
28911
  envInput.KIBANA_BASE_URL?.trim() && envInput.KIBANA_USERNAME?.trim() && envInput.KIBANA_PASSWORD?.trim()
@@ -28960,26 +28929,8 @@ function parseTimeoutOverride(value) {
28960
28929
  }
28961
28930
  return numericValue;
28962
28931
  }
28963
- async function loadSourceCatalog(candidatePaths, readFileImpl) {
28964
- let sourceCatalogRaw;
28965
- let loadedFrom = candidatePaths[0];
28966
- let lastError;
28967
- for (const candidatePath of candidatePaths) {
28968
- try {
28969
- sourceCatalogRaw = await readFileImpl(candidatePath, "utf8");
28970
- loadedFrom = candidatePath;
28971
- break;
28972
- } catch (error48) {
28973
- lastError = error48;
28974
- }
28975
- }
28976
- if (!sourceCatalogRaw) {
28977
- throw lastError instanceof Error ? lastError : new Error("Source catalog could not be loaded");
28978
- }
28979
- return {
28980
- loadedFrom,
28981
- value: JSON.parse(sourceCatalogRaw)
28982
- };
28932
+ async function loadSourceCatalog(path, readFileImpl) {
28933
+ return sourceCatalogSchema.parse(JSON.parse(await readFileImpl(path, "utf8"))).sources;
28983
28934
  }
28984
28935
 
28985
28936
  // src/config/config_file.ts
@@ -29097,17 +29048,10 @@ async function resolveConfiguration(envInput, dependencies, customStore) {
29097
29048
  { KIBANA_TIMEOUT_MS: envInput.KIBANA_TIMEOUT_MS, [PROFILE_NAME_ENV]: profileName },
29098
29049
  { ...dependencies, exactProfileName: true }
29099
29050
  );
29100
- try {
29101
- return {
29102
- defaultConfig: await loadConfigFromEnvironment(envInput, dependencies),
29103
- loadProfileConfig
29104
- };
29105
- } catch (error48) {
29106
- if (error48 instanceof NoSavedProfileError) {
29107
- return {};
29108
- }
29109
- throw error48;
29110
- }
29051
+ return {
29052
+ defaultConfig: await loadConfigFromEnvironment(envInput, dependencies),
29053
+ loadProfileConfig
29054
+ };
29111
29055
  }
29112
29056
 
29113
29057
  // src/kibana/client.ts
@@ -30003,6 +29947,9 @@ var ProfileRuntimeRegistry = class {
30003
29947
  refreshes = /* @__PURE__ */ new WeakMap();
30004
29948
  defaultRuntime;
30005
29949
  constructor(options = {}) {
29950
+ if (!options.defaultConfig && !options.loadProfileConfig) {
29951
+ throw new Error("No saved Kibana connection is available. Run connection bootstrap first.");
29952
+ }
30006
29953
  this.kibanaClientFactory = options.kibanaClientFactory ?? ((config2) => new KibanaClient(config2));
30007
29954
  this.loadProfileConfig = options.loadProfileConfig;
30008
29955
  this.maxProfiles = options.maxProfiles ?? DEFAULT_MAX_PROFILES;
@@ -30016,24 +29963,13 @@ var ProfileRuntimeRegistry = class {
30016
29963
  get supportsNamedProfiles() {
30017
29964
  return this.loadProfileConfig !== void 0;
30018
29965
  }
30019
- /** False only for a server started without any configuration, waiting for the legacy 'configure' tool. */
30020
- get isConfigured() {
30021
- return this.hasDefault || this.supportsNamedProfiles;
30022
- }
30023
- getDefault() {
30024
- return this.defaultRuntime;
30025
- }
30026
- setDefault(config2) {
30027
- this.defaultRuntime = this.createRuntime(config2);
30028
- return this.defaultRuntime;
30029
- }
30030
29966
  async resolve(profileName) {
30031
29967
  if (profileName === void 0) {
30032
29968
  if (this.defaultRuntime) {
30033
29969
  return this.refresh(this.defaultRuntime);
30034
29970
  }
30035
29971
  throw new Error(
30036
- this.loadProfileConfig ? "No profile was supplied for this call and no default profile is configured." : "Server is not configured. Call the 'configure' tool first."
29972
+ "No profile was supplied for this call and no default profile is configured."
30037
29973
  );
30038
29974
  }
30039
29975
  const key = normalizeProfileName(profileName);
@@ -30129,59 +30065,6 @@ var ProfileRuntimeRegistry = class {
30129
30065
  }
30130
30066
  };
30131
30067
 
30132
- // src/tools/configure.ts
30133
- var configureInputSchema = external_exports.object({
30134
- kibana: external_exports.object({
30135
- baseUrl: external_exports.url(),
30136
- username: external_exports.string().min(1),
30137
- password: external_exports.string().min(1),
30138
- timeoutMs: external_exports.number().int().positive().max(12e4).default(1e4)
30139
- }),
30140
- sources: external_exports.array(sourceDefinitionSchema).min(1)
30141
- });
30142
- var configureOutputSchema = external_exports.object({
30143
- configured: external_exports.literal(true),
30144
- persisted: external_exports.literal(true),
30145
- source_catalog_path: external_exports.string(),
30146
- source_count: external_exports.number().int().positive(),
30147
- source_ids: external_exports.array(external_exports.string()),
30148
- base_url: external_exports.string()
30149
- });
30150
- async function executeConfigure(input, options = {}) {
30151
- const nextConfig = {
30152
- kibana: {
30153
- baseUrl: input.kibana.baseUrl.replace(/\/+$/, ""),
30154
- username: input.kibana.username,
30155
- password: input.kibana.password,
30156
- timeoutMs: input.kibana.timeoutMs
30157
- },
30158
- sources: input.sources
30159
- };
30160
- const sourceCatalogPath = await persistSourceCatalog(input.sources, options);
30161
- return {
30162
- nextConfig,
30163
- result: {
30164
- configured: true,
30165
- persisted: true,
30166
- source_catalog_path: sourceCatalogPath,
30167
- source_count: input.sources.length,
30168
- source_ids: input.sources.map((source) => source.id),
30169
- base_url: nextConfig.kibana.baseUrl
30170
- }
30171
- };
30172
- }
30173
- function createConfigureCallToolResult(result) {
30174
- return {
30175
- content: [
30176
- {
30177
- type: "text",
30178
- text: `Configured Kibana connection for ${result.base_url} with ${result.source_count} sources. Source catalog persisted to ${result.source_catalog_path}.`
30179
- }
30180
- ],
30181
- structuredContent: result
30182
- };
30183
- }
30184
-
30185
30068
  // src/setup/bootstrap.ts
30186
30069
  import { createHash as createHash5 } from "node:crypto";
30187
30070
  function buildGeneratedSource(rawIndexes, sourceName, sourceId) {
@@ -30253,8 +30136,7 @@ function chooseGeneratedSourceFields(fields, explicitTimeField) {
30253
30136
  ].filter((name) => byName.has(name));
30254
30137
  return { timeField, defaultTextFields, evidenceFields };
30255
30138
  }
30256
- function createGeneratedCatalog(sourceOrSources) {
30257
- const sources = Array.isArray(sourceOrSources) ? sourceOrSources : [sourceOrSources];
30139
+ function createGeneratedCatalog(sources) {
30258
30140
  const parsedSources = sources.map((source) => sourceDefinitionSchema.parse(source));
30259
30141
  return sourceCatalogSchema.parse({
30260
30142
  generatedBy: {
@@ -31855,23 +31737,7 @@ function createApplication(initialConfig, dependencies) {
31855
31737
  );
31856
31738
  }
31857
31739
  }
31858
- const configureHandler = async (input) => {
31859
- const current = registry2.getDefault()?.config;
31860
- const { nextConfig, result } = await executeConfigure(configureInputSchema.parse(input), {
31861
- sourceCatalogPath: current?.sourceCatalogPath
31862
- });
31863
- registry2.setDefault({
31864
- ...nextConfig,
31865
- sourceCatalogPath: result.source_catalog_path,
31866
- profileName: current?.profileName,
31867
- sourceCatalogOrigin: current?.sourceCatalogOrigin
31868
- });
31869
- return result;
31870
- };
31871
31740
  const configureIndexHandler = async (input, callerSignal) => {
31872
- if (!registry2.isConfigured) {
31873
- throw new Error("No saved Kibana connection is available. Run connection bootstrap first.");
31874
- }
31875
31741
  const configureIndexFn = dependencies?.configureIndexFn ?? executeConfigureIndex;
31876
31742
  return withProfile(
31877
31743
  (runtime) => withKibanaToolDeadline(callerSignal, async (signal) => {
@@ -31920,31 +31786,19 @@ function createApplication(initialConfig, dependencies) {
31920
31786
  })
31921
31787
  )
31922
31788
  );
31923
- if (!registry2.isConfigured) {
31924
- server.registerTool(
31925
- "configure",
31926
- {
31927
- description: "Advanced legacy setup for a server started without a saved profile. Replaces the Kibana connection and full source catalog.",
31928
- inputSchema: configureInputSchema,
31929
- outputSchema: configureOutputSchema
31930
- },
31931
- async (input) => createConfigureCallToolResult(await configureHandler(input))
31932
- );
31933
- } else {
31934
- server.registerTool(
31935
- "configure_index",
31936
- {
31937
- description: "Configure exactly one Kibana index or index pattern after the user has explicitly named it. Never guess, list, scan, or auto-select indexes before calling this tool.",
31938
- inputSchema: configureIndexInputSchema,
31939
- outputSchema: configureIndexOutputSchema
31940
- },
31941
- async (input, context) => withKibanaRequestDiagnostics(
31942
- async () => createConfigureIndexCallToolResult(
31943
- await configureIndexHandler(input, context.mcpReq.signal)
31944
- )
31789
+ server.registerTool(
31790
+ "configure_index",
31791
+ {
31792
+ description: "Configure exactly one Kibana index or index pattern after the user has explicitly named it. Never guess, list, scan, or auto-select indexes before calling this tool.",
31793
+ inputSchema: configureIndexInputSchema,
31794
+ outputSchema: configureIndexOutputSchema
31795
+ },
31796
+ async (input, context) => withKibanaRequestDiagnostics(
31797
+ async () => createConfigureIndexCallToolResult(
31798
+ await configureIndexHandler(input, context.mcpReq.signal)
31945
31799
  )
31946
- );
31947
- }
31800
+ )
31801
+ );
31948
31802
  server.registerTool(
31949
31803
  "describe_fields",
31950
31804
  {
@@ -31992,7 +31846,6 @@ function createApplication(initialConfig, dependencies) {
31992
31846
  return {
31993
31847
  server,
31994
31848
  handlers: {
31995
- configure: configureHandler,
31996
31849
  configure_index: configureIndexHandler,
31997
31850
  describe_fields: describeFieldsHandler,
31998
31851
  discover: discoverHandler,
@@ -38,7 +38,7 @@ npm run build
38
38
  node dist/src/bin/kibana-mcp-server.js bootstrap --client none
39
39
  ```
40
40
 
41
- 7. Confirm bootstrap reports an empty source catalog. It must not inspect or configure indexes.
41
+ 7. Confirm first-time bootstrap reports an empty source catalog. It must not inspect or configure indexes. Remove `KIBANA_BASE_URL`, `KIBANA_USERNAME`, and `KIBANA_PASSWORD` from the plugin process environment before reconnecting so startup uses the saved profile. In Bash: `unset KIBANA_BASE_URL KIBANA_USERNAME KIBANA_PASSWORD`; in PowerShell: `Remove-Item Env:KIBANA_BASE_URL, Env:KIBANA_USERNAME, Env:KIBANA_PASSWORD -ErrorAction SilentlyContinue`.
42
42
  8. In a fresh Codex session, call `discover`. When it reports no sources, ask the operator which exact index or index pattern to configure.
43
43
  9. Only after the operator answers, call `configure_index` with exactly that value. Then run `discover` again.
44
44
 
@@ -57,7 +57,7 @@ If you are not running in Codex and the tools of this server (`discover`, `confi
57
57
  - in Claude Code, by installing the plugin: `/plugin marketplace add Havesomecode/kibana-mcp-server`, then `/plugin install kibana-log-investigation@kibana-mcp-server`;
58
58
  - in any other agent, by declaring the stdio command `npx -y @havesomecode/kibana-mcp-server serve` in its MCP configuration.
59
59
 
60
- With `--client none`, a server that was already running when bootstrap completed still has no connection. Ask the operator to reconnect the MCP server or start a new session before calling its tools.
60
+ With `--client none`, reconnect the MCP server or start a new session after bootstrap. A server without a configured connection fails at startup; it does not wait for an MCP tool to supply credentials.
61
61
 
62
62
  ### What bootstrap does
63
63
 
@@ -120,6 +120,8 @@ To select an existing non-default profile:
120
120
  npx -y @havesomecode/kibana-mcp-server serve --profile prod
121
121
  ```
122
122
 
123
+ For saved-profile startup, do not pass `KIBANA_BASE_URL`, `KIBANA_USERNAME`, or `KIBANA_PASSWORD` to the server process. Supplying all three selects direct environment startup, which requires an explicit `KIBANA_SOURCE_CATALOG_PATH` or a configuration-file catalog store. Saved profiles already carry their catalog path; no checkout catalog is loaded implicitly. Guided `setup` remains available when the operator explicitly wants to import a hand-authored catalog.
124
+
123
125
  ## Failure rules
124
126
 
125
127
  - Never silently import the bundled example catalog.
@@ -128,7 +130,7 @@ npx -y @havesomecode/kibana-mcp-server serve --profile prod
128
130
  - Never add `--replace` merely to make a failed install pass. It intentionally replaces an existing catalog with an empty managed catalog.
129
131
  - Never overwrite a conflicting MCP client registration.
130
132
  - Never write or edit an MCP client configuration file to register the server; only bootstrap in Codex, a plugin, or the operator does that.
131
- - Never use the legacy `configure` tool to supply credentials. If it is the only setup tool offered, the server has no saved connection: run bootstrap, then have the server reconnected.
133
+ - If startup fails with `No saved Kibana connection is available. Run connection bootstrap first.`, run connection-only bootstrap, then reconnect the server. No MCP tool accepts connection credentials.
132
134
  - Never expose a password in logs, summaries, shell history, generated catalogs, or MCP client configuration.
133
135
  - If URL, username, or password is unavailable, report the exact missing prerequisite rather than inventing it.
134
136
 
@@ -1,59 +0,0 @@
1
- import type { CallToolResult } from "@modelcontextprotocol/server";
2
- import { z } from "zod";
3
- import type { AppConfig } from "../types.js";
4
- export declare const configureInputSchema: z.ZodObject<{
5
- kibana: z.ZodObject<{
6
- baseUrl: z.ZodURL;
7
- username: z.ZodString;
8
- password: z.ZodString;
9
- timeoutMs: z.ZodDefault<z.ZodNumber>;
10
- }, z.core.$strip>;
11
- sources: z.ZodArray<z.ZodObject<{
12
- id: z.ZodString;
13
- name: z.ZodString;
14
- description: z.ZodOptional<z.ZodString>;
15
- tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
16
- timeField: z.ZodString;
17
- backend: z.ZodObject<{
18
- kind: z.ZodEnum<{
19
- elasticsearch_search: "elasticsearch_search";
20
- kibana_internal_search_es: "kibana_internal_search_es";
21
- }>;
22
- path: z.ZodString;
23
- index: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
24
- }, z.core.$strip>;
25
- schema: z.ZodOptional<z.ZodObject<{
26
- kind: z.ZodEnum<{
27
- elasticsearch_field_caps: "elasticsearch_field_caps";
28
- kibana_data_views_fields: "kibana_data_views_fields";
29
- kibana_index_patterns_fields: "kibana_index_patterns_fields";
30
- }>;
31
- path: z.ZodOptional<z.ZodString>;
32
- index: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
33
- }, z.core.$strip>>;
34
- fieldHints: z.ZodDefault<z.ZodArray<z.ZodObject<{
35
- name: z.ZodString;
36
- type: z.ZodOptional<z.ZodString>;
37
- description: z.ZodOptional<z.ZodString>;
38
- aliases: z.ZodDefault<z.ZodArray<z.ZodString>>;
39
- }, z.core.$strip>>>;
40
- defaultTextFields: z.ZodDefault<z.ZodArray<z.ZodString>>;
41
- evidenceFields: z.ZodDefault<z.ZodArray<z.ZodString>>;
42
- }, z.core.$strip>>;
43
- }, z.core.$strip>;
44
- export declare const configureOutputSchema: z.ZodObject<{
45
- configured: z.ZodLiteral<true>;
46
- persisted: z.ZodLiteral<true>;
47
- source_catalog_path: z.ZodString;
48
- source_count: z.ZodNumber;
49
- source_ids: z.ZodArray<z.ZodString>;
50
- base_url: z.ZodString;
51
- }, z.core.$strip>;
52
- export declare function executeConfigure(input: z.infer<typeof configureInputSchema>, options?: {
53
- envInput?: NodeJS.ProcessEnv;
54
- sourceCatalogPath?: string;
55
- }): Promise<{
56
- nextConfig: AppConfig;
57
- result: z.infer<typeof configureOutputSchema>;
58
- }>;
59
- export declare function createConfigureCallToolResult(result: z.infer<typeof configureOutputSchema>): CallToolResult;
@@ -1,53 +0,0 @@
1
- import { z } from "zod";
2
- import { persistSourceCatalog, sourceDefinitionSchema } from "../config/environment.js";
3
- export const configureInputSchema = z.object({
4
- kibana: z.object({
5
- baseUrl: z.url(),
6
- username: z.string().min(1),
7
- password: z.string().min(1),
8
- timeoutMs: z.number().int().positive().max(120000).default(10000),
9
- }),
10
- sources: z.array(sourceDefinitionSchema).min(1),
11
- });
12
- export const configureOutputSchema = z.object({
13
- configured: z.literal(true),
14
- persisted: z.literal(true),
15
- source_catalog_path: z.string(),
16
- source_count: z.number().int().positive(),
17
- source_ids: z.array(z.string()),
18
- base_url: z.string(),
19
- });
20
- export async function executeConfigure(input, options = {}) {
21
- const nextConfig = {
22
- kibana: {
23
- baseUrl: input.kibana.baseUrl.replace(/\/+$/, ""),
24
- username: input.kibana.username,
25
- password: input.kibana.password,
26
- timeoutMs: input.kibana.timeoutMs,
27
- },
28
- sources: input.sources,
29
- };
30
- const sourceCatalogPath = await persistSourceCatalog(input.sources, options);
31
- return {
32
- nextConfig,
33
- result: {
34
- configured: true,
35
- persisted: true,
36
- source_catalog_path: sourceCatalogPath,
37
- source_count: input.sources.length,
38
- source_ids: input.sources.map((source) => source.id),
39
- base_url: nextConfig.kibana.baseUrl,
40
- },
41
- };
42
- }
43
- export function createConfigureCallToolResult(result) {
44
- return {
45
- content: [
46
- {
47
- type: "text",
48
- text: `Configured Kibana connection for ${result.base_url} with ${result.source_count} sources. Source catalog persisted to ${result.source_catalog_path}.`,
49
- },
50
- ],
51
- structuredContent: result,
52
- };
53
- }