@hypequery/cli 1.24.1 → 1.25.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.
Files changed (40) hide show
  1. package/README.md +65 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +4 -1
  6. package/dist/commands/generate-client.d.ts +7 -0
  7. package/dist/commands/generate-client.d.ts.map +1 -0
  8. package/dist/commands/generate-client.js +45 -0
  9. package/dist/commands/mcp.d.ts +2 -0
  10. package/dist/commands/mcp.d.ts.map +1 -1
  11. package/dist/commands/mcp.js +9 -1
  12. package/dist/generators/client-contract/input-types.d.ts +7 -0
  13. package/dist/generators/client-contract/input-types.d.ts.map +1 -0
  14. package/dist/generators/client-contract/input-types.js +34 -0
  15. package/dist/generators/client-contract/render.d.ts +3 -0
  16. package/dist/generators/client-contract/render.d.ts.map +1 -0
  17. package/dist/generators/client-contract/render.js +46 -0
  18. package/dist/utils/client-contract-file.d.ts +6 -0
  19. package/dist/utils/client-contract-file.d.ts.map +1 -0
  20. package/dist/utils/client-contract-file.js +58 -0
  21. package/dist/utils/dependency-installer.d.ts.map +1 -1
  22. package/dist/utils/dependency-installer.js +25 -1
  23. package/dist/utils/load-env-files.js +1 -1
  24. package/dist/utils/load-serve-dev.d.ts +6 -0
  25. package/dist/utils/load-serve-dev.d.ts.map +1 -0
  26. package/dist/utils/load-serve-dev.js +18 -0
  27. package/dist/utils/mcp-http.d.ts +12 -0
  28. package/dist/utils/mcp-http.d.ts.map +1 -0
  29. package/dist/utils/mcp-http.js +65 -0
  30. package/dist/utils/mcp-lifecycle.d.ts +1 -1
  31. package/dist/utils/mcp-lifecycle.d.ts.map +1 -1
  32. package/dist/utils/mcp-lifecycle.js +10 -2
  33. package/dist/utils/telemetry/catalog.d.ts +111 -5
  34. package/dist/utils/telemetry/catalog.d.ts.map +1 -1
  35. package/dist/utils/telemetry/catalog.js +13 -4
  36. package/dist/utils/telemetry/domains.d.ts +5 -3
  37. package/dist/utils/telemetry/domains.d.ts.map +1 -1
  38. package/dist/utils/telemetry/domains.js +5 -3
  39. package/dist/utils/telemetry/mcp-session.js +2 -2
  40. package/package.json +15 -12
package/README.md CHANGED
@@ -37,12 +37,44 @@ npx hypequery init \
37
37
  | `hypequery dev` | Local API, docs, and hot reload |
38
38
  | `hypequery generate` | Fresh TypeScript types from ClickHouse or chDB |
39
39
  | `hypequery generate:datasets` | Dataset definitions scaffolded from tables |
40
+ | `hypequery generate:client` | TypeScript API types and routes from a Python client contract |
40
41
  | `hypequery generate:manifest` | A browser-safe route manifest for React hooks |
41
42
  | `hypequery pull` / `diff` | Live source inspection and comparison |
42
43
  | `hypequery deployment:status` | Live release and Cloud-provided connection URLs |
43
44
 
44
45
  Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`, `CLICKHOUSE_USERNAME`, and `CLICKHOUSE_PASSWORD`.
45
46
 
47
+ ## Generate a client for Python endpoints
48
+
49
+ Export a build contract from your Python definition-only factory, then generate
50
+ one TypeScript file with no backend imports:
51
+
52
+ ```bash
53
+ # In the Python project (the factory returns export_client_contract(...)):
54
+ hypequery export client-contract --factory analytics.contract:build --output client-contract.json
55
+
56
+ # In the frontend project:
57
+ npx hypequery generate:client ../backend/client-contract.json --output src/hypequery-client.ts
58
+ npx hypequery generate:client ../backend/client-contract.json --output src/hypequery-client.ts --check
59
+ ```
60
+
61
+ The file exports `ClientApi`, `clientManifest`, `clientContractHash` and public
62
+ response metadata types. It preserves installed POST paths, endpoint schema
63
+ hashes, metric aliases, default measures, field-specific filter types and wire
64
+ nullability. Numeric dimensions can be strings on the wire; aggregate values are
65
+ strings or null. Time buckets may be null when the physical time column is null.
66
+ Results use optional fields until a consumer narrows the query's selections.
67
+
68
+ Generation reads local JSON only (1 MiB maximum); it never evaluates a backend
69
+ module or fetches an endpoint. Signed identities, duplicate JSON members and
70
+ strict contract bounds are checked before writing. Use the owner-controlled
71
+ `build` view; principal-specific live discovery is for runtime consumers.
72
+ `--check` detects drift without creating directories or changing files. Existing
73
+ output requires `--force` to replace, and symlinked output paths are refused.
74
+ Generated output is bounded to 16 MiB. TypeScript describes structural inputs;
75
+ the Python endpoint still validates timezones, selection dependencies, query
76
+ budgets and authorization on every request.
77
+
46
78
  ## Why use the CLI
47
79
 
48
80
  - Get correct ClickHouse runtime types instead of guessing interfaces.
@@ -58,3 +90,36 @@ Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`
58
90
  ## License
59
91
 
60
92
  Apache-2.0.
93
+
94
+ ### Python HTTP datasets over MCP
95
+
96
+ For a Python API exposing an authorized client-contract endpoint, install a
97
+ compatible `@hypequery/mcp` alongside the CLI in your Node project, then run:
98
+
99
+ ```bash
100
+ # Supply the backend principal's Bearer credential through your environment.
101
+ # Omit it only when your backend intentionally authorizes public access.
102
+ export HYPEQUERY_API_KEY='your-backend-credential'
103
+ hypequery mcp --client-contract-url https://api.example.com/v1/client-contract --self-test
104
+ hypequery mcp --client-contract-url https://api.example.com/v1/client-contract
105
+ ```
106
+
107
+ The self-test checks authorized discovery and tool schemas without querying
108
+ business data. Serving uses MCP stdio: stdout carries protocol messages and
109
+ status/errors go to stderr. Use the second command as your MCP client's command
110
+ and arguments; supply the credential using that client's secret/environment
111
+ configuration. Never put the credential in a URL or command argument.
112
+
113
+ Python owns authentication, tenant resolution, policy and SQL execution. One
114
+ bridge process serves one backend principal. Restart it after changing principal
115
+ or after a stale-contract error; it never silently replaces pinned schemas or
116
+ replays a query. HTTPS is required except on loopback, and redirects are refused.
117
+
118
+ `--client-contract-url` cannot be combined with a local file, `--path`, `--tenant`
119
+ or `--url`. The existing `--self-test --url` checks a hosted MCP endpoint; the
120
+ new flag points at a Python HTTP discovery endpoint. No local analytics module
121
+ or Python process is loaded by this mode. Node is needed for the MCP bridge;
122
+ the Python API itself does not require Node.
123
+
124
+ The npm packages must contain the matching client-contract features. Workspace
125
+ qualification does not imply that these changes have already been published.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA8RD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAySD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { mcpCommand } from './commands/mcp.js';
4
4
  import { devCommand } from './commands/dev.js';
5
5
  import { generateCommand } from './commands/generate.js';
6
6
  import { generateDatasetsCommand } from './commands/generate-datasets.js';
7
+ import { generateClientCommand } from './commands/generate-client.js';
7
8
  import { generateManifestCommand } from './commands/generate-manifest.js';
