@havesomecode/kibana-mcp-server 1.4.0 → 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 +19 -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/mcp_entry.js +1 -4
- 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 +6 -2
- package/plugins/kibana-log-investigation/.mcp.json +3 -2
- package/plugins/kibana-log-investigation/dist/LICENSE +21 -0
- package/plugins/kibana-log-investigation/dist/THIRD_PARTY_NOTICES.md +617 -0
- package/plugins/kibana-log-investigation/dist/mcp_entry.mjs +31899 -0
- package/plugins/kibana-log-investigation/dist/mcp_entry.mjs.LEGAL.txt +8 -0
- package/plugins/kibana-log-investigation/mcp-launcher.mjs +26 -0
- package/plugins/kibana-log-investigation/skills/ensure-node-runtime/SKILL.md +118 -0
- package/plugins/kibana-log-investigation/skills/install-and-configure/SKILL.md +55 -0
- package/skills/kibana-log-investigation/SKILL.md +16 -2
- package/dist/src/tools/configure.d.ts +0 -59
- package/dist/src/tools/configure.js +0 -53
package/README.md
CHANGED
|
@@ -24,7 +24,13 @@ The server never writes to Elasticsearch or Kibana, and never guesses an index.
|
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Bootstrap the connection; missing values are prompted for:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y @havesomecode/kibana-mcp-server bootstrap --client codex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For prompt-free automation, provision the connection values in the environment:
|
|
28
34
|
|
|
29
35
|
```bash
|
|
30
36
|
export KIBANA_BASE_URL='https://kibana.example.com'
|
|
@@ -32,10 +38,15 @@ export KIBANA_USERNAME='elastic'
|
|
|
32
38
|
export KIBANA_PASSWORD='use-a-secret-provider'
|
|
33
39
|
|
|
34
40
|
npx -y @havesomecode/kibana-mcp-server bootstrap --client codex
|
|
41
|
+
unset KIBANA_BASE_URL KIBANA_USERNAME KIBANA_PASSWORD
|
|
35
42
|
```
|
|
36
43
|
|
|
37
44
|
Bootstrap verifies only the Kibana connection, creates an empty managed catalog, stores the credentials securely and registers the server. It does not list, scan, infer, or configure indexes: the agent asks which exact index or pattern to use, then calls `configure_index`.
|
|
38
45
|
|
|
46
|
+
Clear the connection variables from the MCP server environment after bootstrap so startup uses the saved profile. Direct environment connections take precedence over profiles and require an explicit catalog path or store.
|
|
47
|
+
|
|
48
|
+
The server requires a connection at startup. Use bootstrap, a configuration file, or connection environment variables with an explicit `KIBANA_SOURCE_CATALOG_PATH`. It does not accept credentials through MCP tools or search the checkout for a catalog. `bootstrap` always saves only the connection. Bare `setup` guides hand-authored catalog import; `setup` with connection options uses the same bootstrap flow.
|
|
49
|
+
|
|
39
50
|
Never pass passwords as command-line arguments. Use `KIBANA_PASSWORD`, `--password-env NAME`, or `--password-stdin`.
|
|
40
51
|
|
|
41
52
|
Install the agent skill that teaches the investigation workflow:
|
|
@@ -50,6 +61,8 @@ npx skills add Havesomecode/kibana-mcp-server \
|
|
|
50
61
|
|
|
51
62
|
`INSTALL.md` is the deterministic install and handoff guide for agents.
|
|
52
63
|
|
|
64
|
+
For a version-matched handoff, resolve the selected branch, tag, or package metadata to the full commit SHA. That SHA is the **resolved installation commit** used in `https://raw.githubusercontent.com/Havesomecode/kibana-mcp-server/<resolved-commit>/README.md`. Read `INSTALL.md` and the skills from the same commit. Do not infer a Git tag from an npm version; use the exact package's `gitHead`. A maintained checkout can verify the handoff with `node scripts/readme-handoff.mjs <resolved-installation-ref>`.
|
|
65
|
+
|
|
53
66
|
## Run it as a shared service
|
|
54
67
|
|
|
55
68
|
By default the server speaks stdio and serves one configuration. Over HTTP, one process serves every caller of your gateway, each with their own source catalog:
|
|
@@ -75,11 +88,16 @@ kibana-mcp-server serve --http --config ./kibana.mcp.config.ts
|
|
|
75
88
|
|
|
76
89
|
Catalogs can live in files, Redis, PostgreSQL, MongoDB, SQLite, or a store you write. The HTTP transport trusts the profile header as sent: only a gateway that has authenticated the caller should reach it.
|
|
77
90
|
|
|
91
|
+
## Repository Codex plugin
|
|
92
|
+
|
|
93
|
+
`npm run build` creates a self-contained runtime in `plugins/kibana-log-investigation/dist/`. The repo-local Codex plugin launches that installed bundle through `mcp-launcher.mjs`, without depending on the original checkout or its `node_modules`. Use `npm run verify:entrypoint` for the isolated copied-plugin handshake, and inspect Codex's MCP startup status plus sanitized stderr before restarting a discussion.
|
|
94
|
+
|
|
78
95
|
## Documentation
|
|
79
96
|
|
|
80
97
|
| Topic | Link |
|
|
81
98
|
| --- | --- |
|
|
82
99
|
| Getting started | https://havesomecode.github.io/kibana-mcp-server/guide/getting-started |
|
|
100
|
+
| Agent plugins and skills | https://havesomecode.github.io/kibana-mcp-server/ai/agent-plugins |
|
|
83
101
|
| Investigating with an agent | https://havesomecode.github.io/kibana-mcp-server/guide/investigating |
|
|
84
102
|
| HTTP transport and security model | https://havesomecode.github.io/kibana-mcp-server/deployment/http |
|
|
85
103
|
| Catalog stores | https://havesomecode.github.io/kibana-mcp-server/deployment/catalog-stores |
|
package/dist/src/bin/run_cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, CommanderError } from "commander";
|
|
2
|
+
import registerBootstrapCommand from "../commands/bootstrap.js";
|
|
2
3
|
import registerServeCommand from "../commands/serve.js";
|
|
3
|
-
import
|
|
4
|
+
import registerSetupCommand from "../commands/setup.js";
|
|
4
5
|
import { startMcpServer } from "../transports/stdio.js";
|
|
5
6
|
import { createCliIo } from "../utils/cli_io.js";
|
|
6
7
|
import { createCliUi } from "../utils/cli_ui.js";
|
|
@@ -8,7 +9,6 @@ export async function runCli(argv, overrides = {}, dependencies = {}) {
|
|
|
8
9
|
const io = createCliIo(overrides);
|
|
9
10
|
let exitCode = 0;
|
|
10
11
|
const context = {
|
|
11
|
-
argv,
|
|
12
12
|
io,
|
|
13
13
|
ui: createCliUi(io),
|
|
14
14
|
dependencies,
|
|
@@ -34,7 +34,8 @@ export async function runCli(argv, overrides = {}, dependencies = {}) {
|
|
|
34
34
|
exitCode = 1;
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
|
-
|
|
37
|
+
registerSetupCommand(program, context);
|
|
38
|
+
registerBootstrapCommand(program, context);
|
|
38
39
|
registerServeCommand(program, context);
|
|
39
40
|
try {
|
|
40
41
|
await program.parseAsync(argv);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import type { CliCommandContext } from "./context.js";
|
|
3
|
+
export interface BootstrapOptions {
|
|
4
|
+
profile?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
username?: string;
|
|
7
|
+
passwordStdin?: boolean;
|
|
8
|
+
passwordEnv?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
client?: "codex" | "none";
|
|
11
|
+
package?: string;
|
|
12
|
+
mcpName?: string;
|
|
13
|
+
replace?: boolean;
|
|
14
|
+
default: boolean;
|
|
15
|
+
}
|
|
16
|
+
export default function registerBootstrap(program: Command, context: CliCommandContext): void;
|
|
17
|
+
export declare function addBootstrapOptions(command: Command): Command;
|
|
18
|
+
export declare function runBootstrapCommand(options: BootstrapOptions, context: CliCommandContext): Promise<void>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { InvalidArgumentError, Option } from "commander";
|
|
2
|
+
import { runBootstrap } from "../setup/bootstrap.js";
|
|
3
|
+
import { createPromptIo } from "../utils/cli_prompts.js";
|
|
4
|
+
export default function registerBootstrap(program, context) {
|
|
5
|
+
addBootstrapOptions(program
|
|
6
|
+
.command("bootstrap")
|
|
7
|
+
.description("Verify and save only the Kibana connection")
|
|
8
|
+
.addHelpText("after", "\nBootstrap does not inspect or configure indexes. Missing connection values are prompted for.\n\nConnection options use KIBANA_BASE_URL, KIBANA_USERNAME, and KIBANA_PASSWORD as fallbacks. Passwords are accepted only through --password-stdin, --password-env <NAME>, KIBANA_PASSWORD, or a secret prompt. Profile defaults to KIBANA_PROFILE, then default.")).action((options) => runBootstrapCommand(options, context));
|
|
9
|
+
}
|
|
10
|
+
export function addBootstrapOptions(command) {
|
|
11
|
+
return command
|
|
12
|
+
.option("--profile <name>", "Saved profile name")
|
|
13
|
+
.option("--url <url>", "Kibana base URL")
|
|
14
|
+
.option("--username <name>", "Kibana basic-auth username")
|
|
15
|
+
.addOption(new Option("--password-stdin", "Read the password from stdin").conflicts("passwordEnv"))
|
|
16
|
+
.option("--password-env <name>", "Read the password from an environment variable")
|
|
17
|
+
.addOption(new Option("--timeout <milliseconds>", "Kibana request timeout").argParser(parseTimeout))
|
|
18
|
+
.addOption(new Option("--client <codex|none>", "Client registration mode").choices(["codex", "none"]))
|
|
19
|
+
.option("--package <specifier>", "Package specifier for client registration")
|
|
20
|
+
.option("--mcp-name <name>", "MCP registration name")
|
|
21
|
+
.option("--replace", "Replace an existing catalog")
|
|
22
|
+
.option("--no-default", "Do not make this profile the default");
|
|
23
|
+
}
|
|
24
|
+
export async function runBootstrapCommand(options, context) {
|
|
25
|
+
const { env, stdin } = context.io;
|
|
26
|
+
let promptIo;
|
|
27
|
+
try {
|
|
28
|
+
let baseUrl = options.url?.trim() || env.KIBANA_BASE_URL?.trim();
|
|
29
|
+
let username = options.username?.trim() || env.KIBANA_USERNAME?.trim();
|
|
30
|
+
let password = options.passwordStdin
|
|
31
|
+
? (await readAllInput(stdin)).replace(/\r?\n$/, "")
|
|
32
|
+
: options.passwordEnv !== undefined
|
|
33
|
+
? env[options.passwordEnv]
|
|
34
|
+
: env.KIBANA_PASSWORD;
|
|
35
|
+
const promptForPassword = !password && !options.passwordStdin && options.passwordEnv === undefined;
|
|
36
|
+
if (!baseUrl || !username || promptForPassword) {
|
|
37
|
+
promptIo = await createPromptIo(context.io);
|
|
38
|
+
}
|
|
39
|
+
baseUrl ||= (await promptIo?.prompt("Kibana base URL"))?.trim();
|
|
40
|
+
if (!baseUrl) {
|
|
41
|
+
throw new Error("Kibana base URL is required via --url, KIBANA_BASE_URL, or the associated prompt.");
|
|
42
|
+
}
|
|
43
|
+
username ||= (await promptIo?.prompt("Kibana username"))?.trim();
|
|
44
|
+
if (!username) {
|
|
45
|
+
throw new Error("Kibana username is required via --username, KIBANA_USERNAME, or the associated prompt.");
|
|
46
|
+
}
|
|
47
|
+
if (promptForPassword) {
|
|
48
|
+
password = (await promptIo?.prompt("Kibana password", { secret: true }))?.trim();
|
|
49
|
+
}
|
|
50
|
+
if (!password) {
|
|
51
|
+
throw new Error("Kibana password is required via an option, KIBANA_PASSWORD, or the associated prompt.");
|
|
52
|
+
}
|
|
53
|
+
const result = await (context.dependencies.runBootstrapFn ?? runBootstrap)({
|
|
54
|
+
profileName: options.profile?.trim() || env.KIBANA_PROFILE?.trim() || "default",
|
|
55
|
+
baseUrl,
|
|
56
|
+
username,
|
|
57
|
+
password,
|
|
58
|
+
client: options.client ?? "codex",
|
|
59
|
+
packageSpecifier: options.package,
|
|
60
|
+
mcpName: options.mcpName,
|
|
61
|
+
makeDefault: options.default,
|
|
62
|
+
replaceExisting: options.replace ?? false,
|
|
63
|
+
timeoutMs: options.timeout,
|
|
64
|
+
});
|
|
65
|
+
context.ui.success(renderBootstrapResult(result));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
context.ui.error(error instanceof Error ? error.message : String(error));
|
|
69
|
+
context.setExitCode(1);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await promptIo?.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function parseTimeout(value) {
|
|
76
|
+
const timeout = Number(value);
|
|
77
|
+
if (!Number.isInteger(timeout) || timeout <= 0 || timeout > 120000) {
|
|
78
|
+
throw new InvalidArgumentError("--timeout must be a positive integer no greater than 120000 milliseconds.");
|
|
79
|
+
}
|
|
80
|
+
return timeout;
|
|
81
|
+
}
|
|
82
|
+
function renderBootstrapResult(result) {
|
|
83
|
+
return [
|
|
84
|
+
`Bootstrap verified for profile '${result.profileName}'.`,
|
|
85
|
+
result.sourceCount === 0
|
|
86
|
+
? "Source catalog is empty. No Kibana indexes were inspected or configured. Ask the user which index or index pattern to configure before querying logs."
|
|
87
|
+
: `Preserved ${result.sourceCount} explicitly configured source${result.sourceCount === 1 ? "" : "s"}; bootstrap did not inspect them.`,
|
|
88
|
+
result.registered
|
|
89
|
+
? `Codex MCP registration is installed and verified for profile '${result.profileName}'.`
|
|
90
|
+
: "Client registration was skipped by request.",
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
async function readAllInput(stdin) {
|
|
94
|
+
let raw = "";
|
|
95
|
+
for await (const chunk of stdin)
|
|
96
|
+
raw += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
97
|
+
return raw;
|
|
98
|
+
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import { InvalidArgumentError, Option } from "commander";
|
|
2
|
-
import { runBootstrap as runBootstrapFlow } from "../setup/bootstrap.js";
|
|
3
1
|
import { runSetupFlow } from "../setup/setup_flow.js";
|
|
4
2
|
import { PromptCancelledError, createPromptIo } from "../utils/cli_prompts.js";
|
|
3
|
+
import { addBootstrapOptions, runBootstrapCommand } from "./bootstrap.js";
|
|
5
4
|
export default function registerSetup(program, context) {
|
|
6
|
-
|
|
5
|
+
addBootstrapOptions(program
|
|
7
6
|
.command("setup")
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (context.argv.some((argument) => argument.startsWith("-")))
|
|
14
|
-
return runBootstrap(options, context);
|
|
7
|
+
.description("Run guided setup, or connection-only bootstrap with options")
|
|
8
|
+
.addHelpText("after", "\nWithout connection options, setup is guided even when connection environment values exist. With any connection option, setup uses bootstrap: it does not inspect or configure indexes and prompts only for missing connection values.")).action(async (options, command) => {
|
|
9
|
+
if (command.options.some((option) => command.getOptionValueSource(option.attributeName()) === "cli")) {
|
|
10
|
+
return runBootstrapCommand(options, context);
|
|
11
|
+
}
|
|
15
12
|
const promptIo = await createPromptIo(context.io);
|
|
16
13
|
try {
|
|
17
14
|
const result = await (context.dependencies.runSetupFlowFn ?? runSetupFlow)(promptIo);
|
|
@@ -28,105 +25,3 @@ export default function registerSetup(program, context) {
|
|
|
28
25
|
}
|
|
29
26
|
});
|
|
30
27
|
}
|
|
31
|
-
function readOptions(argv) {
|
|
32
|
-
const value = (flag) => {
|
|
33
|
-
const index = argv.indexOf(flag);
|
|
34
|
-
return index === -1 ? undefined : argv[index + 1];
|
|
35
|
-
};
|
|
36
|
-
return {
|
|
37
|
-
profile: value("--profile"),
|
|
38
|
-
url: value("--url"),
|
|
39
|
-
username: value("--username"),
|
|
40
|
-
passwordStdin: argv.includes("--password-stdin"),
|
|
41
|
-
passwordEnv: value("--password-env"),
|
|
42
|
-
timeout: value("--timeout") === undefined ? undefined : Number(value("--timeout")),
|
|
43
|
-
client: value("--client"),
|
|
44
|
-
package: value("--package"),
|
|
45
|
-
mcpName: value("--mcp-name"),
|
|
46
|
-
replace: argv.includes("--replace"),
|
|
47
|
-
default: !argv.includes("--no-default"),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function addOptions(command) {
|
|
51
|
-
return command
|
|
52
|
-
.option("--profile <name>", "Saved profile name")
|
|
53
|
-
.option("--url <url>", "Kibana base URL")
|
|
54
|
-
.option("--username <name>", "Kibana basic-auth username")
|
|
55
|
-
.addOption(new Option("--password-stdin", "Read the password from stdin").conflicts("passwordEnv"))
|
|
56
|
-
.option("--password-env <name>", "Read the password from an environment variable")
|
|
57
|
-
.addOption(new Option("--timeout <milliseconds>", "Kibana request timeout").argParser(parseTimeout))
|
|
58
|
-
.addOption(new Option("--client <codex|none>", "Client registration mode").choices(["codex", "none"]))
|
|
59
|
-
.option("--package <specifier>", "Package specifier for client registration")
|
|
60
|
-
.option("--mcp-name <name>", "MCP registration name")
|
|
61
|
-
.option("--replace", "Replace an existing catalog")
|
|
62
|
-
.option("--no-default", "Do not make this profile the default");
|
|
63
|
-
}
|
|
64
|
-
async function runBootstrap(options, context) {
|
|
65
|
-
const needsPrompt = !options.url ||
|
|
66
|
-
!options.username ||
|
|
67
|
-
(!options.passwordStdin && options.passwordEnv === undefined);
|
|
68
|
-
const promptIo = needsPrompt ? await createPromptIo(context.io) : undefined;
|
|
69
|
-
try {
|
|
70
|
-
const result = await (context.dependencies.runBootstrapFn ?? runBootstrapFlow)(await resolveBootstrapOptions(options, context, promptIo));
|
|
71
|
-
context.ui.success(renderBootstrapResult(result));
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
context.ui.error(error instanceof Error ? error.message : String(error));
|
|
75
|
-
context.setExitCode(1);
|
|
76
|
-
}
|
|
77
|
-
finally {
|
|
78
|
-
await promptIo?.close();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function resolveBootstrapOptions(values, context, prompter) {
|
|
82
|
-
const { env, stdin } = context.io;
|
|
83
|
-
const baseUrl = values.url?.trim() || (await prompter?.prompt("Kibana base URL"))?.trim();
|
|
84
|
-
if (!baseUrl)
|
|
85
|
-
throw new Error("Kibana base URL is required via --url or the associated prompt.");
|
|
86
|
-
const username = values.username?.trim() || (await prompter?.prompt("Kibana username"))?.trim();
|
|
87
|
-
if (!username)
|
|
88
|
-
throw new Error("Kibana username is required via --username or the associated prompt.");
|
|
89
|
-
const password = values.passwordStdin
|
|
90
|
-
? (await readAllInput(stdin)).replace(/\r?\n$/, "")
|
|
91
|
-
: values.passwordEnv
|
|
92
|
-
? env[values.passwordEnv]
|
|
93
|
-
: (await prompter?.prompt("Kibana password", { secret: true }))?.trim();
|
|
94
|
-
if (!password)
|
|
95
|
-
throw new Error("Kibana password is required via an option or the associated prompt.");
|
|
96
|
-
return {
|
|
97
|
-
profileName: values.profile?.trim() || env.KIBANA_PROFILE?.trim() || "default",
|
|
98
|
-
baseUrl,
|
|
99
|
-
username,
|
|
100
|
-
password,
|
|
101
|
-
client: values.client ?? "codex",
|
|
102
|
-
packageSpecifier: values.package,
|
|
103
|
-
mcpName: values.mcpName,
|
|
104
|
-
makeDefault: values.default,
|
|
105
|
-
replaceExisting: values.replace ?? false,
|
|
106
|
-
timeoutMs: values.timeout,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function parseTimeout(value) {
|
|
110
|
-
const timeout = Number(value);
|
|
111
|
-
if (!Number.isFinite(timeout)) {
|
|
112
|
-
throw new InvalidArgumentError("--timeout must be a number of milliseconds.");
|
|
113
|
-
}
|
|
114
|
-
return timeout;
|
|
115
|
-
}
|
|
116
|
-
function renderBootstrapResult(result) {
|
|
117
|
-
return [
|
|
118
|
-
`Bootstrap verified for profile '${result.profileName}'.`,
|
|
119
|
-
result.sourceCount === 0
|
|
120
|
-
? "Source catalog is empty. No Kibana indexes were inspected or configured. Ask the user which index or index pattern to configure before querying logs."
|
|
121
|
-
: `Preserved ${result.sourceCount} explicitly configured source${result.sourceCount === 1 ? "" : "s"}; bootstrap did not inspect them.`,
|
|
122
|
-
result.registered
|
|
123
|
-
? `Codex MCP registration is installed and verified for profile '${result.profileName}'.`
|
|
124
|
-
: "Client registration was skipped by request.",
|
|
125
|
-
].join("\n");
|
|
126
|
-
}
|
|
127
|
-
async function readAllInput(stdin) {
|
|
128
|
-
let raw = "";
|
|
129
|
-
for await (const chunk of stdin)
|
|
130
|
-
raw += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
131
|
-
return raw;
|
|
132
|
-
}
|
|
@@ -3,13 +3,8 @@ import { z } from "zod";
|
|
|
3
3
|
import { type SecretStore } from "../profiles/secret_store.js";
|
|
4
4
|
import { ProfileStore } from "../profiles/store.js";
|
|
5
5
|
import type { AppConfig, ResolvedAppConfig } from "../types.js";
|
|
6
|
-
export declare const DEFAULT_RUNTIME_SOURCE_CATALOG_PATH = "config/sources.runtime.json";
|
|
7
|
-
export declare const DEFAULT_STATIC_SOURCE_CATALOG_PATH = "config/sources.json";
|
|
8
6
|
export declare const PROFILE_NAME_ENV = "KIBANA_PROFILE";
|
|
9
7
|
export declare const PROFILE_PLACEHOLDER = "{profile}";
|
|
10
|
-
export declare class NoSavedProfileError extends Error {
|
|
11
|
-
constructor(message: string);
|
|
12
|
-
}
|
|
13
8
|
export declare const sourceDefinitionSchema: z.ZodObject<{
|
|
14
9
|
id: z.ZodString;
|
|
15
10
|
name: z.ZodString;
|
|
@@ -81,7 +76,6 @@ export declare const sourceCatalogSchema: z.ZodObject<{
|
|
|
81
76
|
evidenceFields: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
82
77
|
}, z.core.$strip>>;
|
|
83
78
|
}, z.core.$strip>;
|
|
84
|
-
export declare function resolveSourceCatalogPath(envInput?: NodeJS.ProcessEnv): string;
|
|
85
79
|
export declare function parseAppConfig(envInput: unknown, sourceCatalogInput: unknown): AppConfig;
|
|
86
80
|
export declare function loadConfigFromEnvironment(envInput?: NodeJS.ProcessEnv, dependencies?: {
|
|
87
81
|
profileStore?: Pick<ProfileStore, "findProfile" | "getDefaultProfile">;
|
|
@@ -93,8 +87,3 @@ export declare function loadConfigFromEnvironment(envInput?: NodeJS.ProcessEnv,
|
|
|
93
87
|
* Reads the connection supplied through the bootstrap environment variables, if any.
|
|
94
88
|
*/
|
|
95
89
|
export declare function readBootstrapConnection(envInput?: NodeJS.ProcessEnv): AppConfig["kibana"] | undefined;
|
|
96
|
-
export declare function persistSourceCatalog(sources: AppConfig["sources"], options?: {
|
|
97
|
-
envInput?: NodeJS.ProcessEnv;
|
|
98
|
-
sourceCatalogPath?: string;
|
|
99
|
-
catalog?: unknown;
|
|
100
|
-
}): Promise<string>;
|
|
@@ -3,17 +3,8 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { SecretStoreError, createSecretStore } from "../profiles/secret_store.js";
|
|
5
5
|
import { ProfileStore } from "../profiles/store.js";
|
|
6
|
-
import { writeTextAtomically } from "../utils/fs.js";
|
|
7
|
-
export const DEFAULT_RUNTIME_SOURCE_CATALOG_PATH = "config/sources.runtime.json";
|
|
8
|
-
export const DEFAULT_STATIC_SOURCE_CATALOG_PATH = "config/sources.json";
|
|
9
6
|
export const PROFILE_NAME_ENV = "KIBANA_PROFILE";
|
|
10
7
|
export const PROFILE_PLACEHOLDER = "{profile}";
|
|
11
|
-
export class NoSavedProfileError extends Error {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "NoSavedProfileError";
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
8
|
const bootstrapEnvSchema = z.object({
|
|
18
9
|
KIBANA_BASE_URL: z.url(),
|
|
19
10
|
KIBANA_USERNAME: z.string().min(1),
|
|
@@ -112,13 +103,6 @@ function buildKibanaConfig(env) {
|
|
|
112
103
|
timeoutMs: env.KIBANA_TIMEOUT_MS ?? 10000,
|
|
113
104
|
};
|
|
114
105
|
}
|
|
115
|
-
export function resolveSourceCatalogPath(envInput = process.env) {
|
|
116
|
-
const explicitPath = envInput.KIBANA_SOURCE_CATALOG_PATH?.trim();
|
|
117
|
-
if (explicitPath) {
|
|
118
|
-
return explicitPath;
|
|
119
|
-
}
|
|
120
|
-
return DEFAULT_RUNTIME_SOURCE_CATALOG_PATH;
|
|
121
|
-
}
|
|
122
106
|
export function parseAppConfig(envInput, sourceCatalogInput) {
|
|
123
107
|
const env = bootstrapEnvSchema.parse(envInput);
|
|
124
108
|
const sourceCatalog = sourceCatalogSchema.parse(sourceCatalogInput);
|
|
@@ -132,19 +116,14 @@ export async function loadConfigFromEnvironment(envInput = process.env, dependen
|
|
|
132
116
|
const readFileImpl = dependencies.readFile ?? readFile;
|
|
133
117
|
const kibana = readBootstrapConnection(envInput);
|
|
134
118
|
if (kibana) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
119
|
+
const sourceCatalogPath = overrides.KIBANA_SOURCE_CATALOG_PATH?.trim();
|
|
120
|
+
if (!sourceCatalogPath) {
|
|
121
|
+
throw new Error("KIBANA_SOURCE_CATALOG_PATH is required for an environment connection.");
|
|
122
|
+
}
|
|
139
123
|
return {
|
|
140
124
|
kibana,
|
|
141
|
-
sources:
|
|
142
|
-
sourceCatalogPath
|
|
143
|
-
sourceCatalogOrigin: sourceCatalog.loadedFrom === DEFAULT_STATIC_SOURCE_CATALOG_PATH
|
|
144
|
-
? "static_default"
|
|
145
|
-
: preferredPath === DEFAULT_RUNTIME_SOURCE_CATALOG_PATH
|
|
146
|
-
? "runtime_default"
|
|
147
|
-
: "environment",
|
|
125
|
+
sources: await loadSourceCatalog(sourceCatalogPath, readFileImpl),
|
|
126
|
+
sourceCatalogPath,
|
|
148
127
|
};
|
|
149
128
|
}
|
|
150
129
|
const profileStore = dependencies.profileStore ?? new ProfileStore();
|
|
@@ -156,12 +135,9 @@ export async function loadConfigFromEnvironment(envInput = process.env, dependen
|
|
|
156
135
|
})
|
|
157
136
|
: await profileStore.getDefaultProfile();
|
|
158
137
|
if (!profile) {
|
|
159
|
-
if (!requestedProfileName) {
|
|
160
|
-
throw new NoSavedProfileError("No saved Kibana profile is available.");
|
|
161
|
-
}
|
|
162
138
|
throw new Error(requestedProfileName
|
|
163
139
|
? `Saved Kibana profile '${requestedProfileName}' was not found.`
|
|
164
|
-
: "No saved Kibana
|
|
140
|
+
: "No saved Kibana connection is available. Run connection bootstrap first.");
|
|
165
141
|
}
|
|
166
142
|
const savedSecret = await secretStore.load(profile.id).catch((error) => {
|
|
167
143
|
if (error instanceof SecretStoreError && error.code === "NOT_FOUND") {
|
|
@@ -170,7 +146,6 @@ export async function loadConfigFromEnvironment(envInput = process.env, dependen
|
|
|
170
146
|
throw error;
|
|
171
147
|
});
|
|
172
148
|
const sourceCatalogPath = overrides.KIBANA_SOURCE_CATALOG_PATH?.trim() || profile.sourceCatalogPath;
|
|
173
|
-
const sourceCatalog = await loadSourceCatalog([sourceCatalogPath], readFileImpl);
|
|
174
149
|
return {
|
|
175
150
|
kibana: {
|
|
176
151
|
baseUrl: profile.baseUrl.replace(/\/+$/, ""),
|
|
@@ -178,10 +153,9 @@ export async function loadConfigFromEnvironment(envInput = process.env, dependen
|
|
|
178
153
|
password: savedSecret.password,
|
|
179
154
|
timeoutMs: parseTimeoutOverride(overrides.KIBANA_TIMEOUT_MS) ?? profile.timeoutMs,
|
|
180
155
|
},
|
|
181
|
-
sources:
|
|
156
|
+
sources: await loadSourceCatalog(sourceCatalogPath, readFileImpl),
|
|
182
157
|
profileName: profile.name,
|
|
183
158
|
sourceCatalogPath,
|
|
184
|
-
sourceCatalogOrigin: overrides.KIBANA_SOURCE_CATALOG_PATH?.trim() ? "environment" : "profile",
|
|
185
159
|
};
|
|
186
160
|
}
|
|
187
161
|
/**
|
|
@@ -196,11 +170,6 @@ export function readBootstrapConnection(envInput = process.env) {
|
|
|
196
170
|
? buildKibanaConfig(bootstrapEnvSchema.parse(overrides))
|
|
197
171
|
: undefined;
|
|
198
172
|
}
|
|
199
|
-
export async function persistSourceCatalog(sources, options = {}) {
|
|
200
|
-
const sourceCatalogPath = options.sourceCatalogPath ?? resolveSourceCatalogPath(options.envInput);
|
|
201
|
-
await writeTextAtomically(sourceCatalogPath, `${JSON.stringify(options.catalog ?? { sources }, null, 2)}\n`);
|
|
202
|
-
return sourceCatalogPath;
|
|
203
|
-
}
|
|
204
173
|
function hasBootstrapConfig(envInput) {
|
|
205
174
|
return Boolean(envInput.KIBANA_BASE_URL?.trim() &&
|
|
206
175
|
envInput.KIBANA_USERNAME?.trim() &&
|
|
@@ -224,25 +193,6 @@ function parseTimeoutOverride(value) {
|
|
|
224
193
|
}
|
|
225
194
|
return numericValue;
|
|
226
195
|
}
|
|
227
|
-
async function loadSourceCatalog(
|
|
228
|
-
|
|
229
|
-
let loadedFrom = candidatePaths[0];
|
|
230
|
-
let lastError;
|
|
231
|
-
for (const candidatePath of candidatePaths) {
|
|
232
|
-
try {
|
|
233
|
-
sourceCatalogRaw = await readFileImpl(candidatePath, "utf8");
|
|
234
|
-
loadedFrom = candidatePath;
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
lastError = error;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (!sourceCatalogRaw) {
|
|
242
|
-
throw lastError instanceof Error ? lastError : new Error("Source catalog could not be loaded");
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
loadedFrom,
|
|
246
|
-
value: JSON.parse(sourceCatalogRaw),
|
|
247
|
-
};
|
|
196
|
+
async function loadSourceCatalog(path, readFileImpl) {
|
|
197
|
+
return sourceCatalogSchema.parse(JSON.parse(await readFileImpl(path, "utf8"))).sources;
|
|
248
198
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { bindCatalogStore } from "../stores/catalog_store.js";
|
|
2
2
|
import { createFileCatalogStore } from "../stores/file.js";
|
|
3
3
|
import { CONFIG_FILE_ENV, applyConfigFile, loadConfigFile, } from "./config_file.js";
|
|
4
|
-
import {
|
|
4
|
+
import { PROFILE_NAME_ENV, PROFILE_PLACEHOLDER, loadConfigFromEnvironment, readBootstrapConnection, } from "./environment.js";
|
|
5
5
|
/** Profile served over stdio when per-profile catalogs are configured and none is named. */
|
|
6
6
|
export const DEFAULT_PROFILE_NAME = "default";
|
|
7
7
|
/**
|
|
@@ -71,16 +71,8 @@ async function resolveConfiguration(envInput, dependencies, customStore) {
|
|
|
71
71
|
}
|
|
72
72
|
// Named saved profiles carry their own catalog: the global catalog override is not applied.
|
|
73
73
|
const loadProfileConfig = (profileName) => loadConfigFromEnvironment({ KIBANA_TIMEOUT_MS: envInput.KIBANA_TIMEOUT_MS, [PROFILE_NAME_ENV]: profileName }, { ...dependencies, exactProfileName: true });
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
if (error instanceof NoSavedProfileError) {
|
|
82
|
-
return {};
|
|
83
|
-
}
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
74
|
+
return {
|
|
75
|
+
defaultConfig: await loadConfigFromEnvironment(envInput, dependencies),
|
|
76
|
+
loadProfileConfig,
|
|
77
|
+
};
|
|
86
78
|
}
|
package/dist/src/mcp_entry.js
CHANGED
|
@@ -26,14 +26,10 @@ export declare class ProfileRuntimeRegistry {
|
|
|
26
26
|
private readonly inFlight;
|
|
27
27
|
private readonly clients;
|
|
28
28
|
private readonly refreshes;
|
|
29
|
-
private defaultRuntime?;
|
|
29
|
+
private readonly defaultRuntime?;
|
|
30
30
|
constructor(options?: ProfileRuntimeRegistryOptions);
|
|
31
31
|
get hasDefault(): boolean;
|
|
32
32
|
get supportsNamedProfiles(): boolean;
|
|
33
|
-
/** False only for a server started without any configuration, waiting for the legacy 'configure' tool. */
|
|
34
|
-
get isConfigured(): boolean;
|
|
35
|
-
getDefault(): ProfileRuntime | undefined;
|
|
36
|
-
setDefault(config: ResolvedAppConfig): ProfileRuntime;
|
|
37
33
|
resolve(profileName?: string): Promise<ProfileRuntime>;
|
|
38
34
|
private refresh;
|
|
39
35
|
replaceSources(runtime: ProfileRuntime, sources: SourceDefinition[], catalogContent?: string | undefined): void;
|
|
@@ -15,6 +15,9 @@ export class ProfileRuntimeRegistry {
|
|
|
15
15
|
refreshes = new WeakMap();
|
|
16
16
|
defaultRuntime;
|
|
17
17
|
constructor(options = {}) {
|
|
18
|
+
if (!options.defaultConfig && !options.loadProfileConfig) {
|
|
19
|
+
throw new Error("No saved Kibana connection is available. Run connection bootstrap first.");
|
|
20
|
+
}
|
|
18
21
|
this.kibanaClientFactory =
|
|
19
22
|
options.kibanaClientFactory ?? ((config) => new KibanaClient(config));
|
|
20
23
|
this.loadProfileConfig = options.loadProfileConfig;
|
|
@@ -29,25 +32,12 @@ export class ProfileRuntimeRegistry {
|
|
|
29
32
|
get supportsNamedProfiles() {
|
|
30
33
|
return this.loadProfileConfig !== undefined;
|
|
31
34
|
}
|
|
32
|
-
/** False only for a server started without any configuration, waiting for the legacy 'configure' tool. */
|
|
33
|
-
get isConfigured() {
|
|
34
|
-
return this.hasDefault || this.supportsNamedProfiles;
|
|
35
|
-
}
|
|
36
|
-
getDefault() {
|
|
37
|
-
return this.defaultRuntime;
|
|
38
|
-
}
|
|
39
|
-
setDefault(config) {
|
|
40
|
-
this.defaultRuntime = this.createRuntime(config);
|
|
41
|
-
return this.defaultRuntime;
|
|
42
|
-
}
|
|
43
35
|
async resolve(profileName) {
|
|
44
36
|
if (profileName === undefined) {
|
|
45
37
|
if (this.defaultRuntime) {
|
|
46
38
|
return this.refresh(this.defaultRuntime);
|
|
47
39
|
}
|
|
48
|
-
throw new Error(this.
|
|
49
|
-
? "No profile was supplied for this call and no default profile is configured."
|
|
50
|
-
: "Server is not configured. Call the 'configure' tool first.");
|
|
40
|
+
throw new Error("No profile was supplied for this call and no default profile is configured.");
|
|
51
41
|
}
|
|
52
42
|
// Names are compared exactly: two identities differing only by case are two profiles, each
|
|
53
43
|
// with its own catalog.
|
package/dist/src/server.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { type KibanaClient } from "./kibana/client.js";
|
|
3
3
|
import { ProfileRuntimeRegistry } from "./profiles/runtime.js";
|
|
4
|
-
import { executeConfigure } from "./tools/configure.js";
|
|
5
4
|
import { executeConfigureIndex } from "./tools/configure_index.js";
|
|
6
5
|
import { executeDescribeFields } from "./tools/describe_fields.js";
|
|
7
6
|
import { executeDiscover } from "./tools/discover.js";
|
|
@@ -12,7 +11,6 @@ import type { ResolvedAppConfig } from "./types.js";
|
|
|
12
11
|
export interface Application {
|
|
13
12
|
server: McpServer;
|
|
14
13
|
handlers: {
|
|
15
|
-
configure: (input: unknown) => Promise<Awaited<ReturnType<typeof executeConfigure>>["result"]>;
|
|
16
14
|
configure_index: (input: unknown, callerSignal?: AbortSignal) => Promise<Awaited<ReturnType<typeof executeConfigureIndex>>["result"]>;
|
|
17
15
|
describe_fields: (input: unknown, callerSignal?: AbortSignal) => Promise<Awaited<ReturnType<typeof executeDescribeFields>>>;
|
|
18
16
|
discover: (input: unknown) => Promise<ReturnType<typeof executeDiscover>>;
|