@gozzle/cli 0.0.1-canary.3

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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +38 -0
  5. package/dist/src/cli.js.map +1 -0
  6. package/dist/src/clickhouse/client.d.ts +13 -0
  7. package/dist/src/clickhouse/client.js +27 -0
  8. package/dist/src/clickhouse/client.js.map +1 -0
  9. package/dist/src/clickhouse/identifier.d.ts +12 -0
  10. package/dist/src/clickhouse/identifier.js +38 -0
  11. package/dist/src/clickhouse/identifier.js.map +1 -0
  12. package/dist/src/clickhouse/introspection.d.ts +15 -0
  13. package/dist/src/clickhouse/introspection.js +88 -0
  14. package/dist/src/clickhouse/introspection.js.map +1 -0
  15. package/dist/src/clickhouse/table-inspection.d.ts +48 -0
  16. package/dist/src/clickhouse/table-inspection.js +162 -0
  17. package/dist/src/clickhouse/table-inspection.js.map +1 -0
  18. package/dist/src/config/clickhouse.d.ts +19 -0
  19. package/dist/src/config/clickhouse.js +24 -0
  20. package/dist/src/config/clickhouse.js.map +1 -0
  21. package/dist/src/mcp/server.d.ts +4 -0
  22. package/dist/src/mcp/server.js +36 -0
  23. package/dist/src/mcp/server.js.map +1 -0
  24. package/dist/src/shared/package-metadata.d.ts +4 -0
  25. package/dist/src/shared/package-metadata.js +33 -0
  26. package/dist/src/shared/package-metadata.js.map +1 -0
  27. package/dist/src/tools/connect.d.ts +2 -0
  28. package/dist/src/tools/connect.js +64 -0
  29. package/dist/src/tools/connect.js.map +1 -0
  30. package/dist/src/tools/health.d.ts +2 -0
  31. package/dist/src/tools/health.js +15 -0
  32. package/dist/src/tools/health.js.map +1 -0
  33. package/dist/src/tools/inspect-table.d.ts +4 -0
  34. package/dist/src/tools/inspect-table.js +77 -0
  35. package/dist/src/tools/inspect-table.js.map +1 -0
  36. package/package.json +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gozzle
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.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Gozzle
2
+
3
+ A safety harness for your ClickHouse, inside your own AI.
4
+
5
+ Gozzle is a local developer toolkit for ClickHouse. The AI reasons; Gozzle runs checks and produces proof.
6
+
7
+ ## Install
8
+
9
+ For early canary builds:
10
+
11
+ ```bash
12
+ npm install -g @gozzle/cli@canary
13
+ ```
14
+
15
+ Then print the MCP config snippet:
16
+
17
+ ```bash
18
+ gozzle init
19
+ ```
20
+
21
+ Add the printed config to Claude, Cursor, Codex, or another MCP host.
22
+
23
+ ## Development
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ npm test
29
+ ```
30
+
31
+ ## ClickHouse Connection
32
+
33
+ Gozzle reads ClickHouse connection details from environment variables:
34
+
35
+ ```bash
36
+ GOZZLE_CLICKHOUSE_URL=http://localhost:8123
37
+ GOZZLE_CLICKHOUSE_USER=default
38
+ GOZZLE_CLICKHOUSE_PASSWORD=
39
+ GOZZLE_CLICKHOUSE_DATABASE=default
40
+ ```
41
+
42
+ The `GOZZLE_` variables take precedence over the equivalent `CLICKHOUSE_` variables.
43
+ Use a read-only ClickHouse user; Gozzle does not need write access.
44
+
45
+ ## Entry Points
46
+
47
+ - `gozzle`: CLI entrypoint.
48
+ - `gozzle-mcp`: MCP stdio server entrypoint.
49
+
50
+ ## Canary Publishing
51
+
52
+ ```bash
53
+ npm login
54
+ npm run build
55
+ npm test
56
+ npm publish --tag canary --access public
57
+ ```
58
+
59
+ For later canaries:
60
+
61
+ ```bash
62
+ npm version prerelease --preid canary
63
+ npm publish --tag canary --access public
64
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { readPackageMetadata } from "./shared/package-metadata.js";
3
+ const metadata = readPackageMetadata();
4
+ const command = process.argv[2] ?? "help";
5
+ if (command === "version" || command === "--version" || command === "-v") {
6
+ console.log(metadata.version);
7
+ process.exit(0);
8
+ }
9
+ if (command === "init") {
10
+ printInit();
11
+ process.exit(0);
12
+ }
13
+ console.log(`gozzle ${metadata.version}`);
14
+ console.log("");
15
+ console.log("Commands:");
16
+ console.log(" gozzle init Print MCP config for your AI host");
17
+ console.log(" gozzle version Print the CLI version");
18
+ console.log(" gozzle-mcp Start the MCP stdio server");
19
+ function printInit() {
20
+ console.log("Add this MCP server config to Claude, Cursor, Codex, or another MCP host:");
21
+ console.log("");
22
+ console.log(JSON.stringify({
23
+ mcpServers: {
24
+ gozzle: {
25
+ command: "gozzle-mcp",
26
+ env: {
27
+ GOZZLE_CLICKHOUSE_URL: "https://your-cluster.clickhouse.cloud:8443",
28
+ GOZZLE_CLICKHOUSE_USER: "gozzle_readonly",
29
+ GOZZLE_CLICKHOUSE_PASSWORD: "replace-me",
30
+ GOZZLE_CLICKHOUSE_DATABASE: "default"
31
+ }
32
+ }
33
+ }
34
+ }, null, 2));
35
+ console.log("");
36
+ console.log("Use a read-only ClickHouse user. Gozzle does not need write access.");
37
+ }
38
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;AACvC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;AAE1C,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,CAAC;IACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,UAAU,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AACzB,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;AACtE,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;AAC1D,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;AAE/D,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;IACzF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ;QACE,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,OAAO,EAAE,YAAY;gBACrB,GAAG,EAAE;oBACH,qBAAqB,EAAE,4CAA4C;oBACnE,sBAAsB,EAAE,iBAAiB;oBACzC,0BAA0B,EAAE,YAAY;oBACxC,0BAA0B,EAAE,SAAS;iBACtC;aACF;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC;AACrF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ClickHouseConnectionConfig } from "../config/clickhouse.js";
2
+ export interface ClickHouseMetadataClient {
3
+ ping(): Promise<boolean>;
4
+ queryJson<T>(query: string): Promise<T[]>;
5
+ close(): Promise<void>;
6
+ }
7
+ export declare class ClickHouseHttpMetadataClient implements ClickHouseMetadataClient {
8
+ private readonly client;
9
+ constructor(config: ClickHouseConnectionConfig);
10
+ ping(): Promise<boolean>;
11
+ queryJson<T>(query: string): Promise<T[]>;
12
+ close(): Promise<void>;
13
+ }
@@ -0,0 +1,27 @@
1
+ import { createClient } from "@clickhouse/client";
2
+ export class ClickHouseHttpMetadataClient {
3
+ client;
4
+ constructor(config) {
5
+ this.client = createClient({
6
+ url: config.url,
7
+ username: config.username,
8
+ password: config.password,
9
+ database: config.database
10
+ });
11
+ }
12
+ async ping() {
13
+ const result = await this.client.ping();
14
+ return result.success;
15
+ }
16
+ async queryJson(query) {
17
+ const resultSet = await this.client.query({
18
+ query,
19
+ format: "JSONEachRow"
20
+ });
21
+ return (await resultSet.json());
22
+ }
23
+ async close() {
24
+ await this.client.close();
25
+ }
26
+ }
27
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/clickhouse/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAUzE,MAAM,OAAO,4BAA4B;IACtB,MAAM,CAAmB;IAE1C,YAAY,MAAkC;QAC5C,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;YACzB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACxC,OAAO,MAAM,CAAC,OAAO,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,SAAS,CAAI,KAAa;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YACxC,KAAK;YACL,MAAM,EAAE,aAAa;SACtB,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,CAAQ,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ export interface TableIdentifier {
2
+ database?: string;
3
+ table: string;
4
+ }
5
+ export interface ResolvedTableIdentifier {
6
+ database: string;
7
+ table: string;
8
+ }
9
+ export declare function parseTableIdentifier(input: string): TableIdentifier;
10
+ export declare function resolveTableIdentifier(identifier: TableIdentifier, defaultDatabase: string): ResolvedTableIdentifier;
11
+ export declare function quoteIdentifier(identifier: string): string;
12
+ export declare function formatTableIdentifier(identifier: ResolvedTableIdentifier): string;
@@ -0,0 +1,38 @@
1
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2
+ export function parseTableIdentifier(input) {
3
+ const trimmed = input.trim();
4
+ if (trimmed === "") {
5
+ throw new Error("Table name is required.");
6
+ }
7
+ const parts = trimmed.split(".");
8
+ if (parts.length > 2) {
9
+ throw new Error("Use table or database.table format.");
10
+ }
11
+ const [database, table] = parts.length === 2 ? parts : [undefined, parts[0]];
12
+ if (database !== undefined) {
13
+ validateIdentifierPart(database, "database");
14
+ }
15
+ validateIdentifierPart(table, "table");
16
+ return {
17
+ database,
18
+ table
19
+ };
20
+ }
21
+ export function resolveTableIdentifier(identifier, defaultDatabase) {
22
+ return {
23
+ database: identifier.database ?? defaultDatabase,
24
+ table: identifier.table
25
+ };
26
+ }
27
+ export function quoteIdentifier(identifier) {
28
+ return `\`${identifier.replaceAll("`", "``")}\``;
29
+ }
30
+ export function formatTableIdentifier(identifier) {
31
+ return `${quoteIdentifier(identifier.database)}.${quoteIdentifier(identifier.table)}`;
32
+ }
33
+ function validateIdentifierPart(value, label) {
34
+ if (!IDENTIFIER_PATTERN.test(value)) {
35
+ throw new Error(`Invalid ${label} identifier "${value}". Use an unquoted ClickHouse identifier.`);
36
+ }
37
+ }
38
+ //# sourceMappingURL=identifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifier.js","sourceRoot":"","sources":["../../../src/clickhouse/identifier.ts"],"names":[],"mappings":"AAUA,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE7B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEjC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,sBAAsB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/C,CAAC;IAED,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAEvC,OAAO;QACL,QAAQ;QACR,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,UAA2B,EAC3B,eAAuB;IAEvB,OAAO;QACL,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,eAAe;QAChD,KAAK,EAAE,UAAU,CAAC,KAAK;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,OAAO,KAAK,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAmC;IACvE,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,eAAe,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa,EAAE,KAAa;IAC1D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,gBAAgB,KAAK,2CAA2C,CACjF,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { ClickHouseConnectionConfig } from "../config/clickhouse.js";
2
+ import type { ClickHouseMetadataClient } from "./client.js";
3
+ export interface ClickHouseConnectionInfo {
4
+ connected: true;
5
+ version: string;
6
+ database: string;
7
+ currentUser: string;
8
+ hostName: string;
9
+ deployment: "cloud" | "self_hosted_or_unknown";
10
+ readonlySetting?: string;
11
+ writePrivileges: string[];
12
+ warnings: string[];
13
+ }
14
+ export declare function inspectClickHouseConnection(client: ClickHouseMetadataClient, config: ClickHouseConnectionConfig): Promise<ClickHouseConnectionInfo>;
15
+ export declare function detectDeployment(url: string): ClickHouseConnectionInfo["deployment"];
@@ -0,0 +1,88 @@
1
+ const WRITE_PRIVILEGES = new Set([
2
+ "INSERT",
3
+ "ALTER",
4
+ "CREATE",
5
+ "DROP",
6
+ "TRUNCATE",
7
+ "OPTIMIZE",
8
+ "SYSTEM",
9
+ "KILL QUERY"
10
+ ]);
11
+ export async function inspectClickHouseConnection(client, config) {
12
+ const pingOk = await client.ping();
13
+ if (!pingOk) {
14
+ throw new Error("ClickHouse ping failed.");
15
+ }
16
+ const [serverInfo] = await client.queryJson(`
17
+ SELECT
18
+ version() AS version,
19
+ currentDatabase() AS database,
20
+ currentUser() AS current_user,
21
+ hostName() AS host_name
22
+ `);
23
+ if (!serverInfo) {
24
+ throw new Error("ClickHouse did not return server metadata.");
25
+ }
26
+ const warnings = [];
27
+ const readonlySetting = await readReadonlySetting(client, warnings);
28
+ const writePrivileges = await readWritePrivileges(client, warnings);
29
+ if (readonlySetting !== undefined && readonlySetting !== "1") {
30
+ warnings.push("Session readonly setting is not enabled. Use a read-only ClickHouse user for Gozzle.");
31
+ }
32
+ if (writePrivileges.length > 0) {
33
+ warnings.push(`Connected user appears to have write-capable grants: ${writePrivileges.join(", ")}. Gozzle only needs read-only access.`);
34
+ }
35
+ return {
36
+ connected: true,
37
+ version: String(serverInfo.version),
38
+ database: serverInfo.database,
39
+ currentUser: serverInfo.current_user,
40
+ hostName: serverInfo.host_name,
41
+ deployment: detectDeployment(config.url),
42
+ readonlySetting,
43
+ writePrivileges,
44
+ warnings
45
+ };
46
+ }
47
+ export function detectDeployment(url) {
48
+ const hostname = new URL(url).hostname;
49
+ return hostname.endsWith(".clickhouse.cloud")
50
+ ? "cloud"
51
+ : "self_hosted_or_unknown";
52
+ }
53
+ async function readReadonlySetting(client, warnings) {
54
+ try {
55
+ const [row] = await client.queryJson(`
56
+ SELECT value
57
+ FROM system.settings
58
+ WHERE name = 'readonly'
59
+ LIMIT 1
60
+ `);
61
+ return row ? String(row.value) : undefined;
62
+ }
63
+ catch (error) {
64
+ warnings.push(`Could not inspect readonly setting: ${formatErrorMessage(error)}`);
65
+ return undefined;
66
+ }
67
+ }
68
+ async function readWritePrivileges(client, warnings) {
69
+ try {
70
+ const rows = await client.queryJson(`
71
+ SELECT DISTINCT access_type
72
+ FROM system.grants
73
+ WHERE user_name = currentUser()
74
+ ORDER BY access_type
75
+ `);
76
+ return rows
77
+ .map((row) => row.access_type)
78
+ .filter((accessType) => WRITE_PRIVILEGES.has(accessType));
79
+ }
80
+ catch (error) {
81
+ warnings.push(`Could not inspect grants: ${formatErrorMessage(error)}`);
82
+ return [];
83
+ }
84
+ }
85
+ function formatErrorMessage(error) {
86
+ return error instanceof Error ? error.message : String(error);
87
+ }
88
+ //# sourceMappingURL=introspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.js","sourceRoot":"","sources":["../../../src/clickhouse/introspection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { ClickHouseMetadataClient } from "./client.js";
2
+ import { type ResolvedTableIdentifier } from "./identifier.js";
3
+ export interface TableInspectionOptions {
4
+ table: string;
5
+ defaultDatabase: string;
6
+ }
7
+ export interface TableColumn {
8
+ name: string;
9
+ type: string;
10
+ defaultKind?: string;
11
+ defaultExpression?: string;
12
+ codecExpression?: string;
13
+ }
14
+ export interface TablePartsSummary {
15
+ activeParts: number;
16
+ rows: number;
17
+ bytesOnDisk: number;
18
+ partitions: number;
19
+ }
20
+ export interface ReplacingMergeTreeDetails {
21
+ versionColumn?: string;
22
+ deletedColumn?: string;
23
+ }
24
+ export interface TableInspection {
25
+ identifier: ResolvedTableIdentifier;
26
+ engine: string;
27
+ engineFull: string;
28
+ createStatement: string;
29
+ orderBy?: string;
30
+ partitionBy?: string;
31
+ primaryKey?: string;
32
+ sortingKey?: string;
33
+ totalRows: number;
34
+ totalBytes: number;
35
+ isDistributed: boolean;
36
+ isReplacingMergeTree: boolean;
37
+ replacingMergeTree?: ReplacingMergeTreeDetails;
38
+ columns: TableColumn[];
39
+ parts: TablePartsSummary;
40
+ eligibleChecks: {
41
+ verifyDedup: boolean;
42
+ dryRunMigration: boolean;
43
+ diagnoseQuery: boolean;
44
+ };
45
+ warnings: string[];
46
+ }
47
+ export declare function inspectTable(client: ClickHouseMetadataClient, options: TableInspectionOptions): Promise<TableInspection>;
48
+ export declare function extractClause(createStatement: string, clauseName: "ORDER BY" | "PARTITION BY"): string | undefined;
@@ -0,0 +1,162 @@
1
+ import { formatTableIdentifier, parseTableIdentifier, resolveTableIdentifier } from "./identifier.js";
2
+ export async function inspectTable(client, options) {
3
+ const identifier = resolveTableIdentifier(parseTableIdentifier(options.table), options.defaultDatabase);
4
+ const fullTableName = formatTableIdentifier(identifier);
5
+ const databaseLiteral = quoteStringLiteral(identifier.database);
6
+ const tableLiteral = quoteStringLiteral(identifier.table);
7
+ const [showCreateRows, tableRows, columns, partsRows] = await Promise.all([
8
+ client.queryJson(`SHOW CREATE TABLE ${fullTableName}`),
9
+ client.queryJson(`
10
+ SELECT
11
+ engine,
12
+ engine_full,
13
+ sorting_key,
14
+ primary_key,
15
+ partition_key,
16
+ total_rows,
17
+ total_bytes
18
+ FROM system.tables
19
+ WHERE database = ${databaseLiteral}
20
+ AND name = ${tableLiteral}
21
+ LIMIT 1
22
+ `),
23
+ client.queryJson(`
24
+ SELECT
25
+ name,
26
+ type,
27
+ default_kind,
28
+ default_expression,
29
+ codec_expression
30
+ FROM system.columns
31
+ WHERE database = ${databaseLiteral}
32
+ AND table = ${tableLiteral}
33
+ ORDER BY position
34
+ `),
35
+ client.queryJson(`
36
+ SELECT
37
+ count() AS active_parts,
38
+ sum(rows) AS rows,
39
+ sum(bytes_on_disk) AS bytes_on_disk,
40
+ uniqExact(partition) AS partitions
41
+ FROM system.parts
42
+ WHERE database = ${databaseLiteral}
43
+ AND table = ${tableLiteral}
44
+ AND active
45
+ `)
46
+ ]);
47
+ const [showCreate] = showCreateRows;
48
+ const [table] = tableRows;
49
+ const [parts] = partsRows;
50
+ if (!table) {
51
+ throw new Error(`Table not found: ${identifier.database}.${identifier.table}`);
52
+ }
53
+ const createStatement = showCreate?.statement ?? "";
54
+ const engineFull = table.engine_full || table.engine;
55
+ const isReplacingMergeTree = table.engine.includes("ReplacingMergeTree");
56
+ const isDistributed = table.engine === "Distributed";
57
+ const warnings = buildWarnings(table.engine, isReplacingMergeTree, isDistributed);
58
+ const replacingMergeTree = isReplacingMergeTree
59
+ ? parseReplacingMergeTreeDetails(engineFull)
60
+ : undefined;
61
+ return {
62
+ identifier,
63
+ engine: table.engine,
64
+ engineFull,
65
+ createStatement,
66
+ orderBy: extractClause(createStatement, "ORDER BY"),
67
+ partitionBy: extractClause(createStatement, "PARTITION BY"),
68
+ primaryKey: normalizeOptional(table.primary_key),
69
+ sortingKey: normalizeOptional(table.sorting_key),
70
+ totalRows: toNumber(table.total_rows),
71
+ totalBytes: toNumber(table.total_bytes),
72
+ isDistributed,
73
+ isReplacingMergeTree,
74
+ replacingMergeTree,
75
+ columns: columns.map(toTableColumn),
76
+ parts: {
77
+ activeParts: toNumber(parts?.active_parts ?? 0),
78
+ rows: toNumber(parts?.rows ?? 0),
79
+ bytesOnDisk: toNumber(parts?.bytes_on_disk ?? 0),
80
+ partitions: toNumber(parts?.partitions ?? 0)
81
+ },
82
+ eligibleChecks: {
83
+ verifyDedup: isReplacingMergeTree && !isDistributed,
84
+ dryRunMigration: true,
85
+ diagnoseQuery: true
86
+ },
87
+ warnings
88
+ };
89
+ }
90
+ export function extractClause(createStatement, clauseName) {
91
+ const index = createStatement.indexOf(clauseName);
92
+ if (index === -1) {
93
+ return undefined;
94
+ }
95
+ const start = index + clauseName.length;
96
+ const rest = createStatement.slice(start).trim();
97
+ const nextClauseIndex = findNextClauseIndex(rest);
98
+ const rawClause = nextClauseIndex === -1 ? rest : rest.slice(0, nextClauseIndex).trim();
99
+ return rawClause || undefined;
100
+ }
101
+ function findNextClauseIndex(input) {
102
+ const candidates = [
103
+ "\nORDER BY",
104
+ "\nPARTITION BY",
105
+ "\nPRIMARY KEY",
106
+ "\nSAMPLE BY",
107
+ "\nTTL",
108
+ "\nSETTINGS",
109
+ "\nCOMMENT"
110
+ ];
111
+ const indexes = candidates
112
+ .map((candidate) => input.indexOf(candidate))
113
+ .filter((index) => index >= 0);
114
+ return indexes.length === 0 ? -1 : Math.min(...indexes);
115
+ }
116
+ function parseReplacingMergeTreeDetails(engineFull) {
117
+ const match = engineFull.match(/ReplacingMergeTree\s*\(([^)]*)\)/);
118
+ if (!match) {
119
+ return {};
120
+ }
121
+ const args = match[1]
122
+ .split(",")
123
+ .map((arg) => arg.trim())
124
+ .filter(Boolean);
125
+ return {
126
+ versionColumn: args[0],
127
+ deletedColumn: args[1]
128
+ };
129
+ }
130
+ function buildWarnings(engine, isReplacingMergeTree, isDistributed) {
131
+ const warnings = [];
132
+ if (isReplacingMergeTree) {
133
+ warnings.push("ReplacingMergeTree table: queries without FINAL may expose duplicate rows.");
134
+ }
135
+ if (isDistributed) {
136
+ warnings.push("Distributed table: local checks may be advisory until shard topology is inspected.");
137
+ }
138
+ if (!engine.includes("MergeTree") && !isDistributed) {
139
+ warnings.push(`Unsupported or uncommon table engine for MVP checks: ${engine}.`);
140
+ }
141
+ return warnings;
142
+ }
143
+ function toTableColumn(row) {
144
+ return {
145
+ name: row.name,
146
+ type: row.type,
147
+ defaultKind: normalizeOptional(row.default_kind),
148
+ defaultExpression: normalizeOptional(row.default_expression),
149
+ codecExpression: normalizeOptional(row.codec_expression)
150
+ };
151
+ }
152
+ function normalizeOptional(value) {
153
+ return value && value.trim() !== "" ? value : undefined;
154
+ }
155
+ function toNumber(value) {
156
+ const parsed = Number(value);
157
+ return Number.isFinite(parsed) ? parsed : 0;
158
+ }
159
+ function quoteStringLiteral(value) {
160
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
161
+ }
162
+ //# sourceMappingURL=table-inspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-inspection.js","sourceRoot":"","sources":["../../../src/clickhouse/table-inspection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface ClickHouseConnectionConfig {
2
+ url: string;
3
+ username: string;
4
+ password: string;
5
+ database?: string;
6
+ }
7
+ export interface ClickHouseConfigEnv {
8
+ CLICKHOUSE_URL?: string;
9
+ CLICKHOUSE_USER?: string;
10
+ CLICKHOUSE_USERNAME?: string;
11
+ CLICKHOUSE_PASSWORD?: string;
12
+ CLICKHOUSE_DATABASE?: string;
13
+ GOZZLE_CLICKHOUSE_URL?: string;
14
+ GOZZLE_CLICKHOUSE_USER?: string;
15
+ GOZZLE_CLICKHOUSE_USERNAME?: string;
16
+ GOZZLE_CLICKHOUSE_PASSWORD?: string;
17
+ GOZZLE_CLICKHOUSE_DATABASE?: string;
18
+ }
19
+ export declare function readClickHouseConfig(env?: ClickHouseConfigEnv): ClickHouseConnectionConfig;
@@ -0,0 +1,24 @@
1
+ export function readClickHouseConfig(env = process.env) {
2
+ const url = firstNonEmpty(env.GOZZLE_CLICKHOUSE_URL, env.CLICKHOUSE_URL);
3
+ if (!url) {
4
+ throw new Error("Missing ClickHouse URL. Set GOZZLE_CLICKHOUSE_URL or CLICKHOUSE_URL.");
5
+ }
6
+ validateUrl(url);
7
+ return {
8
+ url,
9
+ username: firstNonEmpty(env.GOZZLE_CLICKHOUSE_USER, env.GOZZLE_CLICKHOUSE_USERNAME, env.CLICKHOUSE_USER, env.CLICKHOUSE_USERNAME) ?? "default",
10
+ password: firstNonEmpty(env.GOZZLE_CLICKHOUSE_PASSWORD, env.CLICKHOUSE_PASSWORD) ??
11
+ "",
12
+ database: firstNonEmpty(env.GOZZLE_CLICKHOUSE_DATABASE, env.CLICKHOUSE_DATABASE)
13
+ };
14
+ }
15
+ function firstNonEmpty(...values) {
16
+ return values.find((value) => value !== undefined && value.trim() !== "");
17
+ }
18
+ function validateUrl(url) {
19
+ const parsed = new URL(url);
20
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
21
+ throw new Error("ClickHouse URL must use http or https.");
22
+ }
23
+ }
24
+ //# sourceMappingURL=clickhouse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../../src/config/clickhouse.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,oBAAoB,CAClC,MAA2B,OAAO,CAAC,GAAG;IAEtC,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,qBAAqB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAEzE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,GAAG,CAAC,CAAC;IAEjB,OAAO;QACL,GAAG;QACH,QAAQ,EACN,aAAa,CACX,GAAG,CAAC,sBAAsB,EAC1B,GAAG,CAAC,0BAA0B,EAC9B,GAAG,CAAC,eAAe,EACnB,GAAG,CAAC,mBAAmB,CACxB,IAAI,SAAS;QAChB,QAAQ,EACN,aAAa,CAAC,GAAG,CAAC,0BAA0B,EAAE,GAAG,CAAC,mBAAmB,CAAC;YACtE,EAAE;QACJ,QAAQ,EAAE,aAAa,CACrB,GAAG,CAAC,0BAA0B,EAC9B,GAAG,CAAC,mBAAmB,CACxB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CACpB,GAAG,MAAiC;IAEpC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAE5B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare function createGozzleMcpServer(): McpServer;
4
+ export declare function startMcpServer(): Promise<void>;
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { pathToFileURL } from "node:url";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { createConnectTool } from "../tools/connect.js";
7
+ import { createHealthTool } from "../tools/health.js";
8
+ import { createInspectTableTool } from "../tools/inspect-table.js";
9
+ import { readPackageMetadata } from "../shared/package-metadata.js";
10
+ export function createGozzleMcpServer() {
11
+ const metadata = readPackageMetadata();
12
+ const server = new McpServer({
13
+ name: "@gozzle/cli",
14
+ version: metadata.version
15
+ });
16
+ createHealthTool(server);
17
+ createConnectTool(server);
18
+ createInspectTableTool(server);
19
+ return server;
20
+ }
21
+ export async function startMcpServer() {
22
+ const server = createGozzleMcpServer();
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ }
26
+ // Resolve the entry path through symlinks: npm global bins (e.g. `gozzle-mcp`)
27
+ // and how Claude Code launches MCP servers invoke this file via a symlink, so
28
+ // process.argv[1] is the symlink while import.meta.url is the realpath.
29
+ const entry = process.argv[1];
30
+ if (entry && import.meta.url === pathToFileURL(realpathSync(entry)).href) {
31
+ startMcpServer().catch((error) => {
32
+ console.error(error);
33
+ process.exit(1);
34
+ });
35
+ }
36
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,MAAM,UAAU,qBAAqB;IACnC,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAC;IAEH,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAE/B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,+EAA+E;AAC/E,8EAA8E;AAC9E,wEAAwE;AACxE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9B,IAAI,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzE,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACxC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,4 @@
1
+ export interface PackageMetadata {
2
+ version: string;
3
+ }
4
+ export declare function readPackageMetadata(): PackageMetadata;
@@ -0,0 +1,33 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ export function readPackageMetadata() {
5
+ const packageJsonPath = findPackageJson(dirname(fileURLToPath(import.meta.url)));
6
+ if (packageJsonPath) {
7
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
8
+ if (typeof packageJson.version === "string") {
9
+ return {
10
+ version: packageJson.version
11
+ };
12
+ }
13
+ }
14
+ return {
15
+ version: "0.0.1-canary.0"
16
+ };
17
+ }
18
+ function findPackageJson(startDirectory) {
19
+ let currentDirectory = startDirectory;
20
+ for (let index = 0; index < 5; index += 1) {
21
+ const candidate = join(currentDirectory, "package.json");
22
+ if (existsSync(candidate)) {
23
+ return candidate;
24
+ }
25
+ const parentDirectory = dirname(currentDirectory);
26
+ if (parentDirectory === currentDirectory) {
27
+ return undefined;
28
+ }
29
+ currentDirectory = parentDirectory;
30
+ }
31
+ return undefined;
32
+ }
33
+ //# sourceMappingURL=package-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-metadata.js","sourceRoot":"","sources":["../../../src/shared/package-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAMzC,MAAM,UAAU,mBAAmB;IACjC,MAAM,eAAe,GAAG,eAAe,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEjF,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAEnE,CAAC;QAEF,IAAI,OAAO,WAAW,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC5C,OAAO;gBACL,OAAO,EAAE,WAAW,CAAC,OAAO;aAC7B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,gBAAgB;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,cAAsB;IAC7C,IAAI,gBAAgB,GAAG,cAAc,CAAC;IAEtC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;QAEzD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAElD,IAAI,eAAe,KAAK,gBAAgB,EAAE,CAAC;YACzC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,gBAAgB,GAAG,eAAe,CAAC;IACrC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createConnectTool(server: McpServer): void;
@@ -0,0 +1,64 @@
1
+ import { ClickHouseHttpMetadataClient } from "../clickhouse/client.js";
2
+ import { inspectClickHouseConnection } from "../clickhouse/introspection.js";
3
+ import { readClickHouseConfig } from "../config/clickhouse.js";
4
+ export function createConnectTool(server) {
5
+ server.registerTool("connect", {
6
+ title: "Connect to ClickHouse",
7
+ description: "Validate the configured ClickHouse connection and report read-only guardrails.",
8
+ inputSchema: {}
9
+ }, async () => {
10
+ let client;
11
+ try {
12
+ const config = readClickHouseConfig();
13
+ client = new ClickHouseHttpMetadataClient(config);
14
+ const info = await inspectClickHouseConnection(client, config);
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: formatConnectionInfo(info)
20
+ }
21
+ ]
22
+ };
23
+ }
24
+ catch (error) {
25
+ return {
26
+ isError: true,
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: `Gozzle could not connect to ClickHouse.\n\n${formatErrorMessage(error)}`
31
+ }
32
+ ]
33
+ };
34
+ }
35
+ finally {
36
+ await client?.close();
37
+ }
38
+ });
39
+ }
40
+ function formatConnectionInfo(info) {
41
+ const lines = [
42
+ "Connected read-only check complete.",
43
+ "No data leaves this machine.",
44
+ "",
45
+ `Version: ${info.version}`,
46
+ `Database: ${info.database}`,
47
+ `User: ${info.currentUser}`,
48
+ `Host: ${info.hostName}`,
49
+ `Deployment: ${info.deployment}`,
50
+ `Readonly setting: ${info.readonlySetting ?? "unknown"}`
51
+ ];
52
+ if (info.writePrivileges.length > 0) {
53
+ lines.push(`Write-capable grants: ${info.writePrivileges.join(", ")}`);
54
+ }
55
+ if (info.warnings.length > 0) {
56
+ lines.push("", "Warnings:");
57
+ lines.push(...info.warnings.map((warning) => `- ${warning}`));
58
+ }
59
+ return lines.join("\n");
60
+ }
61
+ function formatErrorMessage(error) {
62
+ return error instanceof Error ? error.message : String(error);
63
+ }
64
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../../../src/tools/connect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,MAAM,CAAC,YAAY,CACjB,SAAS,EACT;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,gFAAgF;QAClF,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE;QACT,IAAI,MAAgD,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAC;YACtC,MAAM,GAAG,IAAI,4BAA4B,CAAC,MAAM,CAAC,CAAC;YAClD,MAAM,IAAI,GAAG,MAAM,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAE/D,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,oBAAoB,CAAC,IAAI,CAAC;qBACjC;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,8CAA8C,kBAAkB,CACpE,KAAK,CACN,EAAE;qBACJ;iBACF;aACF,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAID,SAAS,oBAAoB,CAAC,IAAoB;IAChD,MAAM,KAAK,GAAG;QACZ,qCAAqC;QACrC,8BAA8B;QAC9B,EAAE;QACF,YAAY,IAAI,CAAC,OAAO,EAAE;QAC1B,aAAa,IAAI,CAAC,QAAQ,EAAE;QAC5B,SAAS,IAAI,CAAC,WAAW,EAAE;QAC3B,SAAS,IAAI,CAAC,QAAQ,EAAE;QACxB,eAAe,IAAI,CAAC,UAAU,EAAE;QAChC,qBAAqB,IAAI,CAAC,eAAe,IAAI,SAAS,EAAE;KACzD,CAAC;IAEF,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,yBAAyB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createHealthTool(server: McpServer): void;
@@ -0,0 +1,15 @@
1
+ export function createHealthTool(server) {
2
+ server.registerTool("health", {
3
+ title: "Health Check",
4
+ description: "Confirm the Gozzle MCP server is running.",
5
+ inputSchema: {}
6
+ }, async () => ({
7
+ content: [
8
+ {
9
+ type: "text",
10
+ text: "Gozzle MCP server is running."
11
+ }
12
+ ]
13
+ }));
14
+ }
15
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/tools/health.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,YAAY,CACjB,QAAQ,EACR;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,2CAA2C;QACxD,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,+BAA+B;aACtC;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type TableInspection } from "../clickhouse/table-inspection.js";
3
+ export declare function createInspectTableTool(server: McpServer): void;
4
+ export declare function formatTableInspection(inspection: TableInspection): string;
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ import { ClickHouseHttpMetadataClient } from "../clickhouse/client.js";
3
+ import { inspectTable } from "../clickhouse/table-inspection.js";
4
+ import { readClickHouseConfig } from "../config/clickhouse.js";
5
+ export function createInspectTableTool(server) {
6
+ server.registerTool("inspect_table", {
7
+ title: "Inspect ClickHouse Table",
8
+ description: "Inspect a ClickHouse table's physical layout and eligible Gozzle checks.",
9
+ inputSchema: {
10
+ table: z
11
+ .string()
12
+ .min(1)
13
+ .describe("Table name in table or database.table format.")
14
+ }
15
+ }, async ({ table }) => {
16
+ let client;
17
+ try {
18
+ const config = readClickHouseConfig();
19
+ client = new ClickHouseHttpMetadataClient(config);
20
+ const inspection = await inspectTable(client, {
21
+ table,
22
+ defaultDatabase: config.database ?? "default"
23
+ });
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text: formatTableInspection(inspection)
29
+ }
30
+ ]
31
+ };
32
+ }
33
+ catch (error) {
34
+ return {
35
+ isError: true,
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Gozzle could not inspect the table.\n\n${formatErrorMessage(error)}`
40
+ }
41
+ ]
42
+ };
43
+ }
44
+ finally {
45
+ await client?.close();
46
+ }
47
+ });
48
+ }
49
+ export function formatTableInspection(inspection) {
50
+ const lines = [
51
+ `Table: ${inspection.identifier.database}.${inspection.identifier.table}`,
52
+ `Engine: ${inspection.engineFull}`,
53
+ `Order by: ${inspection.orderBy ?? inspection.sortingKey ?? "none"}`,
54
+ `Partition by: ${inspection.partitionBy ?? "none"}`,
55
+ `Primary key: ${inspection.primaryKey ?? "none"}`,
56
+ `Active parts: ${inspection.parts.activeParts}`,
57
+ `Rows: ${inspection.totalRows}`,
58
+ `Bytes on disk: ${inspection.totalBytes}`,
59
+ "",
60
+ "Eligible checks:",
61
+ `- verify_dedup: ${inspection.eligibleChecks.verifyDedup ? "yes" : "no"}`,
62
+ `- dry_run_migration: ${inspection.eligibleChecks.dryRunMigration ? "yes" : "no"}`,
63
+ `- diagnose_query: ${inspection.eligibleChecks.diagnoseQuery ? "yes" : "no"}`
64
+ ];
65
+ if (inspection.replacingMergeTree) {
66
+ lines.push("", "ReplacingMergeTree:", `- version column: ${inspection.replacingMergeTree.versionColumn ?? "none"}`, `- deleted column: ${inspection.replacingMergeTree.deletedColumn ?? "none"}`);
67
+ }
68
+ if (inspection.warnings.length > 0) {
69
+ lines.push("", "Warnings:");
70
+ lines.push(...inspection.warnings.map((warning) => `- ${warning}`));
71
+ }
72
+ return lines.join("\n");
73
+ }
74
+ function formatErrorMessage(error) {
75
+ return error instanceof Error ? error.message : String(error);
76
+ }
77
+ //# sourceMappingURL=inspect-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect-table.js","sourceRoot":"","sources":["../../../src/tools/inspect-table.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gozzle/cli",
3
+ "version": "0.0.1-canary.3",
4
+ "description": "A local safety harness and developer toolkit for ClickHouse.",
5
+ "type": "module",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "bin": {
9
+ "gozzle": "./dist/src/cli.js",
10
+ "gozzle-mcp": "./dist/src/mcp/server.js"
11
+ },
12
+ "files": [
13
+ "dist/src",
14
+ "README.md"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "tag": "canary"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "dev:mcp": "tsx src/mcp/server.ts",
23
+ "dev": "tsx src/cli.ts",
24
+ "lint": "tsc -p tsconfig.json --noEmit",
25
+ "prepublishOnly": "npm run build && npm test",
26
+ "smoke:mcp": "npm run build && node dist/scripts/mcp-smoke.js",
27
+ "test": "node --import tsx --test tests/**/*.test.ts"
28
+ },
29
+ "dependencies": {
30
+ "@clickhouse/client": "^1.21.0",
31
+ "@modelcontextprotocol/sdk": "^1.18.0",
32
+ "zod": "^3.25.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.15.0",
36
+ "tsx": "^4.20.0",
37
+ "typescript": "^5.8.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=22"
41
+ }
42
+ }