@hypequery/mcp 0.8.6 → 0.10.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 (52) hide show
  1. package/README.md +70 -0
  2. package/dist/executor.d.ts +3 -3
  3. package/dist/executor.d.ts.map +1 -1
  4. package/dist/http-executor.d.ts +40 -0
  5. package/dist/http-executor.d.ts.map +1 -0
  6. package/dist/http-executor.js +178 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/protocol-server.js +3 -3
  11. package/dist/tools/args.d.ts +65 -123
  12. package/dist/tools/args.d.ts.map +1 -1
  13. package/dist/tools/args.js +3 -1
  14. package/dist/tools/query-dataset.d.ts.map +1 -1
  15. package/dist/tools/query-dataset.js +4 -1
  16. package/dist/tools/tool-manifest.d.ts +1 -1
  17. package/dist/tools/tool-manifest.d.ts.map +1 -1
  18. package/dist/tools/tool-manifest.js +1 -47
  19. package/dist/tools/utils/canonical-query-schemas.d.ts +10 -2
  20. package/dist/tools/utils/canonical-query-schemas.d.ts.map +1 -1
  21. package/dist/tools/utils/canonical-query-schemas.js +10 -3
  22. package/dist/tools/utils/result-schema.d.ts +5 -0
  23. package/dist/tools/utils/result-schema.d.ts.map +1 -0
  24. package/dist/tools/utils/result-schema.js +47 -0
  25. package/dist/utils/http-client/arguments.d.ts +3 -0
  26. package/dist/utils/http-client/arguments.d.ts.map +1 -0
  27. package/dist/utils/http-client/arguments.js +26 -0
  28. package/dist/utils/http-client/discovery.d.ts +6 -0
  29. package/dist/utils/http-client/discovery.d.ts.map +1 -0
  30. package/dist/utils/http-client/discovery.js +24 -0
  31. package/dist/utils/http-client/json.d.ts +6 -0
  32. package/dist/utils/http-client/json.d.ts.map +1 -0
  33. package/dist/utils/http-client/json.js +79 -0
  34. package/dist/utils/http-client/manifest.d.ts +5 -0
  35. package/dist/utils/http-client/manifest.d.ts.map +1 -0
  36. package/dist/utils/http-client/manifest.js +53 -0
  37. package/dist/utils/http-client/profile.d.ts +5 -0
  38. package/dist/utils/http-client/profile.d.ts.map +1 -0
  39. package/dist/utils/http-client/profile.js +29 -0
  40. package/dist/utils/http-client/result.d.ts +4 -0
  41. package/dist/utils/http-client/result.d.ts.map +1 -0
  42. package/dist/utils/http-client/result.js +98 -0
  43. package/dist/utils/http-client/transport.d.ts +12 -0
  44. package/dist/utils/http-client/transport.d.ts.map +1 -0
  45. package/dist/utils/http-client/transport.js +88 -0
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.d.ts.map +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +14 -8
  50. package/dist/api.type-test.d.ts +0 -2
  51. package/dist/api.type-test.d.ts.map +0 -1
  52. package/dist/api.type-test.js +0 -31
@@ -1,4 +1,5 @@
1
1
  import { SUPPORTED_TIME_GRAINS } from '@hypequery/datasets';
2
+ import { resultSchema } from './utils/result-schema.js';
2
3
  const semanticMetadataProperties = {
3
4
  examples: { type: 'array', items: { type: 'string' } },
4
5
  synonyms: { type: 'array', items: { type: 'string' } },
@@ -10,53 +11,6 @@ const semanticMetadataProperties = {
10
11
  enum: ['public', 'internal', 'confidential', 'restricted'],
11
12
  },
12
13
  };
