@hypequery/cli 1.18.3 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAmQD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAqRD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { Command } from 'commander';
4
4
  import { initCommand } from './commands/init.js';
5
+ import { mcpCommand } from './commands/mcp.js';
5
6
  import { devCommand } from './commands/dev.js';
6
7
  import { generateCommand } from './commands/generate.js';
7
8
  import { generateDatasetsCommand } from './commands/generate-datasets.js';
@@ -126,6 +127,15 @@ program
126
127
  .action(runCommand(async (file, options) => {
127
128
  await devCommand(file, options);
128
129
  }));
130
+ program
131
+ .command('mcp [file]')
132
+ .description('Serve this project\'s datasets to an MCP client over stdio')
133
+ .option('--path <path>', 'Analytics directory (loads <path>/api.ts or <path>/queries.ts)')
134
+ .option('--tenant <id>', 'Trusted tenant applied to every tenant-scoped dataset')
135
+ .option('--self-test', 'Check the entrypoint and exit without speaking MCP')
136
+ .action(runCommand(async (file, options) => {
137
+ await mcpCommand(file, options);
138
+ }));
129
139
  // Generate command
130
140
  addTypeGenerationOptions(program.command('generate'))
131
141
  .action(runCommand(async (options) => {
@@ -168,6 +178,7 @@ program
168
178
  .option('--entrypoint-prefix <prefix>', 'Runtime entrypoint prefix (default: queries)')
169
179
  .option('--hash-output <path>', 'Deployment identity sidecar path (default: <output>.sha256)')
170
180
  .option('--no-source', 'Exclude project source files from the deployment bundle')
181
+ .option('--allow-unsupported-config', 'Deploy even though some Serve config will not be honoured in Cloud')
171
182
  .action(runCommand(async (api, options) => {
172
183
  await buildDeploymentCommand(api, options);
173
184
  }));
@@ -206,6 +217,7 @@ program
206
217
  .option('--no-source', 'Exclude project source files from the deployment bundle')
207
218
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
208
219
  .option('--replace-restored', 'Intentionally replace a restored live release')
220
+ .option('--allow-unsupported-config', 'Deploy even though some Serve config will not be honoured in Cloud')
209
221
  .action(runCommand(async (source, options) => {
210
222
  await deployCommand(source, options);
211
223
  }));
@@ -13,6 +13,7 @@ export interface DeployOptions extends SubmitDeploymentOptions {
13
13
  bundleOutput?: string;
14
14
  releaseOutput?: string;
15
15
  source?: boolean;
16
+ allowUnsupportedConfig?: boolean;
16
17
  }
17
18
  export interface SubmitDeploymentDependencies {
18
19
  readonly env?: Readonly<Record<string, string | undefined>>;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACjD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA0EvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAqDvC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACjD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA0EvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAyDvC"}
@@ -189,7 +189,11 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
189
189
  // a missing or expired login only surfaces after a full bundle build.
190
190
  await resolveDeploymentCredential(options.endpoint, dependencies);
191
191
  try {
192
- await build(sourcePath, { bundleOutput: bundlePath, source: options.source });
192
+ await build(sourcePath, {
193
+ bundleOutput: bundlePath,
194
+ source: options.source,
195
+ allowUnsupportedConfig: options.allowUnsupportedConfig,
196
+ });
193
197
  }
194
198
  catch (error) {
195
199
  throw withRuntimeFlagHint(error);
@@ -10,6 +10,7 @@ export interface BuildDeploymentOptions {
10
10
  entrypointPrefix?: string;
11
11
  hashOutput?: string;
12
12
  source?: boolean;
13
+ allowUnsupportedConfig?: boolean;
13
14
  }
14
15
  export interface PrepareDeploymentReleaseOptions {
15
16
  project?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAI5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAsJrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA2ErC;AAiED,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,EAC7C,YAAY,GAAE,oCAAyC,GACtD,OAAO,CAAC,iCAAiC,CAAC,CAkE5C"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAgB7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAI5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CA0JrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA2ErC;AAiED,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,EAC7C,YAAY,GAAE,oCAAyC,GACtD,OAAO,CAAC,iCAAiC,CAAC,CAkE5C"}
@@ -7,6 +7,7 @@ import { buildNodeRuntimeArtifact, getDeploymentRuntimeEntrypoints, } from '../u
7
7
  import { captureDeploymentSourceSnapshot } from '../utils/deployment-source-snapshot.js';
8
8
  import { loadApiModule } from '../utils/load-api.js';
9
9
  import { logger } from '../utils/logger.js';
10
+ import { reportCloudDiagnostic } from '../utils/cloud-diagnostic.js';
10
11
  import { loadCloudCredential, } from '../utils/cloud-credential-store.js';
11
12
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
12
13
  const DEFAULT_BUNDLE_OUTPUT = 'analytics/hypequery-deployment';
@@ -103,7 +104,11 @@ export async function buildDeploymentCommand(apiPath, options = {}) {
103
104
  };
104
105
  }
105
106
  }
106
- const contract = api.deploymentContract(artifact ? { runtimeArtifact: artifact } : {});
107
+ const contract = api.deploymentContract({
108
+ ...(artifact ? { runtimeArtifact: artifact } : {}),
109
+ ...(options.allowUnsupportedConfig ? { allowUnsupportedConfig: true } : {}),
110
+ onCloudDiagnostic: diagnostic => reportCloudDiagnostic(diagnostic, options.allowUnsupportedConfig === true),
111
+ });
107
112
  const prepared = prepareProtocolDeploymentContract(contract);
108
113
  const { canonical, contract: validated, identity: digest } = prepared;
109
114
  if (bundleOutput !== undefined) {
@@ -0,0 +1,19 @@
1
+ import { type CloseableMcpServer } from '../utils/mcp-lifecycle.js';
2
+ export interface McpOptions {
3
+ /** Analytics directory, matching `hypequery dev --path`. */
4
+ path?: string;
5
+ /** Trusted tenant applied to every tenant-scoped dataset. */
6
+ tenant?: string;
7
+ /** Check the entrypoint and exit instead of speaking MCP over stdio. */
8
+ selfTest?: boolean;
9
+ }
10
+ export interface McpDependencies {
11
+ loadApi?: (file: string) => Promise<unknown>;
12
+ start?: (config: {
13
+ datasets: Record<string, unknown>;
14
+ analytics: unknown;
15
+ tenantId?: string;
16
+ }) => Promise<CloseableMcpServer>;
17
+ }
18
+ export declare function mcpCommand(file?: string, options?: McpOptions, dependencies?: McpDependencies): Promise<void>;
19
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQvF,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;CACpB;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;CACnC;AAyCD,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAgDf"}
@@ -0,0 +1,94 @@
1
+ import { runMcpUntilSignal } from '../utils/mcp-lifecycle.js';
2
+ import { routeConsoleOutputToStderr } from '../utils/mcp-console.js';
3
+ import { readServeMcpSource, tenantScopedDatasets } from '../utils/mcp-source.js';
4
+ import path from 'node:path';
5
+ import { findApiFileForPath, findQueriesFile } from '../utils/find-files.js';
6
+ import { loadApiModule } from '../utils/load-api.js';
7
+ import { logger } from '../utils/logger.js';
8
+ function entrypointNotFound() {
9
+ logger.error('Could not find a hypequery API file to serve over MCP');
10
+ logger.newline();
11
+ logger.info('Expected one of:');
12
+ for (const candidate of [
13
+ 'hypequery.ts', 'analytics/api.ts', 'src/analytics/api.ts',
14
+ 'api.ts', 'src/api.ts', 'analytics/queries.ts', 'src/analytics/queries.ts',
15
+ ]) {
16
+ logger.indent(`• ${candidate}`);
17
+ }
18
+ logger.newline();
19
+ logger.info('Or specify the file explicitly:');
20
+ logger.indent('hypequery mcp ./path/to/api.ts');
21
+ logger.newline();
22
+ process.exit(1);
23
+ }
24
+ function noDatasets(file) {
25
+ logger.error(`${path.relative(process.cwd(), file)} does not register any datasets`);
26
+ logger.newline();
27
+ logger.info('MCP exposes datasets and named metrics, so add them to defineServe:');
28
+ logger.indent('defineServe({ queryBuilder: db, datasets: { orders: Orders } })');
29
+ logger.newline();
30
+ process.exit(1);
31
+ }
32
+ async function resolveEntrypoint(file, options) {
33
+ const resolved = file
34
+ ? await findQueriesFile(file)
35
+ : options.path
36
+ ? await findApiFileForPath(options.path)
37
+ : await findQueriesFile();
38
+ return resolved ?? entrypointNotFound();
39
+ }
40
+ function resolveSource(api, file) {
41
+ return readServeMcpSource(api) ?? noDatasets(file);
42
+ }
43
+ export async function mcpCommand(file, options = {}, dependencies = {}) {
44
+ const restoreConsole = routeConsoleOutputToStderr();
45
+ try {
46
+ const entrypoint = await resolveEntrypoint(file, options);
47
+ const loadApi = dependencies.loadApi ?? loadApiModule;
48
+ const source = resolveSource(await loadApi(entrypoint), entrypoint);
49
+ const datasets = source.datasets;
50
+ const names = Object.keys(datasets).sort();
51
+ if (names.length === 0)
52
+ noDatasets(entrypoint);
53
+ const scoped = tenantScopedDatasets(datasets);
54
+ if (scoped.length > 0 && !options.tenant) {
55
+ // Fail closed rather than serving a tenant-scoped dataset unscoped.
56
+ logger.error(`--tenant is required for tenant-scoped datasets: ${scoped.join(', ')}`);
57
+ logger.newline();
58
+ logger.info('MCP has no request to resolve a tenant from, so it must be given one:');
59
+ logger.indent('hypequery mcp --tenant acme');
60
+ logger.newline();
61
+ process.exit(1);
62
+ }
63
+ const analytics = source.resolveAnalytics();
64
+ if (options.selfTest) {
65
+ logger.success(`Loaded ${path.relative(process.cwd(), entrypoint)}`);
66
+ logger.info(`Datasets: ${names.join(', ')}`);
67
+ logger.info(options.tenant
68
+ ? `Trusted tenant: ${options.tenant}`
69
+ : 'Trusted tenant: none (no dataset is tenant-scoped)');
70
+ logger.newline();
71
+ logger.info('The entrypoint is ready to serve over MCP.');
72
+ return;
73
+ }
74
+ const start = dependencies.start ?? defaultStart;
75
+ await runMcpUntilSignal(async () => {
76
+ const server = await start({
77
+ datasets,
78
+ analytics,
79
+ ...(options.tenant ? { tenantId: options.tenant } : {}),
80
+ });
81
+ process.stderr.write(`hypequery MCP serving ${names.length} dataset(s): ${names.join(', ')}\n`);
82
+ return server;
83
+ });
84
+ }
85
+ finally {
86
+ restoreConsole();
87
+ }
88
+ }
89
+ async function defaultStart(config) {
90
+ // Imported lazily so `--self-test` and the argument errors above do not pay
91
+ // for the MCP SDK, and so the CLI still loads when it is not installed.
92
+ const { startStdioMCPServer } = await import('@hypequery/mcp');
93
+ return startStdioMCPServer(config);
94
+ }
@@ -0,0 +1,10 @@
1
+ export interface CloudCompatibilityDiagnosticLike {
2
+ readonly severity: 'error' | 'warning';
3
+ readonly code: string;
4
+ readonly subject: string;
5
+ readonly message: string;
6
+ readonly remedy: string;
7
+ }
8
+ /** Report warnings and explicitly overridden errors before submitting a release. */
9
+ export declare function reportCloudDiagnostic(diagnostic: CloudCompatibilityDiagnosticLike, allowUnsupportedConfig: boolean): void;
10
+ //# sourceMappingURL=cloud-diagnostic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-diagnostic.d.ts","sourceRoot":"","sources":["../../src/utils/cloud-diagnostic.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,gCAAgC,EAC5C,sBAAsB,EAAE,OAAO,GAC9B,IAAI,CAMN"}
@@ -0,0 +1,10 @@
1
+ import { logger } from './logger.js';
2
+ /** Report warnings and explicitly overridden errors before submitting a release. */
3
+ export function reportCloudDiagnostic(diagnostic, allowUnsupportedConfig) {
4
+ // Blocking errors are reported by the contract builder's exception.
5
+ if (diagnostic.severity === 'error' && !allowUnsupportedConfig)
6
+ return;
7
+ logger.warn(`${diagnostic.code} (${diagnostic.subject})`);
8
+ logger.indent(diagnostic.message);
9
+ logger.indent(`→ ${diagnostic.remedy}`);
10
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP speaks its protocol over stdout, so anything an application logs while
3
+ * loading would corrupt the stream. Route it to stderr before importing the
4
+ * entrypoint, not after.
5
+ */
6
+ export declare function routeConsoleOutputToStderr(): () => void;
7
+ //# sourceMappingURL=mcp-console.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-console.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-console.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,IAAI,CAavD"}
@@ -0,0 +1,20 @@
1
+ import { format } from 'node:util';
2
+ /**
3
+ * MCP speaks its protocol over stdout, so anything an application logs while
4
+ * loading would corrupt the stream. Route it to stderr before importing the
5
+ * entrypoint, not after.
6
+ */
7
+ export function routeConsoleOutputToStderr() {
8
+ const original = {
9
+ log: console.log,
10
+ info: console.info,
11
+ debug: console.debug,
12
+ };
13
+ const write = (...args) => {
14
+ process.stderr.write(`${format(...args)}\n`);
15
+ };
16
+ console.log = write;
17
+ console.info = write;
18
+ console.debug = write;
19
+ return () => Object.assign(console, original);
20
+ }
@@ -0,0 +1,6 @@
1
+ export interface CloseableMcpServer {
2
+ close(): Promise<void>;
3
+ }
4
+ /** Own signal listeners for exactly the lifetime of the stdio server. */
5
+ export declare function runMcpUntilSignal(start: () => Promise<CloseableMcpServer>): Promise<void>;
6
+ //# sourceMappingURL=mcp-lifecycle.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,22 @@
1
+ /** Own signal listeners for exactly the lifetime of the stdio server. */
2
+ export async function runMcpUntilSignal(start) {
3
+ let stop;
4
+ const stopped = new Promise(resolve => { stop = resolve; });
5
+ // Install before awaiting startup so a signal during connection is retained.
6
+ process.on('SIGINT', stop);
7
+ process.on('SIGTERM', stop);
8
+ let server;
9
+ try {
10
+ server = await start();
11
+ await stopped;
12
+ }
13
+ finally {
14
+ try {
15
+ await server?.close();
16
+ }
17
+ finally {
18
+ process.removeListener('SIGINT', stop);
19
+ process.removeListener('SIGTERM', stop);
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ export interface ServeMcpSource {
2
+ readonly version: 1;
3
+ readonly datasets: Readonly<Record<string, unknown>>;
4
+ readonly resolveAnalytics: () => unknown;
5
+ }
6
+ export declare function readServeMcpSource(value: unknown): ServeMcpSource | undefined;
7
+ /** Datasets a caller cannot query unless a trusted tenant is configured. */
8
+ export declare function tenantScopedDatasets(datasets: Record<string, unknown>): string[];
9
+ //# sourceMappingURL=mcp-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-source.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-source.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAU7E;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAShF"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Mirrors `@hypequery/serve`'s `ServeMcpSource`. Read through the global symbol
3
+ * registry rather than by importing serve: serve is a peer dependency that
4
+ * lives in the user's project, and a static import here would be resolved by
5
+ * Node before any CLI code runs, breaking even `hypequery --help` on a clean
6
+ * install. Keep the key in step with `server/mcp-source.ts`.
7
+ */
8
+ const MCP_SOURCE_SYMBOL = Symbol.for('hypequery.mcp-source.v1');
9
+ export function readServeMcpSource(value) {
10
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
11
+ return undefined;
12
+ }
13
+ const source = value[MCP_SOURCE_SYMBOL];
14
+ return source?.version === 1 && typeof source.resolveAnalytics === 'function'
15
+ ? source
16
+ : undefined;
17
+ }
18
+ /** Datasets a caller cannot query unless a trusted tenant is configured. */
19
+ export function tenantScopedDatasets(datasets) {
20
+ return Object.entries(datasets)
21
+ .filter(([, dataset]) => {
22
+ const value = dataset;
23
+ const tenantKey = value?.tenantKey ?? value?.config?.tenantKey;
24
+ return typeof tenantKey === 'string' && tenantKey.length > 0;
25
+ })
26
+ .map(([name]) => name)
27
+ .sort();
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.18.3",
3
+ "version": "1.19.0",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -30,22 +30,29 @@
30
30
  "open": "^10.0.0",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/protocol": "0.11.1",
34
- "@hypequery/deployment": "0.7.4"
33
+ "@hypequery/deployment": "0.7.5",
34
+ "@hypequery/protocol": "0.12.0"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "1.3.0"
38
38
  },
39
39
  "peerDependencies": {
40
+ "@hypequery/mcp": "*",
40
41
  "@hypequery/serve": "*"
41
42
  },
43
+ "peerDependenciesMeta": {
44
+ "@hypequery/mcp": {
45
+ "optional": true
46
+ }
47
+ },
42
48
  "devDependencies": {
43
49
  "@types/node": "^20.11.30",
44
50
  "@types/prompts": "^2.4.9",
45
51
  "@vitest/coverage-v8": "^3.2.6",
46
52
  "typescript": "^5.7.3",
47
53
  "vitest": "^3.2.6",
48
- "@hypequery/serve": "0.16.2"
54
+ "@hypequery/mcp": "0.6.0",
55
+ "@hypequery/serve": "0.17.0"
49
56
  },
50
57
  "repository": {
51
58
  "type": "git",