@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/dist/src/server.js
CHANGED
|
@@ -2,7 +2,6 @@ import { McpServer } from "@modelcontextprotocol/server";
|
|
|
2
2
|
import { KibanaRequestError } from "./kibana/client.js";
|
|
3
3
|
import { requireProfileRuntime, runWithProfileRuntime } from "./profiles/context.js";
|
|
4
4
|
import { ProfileRuntimeRegistry } from "./profiles/runtime.js";
|
|
5
|
-
import { configureInputSchema, configureOutputSchema, createConfigureCallToolResult, executeConfigure, } from "./tools/configure.js";
|
|
6
5
|
import { configureIndexInputSchema, configureIndexOutputSchema, createConfigureIndexCallToolResult, executeConfigureIndex, } from "./tools/configure_index.js";
|
|
7
6
|
import { createDescribeFieldsCallToolResult, describeFieldsInputSchema, describeFieldsOutputSchema, executeDescribeFields, } from "./tools/describe_fields.js";
|
|
8
7
|
import { createDiscoverCallToolResult, discoverInputSchema, discoverOutputSchema, executeDiscover, } from "./tools/discover.js";
|
|
@@ -63,23 +62,7 @@ export function createApplication(initialConfig, dependencies) {
|
|
|
63
62
|
throw new KibanaRequestError("KIBANA_TIMEOUT", requestError?.phase ?? "request", requestError?.sourceId, timeoutMs, `Kibana tool call exceeded its ${timeoutMs} ms backend deadline.`, error, requestError?.status, requestError?.causeCode);
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
const configureHandler = async (input) => {
|
|
67
|
-
const current = registry.getDefault()?.config;
|
|
68
|
-
const { nextConfig, result } = await executeConfigure(configureInputSchema.parse(input), {
|
|
69
|
-
sourceCatalogPath: current?.sourceCatalogPath,
|
|
70
|
-
});
|
|
71
|
-
registry.setDefault({
|
|
72
|
-
...nextConfig,
|
|
73
|
-
sourceCatalogPath: result.source_catalog_path,
|
|
74
|
-
profileName: current?.profileName,
|
|
75
|
-
sourceCatalogOrigin: current?.sourceCatalogOrigin,
|
|
76
|
-
});
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
65
|
const configureIndexHandler = async (input, callerSignal) => {
|
|
80
|
-
if (!registry.isConfigured) {
|
|
81
|
-
throw new Error("No saved Kibana connection is available. Run connection bootstrap first.");
|
|
82
|
-
}
|
|
83
66
|
const configureIndexFn = dependencies?.configureIndexFn ?? executeConfigureIndex;
|
|
84
67
|
return withProfile((runtime) => withKibanaToolDeadline(callerSignal, async (signal) => {
|
|
85
68
|
const { sources, result, catalogContent } = await configureIndexFn(configureIndexInputSchema.parse(input), {
|
|
@@ -101,20 +84,11 @@ export function createApplication(initialConfig, dependencies) {
|
|
|
101
84
|
schemaCatalog: runtime.schemaCatalog,
|
|
102
85
|
callerSignal: signal,
|
|
103
86
|
})));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}, async (input) => createConfigureCallToolResult(await configureHandler(input)));
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
server.registerTool("configure_index", {
|
|
113
|
-
description: "Configure exactly one Kibana index or index pattern after the user has explicitly named it. Never guess, list, scan, or auto-select indexes before calling this tool.",
|
|
114
|
-
inputSchema: configureIndexInputSchema,
|
|
115
|
-
outputSchema: configureIndexOutputSchema,
|
|
116
|
-
}, async (input, context) => withKibanaRequestDiagnostics(async () => createConfigureIndexCallToolResult(await configureIndexHandler(input, context.mcpReq.signal))));
|
|
117
|
-
}
|
|
87
|
+
server.registerTool("configure_index", {
|
|
88
|
+
description: "Configure exactly one Kibana index or index pattern after the user has explicitly named it. Never guess, list, scan, or auto-select indexes before calling this tool.",
|
|
89
|
+
inputSchema: configureIndexInputSchema,
|
|
90
|
+
outputSchema: configureIndexOutputSchema,
|
|
91
|
+
}, async (input, context) => withKibanaRequestDiagnostics(async () => createConfigureIndexCallToolResult(await configureIndexHandler(input, context.mcpReq.signal))));
|
|
118
92
|
server.registerTool("describe_fields", {
|
|
119
93
|
description: "Describe the effective field capabilities for a configured logical source.",
|
|
120
94
|
inputSchema: describeFieldsInputSchema,
|
|
@@ -138,7 +112,6 @@ export function createApplication(initialConfig, dependencies) {
|
|
|
138
112
|
return {
|
|
139
113
|
server,
|
|
140
114
|
handlers: {
|
|
141
|
-
configure: configureHandler,
|
|
142
115
|
configure_index: configureIndexHandler,
|
|
143
116
|
describe_fields: describeFieldsHandler,
|
|
144
117
|
discover: discoverHandler,
|
|
@@ -45,6 +45,6 @@ export declare function buildGeneratedSource(rawIndexes: string[], sourceName?:
|
|
|
45
45
|
export declare function chooseGeneratedSourceFields(fields: SourceFieldDescriptor[], explicitTimeField?: string): GeneratedSourceFields;
|
|
46
46
|
export declare function runBootstrap(rawOptions: BootstrapOptions, dependencies?: BootstrapDependencies): Promise<BootstrapResult>;
|
|
47
47
|
export declare function resolveCurrentPackageSpecifier(): Promise<string>;
|
|
48
|
-
export declare function createGeneratedCatalog(
|
|
48
|
+
export declare function createGeneratedCatalog(sources: SourceDefinition[]): ReturnType<typeof sourceCatalogSchema.parse>;
|
|
49
49
|
export declare function parseGeneratedCatalog(content: string): ReturnType<typeof sourceCatalogSchema.parse> | undefined;
|
|
50
50
|
export {};
|
|
@@ -264,8 +264,7 @@ export async function resolveCurrentPackageSpecifier() {
|
|
|
264
264
|
}
|
|
265
265
|
throw new Error("Could not resolve the running package version for deterministic registration.");
|
|
266
266
|
}
|
|
267
|
-
export function createGeneratedCatalog(
|
|
268
|
-
const sources = Array.isArray(sourceOrSources) ? sourceOrSources : [sourceOrSources];
|
|
267
|
+
export function createGeneratedCatalog(sources) {
|
|
269
268
|
const parsedSources = sources.map((source) => sourceDefinitionSchema.parse(source));
|
|
270
269
|
return sourceCatalogSchema.parse({
|
|
271
270
|
generatedBy: {
|
|
@@ -27,8 +27,7 @@ export async function executeConfigureIndex(inputValue, options) {
|
|
|
27
27
|
// only happens if the catalog is still the one that was read, so Kibana is never called under a lock.
|
|
28
28
|
const catalog = options.config.catalog ?? fileCatalogOf(options.config, options.stateRoot);
|
|
29
29
|
const existingContent = await catalog.read();
|
|
30
|
-
//
|
|
31
|
-
// the static fallback catalog: starting from an empty catalog would silently drop them.
|
|
30
|
+
// A disappeared catalog must not silently discard sources already loaded into memory.
|
|
32
31
|
if (existingContent === undefined && options.config.sources.length > 0) {
|
|
33
32
|
throw new Error(`Source catalog '${catalog.location}' does not exist although sources are configured. Refusing to replace them with a new managed catalog.`);
|
|
34
33
|
}
|
|
@@ -41,7 +41,7 @@ async function listen(resolved, startupConfiguration) {
|
|
|
41
41
|
throw new Error("A profile header requires named profiles: use saved profiles, a store in the configuration file, or the {profile} placeholder in KIBANA_SOURCE_CATALOG_PATH.");
|
|
42
42
|
}
|
|
43
43
|
if (!resolved.profileHeader && !registry.hasDefault) {
|
|
44
|
-
throw new Error("The HTTP transport needs a profile header, or a default profile named with --profile or KIBANA_PROFILE to serve every caller the same catalog.
|
|
44
|
+
throw new Error("The HTTP transport needs a profile header, or a default profile named with --profile or KIBANA_PROFILE to serve every caller the same catalog.");
|
|
45
45
|
}
|
|
46
46
|
// Modern and handshake-free legacy calls stay per-request.
|
|
47
47
|
const handler = createMcpHandler((context) => {
|
|
@@ -2,8 +2,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
|
2
2
|
import { DEFAULT_PROFILE_NAME, loadStartupConfiguration } from "../config/startup.js";
|
|
3
3
|
import { createApplication } from "../server.js";
|
|
4
4
|
export async function startMcpServer(envInput = process.env) {
|
|
5
|
-
// stdio
|
|
6
|
-
// It has a single caller, so per-profile catalogs fall back to one well-known profile.
|
|
5
|
+
// stdio has one caller and serves the configured default profile.
|
|
7
6
|
const { startupConfiguration } = await loadStartupConfiguration(envInput);
|
|
8
7
|
let server;
|
|
9
8
|
let closing;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -60,7 +60,6 @@ export interface ResolvedAppConfig extends AppConfig {
|
|
|
60
60
|
/** Set when the catalog of the profile lives in a catalog store rather than at a plain path. */
|
|
61
61
|
catalog?: CatalogHandle;
|
|
62
62
|
sourceCatalogPath?: string;
|
|
63
|
-
sourceCatalogOrigin?: "environment" | "profile" | "runtime_default" | "static_default";
|
|
64
63
|
}
|
|
65
64
|
export interface SavedProfile {
|
|
66
65
|
id: string;
|
|
@@ -15,9 +15,11 @@ This matrix distinguishes guaranteed support from best-effort behavior. Each cla
|
|
|
15
15
|
| Fully autonomous plugin install by prompting alone across Codex model variants | Best-effort | Field reports show model-dependent behavior | Manual install from the local marketplace is the supported fallback when the agent cannot complete the UI step itself. |
|
|
16
16
|
| `kibana_internal_search_es` backend | Guaranteed | Verified in tests | Primary search transport. |
|
|
17
17
|
| `elasticsearch_search` backend | Guaranteed | Verified in tests | Direct Elasticsearch-compatible search path. |
|
|
18
|
-
| Schema metadata endpoints | Best-effort | Known 404s in some deployments | `
|
|
18
|
+
| Schema metadata endpoints | Best-effort | Known 404s in some deployments | The server can sample fields through the configured search backend on `404`; this fallback is heuristic and `describe_fields` can still fail. |
|
|
19
19
|
| Nested query features | Best-effort | Depends on schema availability | Requires schema metadata for validation. |
|
|
20
20
|
| Connection-only package bootstrap | Guaranteed | Verified through CLI, rollback, packlist, and release-contract tests | Use `npx -y @havesomecode/kibana-mcp-server bootstrap --client codex`; configure an index only after the user names it. |
|
|
21
21
|
| Agent Skill installation | Guaranteed after publication | Repository discovery is verified with the `skills` CLI; skills.sh indexing follows the public branch | Install `kibana-log-investigation` from `Havesomecode/kibana-mcp-server`. |
|
|
22
22
|
|
|
23
|
+
Saved profiles carry an explicit catalog path. Direct connection startup requires `KIBANA_SOURCE_CATALOG_PATH` or a configuration-file catalog store; no checkout catalog is loaded implicitly. Without a saved connection, startup fails with `No saved Kibana connection is available. Run connection bootstrap first.` Guided `setup` remains supported for hand-authored catalogs.
|
|
24
|
+
|
|
23
25
|
Upgrade a row from best-effort to guaranteed only after verifying it in CI or in a reproducible environment test and documenting the evidence here.
|
|
@@ -66,7 +66,7 @@ The release artifact must include:
|
|
|
66
66
|
The release artifact must exclude:
|
|
67
67
|
|
|
68
68
|
- tests and fixtures
|
|
69
|
-
- local operator
|
|
69
|
+
- local operator catalogs and saved machine profiles
|
|
70
70
|
- development-only scripts and tooling that do not affect runtime execution
|
|
71
71
|
|
|
72
72
|
## Versioning
|
|
@@ -10,8 +10,11 @@ updated: 2026-04-09
|
|
|
10
10
|
|
|
11
11
|
- CI is green on `master`.
|
|
12
12
|
- `npm run verify` passes locally.
|
|
13
|
+
- Build output must correspond to current source: `npm run build` replaces root `dist/`, and every packed `dist/src/**/*.js` or `dist/src/**/*.d.ts` must have its corresponding `src/**/*.ts` file. `npm run verify:packlist` rejects orphaned outputs from removed modules.
|
|
13
14
|
- The compatibility matrix is up to date (`docs/project/compatibility-matrix.md`).
|
|
14
15
|
- Any new env or config requirements are documented in `README.md` and `INSTALL.md`.
|
|
16
|
+
- Startup requires a connection and an explicit catalog path or store; saved profiles use their stored path. Docs must not advertise a credential-taking MCP setup tool or implicit checkout catalog.
|
|
17
|
+
- Connection-only bootstrap remains the default install flow; guided setup remains supported for explicitly selected hand-authored catalogs.
|
|
15
18
|
- The hosted homepage still matches the supported install paths and support posture.
|
|
16
19
|
- The npm scope and package name (`@havesomecode/kibana-mcp-server`) are owned by the maintainers.
|
|
17
20
|
- Maintainers understand that released versions are tracked in npm, tags, and GitHub Releases, not by the `package.json` version committed in git.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Support Policy
|
|
3
3
|
status: active
|
|
4
|
-
updated: 2026-
|
|
4
|
+
updated: 2026-10-14
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Support Policy
|
|
@@ -15,8 +15,14 @@ This MCP is designed for operators and AI agents that need read-only access to K
|
|
|
15
15
|
- Node.js 22+ runtime
|
|
16
16
|
- Repo-local Codex plugin install
|
|
17
17
|
- `kibana_internal_search_es` and `elasticsearch_search` backends
|
|
18
|
-
-
|
|
19
|
-
-
|
|
18
|
+
- Connection-only bootstrap with saved profiles and platform credentials
|
|
19
|
+
- Guided machine setup for explicitly selected hand-authored source catalogs
|
|
20
|
+
- Startup with a saved profile, or a complete explicit connection plus `KIBANA_SOURCE_CATALOG_PATH` or a configuration-file catalog store
|
|
21
|
+
- Explicit user-approved source configuration through `configure_index`; no MCP tool accepts connection credentials
|
|
22
|
+
|
|
23
|
+
## Connection Setup Boundary
|
|
24
|
+
|
|
25
|
+
Connection setup is an operator step, not an MCP capability. The credential-taking `configure` tool and unconfigured startup are intentionally retired in favor of `bootstrap` plus `configure_index`. Direct environment startup must select a catalog path or store explicitly; the server no longer searches the working directory for one. This does not change the catalog JSON schema or delete existing catalog or profile files.
|
|
20
26
|
|
|
21
27
|
## Best-Effort Support
|
|
22
28
|
|
|
@@ -27,14 +33,16 @@ This MCP is designed for operators and AI agents that need read-only access to K
|
|
|
27
33
|
|
|
28
34
|
Best-effort means we will investigate issues but cannot guarantee behavior across all deployments.
|
|
29
35
|
|
|
36
|
+
Startup without a saved connection fails with `No saved Kibana connection is available. Run connection bootstrap first.` Run connection-only bootstrap before reconnecting the MCP client. For saved-profile use, leave direct connection variables unset in the server environment.
|
|
37
|
+
|
|
30
38
|
## Known Environment Constraints
|
|
31
39
|
|
|
32
40
|
- Some Codex model variants are less reliable at completing repo-local plugin installation without a manual Codex UI step. The supported fallback is to install the plugin manually from the local marketplace, then continue with MCP configuration.
|
|
33
|
-
- Some Kibana deployments proxy or block schema metadata endpoints.
|
|
41
|
+
- Some Kibana deployments proxy or block schema metadata endpoints. When all known Kibana data-view or index-pattern metadata endpoints return `404`, the server can sample fields through the configured search backend. Direct Elasticsearch field-capability errors do not use this fallback. Sampling is heuristic, not authoritative; schema-dependent features can still be unavailable.
|
|
34
42
|
- `KIBANA_BASE_URL` must be the Kibana base prefix, not a full search endpoint such as `/internal/search/es`.
|
|
35
43
|
- Linux secure storage depends on a Secret Service-compatible keyring being available and unlocked.
|
|
36
|
-
- If you run staging and production side by side, prefer saved machine profiles plus `KIBANA_PROFILE` selection.
|
|
37
|
-
- Credentials are not persisted to repo-local files or tracked config.
|
|
44
|
+
- If you run staging and production side by side, prefer saved machine profiles plus `KIBANA_PROFILE` selection. For direct environment startup, name `KIBANA_SOURCE_CATALOG_PATH` explicitly and keep it distinct per entry so managed sources from different environments do not mix.
|
|
45
|
+
- Credentials are not persisted to repo-local files or tracked config. Bootstrap and guided setup store them in the platform credential store.
|
|
38
46
|
|
|
39
47
|
## Security Reporting
|
|
40
48
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@havesomecode/kibana-mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Read-only MCP server for Kibana-backed log investigation and schema-aware querying",
|
|
6
6
|
"homepage": "https://havesomecode.github.io/kibana-mcp-server/",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"dist/**",
|
|
50
50
|
"plugins/kibana-log-investigation/.mcp.json",
|
|
51
51
|
"plugins/kibana-log-investigation/.codex-plugin/plugin.json",
|
|
52
|
+
"plugins/kibana-log-investigation/mcp-launcher.mjs",
|
|
53
|
+
"plugins/kibana-log-investigation/dist/**",
|
|
54
|
+
"plugins/kibana-log-investigation/skills/**",
|
|
52
55
|
"skills/kibana-log-investigation/**",
|
|
53
56
|
"README.md",
|
|
54
57
|
"LICENSE",
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
"node": ">=22.0.0"
|
|
60
63
|
},
|
|
61
64
|
"scripts": {
|
|
62
|
-
"build": "tsc -p tsconfig.build.json",
|
|
65
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node scripts/build-plugin.mjs",
|
|
63
66
|
"check:types": "tsc --noEmit -p tsconfig.json",
|
|
64
67
|
"lint": "biome lint .",
|
|
65
68
|
"format": "biome format . --write",
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
"@commitlint/cli": "^20.1.0",
|
|
112
115
|
"@commitlint/config-conventional": "^20.0.0",
|
|
113
116
|
"@types/node": "^25.5.2",
|
|
117
|
+
"esbuild": "^0.28.2",
|
|
114
118
|
"ioredis": "^6.0.0",
|
|
115
119
|
"mongodb": "^7.6.0",
|
|
116
120
|
"pg": "^8.23.0",
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
"kibana-log-investigation": {
|
|
4
4
|
"type": "stdio",
|
|
5
5
|
"command": "node",
|
|
6
|
-
"args": ["./
|
|
7
|
-
"
|
|
6
|
+
"args": ["./mcp-launcher.mjs"],
|
|
7
|
+
"cwd": ".",
|
|
8
|
+
"note": "Requires Node.js 22+. The built plugin includes its runtime dependencies. Build the checkout before installing the plugin. If runtime files are missing, rebuild and reinstall; opening new discussions does not repair startup failures."
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zacaria
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|