13
- const errorEnvelopeSchema = {
14
- type: 'object',
15
- additionalProperties: false,
16
- properties: {
17
- error: {
18
- type: 'object',
19
- additionalProperties: false,
20
- properties: {
21
- code: {
22
- type: 'string',
23
- enum: [
24
- 'MCP_INVALID_ARGUMENTS',
25
- 'MCP_NOT_FOUND',
26
- 'MCP_UNKNOWN_TOOL',
27
- 'MCP_UNAUTHORIZED',
28
- 'MCP_STALE_CONTRACT',
29
- 'MCP_REQUEST_CANCELLED',
30
- 'MCP_QUERY_TIMEOUT',
31
- 'MCP_RESULT_TOO_LARGE',
32
- 'MCP_EXECUTION_FAILED',
33
- ],
34
- },
35
- message: { type: 'string' },
36
- category: {
37
- type: 'string',
38
- enum: [
39
- 'correctable_input',
40
- 'unauthorized',
41
- 'stale_contract',
42
- 'budget',
43
- 'internal',
44
- ],
45
- },
46
- retryable: { type: 'boolean' },
47
- correctable: { type: 'boolean' },
48
- },
49
- required: ['code', 'category', 'message', 'retryable', 'correctable'],
50
- },
51
- },
52
- required: ['error'],
53
- };
54
- function resultSchema(successSchema) {
55
- return {
56
- type: 'object',
57
- oneOf: [successSchema, errorEnvelopeSchema],
58
- };
59
- }
60
14
  export const DATASET_LIST_OUTPUT_SCHEMA = resultSchema({
61
15
  type: 'object',
62
16
  additionalProperties: false,
@@ -1,4 +1,4 @@
1
- import { type CanonicalSemanticQuerySchemas } from '@hypequery/datasets';
1
+ import { type CanonicalSemanticQuerySchemas } from '@hypequery/datasets/schemas';
2
2
  import type { DatasetRegistry, MCPQueryLimits } from '../../types.js';
3
3
  /**
4
4
  * Compile exact schemas for real Dataset registries. The shipped loose registry
@@ -13,5 +13,13 @@ export declare function buildMCPQuerySchemas(datasets: DatasetRegistry, configur
13
13
  * withheld as a direct target, which a hosted gateway needs when a caller is
14
14
  * entitled to a metric on a dataset it may not query itself.
15
15
  */
16
- queryableDatasets?: readonly string[]): CanonicalSemanticQuerySchemas;
16
+ queryableDatasets?: readonly string[],
17
+ /**
18
+ * Advertise and accept `having` on `query_dataset`. On by default for every
19
+ * MCP surface: CORE-03 requires the hosted gateway to list exactly what the
20
+ * local executor runs, so hosted execution must support it too.
21
+ */
22
+ features?: {
23
+ readonly having?: boolean;
24
+ }): CanonicalSemanticQuerySchemas;
17
25
  //# sourceMappingURL=canonical-query-schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-query-schemas.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/canonical-query-schemas.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,6BAA6B,EAEnC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA0BtE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,EAAE,cAAc;AAC3B;;;;;;GAMG;AACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,GACpC,6BAA6B,CAqG/B"}
1
+ {"version":3,"file":"canonical-query-schemas.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/canonical-query-schemas.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA0BtE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,EAAE,cAAc;AAC3B;;;;;;GAMG;AACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE;AACrC;;;;GAIG;AACH,QAAQ,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAqB,GACzD,6BAA6B,CAsG/B"}
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { buildCanonicalSemanticQuerySchemas, toSemanticJsonSchema, } from '@hypequery/datasets';
2
+ import { buildCanonicalSemanticQuerySchemas, toSemanticJsonSchema, } from '@hypequery/datasets/schemas';
3
3
  import { z } from 'zod';
4
4
  import { queryDatasetArgsSchema, queryMetricArgsSchema } from '../args.js';
5
5
  import { resolveQueryLimits } from './query-limits.js';
