@havesomecode/kibana-mcp-server 1.3.0 → 1.4.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 +54 -490
- package/dist/src/bin/kibana-mcp-server.d.ts +2 -0
- package/dist/src/bin/kibana-mcp-server.js +2 -53
- package/dist/src/bin/run_cli.d.ts +4 -0
- package/dist/src/bin/run_cli.js +49 -0
- package/dist/src/commands/context.d.ts +18 -0
- package/dist/src/commands/serve.d.ts +4 -0
- package/dist/src/commands/serve.js +30 -6
- package/dist/src/commands/setup.d.ts +3 -0
- package/dist/src/commands/setup.js +2 -2
- package/dist/src/config/config_file.d.ts +59 -0
- package/dist/src/config/config_file.js +76 -0
- package/dist/src/config/environment.d.ts +100 -0
- package/dist/src/{config.js → config/environment.js} +26 -24
- package/dist/src/config/startup.d.ts +31 -0
- package/dist/src/config/startup.js +86 -0
- package/dist/src/kibana/client.d.ts +28 -0
- package/dist/src/kibana/schema_catalog.d.ts +11 -0
- package/dist/src/{schema_catalog.js → kibana/schema_catalog.js} +33 -11
- package/dist/src/kibana/source_catalog.d.ts +10 -0
- package/dist/src/mcp_entry.d.ts +2 -0
- package/dist/src/mcp_entry.js +1 -1
- package/dist/src/profiles/context.d.ts +4 -0
- package/dist/src/profiles/context.js +15 -0
- package/dist/src/profiles/names.d.ts +10 -0
- package/dist/src/profiles/names.js +35 -0
- package/dist/src/profiles/paths.d.ts +9 -0
- package/dist/src/profiles/runtime.d.ts +43 -0
- package/dist/src/profiles/runtime.js +150 -0
- package/dist/src/profiles/secret_store.d.ts +20 -0
- package/dist/src/profiles/state_lock.d.ts +13 -0
- package/dist/src/{state_lock.js → profiles/state_lock.js} +14 -5
- package/dist/src/profiles/store.d.ts +34 -0
- package/dist/src/{profile_store.js → profiles/store.js} +7 -5
- package/dist/src/query/compiler.d.ts +9 -0
- package/dist/src/query/cursor.d.ts +8 -0
- package/dist/src/query/normalize.d.ts +2 -0
- package/dist/src/server.d.ts +29 -0
- package/dist/src/server.js +40 -50
- package/dist/src/setup/bootstrap.d.ts +50 -0
- package/dist/src/{bootstrap.js → setup/bootstrap.js} +10 -36
- package/dist/src/setup/client_registration.d.ts +38 -0
- package/dist/src/setup/setup_flow.d.ts +26 -0
- package/dist/src/{setup_flow.js → setup/setup_flow.js} +6 -6
- package/dist/src/stores/catalog_store.d.ts +38 -0
- package/dist/src/stores/catalog_store.js +22 -0
- package/dist/src/stores/file.d.ts +5 -0
- package/dist/src/stores/file.js +48 -0
- package/dist/src/stores/ioredis.d.ts +21 -0
- package/dist/src/stores/ioredis.js +33 -0
- package/dist/src/stores/mongodb.d.ts +42 -0
- package/dist/src/stores/mongodb.js +57 -0
- package/dist/src/stores/postgres.d.ts +24 -0
- package/dist/src/stores/postgres.js +65 -0
- package/dist/src/stores/redis.d.ts +24 -0
- package/dist/src/stores/redis.js +31 -0
- package/dist/src/stores/shared.d.ts +25 -0
- package/dist/src/stores/shared.js +88 -0
- package/dist/src/stores/sqlite.d.ts +26 -0
- package/dist/src/stores/sqlite.js +63 -0
- package/dist/src/tools/configure.d.ts +59 -0
- package/dist/src/tools/configure.js +1 -1
- package/dist/src/tools/configure_index.d.ts +35 -0
- package/dist/src/tools/configure_index.js +75 -66
- package/dist/src/tools/describe_fields.d.ts +29 -0
- package/dist/src/tools/discover.d.ts +27 -0
- package/dist/src/tools/filter.d.ts +180 -0
- package/dist/src/tools/query.d.ts +181 -0
- package/dist/src/transports/http.d.ts +15 -0
- package/dist/src/transports/http.js +291 -0
- package/dist/src/transports/stdio.d.ts +1 -0
- package/dist/src/transports/stdio.js +46 -0
- package/dist/src/types.d.ts +254 -0
- package/dist/src/utils/cli_io.d.ts +11 -0
- package/dist/src/utils/cli_prompts.d.ts +9 -0
- package/dist/src/utils/cli_ui.d.ts +6 -0
- package/dist/src/utils/fs.d.ts +3 -0
- package/dist/src/utils/fs.js +30 -0
- package/package.json +58 -1
- package/skills/kibana-log-investigation/SKILL.md +36 -5
- package/dist/src/mcp_runtime.js +0 -23
- /package/dist/src/{kibana_client.js → kibana/client.js} +0 -0
- /package/dist/src/{source_catalog.js → kibana/source_catalog.js} +0 -0
- /package/dist/src/{profile_paths.js → profiles/paths.js} +0 -0
- /package/dist/src/{secret_store.js → profiles/secret_store.js} +0 -0
- /package/dist/src/{client_registration.js → setup/client_registration.js} +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { KibanaClient } from "../kibana/client.js";
|
|
4
|
+
import type { SchemaCatalog } from "../kibana/schema_catalog.js";
|
|
5
|
+
import type { SourceCatalog } from "../kibana/source_catalog.js";
|
|
6
|
+
export declare const filterInputSchema: z.ZodObject<{
|
|
7
|
+
source_ids: z.ZodArray<z.ZodString>;
|
|
8
|
+
start_time: z.ZodString;
|
|
9
|
+
end_time: z.ZodString;
|
|
10
|
+
field: z.ZodString;
|
|
11
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
12
|
+
filters: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
13
|
+
field: z.ZodString;
|
|
14
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
15
|
+
}, z.core.$strip>>>;
|
|
16
|
+
nested_filters: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
17
|
+
path: z.ZodString;
|
|
18
|
+
field: z.ZodString;
|
|
19
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
20
|
+
}, z.core.$strip>>>;
|
|
21
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
22
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
23
|
+
count: "count";
|
|
24
|
+
grouped_top_hits: "grouped_top_hits";
|
|
25
|
+
histogram: "histogram";
|
|
26
|
+
hits: "hits";
|
|
27
|
+
stats: "stats";
|
|
28
|
+
terms: "terms";
|
|
29
|
+
}>>;
|
|
30
|
+
sort: z.ZodDefault<z.ZodEnum<{
|
|
31
|
+
asc: "asc";
|
|
32
|
+
desc: "desc";
|
|
33
|
+
}>>;
|
|
34
|
+
sort_by: z.ZodOptional<z.ZodString>;
|
|
35
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
extract_nested: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
stats_field: z.ZodOptional<z.ZodString>;
|
|
38
|
+
top_hits_size: z.ZodDefault<z.ZodNumber>;
|
|
39
|
+
histogram_interval: z.ZodOptional<z.ZodString>;
|
|
40
|
+
group_by: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export declare const filterOutputSchema: z.ZodObject<{
|
|
43
|
+
query_echo: z.ZodObject<{
|
|
44
|
+
source_ids: z.ZodArray<z.ZodString>;
|
|
45
|
+
start_time: z.ZodString;
|
|
46
|
+
end_time: z.ZodString;
|
|
47
|
+
text: z.ZodOptional<z.ZodString>;
|
|
48
|
+
filters: z.ZodArray<z.ZodObject<{
|
|
49
|
+
field: z.ZodString;
|
|
50
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
51
|
+
resolved_field: z.ZodString;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
nested_filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
54
|
+
path: z.ZodString;
|
|
55
|
+
field: z.ZodString;
|
|
56
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
57
|
+
resolved_field: z.ZodString;
|
|
58
|
+
query_strategy: z.ZodOptional<z.ZodEnum<{
|
|
59
|
+
flat_object_path: "flat_object_path";
|
|
60
|
+
nested: "nested";
|
|
61
|
+
}>>;
|
|
62
|
+
}, z.core.$strip>>>;
|
|
63
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
64
|
+
mode: z.ZodEnum<{
|
|
65
|
+
count: "count";
|
|
66
|
+
grouped_top_hits: "grouped_top_hits";
|
|
67
|
+
histogram: "histogram";
|
|
68
|
+
hits: "hits";
|
|
69
|
+
stats: "stats";
|
|
70
|
+
terms: "terms";
|
|
71
|
+
}>;
|
|
72
|
+
sort: z.ZodEnum<{
|
|
73
|
+
asc: "asc";
|
|
74
|
+
desc: "desc";
|
|
75
|
+
}>;
|
|
76
|
+
sort_by: z.ZodOptional<z.ZodString>;
|
|
77
|
+
resolved_sort_by_by_source: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
78
|
+
source_id: z.ZodString;
|
|
79
|
+
resolved_sort_by: z.ZodString;
|
|
80
|
+
}, z.core.$strip>>>;
|
|
81
|
+
advisories: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
82
|
+
kind: z.ZodEnum<{
|
|
83
|
+
non_nested_object_array: "non_nested_object_array";
|
|
84
|
+
preferred_exact_field: "preferred_exact_field";
|
|
85
|
+
schema_unavailable: "schema_unavailable";
|
|
86
|
+
}>;
|
|
87
|
+
source_id: z.ZodString;
|
|
88
|
+
purpose: z.ZodEnum<{
|
|
89
|
+
filter: "filter";
|
|
90
|
+
group_by: "group_by";
|
|
91
|
+
sort: "sort";
|
|
92
|
+
}>;
|
|
93
|
+
requested_field: z.ZodString;
|
|
94
|
+
resolved_field: z.ZodString;
|
|
95
|
+
reason: z.ZodString;
|
|
96
|
+
}, z.core.$strip>>>;
|
|
97
|
+
limit: z.ZodNumber;
|
|
98
|
+
stats_field: z.ZodOptional<z.ZodString>;
|
|
99
|
+
top_hits_size: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
histogram_interval: z.ZodOptional<z.ZodString>;
|
|
101
|
+
group_by: z.ZodOptional<z.ZodString>;
|
|
102
|
+
truncated: z.ZodBoolean;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
total: z.ZodNumber;
|
|
105
|
+
next_cursor: z.ZodOptional<z.ZodString>;
|
|
106
|
+
hits: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
107
|
+
source_id: z.ZodString;
|
|
108
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
109
|
+
summary: z.ZodString;
|
|
110
|
+
document_id: z.ZodNullable<z.ZodString>;
|
|
111
|
+
index: z.ZodNullable<z.ZodString>;
|
|
112
|
+
selected_fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
113
|
+
nested_matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
114
|
+
path: z.ZodString;
|
|
115
|
+
documents: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
116
|
+
}, z.core.$strip>>>;
|
|
117
|
+
raw_document: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
118
|
+
}, z.core.$strip>>>;
|
|
119
|
+
counts_by_source: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
120
|
+
source_id: z.ZodString;
|
|
121
|
+
count: z.ZodNumber;
|
|
122
|
+
}, z.core.$strip>>>;
|
|
123
|
+
histograms: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
124
|
+
source_id: z.ZodString;
|
|
125
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
126
|
+
key: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
|
|
127
|
+
key_as_string: z.ZodOptional<z.ZodString>;
|
|
128
|
+
count: z.ZodNumber;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
}, z.core.$strip>>>;
|
|
131
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
132
|
+
source_id: z.ZodString;
|
|
133
|
+
field: z.ZodString;
|
|
134
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
135
|
+
key: z.ZodString;
|
|
136
|
+
count: z.ZodNumber;
|
|
137
|
+
}, z.core.$strip>>;
|
|
138
|
+
}, z.core.$strip>>>;
|
|
139
|
+
stats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
140
|
+
source_id: z.ZodString;
|
|
141
|
+
field: z.ZodString;
|
|
142
|
+
summary: z.ZodObject<{
|
|
143
|
+
count: z.ZodNumber;
|
|
144
|
+
min: z.ZodNullable<z.ZodNumber>;
|
|
145
|
+
max: z.ZodNullable<z.ZodNumber>;
|
|
146
|
+
avg: z.ZodNullable<z.ZodNumber>;
|
|
147
|
+
sum: z.ZodNumber;
|
|
148
|
+
p50: z.ZodNullable<z.ZodNumber>;
|
|
149
|
+
p95: z.ZodNullable<z.ZodNumber>;
|
|
150
|
+
p99: z.ZodNullable<z.ZodNumber>;
|
|
151
|
+
}, z.core.$strip>;
|
|
152
|
+
}, z.core.$strip>>>;
|
|
153
|
+
grouped_hits: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
154
|
+
source_id: z.ZodString;
|
|
155
|
+
group_by: z.ZodString;
|
|
156
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
157
|
+
key: z.ZodString;
|
|
158
|
+
count: z.ZodNumber;
|
|
159
|
+
hits: z.ZodArray<z.ZodObject<{
|
|
160
|
+
source_id: z.ZodString;
|
|
161
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
162
|
+
summary: z.ZodString;
|
|
163
|
+
document_id: z.ZodNullable<z.ZodString>;
|
|
164
|
+
index: z.ZodNullable<z.ZodString>;
|
|
165
|
+
selected_fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
166
|
+
nested_matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
167
|
+
path: z.ZodString;
|
|
168
|
+
documents: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
169
|
+
}, z.core.$strip>>>;
|
|
170
|
+
raw_document: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
171
|
+
}, z.core.$strip>>;
|
|
172
|
+
}, z.core.$strip>>;
|
|
173
|
+
}, z.core.$strip>>>;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export declare function executeFilter(input: z.infer<typeof filterInputSchema>, sourceCatalog: SourceCatalog, kibanaClient: Pick<KibanaClient, "executeMany">, options?: {
|
|
176
|
+
schemaCatalog?: SchemaCatalog;
|
|
177
|
+
callerSignal?: AbortSignal;
|
|
178
|
+
}): Promise<z.infer<typeof filterOutputSchema>>;
|
|
179
|
+
export declare function formatFilterResult(result: z.infer<typeof filterOutputSchema>): string;
|
|
180
|
+
export declare function createFilterCallToolResult(result: z.infer<typeof filterOutputSchema>): CallToolResult;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { KibanaClient } from "../kibana/client.js";
|
|
4
|
+
import type { SchemaCatalog } from "../kibana/schema_catalog.js";
|
|
5
|
+
import type { SourceCatalog } from "../kibana/source_catalog.js";
|
|
6
|
+
export declare const queryInputSchema: z.ZodObject<{
|
|
7
|
+
source_ids: z.ZodArray<z.ZodString>;
|
|
8
|
+
start_time: z.ZodString;
|
|
9
|
+
end_time: z.ZodString;
|
|
10
|
+
text: z.ZodOptional<z.ZodString>;
|
|
11
|
+
filters: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
12
|
+
field: z.ZodString;
|
|
13
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
14
|
+
}, z.core.$strip>>>;
|
|
15
|
+
nested_filters: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
16
|
+
path: z.ZodString;
|
|
17
|
+
field: z.ZodString;
|
|
18
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
19
|
+
}, z.core.$strip>>>;
|
|
20
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
21
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
22
|
+
count: "count";
|
|
23
|
+
grouped_top_hits: "grouped_top_hits";
|
|
24
|
+
histogram: "histogram";
|
|
25
|
+
hits: "hits";
|
|
26
|
+
stats: "stats";
|
|
27
|
+
terms: "terms";
|
|
28
|
+
}>>;
|
|
29
|
+
sort: z.ZodDefault<z.ZodEnum<{
|
|
30
|
+
asc: "asc";
|
|
31
|
+
desc: "desc";
|
|
32
|
+
}>>;
|
|
33
|
+
sort_by: z.ZodOptional<z.ZodString>;
|
|
34
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
35
|
+
extract_nested: z.ZodDefault<z.ZodBoolean>;
|
|
36
|
+
stats_field: z.ZodOptional<z.ZodString>;
|
|
37
|
+
top_hits_size: z.ZodDefault<z.ZodNumber>;
|
|
38
|
+
histogram_interval: z.ZodOptional<z.ZodString>;
|
|
39
|
+
group_by: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const queryOutputSchema: z.ZodObject<{
|
|
42
|
+
query_echo: z.ZodObject<{
|
|
43
|
+
source_ids: z.ZodArray<z.ZodString>;
|
|
44
|
+
start_time: z.ZodString;
|
|
45
|
+
end_time: z.ZodString;
|
|
46
|
+
text: z.ZodOptional<z.ZodString>;
|
|
47
|
+
filters: z.ZodArray<z.ZodObject<{
|
|
48
|
+
field: z.ZodString;
|
|
49
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
50
|
+
resolved_field: z.ZodString;
|
|
51
|
+
}, z.core.$strip>>;
|
|
52
|
+
nested_filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
53
|
+
path: z.ZodString;
|
|
54
|
+
field: z.ZodString;
|
|
55
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
56
|
+
resolved_field: z.ZodString;
|
|
57
|
+
query_strategy: z.ZodOptional<z.ZodEnum<{
|
|
58
|
+
flat_object_path: "flat_object_path";
|
|
59
|
+
nested: "nested";
|
|
60
|
+
}>>;
|
|
61
|
+
}, z.core.$strip>>>;
|
|
62
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
63
|
+
mode: z.ZodEnum<{
|
|
64
|
+
count: "count";
|
|
65
|
+
grouped_top_hits: "grouped_top_hits";
|
|
66
|
+
histogram: "histogram";
|
|
67
|
+
hits: "hits";
|
|
68
|
+
stats: "stats";
|
|
69
|
+
terms: "terms";
|
|
70
|
+
}>;
|
|
71
|
+
sort: z.ZodEnum<{
|
|
72
|
+
asc: "asc";
|
|
73
|
+
desc: "desc";
|
|
74
|
+
}>;
|
|
75
|
+
sort_by: z.ZodOptional<z.ZodString>;
|
|
76
|
+
resolved_sort_by_by_source: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
77
|
+
source_id: z.ZodString;
|
|
78
|
+
resolved_sort_by: z.ZodString;
|
|
79
|
+
}, z.core.$strip>>>;
|
|
80
|
+
advisories: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
81
|
+
kind: z.ZodEnum<{
|
|
82
|
+
non_nested_object_array: "non_nested_object_array";
|
|
83
|
+
preferred_exact_field: "preferred_exact_field";
|
|
84
|
+
schema_unavailable: "schema_unavailable";
|
|
85
|
+
}>;
|
|
86
|
+
source_id: z.ZodString;
|
|
87
|
+
purpose: z.ZodEnum<{
|
|
88
|
+
filter: "filter";
|
|
89
|
+
group_by: "group_by";
|
|
90
|
+
sort: "sort";
|
|
91
|
+
}>;
|
|
92
|
+
requested_field: z.ZodString;
|
|
93
|
+
resolved_field: z.ZodString;
|
|
94
|
+
reason: z.ZodString;
|
|
95
|
+
}, z.core.$strip>>>;
|
|
96
|
+
limit: z.ZodNumber;
|
|
97
|
+
stats_field: z.ZodOptional<z.ZodString>;
|
|
98
|
+
top_hits_size: z.ZodOptional<z.ZodNumber>;
|
|
99
|
+
histogram_interval: z.ZodOptional<z.ZodString>;
|
|
100
|
+
group_by: z.ZodOptional<z.ZodString>;
|
|
101
|
+
truncated: z.ZodBoolean;
|
|
102
|
+
}, z.core.$strip>;
|
|
103
|
+
total: z.ZodNumber;
|
|
104
|
+
next_cursor: z.ZodOptional<z.ZodString>;
|
|
105
|
+
hits: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
106
|
+
source_id: z.ZodString;
|
|
107
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
108
|
+
summary: z.ZodString;
|
|
109
|
+
document_id: z.ZodNullable<z.ZodString>;
|
|
110
|
+
index: z.ZodNullable<z.ZodString>;
|
|
111
|
+
selected_fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
112
|
+
nested_matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
113
|
+
path: z.ZodString;
|
|
114
|
+
documents: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
115
|
+
}, z.core.$strip>>>;
|
|
116
|
+
raw_document: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
117
|
+
}, z.core.$strip>>>;
|
|
118
|
+
counts_by_source: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
119
|
+
source_id: z.ZodString;
|
|
120
|
+
count: z.ZodNumber;
|
|
121
|
+
}, z.core.$strip>>>;
|
|
122
|
+
histograms: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
123
|
+
source_id: z.ZodString;
|
|
124
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
125
|
+
key: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
|
|
126
|
+
key_as_string: z.ZodOptional<z.ZodString>;
|
|
127
|
+
count: z.ZodNumber;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
}, z.core.$strip>>>;
|
|
130
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
131
|
+
source_id: z.ZodString;
|
|
132
|
+
field: z.ZodString;
|
|
133
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
134
|
+
key: z.ZodString;
|
|
135
|
+
count: z.ZodNumber;
|
|
136
|
+
}, z.core.$strip>>;
|
|
137
|
+
}, z.core.$strip>>>;
|
|
138
|
+
stats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
139
|
+
source_id: z.ZodString;
|
|
140
|
+
field: z.ZodString;
|
|
141
|
+
summary: z.ZodObject<{
|
|
142
|
+
count: z.ZodNumber;
|
|
143
|
+
min: z.ZodNullable<z.ZodNumber>;
|
|
144
|
+
max: z.ZodNullable<z.ZodNumber>;
|
|
145
|
+
avg: z.ZodNullable<z.ZodNumber>;
|
|
146
|
+
sum: z.ZodNumber;
|
|
147
|
+
p50: z.ZodNullable<z.ZodNumber>;
|
|
148
|
+
p95: z.ZodNullable<z.ZodNumber>;
|
|
149
|
+
p99: z.ZodNullable<z.ZodNumber>;
|
|
150
|
+
}, z.core.$strip>;
|
|
151
|
+
}, z.core.$strip>>>;
|
|
152
|
+
grouped_hits: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
153
|
+
source_id: z.ZodString;
|
|
154
|
+
group_by: z.ZodString;
|
|
155
|
+
buckets: z.ZodArray<z.ZodObject<{
|
|
156
|
+
key: z.ZodString;
|
|
157
|
+
count: z.ZodNumber;
|
|
158
|
+
hits: z.ZodArray<z.ZodObject<{
|
|
159
|
+
source_id: z.ZodString;
|
|
160
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
161
|
+
summary: z.ZodString;
|
|
162
|
+
document_id: z.ZodNullable<z.ZodString>;
|
|
163
|
+
index: z.ZodNullable<z.ZodString>;
|
|
164
|
+
selected_fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
165
|
+
nested_matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
166
|
+
path: z.ZodString;
|
|
167
|
+
documents: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
168
|
+
}, z.core.$strip>>>;
|
|
169
|
+
raw_document: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
170
|
+
}, z.core.$strip>>;
|
|
171
|
+
}, z.core.$strip>>;
|
|
172
|
+
}, z.core.$strip>>>;
|
|
173
|
+
}, z.core.$strip>;
|
|
174
|
+
export declare function executeQuery(input: z.infer<typeof queryInputSchema>, sourceCatalog: SourceCatalog, kibanaClient: Pick<KibanaClient, "executeMany">, options?: {
|
|
175
|
+
resolveFieldAliases?: boolean;
|
|
176
|
+
resolvePreferredExactFields?: boolean;
|
|
177
|
+
schemaCatalog?: SchemaCatalog;
|
|
178
|
+
callerSignal?: AbortSignal;
|
|
179
|
+
}): Promise<z.infer<typeof queryOutputSchema>>;
|
|
180
|
+
export declare function formatQueryResult(result: z.infer<typeof queryOutputSchema>): string;
|
|
181
|
+
export declare function createQueryCallToolResult(result: z.infer<typeof queryOutputSchema>): CallToolResult;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type StartupConfiguration } from "../config/startup.js";
|
|
2
|
+
export interface HttpServerOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
path?: string;
|
|
6
|
+
profileHeader?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface HttpServerHandle {
|
|
9
|
+
url: string;
|
|
10
|
+
close: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function startHttpMcpServer(envInput?: NodeJS.ProcessEnv, options?: HttpServerOptions, dependencies?: {
|
|
13
|
+
startupConfiguration?: StartupConfiguration;
|
|
14
|
+
}): Promise<HttpServerHandle>;
|
|
15
|
+
export declare function parsePort(value: string | undefined): number | undefined;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer, } from "node:http";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport, createMcpHandler, isLegacyRequest, localhostAllowedHostnames, localhostAllowedOrigins, validateHostHeader, validateOriginHeader, } from "@modelcontextprotocol/server";
|
|
5
|
+
import { HTTP_AUTH_TOKEN_ENV, HTTP_HOST_ENV, HTTP_PATH_ENV, HTTP_PORT_ENV, HTTP_PROFILE_HEADER_ENV, } from "../config/config_file.js";
|
|
6
|
+
import { loadStartupConfiguration } from "../config/startup.js";
|
|
7
|
+
import { normalizeProfileName } from "../profiles/names.js";
|
|
8
|
+
import { ProfileRuntimeRegistry } from "../profiles/runtime.js";
|
|
9
|
+
import { createApplication } from "../server.js";
|
|
10
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
11
|
+
const DEFAULT_PORT = 3000;
|
|
12
|
+
const DEFAULT_PATH = "/mcp";
|
|
13
|
+
const HEALTH_PATH = "/healthz";
|
|
14
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
15
|
+
const MAX_LEGACY_SESSIONS = 128;
|
|
16
|
+
const MAX_LEGACY_SESSION_REQUESTS = 1024;
|
|
17
|
+
const LEGACY_SESSION_IDLE_MS = 30 * 60 * 1000;
|
|
18
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "localhost"]);
|
|
19
|
+
class HttpRequestError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
constructor(status, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.status = status;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function startHttpMcpServer(envInput = process.env, options = {}, dependencies = {}) {
|
|
27
|
+
const { env, startupConfiguration } = dependencies.startupConfiguration
|
|
28
|
+
? { env: envInput, startupConfiguration: dependencies.startupConfiguration }
|
|
29
|
+
: await loadStartupConfiguration(envInput);
|
|
30
|
+
try {
|
|
31
|
+
return await listen(resolveHttpOptions(env, options), startupConfiguration);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
await startupConfiguration.close().catch(() => { });
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function listen(resolved, startupConfiguration) {
|
|
39
|
+
const registry = new ProfileRuntimeRegistry(startupConfiguration);
|
|
40
|
+
if (resolved.profileHeader && !registry.supportsNamedProfiles) {
|
|
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
|
+
}
|
|
43
|
+
if (!resolved.profileHeader && !registry.hasDefault) {
|
|
44
|
+
throw new Error("The HTTP transport needs a profile header, or a default profile named with --profile or KIBANA_PROFILE to serve every caller the same catalog. The legacy 'configure' tool is available over stdio only.");
|
|
45
|
+
}
|
|
46
|
+
// Modern and handshake-free legacy calls stay per-request.
|
|
47
|
+
const handler = createMcpHandler((context) => {
|
|
48
|
+
const requestProfile = resolved.profileHeader
|
|
49
|
+
? readProfileHeader(context.requestInfo?.headers, resolved.profileHeader)
|
|
50
|
+
: undefined;
|
|
51
|
+
return createApplication(undefined, { profileRegistry: registry, requestProfile }).server;
|
|
52
|
+
});
|
|
53
|
+
const legacySessions = new Map();
|
|
54
|
+
let initializingSessions = 0;
|
|
55
|
+
const fetchHandler = async (request) => {
|
|
56
|
+
if (!(await isLegacyRequest(request)))
|
|
57
|
+
return handler.fetch(request);
|
|
58
|
+
for (const session of legacySessions.values()) {
|
|
59
|
+
if (Date.now() - session.lastUsed >= LEGACY_SESSION_IDLE_MS)
|
|
60
|
+
await session.server.close();
|
|
61
|
+
}
|
|
62
|
+
const profile = resolved.profileHeader
|
|
63
|
+
? readProfileHeader(request.headers, resolved.profileHeader)
|
|
64
|
+
: undefined;
|
|
65
|
+
const sessionId = request.headers.get("mcp-session-id");
|
|
66
|
+
if (sessionId) {
|
|
67
|
+
let session = legacySessions.get(sessionId);
|
|
68
|
+
// ponytail: recycle bounded sessions until the SDK releases cancelled request metadata.
|
|
69
|
+
if (session &&
|
|
70
|
+
session.profile === profile &&
|
|
71
|
+
session.requests >= MAX_LEGACY_SESSION_REQUESTS) {
|
|
72
|
+
await session.server.close();
|
|
73
|
+
session = undefined;
|
|
74
|
+
}
|
|
75
|
+
if (!session || session.profile !== profile) {
|
|
76
|
+
return Response.json({
|
|
77
|
+
jsonrpc: "2.0",
|
|
78
|
+
id: null,
|
|
79
|
+
error: { code: -32001, message: "Session not found" },
|
|
80
|
+
}, { status: 404 });
|
|
81
|
+
}
|
|
82
|
+
session.lastUsed = Date.now();
|
|
83
|
+
session.requests += 1;
|
|
84
|
+
return session.transport.handleRequest(request);
|
|
85
|
+
}
|
|
86
|
+
const body = request.method === "POST"
|
|
87
|
+
? await request
|
|
88
|
+
.clone()
|
|
89
|
+
.json()
|
|
90
|
+
.catch(() => undefined)
|
|
91
|
+
: undefined;
|
|
92
|
+
if (body?.method !== "initialize")
|
|
93
|
+
return handler.fetch(request);
|
|
94
|
+
if (legacySessions.size + initializingSessions >= MAX_LEGACY_SESSIONS) {
|
|
95
|
+
return Response.json({ error: "Too many MCP sessions. Close an existing session and retry." }, { status: 503 });
|
|
96
|
+
}
|
|
97
|
+
// The SDK owns request IDs and cancellation inside each unguessable client session.
|
|
98
|
+
initializingSessions += 1;
|
|
99
|
+
const server = createApplication(undefined, {
|
|
100
|
+
profileRegistry: registry,
|
|
101
|
+
requestProfile: profile,
|
|
102
|
+
}).server;
|
|
103
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
104
|
+
sessionIdGenerator: randomUUID,
|
|
105
|
+
onsessioninitialized: (id) => {
|
|
106
|
+
legacySessions.set(id, { server, transport, profile, lastUsed: Date.now(), requests: 0 });
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
transport.onclose = () => {
|
|
110
|
+
legacySessions.delete(transport.sessionId ?? "");
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
const response = await transport.handleRequest(request, { parsedBody: body });
|
|
115
|
+
if (!transport.sessionId || response.status >= 400)
|
|
116
|
+
await server.close();
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
await server.close().catch(() => { });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
initializingSessions -= 1;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const httpServer = createServer((request, response) => {
|
|
128
|
+
void handleRequest(request, response, resolved, fetchHandler).catch(() => {
|
|
129
|
+
if (!response.headersSent) {
|
|
130
|
+
writeJson(response, 500, { error: "internal_error" });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
response.destroy();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
137
|
+
httpServer.once("error", rejectListen);
|
|
138
|
+
httpServer.listen(resolved.port, resolved.host, () => {
|
|
139
|
+
httpServer.off("error", rejectListen);
|
|
140
|
+
resolveListen();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
const address = httpServer.address();
|
|
144
|
+
const urlHost = address.family === "IPv6" ? `[${address.address}]` : address.address;
|
|
145
|
+
return {
|
|
146
|
+
url: `http://${urlHost}:${address.port}${resolved.path}`,
|
|
147
|
+
close: async () => {
|
|
148
|
+
await handler.close();
|
|
149
|
+
await Promise.all([...legacySessions.values()].map((session) => session.server.close()));
|
|
150
|
+
await closeHttpServer(httpServer);
|
|
151
|
+
await startupConfiguration.close();
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function resolveHttpOptions(envInput, options) {
|
|
156
|
+
const host = options.host?.trim() || envInput[HTTP_HOST_ENV]?.trim() || DEFAULT_HOST;
|
|
157
|
+
const port = options.port ?? parsePort(envInput[HTTP_PORT_ENV]) ?? DEFAULT_PORT;
|
|
158
|
+
const path = options.path?.trim() || envInput[HTTP_PATH_ENV]?.trim() || DEFAULT_PATH;
|
|
159
|
+
const profileHeader = (options.profileHeader?.trim() || envInput[HTTP_PROFILE_HEADER_ENV]?.trim())?.toLowerCase();
|
|
160
|
+
const authToken = envInput[HTTP_AUTH_TOKEN_ENV]?.trim() || undefined;
|
|
161
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
162
|
+
throw new Error("HTTP port must be an integer between 0 and 65535.");
|
|
163
|
+
}
|
|
164
|
+
if (!path.startsWith("/") || path === HEALTH_PATH) {
|
|
165
|
+
throw new Error(`HTTP path must start with '/' and differ from ${HEALTH_PATH}.`);
|
|
166
|
+
}
|
|
167
|
+
if (profileHeader && !/^[a-z0-9!#$%&'*+.^_`|~-]+$/.test(profileHeader)) {
|
|
168
|
+
throw new Error("Profile header must be a valid HTTP header name.");
|
|
169
|
+
}
|
|
170
|
+
if (profileHeader === "authorization") {
|
|
171
|
+
throw new Error("Profile header must not be 'authorization'.");
|
|
172
|
+
}
|
|
173
|
+
// Without a token anyone who reaches the port can query Kibana and assert any profile.
|
|
174
|
+
if (!authToken && !LOOPBACK_HOSTS.has(host)) {
|
|
175
|
+
throw new Error(`Refusing to listen on non-loopback host '${host}' without ${HTTP_AUTH_TOKEN_ENV}.`);
|
|
176
|
+
}
|
|
177
|
+
return { host, port, path, profileHeader, authToken };
|
|
178
|
+
}
|
|
179
|
+
export function parsePort(value) {
|
|
180
|
+
const trimmed = value?.trim();
|
|
181
|
+
if (!trimmed) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return /^\d+$/.test(trimmed) ? Number(trimmed) : Number.NaN;
|
|
185
|
+
}
|
|
186
|
+
async function handleRequest(request, response, options, fetchHandler) {
|
|
187
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
188
|
+
if (url.pathname === HEALTH_PATH && request.method === "GET") {
|
|
189
|
+
writeJson(response, 200, { status: "ok" });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (url.pathname !== options.path) {
|
|
193
|
+
writeJson(response, 404, { error: "not_found" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (options.authToken && !isAuthorized(request.headers.authorization, options.authToken)) {
|
|
197
|
+
response.setHeader("WWW-Authenticate", "Bearer");
|
|
198
|
+
writeJson(response, 401, { error: "invalid_token" });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let webRequest;
|
|
202
|
+
const abortController = new AbortController();
|
|
203
|
+
response.once("close", () => abortController.abort());
|
|
204
|
+
try {
|
|
205
|
+
const headers = toWebHeaders(request);
|
|
206
|
+
if (!options.authToken &&
|
|
207
|
+
(!validateHostHeader(headers.get("host"), localhostAllowedHostnames()).ok ||
|
|
208
|
+
!validateOriginHeader(headers.get("origin"), localhostAllowedOrigins()).ok)) {
|
|
209
|
+
throw new HttpRequestError(403, "Untrusted Host or Origin.");
|
|
210
|
+
}
|
|
211
|
+
// Refused here to answer with a plain HTTP status; the MCP handler reads the value again.
|
|
212
|
+
if (options.profileHeader) {
|
|
213
|
+
readProfileHeader(headers, options.profileHeader);
|
|
214
|
+
}
|
|
215
|
+
webRequest = await toWebRequest(request, url, headers, abortController.signal);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (error instanceof HttpRequestError) {
|
|
219
|
+
writeJson(response, error.status, { error: error.message });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
const webResponse = await fetchHandler(webRequest);
|
|
225
|
+
response.writeHead(webResponse.status, Object.fromEntries(webResponse.headers));
|
|
226
|
+
if (!webResponse.body) {
|
|
227
|
+
response.end();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
Readable.fromWeb(webResponse.body, {
|
|
231
|
+
signal: abortController.signal,
|
|
232
|
+
})
|
|
233
|
+
.on("error", () => response.destroy())
|
|
234
|
+
.pipe(response);
|
|
235
|
+
}
|
|
236
|
+
function readProfileHeader(headers, headerName) {
|
|
237
|
+
const value = headers?.get(headerName)?.trim();
|
|
238
|
+
if (!value) {
|
|
239
|
+
throw new HttpRequestError(401, `Missing required '${headerName}' header.`);
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return normalizeProfileName(value);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
throw new HttpRequestError(400, error instanceof Error ? error.message : String(error));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function isAuthorized(authorization, expectedToken) {
|
|
249
|
+
const match = /^Bearer +(.+)$/i.exec(authorization?.trim() ?? "");
|
|
250
|
+
if (!match) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const digest = (value) => createHash("sha256").update(value).digest();
|
|
254
|
+
return timingSafeEqual(digest(match[1]), digest(expectedToken));
|
|
255
|
+
}
|
|
256
|
+
function toWebHeaders(request) {
|
|
257
|
+
const headers = new Headers();
|
|
258
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
259
|
+
if (value === undefined)
|
|
260
|
+
continue;
|
|
261
|
+
headers.set(name, Array.isArray(value) ? value.join(", ") : value);
|
|
262
|
+
}
|
|
263
|
+
return headers;
|
|
264
|
+
}
|
|
265
|
+
async function toWebRequest(request, url, headers, signal) {
|
|
266
|
+
const method = request.method ?? "GET";
|
|
267
|
+
if (method === "GET" || method === "HEAD") {
|
|
268
|
+
return new Request(url, { method, headers, signal });
|
|
269
|
+
}
|
|
270
|
+
const chunks = [];
|
|
271
|
+
let size = 0;
|
|
272
|
+
for await (const chunk of request) {
|
|
273
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
274
|
+
size += buffer.length;
|
|
275
|
+
if (size > MAX_BODY_BYTES) {
|
|
276
|
+
throw new HttpRequestError(413, "Request body is too large.");
|
|
277
|
+
}
|
|
278
|
+
chunks.push(buffer);
|
|
279
|
+
}
|
|
280
|
+
return new Request(url, { method, headers, body: Buffer.concat(chunks), signal });
|
|
281
|
+
}
|
|
282
|
+
function writeJson(response, status, body) {
|
|
283
|
+
response.writeHead(status, { "Content-Type": "application/json" });
|
|
284
|
+
response.end(JSON.stringify(body));
|
|
285
|
+
}
|
|
286
|
+
function closeHttpServer(httpServer) {
|
|
287
|
+
return new Promise((resolveClose, rejectClose) => {
|
|
288
|
+
httpServer.close((error) => (error ? rejectClose(error) : resolveClose()));
|
|
289
|
+
httpServer.closeAllConnections();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMcpServer(envInput?: NodeJS.ProcessEnv): Promise<void>;
|