@hypequery/mcp 0.5.5 → 0.7.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 (72) hide show
  1. package/README.md +83 -6
  2. package/dist/api.type-test.d.ts +2 -0
  3. package/dist/api.type-test.d.ts.map +1 -0
  4. package/dist/api.type-test.js +14 -0
  5. package/dist/bin.js +2 -1
  6. package/dist/discovery-executor.d.ts +78 -0
  7. package/dist/discovery-executor.d.ts.map +1 -0
  8. package/dist/discovery-executor.js +111 -0
  9. package/dist/errors.d.ts +29 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +65 -0
  12. package/dist/executor.d.ts +50 -0
  13. package/dist/executor.d.ts.map +1 -0
  14. package/dist/executor.js +103 -0
  15. package/dist/index.d.ts +9 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +8 -2
  18. package/dist/protocol-server.d.ts +26 -0
  19. package/dist/protocol-server.d.ts.map +1 -0
  20. package/dist/protocol-server.js +48 -0
  21. package/dist/server.d.ts +15 -44
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +21 -271
  24. package/dist/stdio.d.ts +8 -0
  25. package/dist/stdio.d.ts.map +1 -0
  26. package/dist/stdio.js +14 -0
  27. package/dist/tools/args.d.ts +16 -16
  28. package/dist/tools/args.d.ts.map +1 -1
  29. package/dist/tools/args.js +9 -8
  30. package/dist/tools/introspect.d.ts +8 -6
  31. package/dist/tools/introspect.d.ts.map +1 -1
  32. package/dist/tools/introspect.js +45 -168
  33. package/dist/tools/list-datasets.d.ts.map +1 -1
  34. package/dist/tools/list-datasets.js +2 -8
  35. package/dist/tools/query-dataset.d.ts.map +1 -1
  36. package/dist/tools/query-dataset.js +26 -30
  37. package/dist/tools/query-metric.d.ts.map +1 -1
  38. package/dist/tools/query-metric.js +27 -31
  39. package/dist/tools/tool-manifest.d.ts +28 -0
  40. package/dist/tools/tool-manifest.d.ts.map +1 -0
  41. package/dist/tools/tool-manifest.js +302 -0
  42. package/dist/tools/utils/canonical-query-schemas.d.ts +17 -0
  43. package/dist/tools/utils/canonical-query-schemas.d.ts.map +1 -0
  44. package/dist/tools/utils/canonical-query-schemas.js +131 -0
  45. package/dist/tools/utils/execution-budget.d.ts +11 -0
  46. package/dist/tools/utils/execution-budget.d.ts.map +1 -0
  47. package/dist/tools/utils/execution-budget.js +71 -0
  48. package/dist/tools/utils/legacy-agent-catalog.d.ts +19 -0
  49. package/dist/tools/utils/legacy-agent-catalog.d.ts.map +1 -0
  50. package/dist/tools/utils/legacy-agent-catalog.js +171 -0
  51. package/dist/tools/utils/query-limits.d.ts +25 -0
  52. package/dist/tools/utils/query-limits.d.ts.map +1 -0
  53. package/dist/tools/utils/query-limits.js +69 -0
  54. package/dist/tools/utils/query-result.d.ts +4 -0
  55. package/dist/tools/utils/query-result.d.ts.map +1 -0
  56. package/dist/tools/utils/query-result.js +19 -0
  57. package/dist/tools/utils/query-schema.d.ts +8 -0
  58. package/dist/tools/utils/query-schema.d.ts.map +1 -0
  59. package/dist/tools/utils/query-schema.js +36 -0
  60. package/dist/tools/utils/tool-response.d.ts +6 -0
  61. package/dist/tools/utils/tool-response.d.ts.map +1 -0
  62. package/dist/tools/utils/tool-response.js +33 -0
  63. package/dist/types.d.ts +54 -58
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +9 -0
  66. package/dist/utils/tenant-config.d.ts +3 -0
  67. package/dist/utils/tenant-config.d.ts.map +1 -0
  68. package/dist/utils/tenant-config.js +23 -0
  69. package/dist/version.d.ts +2 -0
  70. package/dist/version.d.ts.map +1 -0
  71. package/dist/version.js +5 -0
  72. package/package.json +7 -3
package/README.md CHANGED
@@ -14,12 +14,11 @@ npm install @hypequery/mcp @hypequery/datasets @hypequery/clickhouse
14
14
 
15
15
  ```ts
16
16
  // mcp-config.ts
17
- export const datasets = {
18
- orders: {
19
- ...Orders,
20
- metrics: { revenue },
21
- },
22
- };
17
+ import { publishDatasets } from '@hypequery/datasets';
18
+
19
+ export const datasets = publishDatasets()
20
+ .publish(Orders, { metrics: { revenue } })
21
+ .build();
23
22
 
24
23
  export const analytics = createDatasetClient({ queryBuilder: db });
25
24
  ```