@@ -34,7 +34,13 @@ export function buildMCPQuerySchemas(datasets, configured,
34
34
  * withheld as a direct target, which a hosted gateway needs when a caller is
35
35
  * entitled to a metric on a dataset it may not query itself.
36
36
  */
37
- queryableDatasets) {
37
+ queryableDatasets,
38
+ /**
39
+ * Advertise and accept `having` on `query_dataset`. On by default for every
40
+ * MCP surface: CORE-03 requires the hosted gateway to list exactly what the
41
+ * local executor runs, so hosted execution must support it too.
42
+ */
43
+ features = { having: true }) {
38
44
  const limits = resolveQueryLimits(undefined, configured);
39
45
  const queryable = queryableDatasets === undefined ? undefined : new Set(queryableDatasets);
40
46
  const entries = Object.entries(datasets).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
@@ -58,6 +64,7 @@ queryableDatasets) {
58
64
  grainField: 'grain',
59
65
  ...limits,
60
66
  ...(queryableDatasets === undefined ? {} : { queryableDatasets }),
67
+ ...(features.having ? { having: true } : {}),
61
68
  });
62
69
  }
63
70
  const datasetSchemas = [];
@@ -66,7 +73,7 @@ queryableDatasets) {
66
73
  const metricJsonSchemas = [];
67
74
  for (const [name, dataset] of entries) {
68
75
  if (isCanonicalSchemaSource(dataset)) {
69
- const exact = buildCanonicalSemanticQuerySchemas(withLegacyDirectMetrics({ [name]: dataset }), { grainField: 'grain', ...limits });
76
+ const exact = buildCanonicalSemanticQuerySchemas(withLegacyDirectMetrics({ [name]: dataset }), { grainField: 'grain', ...limits, ...(features.having ? { having: true } : {}) });
70
77
  if (queryable === undefined || queryable.has(name)) {
71
78
  datasetSchemas.push(exact.queryDataset);
72
79
  datasetJsonSchemas.push(exact.queryDatasetJsonSchema);
@@ -0,0 +1,5 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ type ObjectSchema = Tool['outputSchema'] & Record<string, unknown>;
3
+ export declare function resultSchema(successSchema: Record<string, unknown>): ObjectSchema;
4
+ export {};
5
+ //# sourceMappingURL=result-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-schema.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/result-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D,KAAK,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA4CnE,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAKjF"}
@@ -0,0 +1,47 @@
1
+ const errorEnvelopeSchema = {
2
+ type: 'object',
3
+ additionalProperties: false,
4
+ properties: {
5
+ error: {
6
+ type: 'object',
7
+ additionalProperties: false,
8
+ properties: {
9
+ code: {
10
+ type: 'string',
11
+ enum: [
12
+ 'MCP_INVALID_ARGUMENTS',
13
+ 'MCP_NOT_FOUND',
14
+ 'MCP_UNKNOWN_TOOL',
15
+ 'MCP_UNAUTHORIZED',
16
+ 'MCP_STALE_CONTRACT',
17
+ 'MCP_REQUEST_CANCELLED',
18
+ 'MCP_QUERY_TIMEOUT',
19
+ 'MCP_RESULT_TOO_LARGE',
20
+ 'MCP_EXECUTION_FAILED',
21
+ ],
22
+ },
23
+ message: { type: 'string' },
24
+ category: {
25
+ type: 'string',
26
+ enum: [
27
+ 'correctable_input',
28
+ 'unauthorized',
29
+ 'stale_contract',
30
+ 'budget',
31
+ 'internal',
32
+ ],
33
+ },
34
+ retryable: { type: 'boolean' },
35
+ correctable: { type: 'boolean' },
36
+ },
37
+ required: ['code', 'category', 'message', 'retryable', 'correctable'],
38
+ },
39
+ },
40
+ required: ['error'],
41
+ };
42
+ export function resultSchema(successSchema) {
43
+ return {
44
+ type: 'object',
45
+ oneOf: [successSchema, errorEnvelopeSchema],
46
+ };
47
+ }
@@ -0,0 +1,3 @@
1
+ /** Read only own data properties. Nested query values go through the shared bounded validator. */
2
+ export declare function httpToolArguments(input: unknown, allowed: readonly string[]): Record<string, unknown>;
3
+ //# sourceMappingURL=arguments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/arguments.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBrG"}
@@ -0,0 +1,26 @@
1
+ import { MCPToolError } from '../../errors.js';
2
+ /** Read only own data properties. Nested query values go through the shared bounded validator. */
3
+ export function httpToolArguments(input, allowed) {
4
+ try {
5
+ const value = input === undefined ? {} : input;
6
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
7
+ throw new Error();
8
+ const prototype = Object.getPrototypeOf(value);
9
+ if (prototype !== Object.prototype && prototype !== null)
10
+ throw new Error();
11
+ const keys = Reflect.ownKeys(value);
12
+ if (keys.length > allowed.length || keys.some(key => typeof key !== 'string' || !allowed.includes(key)))
13
+ throw new Error();
14
+ const result = Object.create(null);
15
+ for (const key of keys) {
16
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
17
+ if (!descriptor?.enumerable || !('value' in descriptor))
18
+ throw new Error();
19
+ result[key] = descriptor.value;
20
+ }
21
+ return result;
22
+ }
23
+ catch {
24
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'Invalid client tool arguments.');
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ import type { ProtocolClientContract } from '@hypequery/protocol';
2
+ export declare function clientDatasetNames(contract: ProtocolClientContract): string[];
3
+ /** Preserve endpoint-specific permissions instead of merging them into a synthetic dataset. */
4
+ export declare function clientDatasetProfile(contract: ProtocolClientContract, dataset: string): Record<string, unknown>;
5
+ export declare function clientDatasetList(contract: ProtocolClientContract): Record<string, unknown>;
6
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,EAAE,CAE7E;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAM/G;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAY3F"}
@@ -0,0 +1,24 @@
1
+ export function clientDatasetNames(contract) {
2
+ return [...new Set(contract.endpoints.map(endpoint => endpoint.dataset))].sort();
3
+ }
4
+ /** Preserve endpoint-specific permissions instead of merging them into a synthetic dataset. */
5
+ export function clientDatasetProfile(contract, dataset) {
6
+ const endpoints = contract.endpoints.filter(endpoint => endpoint.dataset === dataset);
7
+ return {
8
+ name: dataset, queryable: endpoints.some(endpoint => endpoint.kind === 'dataset'),
9
+ endpoints: endpoints.map(({ path: _path, schemaHash: _hash, ...profile }) => profile),
10
+ };
11
+ }
12
+ export function clientDatasetList(contract) {
13
+ const datasets = clientDatasetNames(contract).map(name => {
14
+ const endpoints = contract.endpoints.filter(endpoint => endpoint.dataset === name);
15
+ return {
16
+ name, description: 'Authorized client endpoints.',
17
+ queryable: endpoints.some(endpoint => endpoint.kind === 'dataset'),
18
+ dimensionCount: new Set(endpoints.flatMap(endpoint => Object.keys(endpoint.dimensions))).size,
19
+ measureCount: new Set(endpoints.flatMap(endpoint => Object.keys(endpoint.measures))).size,
20
+ metricCount: endpoints.filter(endpoint => endpoint.kind === 'metric').length,
21
+ };
22
+ });
23
+ return { datasets, total: datasets.length };
24
+ }
@@ -0,0 +1,6 @@
1
+ export declare class HttpResultJsonBudgetError extends Error {
2
+ constructor();
3
+ }
4
+ /** Budget/duplicate scan before native JSON parsing. Scalars retain HTTP binary64 semantics, including -0. */
5
+ export declare function decodeHttpResultJson(text: string): unknown;
6
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/json.ts"],"names":[],"mappings":"AAAA,qBAAa,yBAA0B,SAAQ,KAAK;;CAEnD;AAED,8GAA8G;AAC9G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAoD1D"}
@@ -0,0 +1,79 @@
1
+ export class HttpResultJsonBudgetError extends Error {
2
+ constructor() { super('HTTP result JSON exceeds validation limits.'); }
3
+ }
4
+ /** Budget/duplicate scan before native JSON parsing. Scalars retain HTTP binary64 semantics, including -0. */
5
+ export function decodeHttpResultJson(text) {
6
+ if (Buffer.byteLength(text) > 1_048_576)
7
+ throw new HttpResultJsonBudgetError();
8
+ const stack = [];
9
+ let nodes = 0;
10
+ let position = 0;
11
+ const value = () => {
12
+ if (++nodes > 10_000)
13
+ throw new HttpResultJsonBudgetError();
14
+ const parent = stack[stack.length - 1];
15
+ if (parent && !parent.object && ++parent.count > 1000)
16
+ throw new HttpResultJsonBudgetError();
17
+ };
18
+ while (position < text.length) {
19
+ const character = text[position];
20
+ if (' \t\r\n:'.includes(character)) {
21
+ position++;
22
+ continue;
23
+ }
24
+ if (character === ',') {
25
+ const parent = stack[stack.length - 1];
26
+ if (parent?.object)
27
+ parent.key = true;
28
+ position++;
29
+ continue;
30
+ }
31
+ if (character === '{' || character === '[') {
32
+ value();
33
+ if (stack.length >= 16)
34
+ throw new HttpResultJsonBudgetError();
35
+ stack.push({ object: character === '{', keys: new Set(), key: character === '{', count: 0 });
36
+ position++;
37
+ continue;
38
+ }
39
+ if (character === '}' || character === ']') {
40
+ stack.pop();
41
+ position++;
42
+ continue;
43
+ }
44
+ if (character === '"') {
45
+ const start = position++;
46
+ let closed = false;
47
+ while (position < text.length) {
48
+ const current = text[position++];
49
+ if (current === '\\')
50
+ position++;
51
+ else if (current === '"') {
52
+ closed = true;
53
+ break;
54
+ }
55
+ }
56
+ if (!closed)
57
+ throw new Error('Invalid JSON string');
58
+ const parent = stack[stack.length - 1];
59
+ if (parent?.object && parent.key) {
60
+ const key = JSON.parse(text.slice(start, position));
61
+ if (parent.keys.has(key))
62
+ throw new Error('Duplicate JSON member');
63
+ parent.keys.add(key);
64
+ if (parent.keys.size > 1000)
65
+ throw new HttpResultJsonBudgetError();
66
+ parent.key = false;
67
+ }
68
+ else
69
+ value();
70
+ continue;
71
+ }
72
+ value();
73
+ position++;
74
+ while (position < text.length && !' \t\r\n,]}:{["'.includes(text[position]))
75
+ position++;
76
+ }
77
+ // Native parsing owns JSON grammar validation; the scan only adds budgets and duplicate refusal.
78
+ return JSON.parse(text);
79
+ }
@@ -0,0 +1,5 @@
1
+ import type { ClientContractQuerySchemas } from '@hypequery/datasets';
2
+ import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
3
+ /** Fixed tool names, canonical client input schemas, and the actual public HTTP result envelope. */
4
+ export declare function clientHttpManifest(schemas: ClientContractQuerySchemas, authorizedHash: string): ListToolsResult;
5
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAkC1E,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,0BAA0B,EAAE,cAAc,EAAE,MAAM,GAAG,eAAe,CAiB/G"}
@@ -0,0 +1,53 @@
1
+ import { buildMCPToolManifest } from '../../tools/tool-manifest.js';
2
+ import { resultSchema } from '../../tools/utils/result-schema.js';
3
+ const profileOutput = resultSchema({
4
+ type: 'object', additionalProperties: false, required: ['name', 'queryable', 'endpoints'],
5
+ properties: { name: { type: 'string' }, queryable: { type: 'boolean' }, endpoints: { type: 'array', items: { type: 'object' } } },
6
+ });
7
+ const listOutput = resultSchema({
8
+ type: 'object', additionalProperties: false, required: ['datasets', 'total'],
9
+ properties: {
10
+ datasets: { type: 'array', items: { type: 'object', additionalProperties: false,
11
+ required: ['name', 'description', 'queryable', 'dimensionCount', 'measureCount', 'metricCount'],
12
+ properties: { name: { type: 'string' }, description: { type: 'string' }, queryable: { type: 'boolean' },
13
+ dimensionCount: { type: 'integer', minimum: 0 }, measureCount: { type: 'integer', minimum: 0 }, metricCount: { type: 'integer', minimum: 0 } },
14
+ } }, total: { type: 'integer', minimum: 0 },
15
+ },
16
+ });
17
+ const queryOutput = resultSchema({
18
+ type: 'object', additionalProperties: false, required: ['data'],
19
+ properties: {
20
+ data: { type: 'array', maxItems: 1000, items: { type: 'object', additionalProperties: { type: ['string', 'number', 'boolean', 'null'] } } },
21
+ meta: { type: ['object', 'null'], additionalProperties: false,
22
+ required: ['requestId', 'timingMs', 'rowCount', 'pagination'],
23
+ properties: {
24
+ requestId: { type: 'string' }, timingMs: { type: 'number', minimum: 0 }, rowCount: { type: 'integer', minimum: 0 },
25
+ pagination: { type: 'object', additionalProperties: false, required: ['limit', 'offset', 'hasMore'],
26
+ properties: { limit: { type: 'integer', minimum: 1 }, offset: { type: 'integer', minimum: 0 }, hasMore: { type: 'boolean' } } },
27
+ cache: { type: 'object', additionalProperties: false, required: ['hit'], properties: { hit: { type: 'boolean' } } },
28
+ },
29
+ },
30
+ },
31
+ });
32
+ /** Fixed tool names, canonical client input schemas, and the actual public HTTP result envelope. */
33
+ export function clientHttpManifest(schemas, authorizedHash) {
34
+ const manifest = buildMCPToolManifest(schemas);
35
+ for (const tool of manifest.tools) {
36
+ if (tool.name === 'get_dataset_schema') {
37
+ tool.description = 'Inspect authorized endpoint profiles for a dataset group. Each endpoint retains its own permissions and defaults.';
38
+ tool.inputSchema = { type: 'object', additionalProperties: false, properties: { dataset: { type: 'string' } }, required: ['dataset'] };
39
+ tool.outputSchema = profileOutput;
40
+ }
41
+ else if (tool.name === 'list_datasets') {
42
+ tool.description = 'List authorized dataset groups, including metric-only groups whose queryable flag is false.';
43
+ tool.inputSchema = { type: 'object', additionalProperties: false, properties: {} };
44
+ tool.outputSchema = listOutput;
45
+ }
46
+ else
47
+ tool.outputSchema = queryOutput;
48
+ }
49
+ return { ...manifest, _meta: {
50
+ 'com.hypequery/clientContractHash': authorizedHash,
51
+ 'com.hypequery/manifestHash': schemas.manifestHash,
52
+ } };
53
+ }
@@ -0,0 +1,5 @@
1
+ import { type ProtocolClientContract } from '@hypequery/protocol';
2
+ import type { MCPQueryLimits } from '../../types.js';
3
+ /** Lower operational limits only; the backend's endpoint schema hashes stay identical. */
4
+ export declare function mcpClientProfile(contract: ProtocolClientContract, configured?: MCPQueryLimits): ProtocolClientContract;
5
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,EAAE,UAAU,CAAC,EAAE,cAAc,GAAG,sBAAsB,CAuBtH"}
@@ -0,0 +1,29 @@
1
+ import { prepareProtocolClientContract } from '@hypequery/protocol';
2
+ import { resolveQueryLimits } from '../../tools/utils/query-limits.js';
3
+ import { MCPToolError } from '../../errors.js';
4
+ /** Lower operational limits only; the backend's endpoint schema hashes stay identical. */
5
+ export function mcpClientProfile(contract, configured) {
6
+ const limits = resolveQueryLimits(undefined, configured);
7
+ try {
8
+ return prepareProtocolClientContract({
9
+ kind: contract.kind, version: contract.version, view: contract.view,
10
+ endpoints: contract.endpoints.map(({ schemaHash: _hash, ...endpoint }) => ({
11
+ ...endpoint,
12
+ limits: {
13
+ ...endpoint.limits,
14
+ maxLimit: Math.min(endpoint.limits.maxLimit, limits.maxResultSize, 1000),
15
+ defaultLimit: Math.min(endpoint.limits.defaultLimit, limits.defaultResultSize, limits.maxResultSize, 1000),
16
+ maxOffset: Math.min(endpoint.limits.maxOffset, limits.maxOffset),
17
+ maxDimensions: Math.min(endpoint.limits.maxDimensions, limits.maxDimensions),
18
+ maxMeasures: Math.min(endpoint.limits.maxMeasures, limits.maxMeasures),
19
+ maxPredicates: Math.min(endpoint.limits.maxPredicates, limits.maxFilters),
20
+ maxOrderBy: Math.min(endpoint.limits.maxOrderBy, limits.maxOrderBy),
21
+ maxFilterValues: Math.min(endpoint.limits.maxFilterValues, 1000),
22
+ },
23
+ })),
24
+ }).contract;
25
+ }
26
+ catch {
27
+ throw new MCPToolError('MCP_CATALOG_TOO_LARGE', 'Client defaults exceed MCP query ceilings; publish a narrower profile.', { retryable: false });
28
+ }
29
+ }
@@ -0,0 +1,4 @@
1
+ import type { ClientQueryPlan } from '@hypequery/protocol';
2
+ /** Duplicate-aware bounded JSON, closed public metadata and exact requested row fields. */
3
+ export declare function clientHttpResult(text: string, plan: ClientQueryPlan): Record<string, unknown>;
4
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA2B3D,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2C7F"}
@@ -0,0 +1,98 @@
1
+ import { MCPExecutionBudgetError, MCPToolError } from '../../errors.js';
2
+ import { decodeHttpResultJson, HttpResultJsonBudgetError } from './json.js';
3
+ function invalid() {
4
+ throw new MCPToolError('MCP_EXECUTION_FAILED', 'Backend returned an invalid client result.', { retryable: false });
5
+ }
6
+ function record(value, allowed) {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value))
8
+ return invalid();
9
+ if (allowed && Object.keys(value).some(key => !allowed.includes(key)))
10
+ return invalid();
11
+ return value;
12
+ }
13
+ function integer(value, minimum = 0) {
14
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum;
15
+ }
16
+ function string(value) {
17
+ if (typeof value !== 'string')
18
+ return false;
19
+ for (let index = 0; index < value.length; index++) {
20
+ const code = value.charCodeAt(index);
21
+ if (code >= 0xd800 && code <= 0xdbff) {
22
+ const low = value.charCodeAt(++index);
23
+ if (!(low >= 0xdc00 && low <= 0xdfff))
24
+ return false;
25
+ }
26
+ else if (code >= 0xdc00 && code <= 0xdfff)
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ /** Duplicate-aware bounded JSON, closed public metadata and exact requested row fields. */
32
+ export function clientHttpResult(text, plan) {
33
+ let parsed;
34
+ try {
35
+ parsed = decodeHttpResultJson(text);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof HttpResultJsonBudgetError)
39
+ throw new MCPExecutionBudgetError('MCP_RESULT_TOO_LARGE', 'Backend result exceeds JSON limits.');
40
+ return invalid();
41
+ }
42
+ const result = record(parsed, ['data', 'meta']);
43
+ if (!Array.isArray(result.data) || result.data.length > plan.effectiveLimit)
44
+ return invalid();
45
+ const dimensions = plan.body.dimensions;
46
+ const fields = [...dimensions, ...plan.selectedMeasures, ...(plan.body.by === undefined ? [] : ['period'])];
47
+ for (const item of result.data) {
48
+ const row = record(item, fields);
49
+ if (Object.keys(row).length !== fields.length || fields.some(field => !Object.hasOwn(row, field)))
50
+ return invalid();
51
+ for (const field of fields) {
52
+ const value = row[field];
53
+ if (typeof value === 'number' && !Number.isFinite(value))
54
+ return invalid();
55
+ if (field === 'period' && plan.body.by !== undefined && value !== null
56
+ && (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2})?$/.test(value)))
57
+ return invalid();
58
+ const dimension = Object.hasOwn(plan.endpoint.dimensions, field) && dimensions.includes(field) ? plan.endpoint.dimensions[field] : undefined;
59
+ if (value === null) {
60
+ if (dimension?.nullable === false)
61
+ return invalid();
62
+ }
63
+ else if (!dimension) {
64
+ if (!string(value))
65
+ return invalid();
66
+ }
67
+ else if (dimension.wireType === 'number-or-string') {
68
+ if (!(string(value) || typeof value === 'number' && Number.isFinite(value)))
69
+ return invalid();
70
+ }
71
+ else if (dimension.wireType === 'string') {
72
+ if (!string(value))
73
+ return invalid();
74
+ }
75
+ else if (typeof value !== dimension.wireType)
76
+ return invalid();
77
+ }
78
+ }
79
+ if (result.meta !== undefined && result.meta !== null) {
80
+ if (!plan.endpoint.metadata)
81
+ return invalid();
82
+ const meta = record(result.meta, ['requestId', 'timingMs', 'rowCount', 'pagination', 'cache']);
83
+ if (!string(meta.requestId) || meta.requestId.length === 0 || meta.requestId.length > 1024
84
+ || typeof meta.timingMs !== 'number' || meta.timingMs < 0 || !Number.isFinite(meta.timingMs)
85
+ || meta.rowCount !== result.data.length)
86
+ return invalid();
87
+ const pagination = record(meta.pagination, ['limit', 'offset', 'hasMore']);
88
+ if (!integer(pagination.limit, 1) || pagination.limit > plan.effectiveLimit
89
+ || pagination.offset !== plan.body.offset || typeof pagination.hasMore !== 'boolean'
90
+ || result.data.length > pagination.limit || pagination.hasMore && result.data.length !== pagination.limit)
91
+ return invalid();
92
+ if (meta.cache !== undefined && typeof record(meta.cache, ['hit']).hit !== 'boolean')
93
+ return invalid();
94
+ }
95
+ else if (plan.body.includeMeta === true)
96
+ return invalid();
97
+ return result;
98
+ }
@@ -0,0 +1,12 @@
1
+ export type BackendHeaders = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
2
+ export interface HttpBackend {
3
+ readonly discovery: URL;
4
+ readonly headers?: BackendHeaders;
5
+ readonly fetchFn: typeof fetch;
6
+ }
7
+ export declare function backendUrl(input: string): URL;
8
+ export declare function backendRequest(backend: HttpBackend, url: URL, signal: AbortSignal, body?: Readonly<Record<string, unknown>>, schemaHash?: string): Promise<Response>;
9
+ export declare function discardBackendBody(response: Response): void;
10
+ /** Bound decompressed streamed bytes, including when Content-Length is absent or dishonest. */
11
+ export declare function backendText(response: Response, maximum: number, signal: AbortSignal): Promise<string>;
12
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/utils/http-client/transport.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AACvH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,KAAK,CAAC;CAChC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAQ7C;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GACjH,OAAO,CAAC,QAAQ,CAAC,CAkBnB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAE3D;AAED,+FAA+F;AAC/F,wBAAsB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAsC3G"}