8
9
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
9
10
  import { deployCommand, submitDeploymentCommand, } from './commands/deploy.js';
@@ -132,11 +133,12 @@ program
132
133
  }));
133
134
  program
134
135
  .command('mcp [file]')
135
- .description('Serve this project\'s datasets to an MCP client over stdio')
136
+ .description('Serve local datasets or authorized HTTP client endpoints over MCP stdio')
136
137
  .option('--path <path>', 'Analytics directory (loads <path>/api.ts or <path>/queries.ts)')
137
138
  .option('--tenant <id>', 'Trusted tenant applied to every tenant-scoped dataset')
138
139
  .option('--self-test', 'Check the entrypoint and exit without speaking MCP')
139
140
  .option('--url <url>', 'With --self-test: check a hosted MCP endpoint (key from HYPEQUERY_API_KEY)')
141
+ .option('--client-contract-url <url>', 'Bridge authorized HTTP client endpoints to MCP (Bearer key from HYPEQUERY_API_KEY)')
140
142
  .action(runCommand(async (file, options) => {
141
143
  await mcpCommand(file, options);
142
144
  }));
@@ -163,6 +165,15 @@ program
163
165
  .action(runCommand(async (options) => {
164
166
  await generateDatasetsCommand(options);
165
167
  }));
168
+ program
169
+ .command('generate:client <contract>')
170
+ .description('Generate browser-safe TypeScript API types and routes from a client contract JSON file')
171
+ .option('-o, --output <path>', 'Output TypeScript file (default: analytics/hypequery-client.ts)')
172
+ .option('--check', 'Check for drift without writing')
173
+ .option('--force', 'Replace an existing output file')
174
+ .action(runCommand(async (contract, options) => {
175
+ await generateClientCommand(contract, options);
176
+ }));
166
177
  program
167
178
  .command('generate:manifest <api>')
168
179
  .description('Generate a static React hook manifest from a hypequery API module')
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAkCA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBA2OvE"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBA8OvE"}
@@ -1,3 +1,4 @@
1
+ import { loadServeDev } from '../utils/load-serve-dev.js';
1
2
  import { exitWith, finishAndExit } from '../utils/command-exit.js';
2
3
  import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
3
4
  import { DevSession } from '../utils/telemetry/dev-session.js';