@@ -56,9 +55,34 @@ Now an agent can ask, “Show revenue by region for the last month,” using the
56
55
  - `query_metric` executes named KPIs;
57
56
  - `query_dataset` explores the fields you chose to publish.
58
57
 
58
+ Every tool declares an output schema and returns the same result twice: MCP
59
+ `structuredContent` for clients that support typed results, and compact JSON in
60
+ the text content block for compatibility. Query metadata includes row count,
61
+ timing when available, pagination state, and cache outcome. Tools also advertise
62
+ human-readable titles and read-only, non-destructive, idempotent annotations.
63
+
64
+ Tool failures use a stable structured envelope:
65
+
66
+ ```json
67
+ {
68
+ "error": {
69
+ "code": "MCP_INVALID_ARGUMENTS",
70
+ "category": "correctable_input",
71
+ "message": "Invalid query_dataset arguments: ...",
72
+ "retryable": false,
73
+ "correctable": true
74
+ }
75
+ }
76
+ ```
77
+
78
+ Unclassified backend failures are redacted to `MCP_EXECUTION_FAILED`; database
79
+ error details and physical SQL are not copied into agent-facing errors.
80
+
59
81
  ## Safer than raw SQL access
60
82
 
61
83
  - Tool schemas come from your TypeScript semantic layer.
84
+ - Advertised schemas and runtime validation use the same canonical catalog
85
+ compiler and deterministic manifest hash.
62
86
  - Filters, fields, ordering, and limits are validated.
63
87
  - ClickHouse credentials remain in the server process.
64
88
  - SQL text stays hidden by default.
@@ -73,9 +97,62 @@ await createMCPServer({
73
97
  name: 'acme-analytics',
74
98
  version: '1.0.0',
75
99
  tenantId: session.accountId,
100
+ queryLimits: {
101
+ defaultResultSize: 100,
102
+ maxResultSize: 1_000,
103
+ maxOffset: 10_000,
104
+ },
105
+ executionBudget: {
106
+ timeoutMs: 30_000,
107
+ maxResponseBytes: 1_048_576,
108
+ },
76
109
  });
