@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.
- package/README.md +12 -1
- package/dist/src/bin/run_cli.js +4 -3
- package/dist/src/commands/bootstrap.d.ts +18 -0
- package/dist/src/commands/bootstrap.js +98 -0
- package/dist/src/commands/context.d.ts +0 -1
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +7 -112
- package/dist/src/config/environment.d.ts +0 -11
- package/dist/src/config/environment.js +10 -60
- package/dist/src/config/startup.js +5 -13
- package/dist/src/profiles/runtime.d.ts +1 -5
- package/dist/src/profiles/runtime.js +4 -14
- package/dist/src/server.d.ts +0 -2
- package/dist/src/server.js +5 -32
- package/dist/src/setup/bootstrap.d.ts +1 -1
- package/dist/src/setup/bootstrap.js +1 -2
- package/dist/src/tools/configure_index.js +1 -2
- package/dist/src/transports/http.js +1 -1
- package/dist/src/transports/stdio.js +1 -2
- package/dist/src/types.d.ts +0 -1
- package/docs/project/compatibility-matrix.md +3 -1
- package/docs/project/distribution-strategy.md +1 -1
- package/docs/project/release-checklist.md +3 -0
- package/docs/project/support-policy.md +14 -6
- package/package.json +2 -2
- package/plugins/kibana-log-investigation/dist/mcp_entry.mjs +32 -179
- package/plugins/kibana-log-investigation/skills/install-and-configure/SKILL.md +1 -1
- package/skills/kibana-log-investigation/SKILL.md +4 -2
- package/dist/src/tools/configure.d.ts +0 -59
- package/dist/src/tools/configure.js +0 -53
|
@@ -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(
|
|
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(
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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 | `
|
|
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
|
|
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-
|
|
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
|
-
-
|
|
19
|
-
-
|
|
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.
|
|
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.
|
|
37
|
-
- Credentials are not persisted to repo-local files or tracked config.
|
|
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": "
|
|
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
|
|
28874
|
-
|
|
28875
|
-
|
|
28876
|
-
|
|
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:
|
|
28881
|
-
sourceCatalogPath:
|
|
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
|
|
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:
|
|
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(
|
|
28964
|
-
|
|
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
|
-
|
|
29101
|
-
|
|
29102
|
-
|
|
29103
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
31924
|
-
|
|
31925
|
-
|
|
31926
|
-
|
|
31927
|
-
|
|
31928
|
-
|
|
31929
|
-
|
|
31930
|
-
|
|
31931
|
-
async (
|
|
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`,
|
|
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
|
-
-
|
|
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
|
-
}
|