@@ -69,6 +70,9 @@ export async function devCommand(file, options = {}) {
69
70
  const dbSpinner = ora('Connecting to ClickHouse...').start();
70
71
  let loading = true;
71
72
  try {
73
+ // Before the API module: it imports @hypequery/serve itself, and failing
74
+ // there would hide which package to install.
75
+ const { serveDev } = await loadServeDev();
72
76
  // Load the API module
73
77
  const api = await loadApiModule(queriesFile);
74
78
  loading = false;
@@ -92,7 +96,6 @@ export async function devCommand(file, options = {}) {
92
96
  logger.success(`Registered ${queryCount} ${queryCount === 1 ? 'query' : 'queries'}`);
93
97
  logger.newline();
94
98
  // Start the server
95
- const { serveDev } = await import('@hypequery/serve/dev');
96
99
  currentServer = await serveDev(api, {
97
100
  port: options.port,
98
101
  hostname: options.hostname,
@@ -0,0 +1,7 @@
1
+ export interface GenerateClientOptions {
2
+ output?: string;
3
+ check?: boolean;
4
+ force?: boolean;
5
+ }
6
+ export declare function generateClientCommand(artifact: string, options?: GenerateClientOptions): Promise<void>;
7
+ //# sourceMappingURL=generate-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/commands/generate-client.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IAAG,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAE5F,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhH"}
@@ -0,0 +1,45 @@
1
+ import path from 'node:path';
2
+ import { decodeProtocolClientContract } from '@hypequery/protocol';
3
+ import { renderClientContract } from '../generators/client-contract/render.js';
4
+ import { checkClientOutputPath, readClientArtifact, readClientOutput } from '../utils/client-contract-file.js';
5
+ import { writeGeneratedFileAtomically } from '../utils/generated-file.js';
6
+ import { logger } from '../utils/logger.js';
7
+ import { updateCommandTelemetry, setCommandTelemetryError } from '../utils/telemetry/command-context.js';
8
+ import { countBucket } from '../utils/telemetry/buckets.js';
9
+ export async function generateClientCommand(artifact, options = {}) {
10
+ updateCommandTelemetry('generate:client', { mode: options.check ? 'check' : 'write', force: !!options.force, custom_output: options.output !== undefined });
11
+ if (options.force && options.check)
12
+ throw new Error('--force cannot be combined with --check.');
13
+ const input = path.resolve(artifact);
14
+ const output = path.resolve(options.output ?? 'analytics/hypequery-client.ts');
15
+ if (input === output)
16
+ throw new Error('Client input and output must be different files.');
17
+ await checkClientOutputPath(output);
18
+ const current = await readClientOutput(output);
19
+ const contract = decodeProtocolClientContract(await readClientArtifact(input));
20
+ if (contract.view !== 'build')
21
+ throw new Error('Client generation requires a build view exported by the definition owner.');
22
+ const contents = renderClientContract(contract);
23
+ if (Buffer.byteLength(contents) > 16 * 1_048_576)
24
+ throw new Error('Generated client exceeds the 16 MiB output limit.');
25
+ updateCommandTelemetry('generate:client', { endpoint_count_bucket: countBucket(contract.endpoints.length) });
26
+ if (current === contents) {
27
+ updateCommandTelemetry('generate:client', { check_result: 'up_to_date' });
28
+ logger.success('Client types and routes are up to date.');
29
+ return;
30
+ }
31
+ if (options.check) {
32
+ updateCommandTelemetry('generate:client', { check_result: current === undefined ? 'missing' : 'out_of_date' });
33
+ setCommandTelemetryError(current === undefined ? 'output_missing' : 'output_out_of_date');
34
+ process.exitCode = 1;
35
+ logger.error('Client types and routes are missing or out of date.');
36
+ return;
37
+ }
38
+ if (current !== undefined && !options.force)
39
+ throw new Error('Refusing to overwrite client types; use --check or --force.');
40
+ await checkClientOutputPath(output);
41
+ if (await readClientOutput(output) !== current)
42
+ throw new Error('Client output changed during generation; retry after reviewing it.');
43
+ await writeGeneratedFileAtomically(output, contents, { overwrite: options.force === true });
44
+ logger.success('Generated client types and routes.');
45
+ }
@@ -9,6 +9,8 @@ export interface McpOptions {
9
9
  selfTest?: boolean;
10
10
  /** With `selfTest`: check this hosted MCP endpoint instead of a local entrypoint. */
11
11
  url?: string;
12
+ /** Authorized HTTP client-contract discovery; no local analytics entrypoint. */
13
+ clientContractUrl?: string;
12
14
  }
13
15
  export interface McpDependencies {
14
16
  loadApi?: (file: string) => Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AASvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAMnF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AAoED,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CA0Ef"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AASvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAMnF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AAoED,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAiFf"}
@@ -1,3 +1,4 @@
1
+ import { runHttpMcp } from '../utils/mcp-http.js';
1
2
  import { exitWith } from '../utils/command-exit.js';
2
3
  import { runMcpUntilSignal } from '../utils/mcp-lifecycle.js';
3
4
  import { routeConsoleOutputToStderr } from '../utils/mcp-console.js';
@@ -77,7 +78,10 @@ function resolveSource(api, file) {
77
78
  }
78
79
  export async function mcpCommand(file, options = {}, dependencies = {}) {
79
80
  const telemetry = new McpSession();
80
- updateCommandTelemetry('mcp', { mode: options.selfTest ? 'self_test' : 'serve', hosted: !!options.url, tenant_used: !!options.tenant });
81
+ updateCommandTelemetry('mcp', { mode: options.selfTest ? 'self_test' : 'serve', hosted: !!options.url, tenant_used: !!options.tenant, http_backend: !!options.clientContractUrl });
82
+ if (options.clientContractUrl !== undefined && (file !== undefined || options.path !== undefined || options.tenant !== undefined || options.url !== undefined)) {
83
+ fail('--client-contract-url cannot be combined with a file, --path, --tenant or --url.');
84
+ }
81
85
  if (options.url && !options.selfTest) {
82
86
  fail('--url is only used with --self-test.', [
83
87
  'MCP clients connect to the hosted endpoint directly; this command serves a local project.',
@@ -85,6 +89,10 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
85
89
  }
86
90
  const restoreConsole = routeConsoleOutputToStderr();
87
91
  try {
92
+ if (options.clientContractUrl !== undefined) {
93
+ await runHttpMcp(options.clientContractUrl, !!options.selfTest, dependencies, telemetry);
94
+ return;
95
+ }
88
96
  if (options.url) {
89
97
  await remoteSelfTest(options.url, dependencies);
90
98
  return;
@@ -0,0 +1,7 @@
1
+ import type { ProtocolClientEndpoint } from '@hypequery/protocol';
2
+ export declare const literal: (value: string) => string;
3
+ export declare const union: (values: readonly string[]) => string;
4
+ export declare const names: (values: readonly string[]) => string;
5
+ /** Structural types mirror the closed profile; runtime also checks selection dependencies and budgets. */
6
+ export declare function clientInputType(endpoint: ProtocolClientEndpoint): string;
7
+ //# sourceMappingURL=input-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-types.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/input-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAA+B,CAAC;AACxE,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAsD,CAAC;AACzG,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAgD,CAAC;AAWnG,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqBxE"}
@@ -0,0 +1,34 @@
1
+ export const literal = (value) => JSON.stringify(value);
2
+ export const union = (values) => values.length ? values.join(' | ') : 'never';
3
+ export const names = (values) => union([...values].sort().map(literal));
4
+ function predicate(field, definition, having = false) {
5
+ const scalar = definition.type === 'number' ? 'number' : definition.type === 'boolean' ? 'boolean' : 'string';
6
+ return [...definition.operators].sort().map(operator => {
7
+ const value = operator === 'between' ? `readonly [${scalar}, ${scalar}]`
8
+ : operator === 'in' || operator === 'notIn' ? `readonly [${scalar}, ...${scalar}[]]` : scalar;
9
+ return `{ readonly ${having ? 'measure' : 'field'}: ${literal(field)}; readonly operator: ${literal(operator)}; readonly value: ${value} }`;
10
+ });
11
+ }
12
+ /** Structural types mirror the closed profile; runtime also checks selection dependencies and budgets. */
13
+ export function clientInputType(endpoint) {
14
+ const dimensions = Object.keys(endpoint.dimensions).filter(key => endpoint.dimensions[key].groupable);
15
+ const measures = Object.keys(endpoint.measures);
16
+ const filters = Object.keys(endpoint.filters).sort().flatMap(key => predicate(key, endpoint.filters[key]));
17
+ const having = measures.filter(key => endpoint.measures[key].having).sort().flatMap(key => predicate(key, {
18
+ type: 'number', operators: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn'],
19
+ }, true));
20
+ const ordered = [...dimensions.filter(key => endpoint.dimensions[key].orderable), ...measures,
21
+ ...(endpoint.time ? ['period'] : [])];
22
+ return `{
23
+ readonly dimensions?: readonly (${names(dimensions)})[];
24
+ readonly filters?: readonly (${union(filters)})[];
25
+ readonly orderBy?: readonly { readonly field: ${names(ordered)}; readonly direction: 'asc' | 'desc' }[];
26
+ readonly limit?: number;
27
+ readonly offset?: number;
28
+ readonly includeMeta?: ${endpoint.metadata ? 'boolean' : 'false'};
29
+ ${endpoint.kind === 'dataset' ? `readonly measures?: readonly (${names(measures)})[];
30
+ readonly having?: readonly (${union(having)})[];` : ''}
31
+ ${endpoint.time ? `readonly by?: ${names(endpoint.time.grains)};
32
+ ${endpoint.time.timezone ? 'readonly timezone?: string;' : ''}` : ''}
33
+ }`;
34
+ }
@@ -0,0 +1,3 @@
1
+ /** Data-only generator: no API module evaluation, credentials, drivers or server dependencies. */
2
+ export declare function renderClientContract(source: unknown): string;
3
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/render.ts"],"names":[],"mappings":"AA0BA,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAqB5D"}
@@ -0,0 +1,46 @@
1
+ import { validateProtocolClientContract } from '@hypequery/protocol';
2
+ import { clientInputType, literal } from './input-types.js';
3
+ function dimensionType(definition) {
4
+ const wire = definition.wireType === 'number-or-string' ? 'number | string' : definition.wireType;
5
+ return wire + (definition.nullable === false ? '' : ' | null');
6
+ }
7
+ function endpointType(endpoint) {
8
+ const dimensions = Object.keys(endpoint.dimensions).sort().filter(key => endpoint.dimensions[key].groupable)
9
+ .map(key => `readonly ${literal(key)}?: ${dimensionType(endpoint.dimensions[key])};`);
10
+ const measures = Object.keys(endpoint.measures).sort().map(key => `readonly ${literal(key)}?: string | null;`);
11
+ // The physical time column may be nullable; the descriptor makes no non-null guarantee.
12
+ const row = `{ ${[...dimensions, ...measures, ...(endpoint.time ? ['readonly period?: string | null;'] : [])].join('\n')} }`;
13
+ const semantic = {
14
+ version: 1, kind: endpoint.kind, dimensions: Object.fromEntries(Object.keys(endpoint.dimensions).sort().map(key => [key, { wireType: endpoint.dimensions[key].wireType, nullable: endpoint.dimensions[key].nullable }])),
15
+ ...(endpoint.kind === 'dataset' ? { measures: Object.fromEntries(Object.keys(endpoint.measures).sort().map(key => [key, { wireType: 'string', nullable: true }])), defaultMeasures: endpoint.defaultMeasures }
16
+ : { metricName: Object.keys(endpoint.measures)[0] }),
17
+ };
18
+ return `{
19
+ input: ${clientInputType(endpoint)};
20
+ output: { data: ${row}[]; meta?: ClientResponseMeta | null };
21
+ readonly __hypequeryClientSemantic?: ${JSON.stringify(semantic)};
22
+ }`;
23
+ }
24
+ /** Data-only generator: no API module evaluation, credentials, drivers or server dependencies. */
25
+ export function renderClientContract(source) {
26
+ const contract = validateProtocolClientContract(source);
27
+ const endpoints = [...contract.endpoints].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
28
+ const entries = endpoints.map(endpoint => ({ endpoint, key: endpoint.kind === 'dataset' ? `dataset:${endpoint.id}` : endpoint.id }));
29
+ return `// Generated by hypequery generate:client. Do not edit.
30
+ // Runtime validation still enforces selection dependencies, timezones and query budgets.
31
+ export interface ClientResponseMeta {
32
+ requestId: string;
33
+ timingMs: number;
34
+ rowCount: number;
35
+ pagination: { limit: number; offset: number; hasMore: boolean };
36
+ cache?: { hit: boolean };
37
+ }
38
+ export type ClientApi = {
39
+ ${entries.map(({ endpoint, key }) => ` ${literal(key)}: ${endpointType(endpoint)};`).join('\n')}
40
+ };
41
+ export const clientManifest = {
42
+ ${entries.map(({ endpoint, key }) => ` [${literal(key)}]: { method: 'POST', path: ${literal(endpoint.path)}, schemaHash: ${literal(endpoint.schemaHash)} },`).join('\n')}
43
+ } as const;
44
+ export const clientContractHash = ${literal(contract.contentHash)} as const;
45
+ `;
46
+ }
@@ -0,0 +1,6 @@
1
+ /** Refuse filesystem indirection for generated output and its existing ancestors. */
2
+ export declare function checkClientOutputPath(filePath: string): Promise<void>;
3
+ /** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
4
+ export declare function readClientArtifact(filePath: string, maxBytes?: number): Promise<string>;
5
+ export declare function readClientOutput(filePath: string): Promise<string | undefined>;
6
+ //# sourceMappingURL=client-contract-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"AAIA,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3E;AAED,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpF"}
@@ -0,0 +1,58 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ /** Refuse filesystem indirection for generated output and its existing ancestors. */
5
+ export async function checkClientOutputPath(filePath) {
6
+ let current = path.resolve(filePath);
7
+ while (true) {
8
+ try {
9
+ const info = await lstat(current);
10
+ if (info.isSymbolicLink() || (current === path.resolve(filePath) ? !info.isFile() : !info.isDirectory())) {
11
+ throw new Error('Client output must be a regular file under ordinary directories.');
12
+ }
13
+ }
14
+ catch (error) {
15
+ if (error.code !== 'ENOENT')
16
+ throw error;
17
+ }
18
+ const parent = path.dirname(current);
19
+ if (parent === current)
20
+ return;
21
+ current = parent;
22
+ }
23
+ }
24
+ /** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
25
+ export async function readClientArtifact(filePath, maxBytes = 1_048_576) {
26
+ const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
27
+ try {
28
+ const before = await handle.stat();
29
+ if (!before.isFile() || before.size > maxBytes)
30
+ throw new Error('Client artifact must be a regular file within the byte limit.');
31
+ const buffer = Buffer.alloc(maxBytes + 1);
32
+ let size = 0;
33
+ while (size < buffer.length) {
34
+ const { bytesRead } = await handle.read(buffer, size, buffer.length - size, null);
35
+ if (!bytesRead)
36
+ break;
37
+ size += bytesRead;
38
+ }
39
+ const after = await handle.stat();
40
+ if (size > maxBytes || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
41
+ throw new Error('Client artifact exceeded its byte limit or changed while reading.');
42
+ }
43
+ return new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, size));
44
+ }
45
+ finally {
46
+ await handle.close();
47
+ }
48
+ }
49
+ export async function readClientOutput(filePath) {
50
+ try {
51
+ return await readClientArtifact(filePath, 16 * 1_048_576);
52
+ }
53
+ catch (error) {
54
+ if (error.code === 'ENOENT')
55
+ return undefined;
56
+ throw error;
57
+ }
58
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AAsBD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAyD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
1
+ {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AA8CD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAyD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { logger } from './logger.js';
7
- const ZOD_SCAFFOLD_VERSION = '^3.23.8';
7
+ const ZOD_SCAFFOLD_VERSION = '^4.6.6';
8
8
  // chdb/hypequery (the embedded adapter subpath) ships from chdb 3.2.0.
9
9
  const CHDB_SCAFFOLD_VERSION = '^3.2.0';
10
10
  const STABLE_SCAFFOLD_PACKAGES = ['@hypequery/clickhouse', '@hypequery/serve', `zod@${ZOD_SCAFFOLD_VERSION}`];
@@ -120,12 +120,36 @@ export function resolveScaffoldPackages(cliVersion, style = 'queries', database
120
120
  ...(includeChdb ? [`chdb@${CHDB_SCAFFOLD_VERSION}`] : []),
121
121
  ];
122
122
  }
123
+ /** The oldest Zod 3 that Serve's `zod` peer range (`^3.23.8 || ^4.0.0`) accepts. */
124
+ const SERVE_MIN_ZOD_3 = [3, 23, 8];
125
+ /**
126
+ * Whether a project's `zod` range can only resolve versions Serve accepts:
127
+ * its lowest version, the first `x.y.z` in it (missing parts count as 0), is
128
+ * Zod 4 or at least 3.23.8. So `^3.23.8`, `~4.1.0` and `4` are kept, while
129
+ * `3.1.0`, `~3.1.0` and `3` are upgraded. Tags such as `latest` are upgraded.
130
+ */
131
+ function isServeCompatibleZodRange(range) {
132
+ const match = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(range);
133
+ if (!match)
134
+ return false;
135
+ const [major, minor, patch] = [match[1], match[2], match[3]].map(part => Number(part ?? 0));
136
+ if (major === 4)
137
+ return true;
138
+ if (major !== 3)
139
+ return false;
140
+ const [, minMinor, minPatch] = SERVE_MIN_ZOD_3;
141
+ return minor > minMinor || (minor === minMinor && patch >= minPatch);
142
+ }
123
143
  function shouldInstallPackage(pkgJson, specifier) {
124
144
  const name = packageNameFromSpecifier(specifier);
125
145
  const existingVersion = getDependencyVersion(pkgJson, name);
126
146
  if (!existingVersion) {
127
147
  return true;
128
148
  }
149
+ // Serve accepts Zod 3 or Zod 4, so keep whichever the project already uses.
150
+ if (name === 'zod' && isServeCompatibleZodRange(existingVersion)) {
151
+ return false;
152
+ }
129
153
  const desiredHasPinnedVersion = specifier.startsWith('@')
130
154
  ? specifier.indexOf('@', specifier.indexOf('/') + 1) !== -1
131
155
  : specifier.includes('@');
@@ -18,7 +18,7 @@ export async function loadEnvFiles(directory) {
18
18
  const { config } = await import('dotenv');
19
19
  for (const file of envFiles(process.env.NODE_ENV)) {
20
20
  // Missing files are a no-op.
21
- config({ path: path.join(resolved, file) });
21
+ config({ path: path.join(resolved, file), quiet: true });
22
22
  }
23
23
  }
24
24
  catch {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `@hypequery/serve` is an optional peer: projects that only generate clients
3
+ * (a React app, for example) should not have server packages installed for them.
4
+ */
5
+ export declare function loadServeDev(load?: () => Promise<typeof import('@hypequery/serve/dev')>): Promise<typeof import('@hypequery/serve/dev')>;
6
+ //# sourceMappingURL=load-serve-dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-serve-dev.d.ts","sourceRoot":"","sources":["../../src/utils/load-serve-dev.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,GAAE,MAAM,OAAO,CAAC,cAAc,sBAAsB,CAAC,CAAwC,GAChG,OAAO,CAAC,cAAc,sBAAsB,CAAC,CAAC,CAYhD"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@hypequery/serve` is an optional peer: projects that only generate clients
3
+ * (a React app, for example) should not have server packages installed for them.
4
+ */
5
+ export async function loadServeDev(load = () => import('@hypequery/serve/dev')) {
6
+ try {
7
+ return await load();
8
+ }
9
+ catch (error) {
10
+ const code = error.code;
11
+ const message = error instanceof Error ? error.message : '';
12
+ // Only serve itself: a module serve fails to find is a different problem.
13
+ if ((code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') && message.includes("'@hypequery/serve")) {
14
+ throw new Error('hypequery dev needs @hypequery/serve. Install it: npm install @hypequery/serve');
15
+ }
16
+ throw error;
17
+ }
18
+ }
@@ -0,0 +1,12 @@
1
+ import type { McpSession } from './telemetry/mcp-session.js';
2
+ interface HttpMcpDependencies {
3
+ loadMcp?: () => Promise<unknown>;
4
+ env?: Readonly<Record<string, string | undefined>>;
5
+ }
6
+ type HttpMcpPackage = Pick<typeof import('@hypequery/mcp'), 'createMCPHttpExecutor' | 'createMCPProtocolServer' | 'connectMCPServerStdio'>;
7
+ /** Load the user's optional peer without requiring or importing an analytics entrypoint. */
8
+ export declare function loadHttpMcp(load?: () => Promise<unknown>): Promise<HttpMcpPackage>;
9
+ /** One backend principal per process. Python resolves authorization and tenant context. */
10
+ export declare function runHttpMcp(url: string, selfTest: boolean, dependencies: HttpMcpDependencies, telemetry: McpSession): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=mcp-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-http.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-http.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,UAAU,mBAAmB;IAC3B,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AACD,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,yBAAyB,GAAG,uBAAuB,CAAC,CAAC;AAE3I,4FAA4F;AAC5F,wBAAsB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAUxF;AAED,2FAA2F;AAC3F,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCxI"}
@@ -0,0 +1,65 @@
1
+ import path from 'node:path';
2
+ import { importFromProject } from './project-root.js';
3
+ import { runMcpUntilSignal } from './mcp-lifecycle.js';
4
+ import { MCP_API_KEY_VARIABLE } from './hosted-endpoints.js';
5
+ import { logger } from './logger.js';
6
+ /** Load the user's optional peer without requiring or importing an analytics entrypoint. */
7
+ export async function loadHttpMcp(load) {
8
+ let candidate;
9
+ try {
10
+ candidate = await (load ?? (() => importFromProject('@hypequery/mcp', path.join(process.cwd(), '__hypequery_mcp__.js'), () => import('@hypequery/mcp'))))();
11
+ }
12
+ catch {
13
+ throw new Error('Install @hypequery/mcp with HTTP client-contract support in this project.');
14
+ }
15
+ const module = candidate;
16
+ if (!module || typeof module.createMCPHttpExecutor !== 'function' || typeof module.createMCPProtocolServer !== 'function' || typeof module.connectMCPServerStdio !== 'function') {
17
+ throw new Error('Upgrade @hypequery/mcp in this project to a version with createMCPHttpExecutor support.');
18
+ }
19
+ return module;
20
+ }
21
+ /** One backend principal per process. Python resolves authorization and tenant context. */
22
+ export async function runHttpMcp(url, selfTest, dependencies, telemetry) {
23
+ const module = await loadHttpMcp(dependencies.loadMcp);
24
+ const key = (dependencies.env ?? process.env)[MCP_API_KEY_VARIABLE]?.trim();
25
+ const create = async (signal) => {
26
+ try {
27
+ return await module.createMCPHttpExecutor({ url, ...(key ? { headers: { authorization: `Bearer ${key}` } } : {}) }, signal);
28
+ }
29
+ catch {
30
+ if (signal?.aborted)
31
+ throw signal.reason;
32
+ // Do not echo a URL, credential, backend response or user-installed peer's error.
33
+ throw new Error('Could not initialize the HTTP MCP bridge. Check the discovery URL, backend access and client-contract compatibility.');
34
+ }
35
+ };
36
+ if (selfTest) {
37
+ const executor = await create();
38
+ let tools;
39
+ try {
40
+ tools = await executor.listTools();
41
+ }
42
+ catch {
43
+ throw new Error('HTTP MCP discovery check failed. Check backend access and reconnect after contract changes.');
44
+ }
45
+ logger.success('Authorized HTTP client contract is ready for MCP.');
46
+ logger.info(`Tools: ${tools.tools.length}`);
47
+ logger.info('No query tool was called. Python resolves the principal and tenant.');
48
+ return;
49
+ }
50
+ await runMcpUntilSignal(async (signal) => {
51
+ const executor = await create(signal);
52
+ signal.throwIfAborted();
53
+ const server = module.createMCPProtocolServer({ executor: telemetry.instrument(executor) });
54
+ try {
55
+ await module.connectMCPServerStdio(server);
56
+ }
57
+ catch {
58
+ await server.close().catch(() => undefined);
59
+ throw new Error('Could not connect the MCP stdio transport.');
60
+ }
61
+ telemetry.start({ mode: 'serve', tenant_used: false, http_backend: true });
62
+ process.stderr.write('hypequery MCP serving an authorized HTTP client contract.\n');
63
+ return server;
64
+ });
65
+ }
@@ -2,5 +2,5 @@ export interface CloseableMcpServer {
2
2
  close(): Promise<void>;
3
3
  }
4
4
  /** Own signal listeners for exactly the lifetime of the stdio server. */
5
- export declare function runMcpUntilSignal(start: () => Promise<CloseableMcpServer>): Promise<void>;
5
+ export declare function runMcpUntilSignal(start: (signal: AbortSignal) => Promise<CloseableMcpServer>): Promise<void>;
6
6
  //# sourceMappingURL=mcp-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-lifecycle.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-lifecycle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/F"}
1
+ {"version":3,"file":"mcp-lifecycle.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-lifecycle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlH"}
@@ -1,13 +1,21 @@
1
1
  /** Own signal listeners for exactly the lifetime of the stdio server. */
2
2
  export async function runMcpUntilSignal(start) {
3
+ const controller = new AbortController();
3
4
  let stop;
4
- const stopped = new Promise(resolve => { stop = resolve; });
5
+ const stopped = new Promise(resolve => { stop = () => { controller.abort(); resolve(); }; });
5
6
  // Install before awaiting startup so a signal during connection is retained.
6
7
  process.on('SIGINT', stop);
7
8
  process.on('SIGTERM', stop);
8
9
  let server;
9
10
  try {
10
- server = await start();
11
+ try {
12
+ server = await start(controller.signal);
13
+ }
14
+ catch (error) {
15
+ if (controller.signal.aborted && error === controller.signal.reason)
16
+ return;
17
+ throw error;
18
+ }
11
19
  await stopped;
12
20
  }
13
21
  finally {
@@ -111,7 +111,7 @@ export declare const COMMAND_PROPERTIES: {
111
111
  readonly help_topic: {
112
112
  readonly kind: "enum";
113
113
  readonly description: string;
114
- readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
114
+ readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
115
115
  } & {
116
116
  readonly optional: true;
117
117
  };
@@ -259,6 +259,12 @@ export declare const COMMAND_PROPERTIES: {
259
259
  } & {
260
260
  readonly optional: true;
261
261
  };
262
+ readonly http_backend: {
263
+ readonly kind: "boolean";
264
+ readonly description: string;
265
+ } & {
266
+ readonly optional: true;
267
+ };
262
268
  readonly dataset_count_bucket: {
263
269
  readonly kind: "enum";
264
270
  readonly description: string;
@@ -436,6 +442,41 @@ export declare const COMMAND_PROPERTIES: {
436
442
  readonly optional: true;
437
443
  };
438
444
  };
445
+ readonly 'generate:client': {
446
+ readonly mode: {
447
+ readonly kind: "enum";
448
+ readonly description: string;
449
+ readonly values: readonly ["write", "check"];
450
+ } & {
451
+ readonly optional: true;
452
+ };
453
+ readonly check_result: {
454
+ readonly kind: "enum";
455
+ readonly description: string;
456
+ readonly values: readonly ["up_to_date", "out_of_date", "missing"];
457
+ } & {
458
+ readonly optional: true;
459
+ };
460
+ readonly force: {
461
+ readonly kind: "boolean";
462
+ readonly description: string;
463
+ } & {
464
+ readonly optional: true;
465
+ };
466
+ readonly custom_output: {
467
+ readonly kind: "boolean";
468
+ readonly description: string;
469
+ } & {
470
+ readonly optional: true;
471
+ };
472
+ readonly endpoint_count_bucket: {
473
+ readonly kind: "enum";
474
+ readonly description: string;
475
+ readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
476
+ } & {
477
+ readonly optional: true;
478
+ };
479
+ };
439
480
  readonly 'generate:manifest': {
440
481
  readonly query_count_bucket: {
441
482
  readonly kind: "enum";
@@ -497,6 +538,14 @@ export declare const SESSION_START_PROPERTIES: {
497
538
  readonly kind: "enum";
498
539
  readonly description: string;
499
540
  readonly values: readonly ["hypequery.ts", "api.ts", "queries.ts", "explicit_file", "unknown"];
541
+ } & {
542
+ readonly optional: true;
543
+ };
544
+ readonly http_backend: {
545
+ readonly kind: "boolean";
546
+ readonly description: string;
547
+ } & {
548
+ readonly optional: true;
500
549
  };
501
550
  readonly mode: {
502
551
  readonly kind: "enum";
@@ -507,6 +556,8 @@ export declare const SESSION_START_PROPERTIES: {
507
556
  readonly kind: "enum";
508
557
  readonly description: string;
509
558
  readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
559
+ } & {
560
+ readonly optional: true;
510
561
  };
511
562
  readonly tenant_used: {
512
563
  readonly kind: "boolean";
@@ -516,6 +567,8 @@ export declare const SESSION_START_PROPERTIES: {
516
567
  readonly kind: "enum";
517
568
  readonly description: string;
518
569
  readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
570
+ } & {
571
+ readonly optional: true;
519
572
  };
520
573
  };
521
574
  };
@@ -567,12 +620,12 @@ export declare const EVENT_CATALOG: {
567
620
  readonly command: {
568
621
  readonly kind: "enum";
569
622
  readonly description: string;
570
- readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
623
+ readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
571
624
  };
572
625
  readonly flags_used: {
573
626
  readonly kind: "enum_list";
574
627
  readonly description: string;
575
- readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--release" | "--no-telemetry" | "--help" | "--version")[];
628
+ readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--release" | "--no-telemetry" | "--help" | "--version")[];
576
629
  };
577
630
  readonly outcome: {
578
631
  readonly kind: "enum";
@@ -597,7 +650,7 @@ export declare const EVENT_CATALOG: {
597
650
  readonly help_topic: {
598
651
  readonly kind: "enum";
599
652
  readonly description: string;
600
- readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
653
+ readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
601
654
  } & {
602
655
  readonly optional: true;
603
656
  };
@@ -745,6 +798,12 @@ export declare const EVENT_CATALOG: {
745
798
  } & {
746
799
  readonly optional: true;
747
800
  };
801
+ readonly http_backend: {
802
+ readonly kind: "boolean";
803
+ readonly description: string;
804
+ } & {
805
+ readonly optional: true;
806
+ };
748
807
  readonly dataset_count_bucket: {
749
808
  readonly kind: "enum";
750
809
  readonly description: string;
@@ -922,6 +981,41 @@ export declare const EVENT_CATALOG: {
922
981
  readonly optional: true;
923
982
  };
924
983
  };
984
+ readonly 'generate:client': {
985
+ readonly mode: {
986
+ readonly kind: "enum";
987
+ readonly description: string;
988
+ readonly values: readonly ["write", "check"];
989
+ } & {
990
+ readonly optional: true;
991
+ };
992
+ readonly check_result: {
993
+ readonly kind: "enum";
994
+ readonly description: string;
995
+ readonly values: readonly ["up_to_date", "out_of_date", "missing"];
996
+ } & {
997
+ readonly optional: true;
998
+ };
999
+ readonly force: {
1000
+ readonly kind: "boolean";
1001
+ readonly description: string;
1002
+ } & {
1003
+ readonly optional: true;
1004
+ };
1005
+ readonly custom_output: {
1006
+ readonly kind: "boolean";
1007
+ readonly description: string;
1008
+ } & {
1009
+ readonly optional: true;
1010
+ };
1011
+ readonly endpoint_count_bucket: {
1012
+ readonly kind: "enum";
1013
+ readonly description: string;
1014
+ readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
1015
+ } & {
1016
+ readonly optional: true;
1017
+ };
1018
+ };
925
1019
  readonly 'generate:manifest': {
926
1020
  readonly query_count_bucket: {
927
1021
  readonly kind: "enum";
@@ -993,6 +1087,14 @@ export declare const EVENT_CATALOG: {
993
1087
  readonly kind: "enum";
994
1088
  readonly description: string;
995
1089
  readonly values: readonly ["hypequery.ts", "api.ts", "queries.ts", "explicit_file", "unknown"];
1090
+ } & {
1091
+ readonly optional: true;
1092
+ };
1093
+ readonly http_backend: {
1094
+ readonly kind: "boolean";
1095
+ readonly description: string;
1096
+ } & {
1097
+ readonly optional: true;
996
1098
  };
997
1099
  readonly mode: {
998
1100
  readonly kind: "enum";
@@ -1003,6 +1105,8 @@ export declare const EVENT_CATALOG: {
1003
1105
  readonly kind: "enum";
1004
1106
  readonly description: string;
1005
1107
  readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
1108
+ } & {
1109
+ readonly optional: true;
1006
1110
  };
1007
1111
  readonly tenant_used: {
1008
1112
  readonly kind: "boolean";
@@ -1012,6 +1116,8 @@ export declare const EVENT_CATALOG: {
1012
1116
  readonly kind: "enum";
1013
1117
  readonly description: string;
1014
1118
  readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
1119
+ } & {
1120
+ readonly optional: true;
1015
1121
  };
1016
1122
  };
1017
1123
  };
@@ -1083,7 +1189,7 @@ export declare const EVENT_CATALOG: {
1083
1189
  readonly command: {
1084
1190
  readonly kind: "enum";
1085
1191
  readonly description: string;
1086
- readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
1192
+ readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
1087
1193
  };
1088
1194
  readonly exception_class: {
1089
1195
  readonly kind: "enum";
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,aAAa,EAA2D,YAAY,EAA8D,MAAM,cAAc,CAAC;AAC1L,OAAO,EAA0E,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnI,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAMtC,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBH,CAAC;AAc5B,oFAAoF;AACpF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD6B,CAAC;AAE7D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUzB,CAAC;AAEX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkChB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,aAAa,CAAC;AAC5D,KAAK,UAAU,CAAC,CAAC,SAAS,kBAAkB,IAAI,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;AACxE,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI;KACxD,CAAC,IAAI,MAAM,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClD,CAAC,MAAM,CAAC,CAAC,CAAC;AACX,KAAK,eAAe,GAAG;KACpB,CAAC,IAAI,MAAM,OAAO,aAAa,GAAG;QAAE,OAAO,EAAE,CAAC,CAAC;QAAC,UAAU,EAAE,SAAS,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAA;KAAE;CAC1I,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAC9B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,kBAAkB,IAAI,UAAU,CAAC,OAAO,iBAAiB,CAAC,GAChG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GACvC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAC7G,CAAC,CAAC,SAAS,uBAAuB,GAAG,eAAe,GAAG,OAAO,CAAC,CAAC;AACpE,MAAM,MAAM,cAAc,GAAG;KAC1B,CAAC,IAAI,kBAAkB,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;KAAE;CAC9F,CAAC,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,7 @@ const generation = {
36
36
  unsupported_type_families: optional(enumList('Which built-in type families need mapping? Never full type expressions.', TYPE_FAMILIES)),
37
37
  custom_output: optional(output),
38
38
  };
39
- const INSTRUMENTED = ['init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets', 'generate:manifest', 'help'];
39
+ const INSTRUMENTED = ['init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets', 'generate:manifest', 'generate:client', 'help'];
40
40
  /** Command-specific completion fields; absence means that stage was not reached. */
41
41
  export const COMMAND_PROPERTIES = {
42
42
  init: {
@@ -62,6 +62,7 @@ export const COMMAND_PROPERTIES = {
62
62
  mcp: {
63
63
  mode: optional(enumeration('Is MCP used for validation or serving?', ['self_test', 'serve'])),
64
64
  hosted: optional(boolean('Is MCP self-test targeting a hosted endpoint?')),
65
+ http_backend: optional(boolean('Is MCP using an authorized HTTP client contract? Never its URL or principal.')),
65
66
  dataset_count_bucket: optional(count('How many datasets are exposed to agents?')),
66
67
  tenant_used: optional(boolean('Is a trusted tenant supplied? Never its value.')),
67
68
  tenant_dataset_count_bucket: optional(count('How many datasets require tenant scope?')),
@@ -78,6 +79,13 @@ export const COMMAND_PROPERTIES = {
78
79
  datasets_generated_bucket: optional(count('How many dataset definitions are generated?')),
79
80
  warning_counts: optional(record('Which tenant-scaffolding warnings need improvement?', fieldsFor(WARNING_CODES, optional(count('Bucketed warning occurrences.'))))),
80
81
  },
82
+ 'generate:client': {
83
+ mode: optional(enumeration('Are client types generated or checked in CI?', ['write', 'check'])),
84
+ check_result: optional(enumeration('Does CI find stale client types?', ['up_to_date', 'out_of_date', 'missing'])),
85
+ force: optional(boolean('Are generated client types replaced?')),
86
+ custom_output: optional(output),
87
+ endpoint_count_bucket: optional(count('How many public endpoints need client types?')),
88
+ },
81
89
  'generate:manifest': {
82
90
  query_count_bucket: optional(count('How much query-based React integration is adopted?')),
83
91
  endpoint_count_bucket: optional(count('How much endpoint-based React integration is adopted?')),
@@ -99,11 +107,12 @@ export const SESSION_START_PROPERTIES = {
99
107
  quiet: boolean('Is quiet startup adopted?'),
100
108
  },
101
109
  mcp: {
102
- entry_type: entry,
110
+ entry_type: optional(entry),
111
+ http_backend: optional(boolean('Is MCP using an authorized HTTP client contract? Never its URL or principal.')),
103
112
  mode: enumeration('Is MCP serving or validating?', ['self_test', 'serve']),
104
- dataset_count_bucket: count('How many datasets are exposed to agents?'),
113
+ dataset_count_bucket: optional(count('How many local datasets are exposed to agents? Omit for HTTP contracts.')),
105
114
  tenant_used: boolean('Is tenant scope supplied? Never its value.'),
106
- tenant_dataset_count_bucket: count('How many exposed datasets require tenant scope?'),
115
+ tenant_dataset_count_bucket: optional(count('How many local datasets require tenant scope? Omit for HTTP contracts.')),
107
116
  },
108
117
  };
109
118
  export const SESSION_END_PROPERTIES = {
@@ -4,17 +4,18 @@ export declare const DURATION_BUCKETS: readonly ["<100ms", "100ms-1s", "1s-10s",
4
4
  export declare const ERROR_CODES: readonly ["unknown", "connection_failed", "auth_failed", "entrypoint_not_found", "compile_error", "prompt_cancelled", "validation_failed", "cloud_network_error", "cloud_rejected", "not_installed", "engine_error", "no_tables", "no_datasets", "tenant_required", "load_error", "output_missing", "output_out_of_date", "unknown_command", "unknown_option"];
5
5
  export declare const OUTCOMES: readonly ["success", "failure", "cancelled", "interrupted"];
6
6
  export declare const DATABASES: readonly ["clickhouse", "chdb", "bigquery", "unknown"];
7
- export declare const COMMANDS: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
7
+ export declare const COMMANDS: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
8
8
  export type TelemetryCommand = typeof COMMANDS[number];
9
9
  /** Canonical long names, including negated flags. Values and aliases never leave the CLI. */
10
10
  export declare const COMMAND_FLAGS: {
11
11
  readonly init: readonly ["--path", "--style", "--database", "--chdb-path", "--auth", "--all-tables", "--tables", "--exclude-tables", "--no-example", "--no-interactive", "--force", "--skip-connection"];
12
12
  readonly dev: readonly ["--port", "--hostname", "--no-watch", "--no-cache", "--cache", "--redis-url", "--open", "--cors", "--path", "--quiet"];
13
- readonly mcp: readonly ["--path", "--tenant", "--self-test", "--url"];
13
+ readonly mcp: readonly ["--path", "--tenant", "--self-test", "--url", "--client-contract-url"];
14
14
  readonly generate: readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
15
15
  readonly 'generate:types': readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
16
16
  readonly 'generate:datasets': readonly ["--output", "--path", "--tables", "--exclude-tables", "--tenant-column", "--force", "--check", "--diff"];
17
17
  readonly 'generate:manifest': readonly ["--output"];
18
+ readonly 'generate:client': readonly ["--output", "--check", "--force"];
18
19
  readonly login: readonly ["--cloud-url", "--environment"];
19
20
  readonly logout: readonly [];
20
21
  readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--no-source", "--endpoint", "--replace-restored", "--mcp-config"];
@@ -35,11 +36,12 @@ export declare const GLOBAL_FLAGS: readonly ["--no-telemetry", "--help", "--vers
35
36
  export declare const VALUE_FLAGS: {
36
37
  readonly init: readonly ["--path", "--style", "--database", "--chdb-path", "--auth", "--tables", "--exclude-tables"];
37
38
  readonly dev: readonly ["--port", "--hostname", "--cache", "--redis-url", "--path"];
38
- readonly mcp: readonly ["--path", "--tenant", "--url"];
39
+ readonly mcp: readonly ["--path", "--tenant", "--url", "--client-contract-url"];
39
40
  readonly generate: readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
40
41
  readonly 'generate:types': readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
41
42
  readonly 'generate:datasets': readonly ["--output", "--path", "--tables", "--exclude-tables", "--tenant-column"];
42
43
  readonly 'generate:manifest': readonly ["--output"];
44
+ readonly 'generate:client': readonly ["--output"];
43
45
  readonly login: readonly ["--cloud-url", "--environment"];
44
46
  readonly logout: readonly [];
45
47
  readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--endpoint"];
@@ -1 +1 @@
1
- {"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/domains.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,eAAO,MAAM,aAAa,sDAAuD,CAAC;AAClF,eAAO,MAAM,gBAAgB,gFAAiF,CAAC;AAC/G,eAAO,MAAM,WAAW,gWAKd,CAAC;AACX,eAAO,MAAM,QAAQ,6DAA8D,CAAC;AACpF,eAAO,MAAM,SAAS,wDAAyD,CAAC;AAChF,eAAO,MAAM,QAAQ,4SAKX,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEvD,6FAA6F;AAC7F,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;CAmBsC,CAAC;AACjE,eAAO,MAAM,YAAY,oDAAqD,CAAC;AAC/E,yFAAyF;AACzF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;CAmBoE,CAAC;AAC7F,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AACzG,eAAO,MAAM,WAAW;;;;;;CAAwG,CAAC;AACjI,eAAO,MAAM,kBAAkB,0NAA2N,CAAC;AAC3P,eAAO,MAAM,aAAa,4DAA6D,CAAC;AACxF,eAAO,MAAM,aAAa,4NAA6N,CAAC"}
1
+ {"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/domains.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,eAAO,MAAM,aAAa,sDAAuD,CAAC;AAClF,eAAO,MAAM,gBAAgB,gFAAiF,CAAC;AAC/G,eAAO,MAAM,WAAW,gWAKd,CAAC;AACX,eAAO,MAAM,QAAQ,6DAA8D,CAAC;AACpF,eAAO,MAAM,SAAS,wDAAyD,CAAC;AAChF,eAAO,MAAM,QAAQ,+TAKX,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEvD,6FAA6F;AAC7F,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;CAoBsC,CAAC;AACjE,eAAO,MAAM,YAAY,oDAAqD,CAAC;AAC/E,yFAAyF;AACzF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;CAoBoE,CAAC;AAC7F,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AACzG,eAAO,MAAM,WAAW;;;;;;CAAwG,CAAC;AACjI,eAAO,MAAM,kBAAkB,0NAA2N,CAAC;AAC3P,eAAO,MAAM,aAAa,4DAA6D,CAAC;AACxF,eAAO,MAAM,aAAa,4NAA6N,CAAC"}
@@ -11,7 +11,7 @@ export const OUTCOMES = ['success', 'failure', 'cancelled', 'interrupted'];
11
11
  export const DATABASES = ['clickhouse', 'chdb', 'bigquery', 'unknown'];
12
12
  export const COMMANDS = [
13
13
  'init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets',
14
- 'generate:manifest', 'login', 'logout', 'deploy', 'deployment:build',
14
+ 'generate:manifest', 'generate:client', 'login', 'logout', 'deploy', 'deployment:build',
15
15
  'deployment:validate', 'deployment:release', 'deployment:submit', 'deployment:status',
16
16
  'pull', 'diff', 'help', 'version', 'telemetry', 'unknown',
17
17
  ];
@@ -19,11 +19,12 @@ export const COMMANDS = [
19
19
  export const COMMAND_FLAGS = {
20
20
  init: ['--path', '--style', '--database', '--chdb-path', '--auth', '--all-tables', '--tables', '--exclude-tables', '--no-example', '--no-interactive', '--force', '--skip-connection'],
21
21
  dev: ['--port', '--hostname', '--no-watch', '--no-cache', '--cache', '--redis-url', '--open', '--cors', '--path', '--quiet'],
22
- mcp: ['--path', '--tenant', '--self-test', '--url'],
22
+ mcp: ['--path', '--tenant', '--self-test', '--url', '--client-contract-url'],
23
23
  generate: ['--output', '--path', '--tables', '--database', '--chdb-path'],
24
24
  'generate:types': ['--output', '--path', '--tables', '--database', '--chdb-path'],
25
25
  'generate:datasets': ['--output', '--path', '--tables', '--exclude-tables', '--tenant-column', '--force', '--check', '--diff'],
26
26
  'generate:manifest': ['--output'],
27
+ 'generate:client': ['--output', '--check', '--force'],
27
28
  login: ['--cloud-url', '--environment'],
28
29
  logout: [],
29
30
  deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--no-source', '--endpoint', '--replace-restored', '--mcp-config'],
@@ -41,11 +42,12 @@ export const GLOBAL_FLAGS = ['--no-telemetry', '--help', '--version'];
41
42
  export const VALUE_FLAGS = {
42
43
  init: ['--path', '--style', '--database', '--chdb-path', '--auth', '--tables', '--exclude-tables'],
43
44
  dev: ['--port', '--hostname', '--cache', '--redis-url', '--path'],
44
- mcp: ['--path', '--tenant', '--url'],
45
+ mcp: ['--path', '--tenant', '--url', '--client-contract-url'],
45
46
  generate: ['--output', '--path', '--tables', '--database', '--chdb-path'],
46
47
  'generate:types': ['--output', '--path', '--tables', '--database', '--chdb-path'],
47
48
  'generate:datasets': ['--output', '--path', '--tables', '--exclude-tables', '--tenant-column'],
48
49
  'generate:manifest': ['--output'],
50
+ 'generate:client': ['--output'],
49
51
  login: ['--cloud-url', '--environment'],
50
52
  logout: [],
51
53
  deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--endpoint'],
@@ -11,8 +11,8 @@ export class McpSession {
11
11
  }
12
12
  instrument(executor) {
13
13
  return {
14
- listTools: () => executor.listTools(), listPrompts: () => executor.listPrompts(),
15
- getPrompt: (name, args) => executor.getPrompt(name, args), getManifestHash: () => executor.getManifestHash(),
14
+ listTools: signal => executor.listTools(signal), listPrompts: signal => executor.listPrompts(signal),
15
+ getPrompt: (name, args, signal) => executor.getPrompt(name, args, signal), getManifestHash: () => executor.getManifestHash(),
16
16
  callTool: async (name, args, signal) => {
17
17
  if (Object.hasOwn(toolKinds, name))
18
18
  this.calls[toolKinds[name]]++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.24.1",
3
+ "version": "1.25.0",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -22,19 +22,19 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@clickhouse/client": "^1.24.0",
25
+ "@clickhouse/client": "^1.24.1",
26
26
  "chalk": "^5.3.0",
27
- "commander": "^12.0.0",
28
- "dotenv": "^16.4.7",
27
+ "commander": "^15.0.0",
28
+ "dotenv": "^18.0.8",
29
29
  "esbuild": "^0.28.2",
30
- "open": "^10.0.0",
30
+ "open": "^11.0.4",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/deployment": "0.9.6",
34
- "@hypequery/protocol": "0.20.0"
33
+ "@hypequery/deployment": "0.10.0",
34
+ "@hypequery/protocol": "0.21.0"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@napi-rs/keyring": "1.3.0"
37
+ "@napi-rs/keyring": "2.1.2"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@hypequery/mcp": "*",
@@ -43,16 +43,19 @@
43
43
  "peerDependenciesMeta": {
44
44
  "@hypequery/mcp": {
45
45
  "optional": true
46
+ },
47
+ "@hypequery/serve": {
48
+ "optional": true
46
49
  }
47
50
  },
48
51
  "devDependencies": {
49
52
  "@types/node": "^20.11.30",
50
53
  "@types/prompts": "^2.4.9",
51
- "@vitest/coverage-v8": "^5.0.2",
54
+ "@vitest/coverage-v8": "^5.0.5",
52
55
  "typescript": "^6.0.3",
53
- "vitest": "^5.0.2",
54
- "@hypequery/mcp": "0.9.0",
55
- "@hypequery/serve": "0.21.0"
56
+ "vitest": "^5.0.5",
57
+ "@hypequery/mcp": "0.10.0",
58
+ "@hypequery/serve": "0.22.0"
56
59
  },
57
60
  "repository": {
58
61
  "type": "git",