@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 +6 -2
- package/dist/src/bin/kibana-mcp-server.js +54 -0
- package/dist/src/commands/context.js +1 -0
- package/dist/src/commands/serve.js +19 -0
- package/dist/src/commands/setup.js +132 -0
- package/dist/src/kibana_client.js +267 -90
- package/dist/src/schema_catalog.js +20 -6
- package/dist/src/server.js +63 -16
- package/dist/src/tools/configure_index.js +2 -2
- package/dist/src/tools/describe_fields.js +2 -2
- package/dist/src/tools/filter.js +1 -0
- package/dist/src/tools/query.js +5 -2
- package/dist/src/utils/cli_io.js +11 -0
- package/dist/src/utils/cli_prompts.js +67 -0
- package/dist/src/utils/cli_ui.js +19 -0
- package/package.json +6 -4
- package/dist/src/cli.js +0 -323
- package/dist/src/index.js +0 -10
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/
|
|
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/
|
|
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
|
+
}
|