@havesomecode/kibana-mcp-server 1.2.0 → 1.3.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 CHANGED
@@ -104,7 +104,7 @@ npm run build
104
104
  5. Open the plugin directory in Codex and install `Kibana Log Investigation` from the repo marketplace.
105
105
  - if the current model cannot complete that install itself, do the Codex UI click manually and let the agent continue with configuration afterward
106
106
  6. Restart Codex if the new MCP server does not appear immediately.
107
- 7. Run `node dist/src/index.js bootstrap --client none` to verify the connection and save an empty profile. The repo plugin already supplies the client registration.
107
+ 7. Run `node dist/src/bin/kibana-mcp-server.js bootstrap --client none` to verify the connection and save an empty profile. The repo plugin already supplies the client registration.
108
108
  8. In a fresh thread, call `discover`, ask the user for the exact index or pattern, then call `configure_index` only after they answer.
109
109
 
110
110
  Repo-scoped plugin files:
@@ -134,7 +134,7 @@ The preferred path is deterministic connection-only bootstrap:
134
134
 
135
135
  Guided setup remains available for hand-authored source catalogs:
136
136
 
137
- - `npm run setup` or `node dist/src/index.js setup`
137
+ - `npm run setup` or `node dist/src/bin/kibana-mcp-server.js setup`
138
138
  - saves non-secret profile metadata in the user’s machine-level app config directory
139
139
  - saves credentials in the platform credential store
140
140
  - macOS Keychain
@@ -177,6 +177,10 @@ Environment variables:
177
177
  - `KIBANA_TIMEOUT_MS` optional, default `10000`
178
178
  - `KIBANA_SOURCE_CATALOG_PATH` optional, default `config/sources.runtime.json`
179
179
 
180
+ `KIBANA_TIMEOUT_MS` bounds each Kibana HTTP request end to end: it includes waiting for a request slot, connecting, and reading the response body. The server allows at most eight concurrent Kibana requests and 32 queued requests per configured client; excess work fails immediately with `KIBANA_OVERLOADED`. Cancelled MCP tool calls abort their queued or in-flight backend work.
181
+
182
+ Backend failures use stable diagnostic codes such as `KIBANA_TIMEOUT`, `KIBANA_CANCELLED`, `KIBANA_OVERLOADED`, `KIBANA_AUTHENTICATION`, `KIBANA_DNS`, `KIBANA_TLS`, `KIBANA_CONNECTION`, and `KIBANA_RESPONSE`. Failed backend tool results also expose the diagnostic fields in `_meta.kibana_request_error`.
183
+
180
184
  `KIBANA_BASE_URL` is the Kibana base prefix that the server joins with each configured backend or schema path.
181
185
 
182
186
  Use:
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from "node:url";
3
+ import { Command, CommanderError } from "commander";
4
+ import registerServeCommand from "../commands/serve.js";
5
+ import registerSetupCommands from "../commands/setup.js";
6
+ import { startMcpServer } from "../mcp_runtime.js";
7
+ import { createCliIo } from "../utils/cli_io.js";
8
+ import { createCliUi } from "../utils/cli_ui.js";
9
+ export async function runCli(argv = process.argv.slice(2), overrides = {}, dependencies = {}) {
10
+ const io = createCliIo(overrides);
11
+ let exitCode = 0;
12
+ const context = {
13
+ argv,
14
+ io,
15
+ ui: createCliUi(io),
16
+ dependencies,
17
+ setExitCode: (code) => {
18
+ exitCode = code;
19
+ },
20
+ };
21
+ const program = new Command()
22
+ .name("kibana-mcp-server")
23
+ .description("Kibana Log Investigation")
24
+ .showHelpAfterError()
25
+ .exitOverride()
26
+ .configureOutput({
27
+ writeOut: (message) => io.stdout(message.trimEnd()),
28
+ writeErr: (message) => io.stderr(message.trimEnd()),
29
+ })
30
+ .action(async () => {
31
+ try {
32
+ await (dependencies.startMcpServerFn ?? startMcpServer)(io.env);
33
+ }
34
+ catch (error) {
35
+ context.ui.error(error instanceof Error ? error.message : String(error));
36
+ exitCode = 1;
37
+ }
38
+ });
39
+ registerSetupCommands(program, context);
40
+ registerServeCommand(program, context);
41
+ try {
42
+ await program.parseAsync(["node", "kibana-mcp-server", ...argv], { from: "node" });
43
+ }
44
+ catch (error) {
45
+ if (error instanceof CommanderError)
46
+ return error.exitCode;
47
+ io.stderr(error instanceof Error ? error.message : String(error));
48
+ return 1;
49
+ }
50
+ return exitCode;
51
+ }
52
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
53
+ process.exitCode = await runCli();
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { PROFILE_NAME_ENV } from "../config.js";
2
+ import { startMcpServer } from "../mcp_runtime.js";
3
+ export default (program, context) => {
4
+ program
5
+ .command("serve")
6
+ .description("Start the stdio MCP server")
7
+ .option("--profile <name>", "Saved profile name")
8
+ .action(async (options) => {
9
+ try {
10
+ await (context.dependencies.startMcpServerFn ?? startMcpServer)(options.profile
11
+ ? { ...context.io.env, [PROFILE_NAME_ENV]: options.profile }
12
+ : context.io.env);
13
+ }
14
+ catch (error) {
15
+ context.ui.error(error instanceof Error ? error.message : String(error));
16
+ context.setExitCode(1);
17
+ }
18
+ });
19
+ };
@@ -0,0 +1,132 @@
1
+ import { InvalidArgumentError, Option } from "commander";
2
+ import { runBootstrap as runBootstrapFlow } from "../bootstrap.js";
3
+ import { runSetupFlow } from "../setup_flow.js";
4
+ import { PromptCancelledError, createPromptIo } from "../utils/cli_prompts.js";
5
+ export default function registerSetup(program, context) {
6
+ const setupCommand = addOptions(program
7
+ .command("setup")
8
+ .alias("bootstrap")
9
+ .description("Run guided machine setup and connection bootstrap")
10
+ .addHelpText("after", "\nBootstrap is an alias for setup. Without options, setup is guided; with options, it verifies and saves only the Kibana connection and does not inspect or configure indexes.\n\nConnection options use KIBANA_BASE_URL, KIBANA_USERNAME, and KIBANA_PASSWORD as fallbacks. Passwords are accepted only through --password-stdin, --password-env <NAME>, or KIBANA_PASSWORD."));
11
+ setupCommand.action(async () => {
12
+ const options = readOptions(context.argv);
13
+ if (context.argv.some((argument) => argument.startsWith("-")))
14
+ return runBootstrap(options, context);
15
+ const promptIo = await createPromptIo(context.io);
16
+ try {
17
+ const result = await (context.dependencies.runSetupFlowFn ?? runSetupFlow)(promptIo);
18
+ context.ui.success(`Saved ${result.profiles.length} environment${result.profiles.length === 1 ? "" : "s"}. Default environment: ${result.defaultProfileName}.`);
19
+ }
20
+ catch (error) {
21
+ if (error instanceof PromptCancelledError)
22
+ context.setExitCode(130);
23
+ else
24
+ throw error;
25
+ }
26
+ finally {
27
+ await promptIo.close();
28
+ }
29
+ });
30
+ }
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
+ }