77
110
  ```
78
111
 
112
+ Every query receives a server-side limit even when the agent omits one. The
113
+ effective ceiling is the lowest applicable server or Dataset limit. Dimensions,
114
+ measures, filters, ordering, and pagination offsets also have hard package
115
+ ceilings that server configuration may lower but cannot raise.
116
+
117
+ Query calls also have a hard wall-clock deadline and UTF-8 response-byte
118
+ ceiling. Client cancellation and local deadlines propagate through the semantic
119
+ client to the backing ClickHouse request. Budget failures use the stable
120
+ `MCP_REQUEST_CANCELLED`, `MCP_QUERY_TIMEOUT`, and `MCP_RESULT_TOO_LARGE`
121
+ classifications.
122
+
123
+ ## Embed in another MCP transport
124
+
125
+ The semantic executor is independent of stdio and network lifecycle. A hosted
126
+ gateway can inject its own MCP transport without reimplementing Hypequery's
127
+ tools, prompts, catalog schemas, or validation:
128
+
129
+ ```ts
130
+ import {
131
+ createMCPExecutor,
132
+ createMCPProtocolServer,
133
+ } from '@hypequery/mcp';
134
+
135
+ const executor = createMCPExecutor({
136
+ datasets,
137
+ analytics,
138
+ tenantId: trustedPrincipal.tenantId,
139
+ });
140
+
141
+ const server = createMCPProtocolServer({
142
+ executor,
143
+ name: 'acme-hosted-analytics',
144
+ version: '1.0.0',
145
+ });
146
+
147
+ await server.connect(hostTransport);
148
+ ```
149
+
150
+ `hostTransport` can be an MCP SDK in-memory, stdio, or hosted transport. This
151
+ package does not create an HTTP endpoint; authentication, routing, and network
152
+ lifecycle remain responsibilities of the host. For an explicit local adapter,
153
+ use `startStdioMCPServer(config)`. The existing `createMCPServer(config)` and
154
+ `HypequeryMCPServer.start()` APIs remain available for compatibility.
155
+
79
156
  ## Learn more
80
157
 
81
158
  - [ClickHouse MCP overview](https://hypequery.com/clickhouse-mcp)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.type-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.type-test.d.ts","sourceRoot":"","sources":["../src/api.type-test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import { expectTypeOf, it } from 'vitest';
2
+ import { HypequeryMCPExecutor, HypequeryMCPProtocolServer, HypequeryMCPServer, MCPToolError, connectMCPServerStdio, createMCPExecutor, createMCPProtocolServer, createMCPServer, startStdioMCPServer, } from './index.js';
3
+ it('exports the transport-neutral and backwards-compatible MCP APIs', () => {
4
+ expectTypeOf(HypequeryMCPExecutor).toBeConstructibleWith({});
5
+ expectTypeOf(HypequeryMCPProtocolServer).toBeConstructibleWith({});
6
+ expectTypeOf(HypequeryMCPServer).toBeConstructibleWith({});
7
+ expectTypeOf(createMCPExecutor).returns.toMatchTypeOf();
8
+ expectTypeOf(createMCPProtocolServer).returns.toMatchTypeOf();
9
+ expectTypeOf(connectMCPServerStdio).returns.toMatchTypeOf();
10
+ expectTypeOf(startStdioMCPServer).returns.toMatchTypeOf();
11
+ expectTypeOf(createMCPServer).returns.toMatchTypeOf();
12
+ expectTypeOf(new MCPToolError('MCP_UNAUTHORIZED', 'Forbidden').code)
13
+ .toMatchTypeOf();
14
+ });
package/dist/bin.js CHANGED
@@ -12,6 +12,7 @@ import { createMCPServer } from './server.js';
12
12
  import { pathToFileURL } from 'url';
13
13
  import { resolve } from 'path';
14
14
  import { format } from 'util';
15
+ import { MCP_PACKAGE_VERSION } from './version.js';
15
16
  function routeConsoleOutputToStderr() {
16
17
  const write = (...args) => {
17
18
  process.stderr.write(`${format(...args)}\n`);
@@ -53,7 +54,7 @@ async function main() {
53
54
  datasets,
54
55
  analytics,
55
56
  name: 'hypequery-mcp-server',
56
- version: '0.1.0',
57
+ version: MCP_PACKAGE_VERSION,
57
58
  });
58
59
  // Keep the process running
59
60
  process.on('SIGINT', () => {
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Discovery without execution.
3
+ *
4
+ * A hosted gateway lists tools for one principal against one activated
5
+ * deployment, and does that before — and independently of — being able to run
6
+ * anything. `HypequeryMCPExecutor` cannot serve that: it requires a
7
+ * `DatasetClient` it would never call, and refuses to construct at all when a
8
+ * dataset is tenant-scoped and no fixed `tenantId` is configured. Both are
9
+ * correct for a local server, where one process serves one tenant and every
10
+ * listed tool is runnable. Neither holds for a gateway, which resolves a tenant
11
+ * per request through the deployment data plane.
12
+ *
13
+ * The alternative would be a second manifest generator in the gateway, which
14
+ * `CORE-03` exists to prevent: the tool schemas an agent is given and the
15
+ * validators a query is checked against have to come from one catalog.
16
+ *
17
+ * So this shares the schema compiler and the catalog tools, and refuses the two
18
+ * tools that need a query engine. Listing a tool it will not run is deliberate:
19
+ * the manifest a client caches must be the one it will keep seeing once
20
+ * execution is wired in behind it, or every client re-lists on the day that
21
+ * lands.
22
+ */
23
+ import type { CallToolResult, GetPromptResult, ListPromptsResult, ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
24
+ import type { MCPToolExecutor } from './executor.js';
25
+ import type { DatasetRegistry, MCPQueryLimits } from './types.js';
26
+ /**
27
+ * Provenance a client needs to cache a manifest and know when it went stale.
28
+ *
29
+ * Namespaced under `com.hypequery/` in the result's `_meta`, as the MCP spec
30
+ * requires of implementation-defined keys.
31
+ */
32
+ export interface MCPToolManifestMeta {
33
+ /** The immutable generation these tools were listed from. */
34
+ readonly activationRevision?: string;
35
+ /** Content-addressed identity of the contract behind them. */
36
+ readonly deploymentIdentity?: string;
37
+ /** How the tools were shaped — `catalog` for the fixed compatibility set. */
38
+ readonly toolMode?: string;
39
+ }
40
+ export interface MCPDiscoveryExecutorConfig {
41
+ /** Datasets to advertise. Already narrowed to what the caller may see. */
42
+ datasets: DatasetRegistry;
43
+ /**
44
+ * Datasets offered as a `query_dataset` target. Defaults to all of them.
45
+ *
46
+ * A deployment authorizes a dataset and each of its metrics through separate
47
+ * endpoint policies, so a caller can be entitled to a metric on a dataset it
48
+ * may not query directly. Such a dataset still belongs in `datasets` — its
49
+ * metrics are reachable, and it may be joined to — but naming it here would
50
+ * advertise a target execution refuses.
51
+ */
52
+ queryableDatasets?: readonly string[];
53
+ /** Server-side query ceilings, so advertised schemas match what will run. */
54
+ queryLimits?: MCPQueryLimits;
55
+ /** Attached to `listTools`, so a client can pin what it listed. */
56
+ meta?: MCPToolManifestMeta;
57
+ }
58
+ /**
59
+ * A read-only executor over a catalog.
60
+ *
61
+ * `list_datasets`, `get_dataset_schema`, and the prompts read the agent-safe
62
+ * catalog and need no query engine, so they answer normally. `query_dataset`
63
+ * and `query_metric` are advertised with their exact schemas and refuse.
64
+ */
65
+ export declare class HypequeryMCPDiscoveryExecutor implements MCPToolExecutor {
66
+ private readonly config;
67
+ private readonly querySchemas;
68
+ private readonly meta;
69
+ constructor(config: MCPDiscoveryExecutorConfig);
70
+ /** The catalog these tools were compiled from, for caching by content. */
71
+ getManifestHash(): string;
72
+ listTools(): Promise<ListToolsResult>;
73
+ callTool(name: string, args?: Record<string, unknown>): Promise<CallToolResult>;
74
+ listPrompts(): Promise<ListPromptsResult>;
75
+ getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult>;
76
+ }
77
+ export declare function createMCPDiscoveryExecutor(config: MCPDiscoveryExecutorConfig): MCPToolExecutor;
78
+ //# sourceMappingURL=discovery-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-executor.d.ts","sourceRoot":"","sources":["../src/discovery-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,eAAe,EAChB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAOrD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAElE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,0EAA0E;IAC1E,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6EAA6E;IAC7E,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,mEAAmE;IACnE,IAAI,CAAC,EAAE,mBAAmB,CAAC;CAC5B;AAeD;;;;;;GAMG;AACH,qBAAa,6BAA8B,YAAW,eAAe;IAIvD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;gBAE7B,MAAM,EAAE,0BAA0B;IAW/D,0EAA0E;IAC1E,eAAe,IAAI,MAAM;IAInB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;IAKrC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAyB/E,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAczC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;CAMvF;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,0BAA0B,GACjC,eAAe,CAEjB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Discovery without execution.
3
+ *
4
+ * A hosted gateway lists tools for one principal against one activated
5
+ * deployment, and does that before — and independently of — being able to run
6
+ * anything. `HypequeryMCPExecutor` cannot serve that: it requires a
7
+ * `DatasetClient` it would never call, and refuses to construct at all when a
8
+ * dataset is tenant-scoped and no fixed `tenantId` is configured. Both are
9
+ * correct for a local server, where one process serves one tenant and every
10
+ * listed tool is runnable. Neither holds for a gateway, which resolves a tenant
11
+ * per request through the deployment data plane.
12
+ *
13
+ * The alternative would be a second manifest generator in the gateway, which
14
+ * `CORE-03` exists to prevent: the tool schemas an agent is given and the
15
+ * validators a query is checked against have to come from one catalog.
16
+ *
17
+ * So this shares the schema compiler and the catalog tools, and refuses the two
18
+ * tools that need a query engine. Listing a tool it will not run is deliberate:
19
+ * the manifest a client caches must be the one it will keep seeing once
20
+ * execution is wired in behind it, or every client re-lists on the day that
21
+ * lands.
22
+ */
23
+ import { MCPToolError } from './errors.js';
24
+ import { datasetGuidePrompt } from './prompts/dataset-guide.js';
25
+ import { getDatasetSchemaTool } from './tools/introspect.js';
26
+ import { listDatasetsTool } from './tools/list-datasets.js';
27
+ import { buildMCPToolManifest } from './tools/tool-manifest.js';
28
+ import { buildMCPQuerySchemas } from './tools/utils/canonical-query-schemas.js';
29
+ import { createMCPErrorResponse } from './tools/utils/tool-response.js';
30
+ const NOT_EXECUTABLE = 'This endpoint lists datasets and metrics but does not execute queries.';
31
+ function metaEntries(meta) {
32
+ if (meta === undefined)
33
+ return undefined;
34
+ const entries = Object.entries({
35
+ 'com.hypequery/activationRevision': meta.activationRevision,
36
+ 'com.hypequery/deploymentIdentity': meta.deploymentIdentity,
37
+ 'com.hypequery/toolMode': meta.toolMode,
38
+ }).filter((entry) => entry[1] !== undefined);
39
+ return entries.length === 0 ? undefined : Object.fromEntries(entries);
40
+ }
41
+ /**
42
+ * A read-only executor over a catalog.
43
+ *
44
+ * `list_datasets`, `get_dataset_schema`, and the prompts read the agent-safe
45
+ * catalog and need no query engine, so they answer normally. `query_dataset`
46
+ * and `query_metric` are advertised with their exact schemas and refuse.
47
+ */
48
+ export class HypequeryMCPDiscoveryExecutor {
49
+ config;
50
+ querySchemas;
51
+ meta;
52
+ constructor(config) {
53
+ this.config = config;
54
+ // Deliberately no tenant assertion. A gateway has no fixed tenant to
55
+ // declare, and none is needed: nothing here reaches a table.
56
+ this.querySchemas = buildMCPQuerySchemas(config.datasets ?? {}, config.queryLimits, config.queryableDatasets);
57
+ this.meta = metaEntries(config.meta);
58
+ }
59
+ /** The catalog these tools were compiled from, for caching by content. */
60
+ getManifestHash() {
61
+ return this.querySchemas.manifestHash;
62
+ }
63
+ async listTools() {
64
+ const manifest = buildMCPToolManifest(this.querySchemas);
65
+ return this.meta === undefined ? manifest : { ...manifest, _meta: this.meta };
66
+ }
67
+ async callTool(name, args) {
68
+ try {
69
+ switch (name) {
70
+ case 'list_datasets':
71
+ return await listDatasetsTool(this.config.datasets);
72
+ case 'get_dataset_schema':
73
+ return await getDatasetSchemaTool(this.config.datasets, args);
74
+ case 'query_dataset':
75
+ case 'query_metric':
76
+ // Not `MCP_UNKNOWN_TOOL`: the tool exists and is listed, and telling a
77
+ // client it does not would invite it to stop asking for good.
78
+ throw new MCPToolError('MCP_EXECUTION_FAILED', NOT_EXECUTABLE, {
79
+ retryable: false,
80
+ });
81
+ default:
82
+ throw new MCPToolError('MCP_UNKNOWN_TOOL', `Unknown tool: ${name}`);
83
+ }
84
+ }
85
+ catch (error) {
86
+ return createMCPErrorResponse(error);
87
+ }
88
+ }
89
+ async listPrompts() {
90
+ return {
91
+ prompts: [{
92
+ name: 'dataset_guide',
93
+ description: 'Guide for querying datasets with natural language',
94
+ arguments: [{
95
+ name: 'dataset',
96
+ description: 'Name of the dataset to get guidance for',
97
+ required: false,
98
+ }],
99
+ }],
100
+ };
101
+ }
102
+ async getPrompt(name, args) {
103
+ if (name === 'dataset_guide') {
104
+ return datasetGuidePrompt(this.config.datasets, args?.dataset);
105
+ }
106
+ throw new Error(`Unknown prompt: ${name}`);
107
+ }
108
+ }
109
+ export function createMCPDiscoveryExecutor(config) {
110
+ return new HypequeryMCPDiscoveryExecutor(config);
111
+ }
@@ -0,0 +1,29 @@
1
+ export type MCPToolErrorCode = 'MCP_INVALID_ARGUMENTS' | 'MCP_NOT_FOUND' | 'MCP_UNKNOWN_TOOL' | 'MCP_UNAUTHORIZED' | 'MCP_STALE_CONTRACT' | 'MCP_REQUEST_CANCELLED' | 'MCP_QUERY_TIMEOUT' | 'MCP_RESULT_TOO_LARGE' | 'MCP_EXECUTION_FAILED';
2
+ export type MCPToolErrorCategory = 'correctable_input' | 'unauthorized' | 'stale_contract' | 'budget' | 'internal';
3
+ /** @deprecated Use MCPToolErrorCode. */
4
+ export type MCPExecutionErrorCode = Extract<MCPToolErrorCode, 'MCP_REQUEST_CANCELLED' | 'MCP_QUERY_TIMEOUT' | 'MCP_RESULT_TOO_LARGE'>;
5
+ export interface MCPErrorDetails {
6
+ code: MCPToolErrorCode;
7
+ category: MCPToolErrorCategory;
8
+ message: string;
9
+ retryable: boolean;
10
+ correctable: boolean;
11
+ }
12
+ export declare class MCPToolError extends Error {
13
+ readonly code: MCPToolErrorCode;
14
+ readonly category: MCPToolErrorCategory;
15
+ readonly retryable: boolean;
16
+ readonly correctable: boolean;
17
+ constructor(code: MCPToolErrorCode, message: string, options?: {
18
+ category?: MCPToolErrorCategory;
19
+ retryable?: boolean;
20
+ correctable?: boolean;
21
+ });
22
+ }
23
+ export declare class MCPExecutionBudgetError extends MCPToolError {
24
+ readonly code: MCPExecutionErrorCode;
25
+ constructor(code: MCPExecutionErrorCode, message: string);
26
+ }
27
+ export declare function classifyMCPToolError(error: unknown): MCPErrorDetails;
28
+ export declare function formatMCPToolError(error: unknown): string;
29
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,uBAAuB,GACvB,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,GACpB,uBAAuB,GACvB,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,oBAAoB,GAC5B,mBAAmB,GACnB,cAAc,GACd,gBAAgB,GAChB,QAAQ,GACR,UAAU,CAAC;AAEf,wCAAwC;AACxC,MAAM,MAAM,qBAAqB,GAAG,OAAO,CACzC,gBAAgB,EAChB,uBAAuB,GAAG,mBAAmB,GAAG,sBAAsB,CACvE,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;gBAG5B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,QAAQ,CAAC,EAAE,oBAAoB,CAAC;QAChC,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KAClB;CAUT;AAED,qBAAa,uBAAwB,SAAQ,YAAY;IACvD,SAAiB,IAAI,EAAE,qBAAqB,CAAC;gBAEjC,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM;CAOzD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAkBpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGzD"}
package/dist/errors.js ADDED
@@ -0,0 +1,65 @@
1
+ function defaultErrorMetadata(code) {
2
+ switch (code) {
3
+ case 'MCP_INVALID_ARGUMENTS':
4
+ case 'MCP_NOT_FOUND':
5
+ case 'MCP_UNKNOWN_TOOL':
6
+ return { category: 'correctable_input', retryable: false, correctable: true };
7
+ case 'MCP_UNAUTHORIZED':
8
+ return { category: 'unauthorized', retryable: false, correctable: false };
9
+ case 'MCP_STALE_CONTRACT':
10
+ return { category: 'stale_contract', retryable: true, correctable: false };
11
+ case 'MCP_REQUEST_CANCELLED':
12
+ case 'MCP_RESULT_TOO_LARGE':
13
+ return { category: 'budget', retryable: false, correctable: false };
14
+ case 'MCP_QUERY_TIMEOUT':
15
+ return { category: 'budget', retryable: true, correctable: false };
16
+ case 'MCP_EXECUTION_FAILED':
17
+ return { category: 'internal', retryable: true, correctable: false };
18
+ }
19
+ }
20
+ export class MCPToolError extends Error {
21
+ code;
22
+ category;
23
+ retryable;
24
+ correctable;
25
+ constructor(code, message, options = {}) {
26
+ super(message);
27
+ const defaults = defaultErrorMetadata(code);
28
+ this.name = 'MCPToolError';
29
+ this.code = code;
30
+ this.category = options.category ?? defaults.category;
31
+ this.retryable = options.retryable ?? defaults.retryable;
32
+ this.correctable = options.correctable ?? defaults.correctable;
33
+ }
34
+ }
35
+ export class MCPExecutionBudgetError extends MCPToolError {
36
+ constructor(code, message) {
37
+ super(code, message, {
38
+ category: 'budget',
39
+ retryable: code === 'MCP_QUERY_TIMEOUT',
40
+ });
41
+ this.name = 'MCPExecutionBudgetError';
42
+ }
43
+ }
44
+ export function classifyMCPToolError(error) {
45
+ if (error instanceof MCPToolError) {
46
+ return {
47
+ code: error.code,
48
+ category: error.category,
49
+ message: error.message,
50
+ retryable: error.retryable,
51
+ correctable: error.correctable,
52
+ };
53
+ }
54
+ return {
55
+ code: 'MCP_EXECUTION_FAILED',
56
+ category: 'internal',
57
+ message: 'Query execution failed',
58
+ retryable: true,
59
+ correctable: false,
60
+ };
61
+ }
62
+ export function formatMCPToolError(error) {
63
+ const classified = classifyMCPToolError(error);
64
+ return `Error [${classified.code}]: ${classified.message}`;
65
+ }
@@ -0,0 +1,50 @@
1
+ import type { CallToolResult, GetPromptResult, ListPromptsResult, ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { DatasetClient } from '@hypequery/datasets';
3
+ import type { DatasetRegistry, MCPExecutionBudget, MCPQueryLimits } from './types.js';
4
+ export interface MCPExecutorConfig {
5
+ /** Dataset registry - map of dataset names to instances. */
6
+ datasets: DatasetRegistry;
7
+ /** Semantic analytics client for running metric and dataset queries. */
8
+ analytics: DatasetClient;
9
+ /** Trusted tenant id used to scope tenant-keyed datasets. */
10
+ tenantId?: string;
11
+ /** Include generated SQL in trusted-debug responses. Defaults to false. */
12
+ includeSql?: boolean;
13
+ /** Server-side query ceilings applied in addition to Dataset limits. */
14
+ queryLimits?: MCPQueryLimits;
15
+ /** Query deadline and serialized-result byte ceilings. */
16
+ executionBudget?: MCPExecutionBudget;
17
+ }
18
+ export interface MCPServerConfig extends MCPExecutorConfig {
19
+ /** Server name shown to MCP clients. */
20
+ name?: string;
21
+ /** Server version shown to MCP clients. */
22
+ version?: string;
23
+ }
24
+ export interface MCPToolExecutor {
25
+ listTools(): Promise<ListToolsResult>;
26
+ callTool(name: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<CallToolResult>;
27
+ listPrompts(): Promise<ListPromptsResult>;
28
+ getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult>;
29
+ getManifestHash(): string;
30
+ }
31
+ /**
32
+ * Transport-neutral Hypequery MCP tool and prompt executor.
33
+ *
34
+ * This class owns semantic discovery and execution, but has no network or stdio
35
+ * lifecycle. It can be called directly or injected into any MCP transport
36
+ * adapter.
37
+ */
38
+ export declare class HypequeryMCPExecutor implements MCPToolExecutor {
39
+ private readonly config;
40
+ private readonly querySchemas;
41
+ private readonly executionBudget;
42
+ constructor(config: MCPExecutorConfig);
43
+ getManifestHash(): string;
44
+ listTools(): Promise<ListToolsResult>;
45
+ callTool(name: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<CallToolResult>;
46
+ listPrompts(): Promise<ListPromptsResult>;
47
+ getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult>;
48
+ }
49
+ export declare function createMCPExecutor(config: MCPExecutorConfig): HypequeryMCPExecutor;
50
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,eAAe,EAChB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,qBAAqB,CAAC;AAe7B,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOtF,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,QAAQ,EAAE,eAAe,CAAC;IAC1B,wEAAwE;IACxE,SAAS,EAAE,aAAa,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,kBAAkB,CAAC;CACtC;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACjF,eAAe,IAAI,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAI9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;gBAE9B,MAAM,EAAE,iBAAiB;IAOtD,eAAe,IAAI,MAAM;IAInB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;IAIrC,QAAQ,CACZ,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC;IAwDpB,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAkBzC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;CAOvF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,oBAAoB,CAEjF"}
@@ -0,0 +1,103 @@
1
+ import { MCPToolError } from './errors.js';
2
+ import { datasetGuidePrompt } from './prompts/dataset-guide.js';
3
+ import { getDatasetSchemaTool } from './tools/introspect.js';
4
+ import { listDatasetsTool } from './tools/list-datasets.js';
5
+ import { queryDatasetTool } from './tools/query-dataset.js';
6
+ import { queryMetricTool } from './tools/query-metric.js';
7
+ import { buildMCPToolManifest } from './tools/tool-manifest.js';
8
+ import { buildMCPQuerySchemas } from './tools/utils/canonical-query-schemas.js';
9
+ import { assertWithinBudget, resolveExecutionBudget, } from './tools/utils/execution-budget.js';
10
+ import { resolveQueryLimits } from './tools/utils/query-limits.js';
11
+ import { validateMCPServerTenantConfig } from './utils/tenant-config.js';
12
+ import { createMCPErrorResponse, createMCPResultTooLargeResponse, } from './tools/utils/tool-response.js';
13
+ /**
14
+ * Transport-neutral Hypequery MCP tool and prompt executor.
15
+ *
16
+ * This class owns semantic discovery and execution, but has no network or stdio
17
+ * lifecycle. It can be called directly or injected into any MCP transport
18
+ * adapter.
19
+ */
20
+ export class HypequeryMCPExecutor {
21
+ config;
22
+ querySchemas;
23
+ executionBudget;
24
+ constructor(config) {
25
+ this.config = config;
26
+ validateMCPServerTenantConfig(config);
27
+ resolveQueryLimits(undefined, config.queryLimits);
28
+ this.executionBudget = resolveExecutionBudget(config.executionBudget);
29
+ this.querySchemas = buildMCPQuerySchemas(config.datasets ?? {}, config.queryLimits);
30
+ }
31
+ getManifestHash() {
32
+ return this.querySchemas.manifestHash;
33
+ }
34
+ async listTools() {
35
+ return buildMCPToolManifest(this.querySchemas);
36
+ }
37
+ async callTool(name, args, signal) {
38
+ try {
39
+ switch (name) {
40
+ case 'list_datasets':
41
+ return await listDatasetsTool(this.config.datasets);
42
+ case 'get_dataset_schema':
43
+ return await getDatasetSchemaTool(this.config.datasets, args);
44
+ case 'query_metric':
45
+ return await queryMetricTool(this.config.datasets, this.config.analytics, args, {
46
+ tenantId: this.config.tenantId,
47
+ includeSql: this.config.includeSql,
48
+ limits: this.config.queryLimits,
49
+ executionBudget: this.config.executionBudget,
50
+ signal,
51
+ inputSchema: this.querySchemas.queryMetric,
52
+ });
53
+ case 'query_dataset':
54
+ return await queryDatasetTool(this.config.datasets, this.config.analytics, args, {
55
+ tenantId: this.config.tenantId,
56
+ includeSql: this.config.includeSql,
57
+ limits: this.config.queryLimits,
58
+ executionBudget: this.config.executionBudget,
59
+ signal,
60
+ inputSchema: this.querySchemas.queryDataset,
61
+ });
62
+ default:
63
+ throw new MCPToolError('MCP_UNKNOWN_TOOL', `Unknown tool: ${name}`);
64
+ }
65
+ }
66
+ catch (error) {
67
+ const response = createMCPErrorResponse(error);
68
+ try {
69
+ assertWithinBudget(response, this.executionBudget);
70
+ return response;
71
+ }
72
+ catch {
73
+ return createMCPResultTooLargeResponse();
74
+ }
75
+ }
76
+ }
77
+ async listPrompts() {
78
+ return {
79
+ prompts: [
80
+ {
81
+ name: 'dataset_guide',
82
+ description: 'Guide for querying datasets with natural language',
83
+ arguments: [
84
+ {
85
+ name: 'dataset',
86
+ description: 'Name of the dataset to get guidance for',
87
+ required: false,
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ async getPrompt(name, args) {
95
+ if (name === 'dataset_guide') {
96
+ return datasetGuidePrompt(this.config.datasets, args?.dataset);
97
+ }
98
+ throw new Error(`Unknown prompt: ${name}`);
99
+ }
100
+ }
101
+ export function createMCPExecutor(config) {
102
+ return new HypequeryMCPExecutor(config);
103
+ }
package/dist/index.d.ts CHANGED
@@ -5,11 +5,17 @@
5
5
  * Exposes datasets and metrics to AI agents via MCP tools.
6
6
  */
7
7
  export { HypequeryMCPServer, createMCPServer, type MCPServerConfig } from './server.js';
8
+ export { HypequeryMCPExecutor, createMCPExecutor, type MCPExecutorConfig, type MCPToolExecutor, } from './executor.js';
9
+ export { HypequeryMCPDiscoveryExecutor, createMCPDiscoveryExecutor, type MCPDiscoveryExecutorConfig, type MCPToolManifestMeta, } from './discovery-executor.js';
10
+ export { HypequeryMCPProtocolServer, createMCPProtocolServer, type MCPProtocolServerOptions, } from './protocol-server.js';
11
+ export { connectMCPServerStdio, startStdioMCPServer } from './stdio.js';
8
12
  export { listDatasetsTool } from './tools/list-datasets.js';
9
- export { getDatasetSchemaTool } from './tools/introspect.js';
13
+ export { getDatasetSchemaTool, getTrustedDatasetSchema } from './tools/introspect.js';
10
14
  export { queryMetricTool } from './tools/query-metric.js';
11
15
  export { queryDatasetTool } from './tools/query-dataset.js';
12
16
  export { datasetGuidePrompt } from './prompts/dataset-guide.js';
13
- export type { DatasetRegistry, QueryMetricArgs, QueryDatasetArgs, QueryToolOptions, SchemaToolOptions, GetDatasetSchemaArgs, MCPToolResponse, DatasetSchema, DimensionSchema, MetricSchema, RelationshipSchema, DatasetListItem, DatasetsListResponse, QueryResultResponse, QueryResultMeta, } from './types.js';
14
- export { MAX_QUERY_LIMIT, DEFAULT_QUERY_LIMIT } from './types.js';
17
+ export type { DatasetRegistry, QueryMetricArgs, QueryDatasetArgs, QueryToolOptions, MCPExecutionBudget, MCPQueryLimits, SchemaToolOptions, GetDatasetSchemaArgs, MCPToolResponse, DatasetSchema, DimensionSchema, MetricSchema, RelationshipSchema, DatasetListItem, DatasetsListResponse, QueryResultResponse, QueryResultMeta, } from './types.js';
18
+ export { MCPExecutionBudgetError, MCPToolError, classifyMCPToolError, formatMCPToolError, type MCPErrorDetails, type MCPExecutionErrorCode, type MCPToolErrorCode, type MCPToolErrorCategory, } from './errors.js';
19
+ export { MAX_QUERY_LIMIT, DEFAULT_QUERY_LIMIT, MAX_QUERY_OFFSET, MAX_QUERY_DIMENSIONS, MAX_QUERY_MEASURES, MAX_QUERY_FILTERS, MAX_QUERY_ORDER_BY, DEFAULT_QUERY_TIMEOUT_MS, MAX_QUERY_TIMEOUT_MS, DEFAULT_RESPONSE_BYTES, MAX_RESPONSE_BYTES, } from './types.js';
20
+ export { MCP_PACKAGE_VERSION } from './version.js';
15
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGhE,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGhE,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}