@malloy-publisher/server 0.0.382-dev → 0.2.1

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 (175) hide show
  1. package/README.docker.md +145 -20
  2. package/README.md +65 -12
  3. package/dist/app/api-doc.html +5 -0
  4. package/dist/app/api-doc.yaml +3929 -534
  5. package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
  6. package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
  7. package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
  8. package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
  9. package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
  10. package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
  11. package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
  12. package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
  13. package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
  14. package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
  15. package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.js} +1 -1
  16. package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
  17. package/dist/app/assets/index-5eLCcNmP.css +1 -0
  18. package/dist/app/assets/index-C5ClsnS7.js +1774 -0
  19. package/dist/app/assets/index-DF9tVqwN.js +1 -0
  20. package/dist/app/assets/index-DSoCUF-f.js +2725 -0
  21. package/dist/app/assets/index-DgVRz2wm.js +2 -0
  22. package/dist/app/assets/index-P-pF8nU5.js +632 -0
  23. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  24. package/dist/app/index.html +8 -4
  25. package/dist/cpufeatures-1yrn0vtw.node +0 -0
  26. package/dist/default-publisher.config.json +23 -0
  27. package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
  28. package/dist/package_load_worker.mjs +15219 -0
  29. package/dist/runtime/publisher.js +331 -0
  30. package/dist/{server.js → server.mjs} +141276 -74705
  31. package/dist/sshcrypto-vd2k5hq9.node +0 -0
  32. package/package.json +41 -28
  33. package/publisher.config.example.bigquery.json +33 -0
  34. package/publisher.config.example.duckdb.json +23 -0
  35. package/.eslintrc.json +0 -44
  36. package/.prettierignore +0 -1
  37. package/.prettierrc +0 -4
  38. package/build.ts +0 -38
  39. package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
  40. package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
  41. package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
  42. package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
  43. package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
  44. package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
  45. package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
  46. package/dist/app/assets/index-15BOvhp0.js +0 -456
  47. package/dist/app/assets/index-Bb2jqquW.js +0 -676
  48. package/dist/app/assets/index-CMlGQMcl.css +0 -1
  49. package/dist/app/assets/index-D68X76-7.js +0 -1276
  50. package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
  51. package/dxt/malloy_bridge.py +0 -354
  52. package/dxt/manifest.json +0 -22
  53. package/eslint.config.mjs +0 -76
  54. package/k6-tests/README.md +0 -144
  55. package/k6-tests/bun.lock +0 -797
  56. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  57. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  58. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  59. package/k6-tests/load-test/load-test-crud.ts +0 -142
  60. package/k6-tests/load-test/load-test.ts +0 -784
  61. package/k6-tests/package.json +0 -27
  62. package/k6-tests/scripts/run-with-otel.sh +0 -50
  63. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  64. package/k6-tests/tsconfig.json +0 -29
  65. package/k6-tests/utils/client_factory.ts +0 -128
  66. package/k6-tests/utils/common.ts +0 -489
  67. package/k6-tests/utils/logger.ts +0 -62
  68. package/k6-tests/utils/types.d.ts +0 -35
  69. package/malloy_mcp.dxt +0 -0
  70. package/publisher.config.json +0 -33
  71. package/src/config.spec.ts +0 -850
  72. package/src/config.ts +0 -305
  73. package/src/constants.ts +0 -10
  74. package/src/controller/compile.controller.ts +0 -35
  75. package/src/controller/connection.controller.ts +0 -515
  76. package/src/controller/database.controller.ts +0 -21
  77. package/src/controller/manifest.controller.ts +0 -29
  78. package/src/controller/materialization.controller.ts +0 -125
  79. package/src/controller/model.controller.ts +0 -107
  80. package/src/controller/package.controller.ts +0 -182
  81. package/src/controller/query.controller.ts +0 -60
  82. package/src/controller/watch-mode.controller.ts +0 -98
  83. package/src/data_styles.ts +0 -71
  84. package/src/dto/connection.dto.spec.ts +0 -131
  85. package/src/dto/connection.dto.ts +0 -174
  86. package/src/dto/index.ts +0 -2
  87. package/src/dto/package.dto.spec.ts +0 -42
  88. package/src/dto/package.dto.ts +0 -12
  89. package/src/dto/validate.spec.ts +0 -76
  90. package/src/dto/validate.ts +0 -31
  91. package/src/errors.ts +0 -116
  92. package/src/health.ts +0 -189
  93. package/src/instrumentation.ts +0 -148
  94. package/src/logger.ts +0 -154
  95. package/src/mcp/error_messages.ts +0 -199
  96. package/src/mcp/handler_utils.ts +0 -239
  97. package/src/mcp/mcp_constants.ts +0 -22
  98. package/src/mcp/prompts/handlers.ts +0 -84
  99. package/src/mcp/prompts/index.ts +0 -11
  100. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  101. package/src/mcp/prompts/prompt_service.ts +0 -67
  102. package/src/mcp/prompts/utils.ts +0 -62
  103. package/src/mcp/resource_metadata.ts +0 -47
  104. package/src/mcp/resources/model_resource.ts +0 -153
  105. package/src/mcp/resources/notebook_resource.ts +0 -137
  106. package/src/mcp/resources/package_resource.ts +0 -370
  107. package/src/mcp/resources/project_resource.ts +0 -184
  108. package/src/mcp/resources/query_resource.ts +0 -117
  109. package/src/mcp/resources/source_resource.ts +0 -141
  110. package/src/mcp/resources/view_resource.ts +0 -136
  111. package/src/mcp/server.ts +0 -62
  112. package/src/mcp/tools/discovery_tools.ts +0 -258
  113. package/src/mcp/tools/execute_query_tool.ts +0 -265
  114. package/src/server.ts +0 -1195
  115. package/src/service/connection.spec.ts +0 -1331
  116. package/src/service/connection.ts +0 -1511
  117. package/src/service/connection_service.spec.ts +0 -641
  118. package/src/service/connection_service.ts +0 -181
  119. package/src/service/db_utils.spec.ts +0 -714
  120. package/src/service/db_utils.ts +0 -1227
  121. package/src/service/filter.spec.ts +0 -447
  122. package/src/service/filter.ts +0 -337
  123. package/src/service/filter_integration.spec.ts +0 -825
  124. package/src/service/gcs_s3_utils.ts +0 -427
  125. package/src/service/manifest_service.spec.ts +0 -201
  126. package/src/service/manifest_service.ts +0 -106
  127. package/src/service/materialization_service.spec.ts +0 -648
  128. package/src/service/materialization_service.ts +0 -929
  129. package/src/service/materialized_table_gc.spec.ts +0 -383
  130. package/src/service/materialized_table_gc.ts +0 -279
  131. package/src/service/model.spec.ts +0 -257
  132. package/src/service/model.ts +0 -1069
  133. package/src/service/package.spec.ts +0 -236
  134. package/src/service/package.ts +0 -463
  135. package/src/service/project.ts +0 -681
  136. package/src/service/project_compile.spec.ts +0 -197
  137. package/src/service/project_store.spec.ts +0 -859
  138. package/src/service/project_store.ts +0 -1531
  139. package/src/service/quoting.ts +0 -41
  140. package/src/service/resolve_project.ts +0 -13
  141. package/src/storage/BaseRepository.ts +0 -31
  142. package/src/storage/DatabaseInterface.ts +0 -180
  143. package/src/storage/StorageManager.mock.ts +0 -59
  144. package/src/storage/StorageManager.ts +0 -210
  145. package/src/storage/duckdb/ConnectionRepository.ts +0 -141
  146. package/src/storage/duckdb/DuckDBConnection.ts +0 -177
  147. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  148. package/src/storage/duckdb/DuckDBRepository.ts +0 -224
  149. package/src/storage/duckdb/ManifestRepository.ts +0 -119
  150. package/src/storage/duckdb/MaterializationRepository.ts +0 -249
  151. package/src/storage/duckdb/PackageRepository.ts +0 -135
  152. package/src/storage/duckdb/ProjectRepository.ts +0 -148
  153. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  154. package/src/storage/duckdb/schema.ts +0 -147
  155. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
  156. package/src/utils.ts +0 -13
  157. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  158. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  159. package/tests/fixtures/persist-test/publisher.json +0 -5
  160. package/tests/fixtures/publisher.config.json +0 -15
  161. package/tests/harness/e2e.ts +0 -96
  162. package/tests/harness/mcp_test_setup.ts +0 -344
  163. package/tests/harness/mocks.ts +0 -37
  164. package/tests/harness/rest_e2e.ts +0 -68
  165. package/tests/harness/uris.ts +0 -31
  166. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
  167. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
  168. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
  169. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
  170. package/tests/integration/mcp/setup.spec.ts +0 -5
  171. package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
  172. package/tests/unit/ducklake/ducklake.test.ts +0 -950
  173. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  174. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
  175. package/tsconfig.json +0 -32
@@ -1,239 +0,0 @@
1
- import { URL } from "url";
2
- import type { ResourceMetadata } from "@modelcontextprotocol/sdk/server/mcp";
3
- import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
4
- import { ProjectStore } from "../service/project_store";
5
- import {
6
- PackageNotFoundError,
7
- ModelNotFoundError,
8
- ModelCompilationError,
9
- ProjectNotFoundError,
10
- } from "../errors";
11
- import {
12
- getNotFoundError,
13
- getInternalError,
14
- getMalloyErrorDetails,
15
- type ErrorDetails,
16
- } from "./error_messages";
17
- import type { Model } from "../service/model";
18
- import { logger } from "../logger";
19
-
20
- // Custom error to wrap specific GetResource application errors
21
- export class McpGetResourceError extends Error {
22
- details: ErrorDetails;
23
-
24
- constructor(details: ErrorDetails) {
25
- super(details.message); // Pass message to the base Error constructor
26
- this.name = "McpGetResourceError"; // Custom error name
27
- this.details = details; // Store the structured details
28
-
29
- // Maintain stack trace (important for V8)
30
- if (Error.captureStackTrace) {
31
- Error.captureStackTrace(this, McpGetResourceError);
32
- }
33
- }
34
- }
35
-
36
- // Helper type for the data fetching logic within a resource handler
37
- type GetDataLogic<TParams, TDefinition> = (
38
- params: TParams,
39
- uri: URL,
40
- ) => Promise<TDefinition>;
41
-
42
- /**
43
- * Handles the common logic for GetResource handlers, fetching data and formatting the response or error.
44
- */
45
- export async function handleResourceGet<
46
- TParams extends Record<string, unknown>,
47
- TDefinition,
48
- >(
49
- uri: URL,
50
- params: TParams,
51
- resourceType: string, // e.g., 'project', 'package' for logging/errors
52
- getData: GetDataLogic<TParams, TDefinition>,
53
- resourceMetadata: ResourceMetadata | undefined,
54
- ): Promise<ReadResourceResult> {
55
- try {
56
- const definition = await getData(params, uri);
57
-
58
- // Combine definition and metadata
59
- const responsePayload = JSON.stringify(
60
- { definition: definition, metadata: resourceMetadata },
61
- null,
62
- 2,
63
- );
64
-
65
- return {
66
- contents: [
67
- {
68
- type: "application/json",
69
- uri: uri.href,
70
- text: responsePayload,
71
- },
72
- ],
73
- };
74
- } catch (error) {
75
- logger.error(
76
- `[MCP Server Error] Error reading ${resourceType} ${uri.href}:`,
77
- { error },
78
- );
79
-
80
- let errorDetails: ErrorDetails;
81
-
82
- // Determine the correct error details based on the error type
83
- if (error instanceof McpGetResourceError) {
84
- // The getData function already caught, formatted, and wrapped the error
85
- errorDetails = error.details;
86
- } else {
87
- // Catch-all for truly unexpected errors not handled by the specific getData logic
88
- logger.error(
89
- "[MCP Server Error] Unexpected error type caught in handleResourceGet:",
90
- { error },
91
- );
92
- errorDetails = getInternalError(
93
- `GetResource (${resourceType})`,
94
- error,
95
- );
96
- }
97
-
98
- // Format the error response consistently
99
- return {
100
- isError: true,
101
- contents: [
102
- {
103
- type: "application/json", // Keep JSON for structured error
104
- uri: uri.href,
105
- text: JSON.stringify(
106
- {
107
- error: errorDetails.message,
108
- suggestions: errorDetails.suggestions,
109
- },
110
- null,
111
- 2,
112
- ),
113
- },
114
- ],
115
- };
116
- }
117
- }
118
-
119
- /**
120
- * Fetches and validates the Package and Model instances needed for query execution.
121
- * Handles errors related to package/model access and initial compilation.
122
- * @returns An object containing the Model instance or a pre-formatted ErrorDetails object.
123
- */
124
- export async function getModelForQuery(
125
- projectStore: ProjectStore,
126
- projectName: string,
127
- packageName: string,
128
- modelPath: string,
129
- ): Promise<{ model: Model } | { error: ErrorDetails }> {
130
- try {
131
- const project = await projectStore.getProject(projectName, false);
132
- const pkg = await project.getPackage(packageName, false);
133
- const model = pkg.getModel(modelPath);
134
- if (!model || model.getModelType() === "notebook") {
135
- // Ensure it's actually a model
136
- const details = getNotFoundError(
137
- `model '${modelPath}' in package '${packageName}' for project '${projectName}'`,
138
- );
139
- return { error: details };
140
- }
141
- // Attempt to get the model definition early to catch initial compilation errors
142
- await model.getModel(); // This might throw ModelCompilationError
143
- return { model };
144
- } catch (error) {
145
- // Handle errors during package/model access or initial compilation
146
- let errorDetails: ErrorDetails;
147
- if (error instanceof ProjectNotFoundError) {
148
- errorDetails = getNotFoundError(`project '${projectName}'`);
149
- } else if (error instanceof PackageNotFoundError) {
150
- errorDetails = getNotFoundError(
151
- `package '${packageName}' in project '${projectName}'`,
152
- );
153
- } else if (error instanceof ModelNotFoundError) {
154
- errorDetails = getNotFoundError(
155
- `model '${modelPath}' in package '${packageName}' for project '${projectName}'`,
156
- );
157
- } else if (error instanceof ModelCompilationError) {
158
- errorDetails = getMalloyErrorDetails(
159
- "executeQuery (load model)",
160
- `${projectName}/${packageName}/${modelPath}`,
161
- error,
162
- );
163
- } else {
164
- // Unexpected error during setup
165
- errorDetails = getInternalError("executeQuery (Setup)", error);
166
- }
167
- logger.error(
168
- `[MCP Server Error] Error accessing package/model for query: ${projectName}/${packageName}/${modelPath}`,
169
- { error },
170
- );
171
- return { error: errorDetails };
172
- }
173
- }
174
-
175
- /**
176
- * Constructs a valid malloy:// URI string from its components.
177
- * Handles encoding of path segments.
178
- *
179
- * @param components An object containing the URI parts (project, package, model, etc.)
180
- * @param fragment Optional fragment identifier (e.g., #queryResult)
181
- * @returns A valid malloy:// URI string.
182
- */
183
- export function buildMalloyUri(
184
- components: {
185
- project?: string;
186
- package?: string;
187
- model?: string;
188
- resourceType?:
189
- | "models"
190
- | "packages"
191
- | "notebooks"
192
- | "sources"
193
- | "queries"
194
- | "views"; // Type of resource list or specific resource
195
- resourceName?: string; // Specific name for model, query, etc.
196
- subResourceType?: "views"; // For views within sources
197
- subResourceName?: string; // Name of the view
198
- },
199
- fragment?: string,
200
- ): string {
201
- let path = "/project/";
202
-
203
- if (components.project) {
204
- path += encodeURIComponent(components.project);
205
- } else {
206
- // Default to 'home' if not provided, consistent with current behavior
207
- path += "home";
208
- }
209
-
210
- if (components.package) {
211
- path += "/package/" + encodeURIComponent(components.package);
212
- }
213
-
214
- if (components.resourceType) {
215
- path += "/" + components.resourceType;
216
- if (components.resourceName) {
217
- path += "/" + encodeURIComponent(components.resourceName);
218
-
219
- if (components.subResourceType && components.subResourceName) {
220
- path +=
221
- "/" +
222
- components.subResourceType +
223
- "/" +
224
- encodeURIComponent(components.subResourceName);
225
- }
226
- }
227
- }
228
-
229
- // The URL constructor seems to normalize malloy://path to malloy:///path
230
- // which breaks the tests expecting malloy://project/...
231
- // We manually construct the string instead.
232
- let uriString = "malloy:/" + path; // Start with one slash after scheme
233
-
234
- if (fragment) {
235
- uriString += "#" + fragment;
236
- }
237
-
238
- return uriString;
239
- }
@@ -1,22 +0,0 @@
1
- export const MCP_ERROR_MESSAGES = {
2
- // Protocol-level errors (validation)
3
- MISSING_REQUIRED_PARAMS:
4
- "Either 'query' or both 'sourceName' and 'queryName' must be provided",
5
- MUTUALLY_EXCLUSIVE_PARAMS: "Cannot provide both 'query' and 'queryName'",
6
- INVALID_ARGUMENTS: (toolName: string, errors: string[]) =>
7
- `Invalid arguments for tool ${toolName}: ${errors.join(", ")}`,
8
-
9
- // Application-level errors (runtime)
10
- PROJECT_NOT_FOUND: (projectName: string) =>
11
- `Project '${projectName}' is not available or does not exist.`,
12
- PACKAGE_NOT_FOUND: (packageName: string) =>
13
- `Package manifest for ${packageName} does not exist.`,
14
- MODEL_NOT_FOUND: (packageName: string, modelPath: string) =>
15
- `Model '${modelPath}' not found in package '${packageName}'`,
16
- QUERY_NOT_FOUND: (queryName: string) => `'${queryName}' is not defined`,
17
- COMPILATION_ERROR: (error: string) => `Error(s) compiling model:\n${error}`,
18
-
19
- // Error message prefixes
20
- ERROR_EXECUTING_QUERY: (packagePath: string) =>
21
- `Error executing query on '${packagePath}':`,
22
- } as const;
@@ -1,84 +0,0 @@
1
- import * as Handlebars from "handlebars";
2
- import { z } from "zod";
3
- import {
4
- PromptMessageSchema,
5
- GetPromptResultSchema,
6
- } from "@modelcontextprotocol/sdk/types.js";
7
- import { PROMPTS } from "./prompt_definitions";
8
- import { getCompiledModel } from "./utils";
9
- import { ProjectStore } from "../../service/project_store";
10
-
11
- /* eslint-disable @typescript-eslint/no-explicit-any */
12
-
13
- /* --------------------------------------------------------------------------
14
- * Helper – create a simple text prompt message
15
- * ----------------------------------------------------------------------- */
16
-
17
- const textMsg = (text: string): z.infer<typeof PromptMessageSchema> => ({
18
- role: "user",
19
- content: { type: "text", text },
20
- });
21
-
22
- /* --------------------------------------------------------------------------
23
- * makePromptHandler – higher-order factory
24
- * ----------------------------------------------------------------------- */
25
-
26
- export function makePromptHandler(id: string) {
27
- const def = PROMPTS[id];
28
- if (!def) throw new Error(`Unknown prompt id: ${id}`);
29
-
30
- // pre-compile Handlebars template
31
- const templateFn = Handlebars.compile(def.template, { noEscape: true });
32
-
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- return async (params: any, ps: ProjectStore) => {
35
- const data: Record<string, unknown> = { ...params };
36
-
37
- // Dynamically add model content / schema context if the args include URIs
38
- if (ps) {
39
- if (typeof params?.model_uri === "string") {
40
- const compiled = await getCompiledModel(params.model_uri, ps);
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- (data as any).model_def_content = (compiled as any).modelDef;
43
- }
44
- if (typeof params?.target_model_uri === "string") {
45
- const compiled = await getCompiledModel(
46
- params.target_model_uri,
47
- ps,
48
- );
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- (data as any).schema_context = (compiled as any).modelInfo;
51
- }
52
- if (typeof params?.model_context_uri === "string") {
53
- const compiled = await getCompiledModel(
54
- params.model_context_uri,
55
- ps,
56
- );
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- (data as any).schema_context = (compiled as any).modelInfo;
59
- }
60
- }
61
-
62
- const rendered = templateFn(data);
63
-
64
- return {
65
- messages: [textMsg(rendered)],
66
- // Ensure shape matches SDK schema
67
- } as z.infer<typeof GetPromptResultSchema>;
68
- };
69
- }
70
-
71
- /* --------------------------------------------------------------------------
72
- * Registry – id -> handler
73
- * ----------------------------------------------------------------------- */
74
-
75
- export const promptHandlerMap = Object.fromEntries(
76
- Object.keys(PROMPTS).map((id) => [id, makePromptHandler(id)]),
77
- ) as Record<
78
- string,
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- (
81
- params: any,
82
- ps: ProjectStore,
83
- ) => Promise<z.infer<typeof GetPromptResultSchema>>
84
- >;
@@ -1,11 +0,0 @@
1
- export {
2
- withVersion,
3
- PROMPTS,
4
- MALLOY_PROMPTS,
5
- ExplainArgsSchema,
6
- GenerateQueryArgsSchema,
7
- TranslateSqlArgsSchema,
8
- SummarizeModelArgsSchema,
9
- } from "./prompt_definitions";
10
- export { promptHandlerMap, makePromptHandler } from "./handlers";
11
- export { registerPromptCapability } from "./prompt_service";
@@ -1,160 +0,0 @@
1
- import { z } from "zod";
2
-
3
- /**
4
- * Helper to build fully-qualified prompt IDs combining name + semver.
5
- */
6
- export const withVersion = (name: string, v: string) => `${name}@${v}`;
7
-
8
- /* --------------------------------------------------------------------------
9
- * Argument Schemas (Zod) – one per prompt
10
- * ----------------------------------------------------------------------- */
11
-
12
- export const ExplainArgsSchema = z
13
- .object({
14
- query_code: z.string(),
15
- model_context_uri: z.string().url().optional(),
16
- })
17
- .describe(
18
- "Explain a Malloy query: purpose, transformations, output structure.",
19
- );
20
-
21
- export const GenerateQueryArgsSchema = z
22
- .object({
23
- natural_language_goal: z.string(),
24
- target_model_uri: z.string().url(),
25
- data_source_hints: z.string().optional(),
26
- })
27
- .describe(
28
- "Generate a Malloy query from a natural-language goal and model context.",
29
- );
30
-
31
- export const TranslateSqlArgsSchema = z
32
- .object({
33
- sql_query: z.string(),
34
- target_model_uri: z.string().url(),
35
- sql_dialect: z.string().default("standard_sql"),
36
- })
37
- .describe("Translate a SQL query to Malloy using model schema context.");
38
-
39
- export const SummarizeModelArgsSchema = z
40
- .object({
41
- model_uri: z.string().url(),
42
- })
43
- .describe("Summarize a Malloy model's purpose, sources, and joins.");
44
-
45
- /* --------------------------------------------------------------------------
46
- * PromptDefinition
47
- * ----------------------------------------------------------------------- */
48
-
49
- export interface PromptDefinition {
50
- id: string;
51
- argsSchema: z.ZodTypeAny;
52
- template: string; // Handlebars syntax
53
- }
54
-
55
- /* --------------------------------------------------------------------------
56
- * Prompt Templates (Handlebars strings)
57
- * ----------------------------------------------------------------------- */
58
-
59
- const explainTemplate = `
60
- <instructions>
61
- You are an expert Malloy data analyst.
62
- Explain the following Malloy query. Provide details on:
63
- • Purpose and intended insight
64
- • Key sources involved
65
- • Main transformations / calculations
66
- • Expected result structure (dimensions/measures)
67
- </instructions>
68
-
69
- <malloy_query>
70
- {{query_code}}
71
- </malloy_query>
72
-
73
- {{#if schema_context}}
74
- <model_schema_context uri="{{model_context_uri}}">
75
- {{schema_context}}
76
- </model_schema_context>
77
- {{/if}}
78
- `;
79
-
80
- const generateTemplate = `
81
- <instructions>
82
- You are an expert Malloy modeler and query writer.
83
- Generate a Malloy query that satisfies the goal below using the model at {{target_model_uri}}.
84
-
85
- Goal: {{natural_language_goal}}
86
-
87
- {{#if data_source_hints}}
88
- Hints to consider: {{data_source_hints}}
89
- {{/if}}
90
-
91
- Return ONLY the Malloy query code.
92
- </instructions>
93
-
94
- <malloy_model_context uri="{{target_model_uri}}" />
95
- {{#if schema_context}}
96
- <fetched_model_schema_context>
97
- {{schema_context}}
98
- </fetched_model_schema_context>
99
- {{/if}}
100
- `;
101
-
102
- const translateTemplate = `
103
- <instructions>
104
- You are fluent in both {{sql_dialect}} SQL and Malloy.
105
- Translate the SQL below into Malloy using the model at {{target_model_uri}} for schema context. Return ONLY Malloy code.
106
- </instructions>
107
-
108
- <sql_query dialect="{{sql_dialect}}">{{sql_query}}</sql_query>
109
-
110
- <malloy_model_context uri="{{target_model_uri}}" />
111
- {{#if schema_context}}
112
- <fetched_model_schema_context>
113
- {{schema_context}}
114
- </fetched_model_schema_context>
115
- {{/if}}
116
- `;
117
-
118
- const summarizeTemplate = `
119
- <instructions>
120
- You are an expert Malloy modeler.
121
- Provide a concise summary of the model at {{model_uri}} covering purpose, key sources, dimensions, measures & joins.
122
- </instructions>
123
-
124
- <malloy_model_content uri="{{model_uri}}" />
125
- {{#if model_def_content}}
126
- <fetched_model_definition>
127
- {{model_def_content}}
128
- </fetched_model_definition>
129
- {{/if}}
130
- `;
131
-
132
- /* --------------------------------------------------------------------------
133
- * PROMPTS registry
134
- * ----------------------------------------------------------------------- */
135
-
136
- export const PROMPTS: Record<string, PromptDefinition> = {
137
- [withVersion("explain-malloy-query", "1.0.0")]: {
138
- id: withVersion("explain-malloy-query", "1.0.0"),
139
- argsSchema: ExplainArgsSchema,
140
- template: explainTemplate,
141
- },
142
- [withVersion("generate-malloy-query-from-description", "1.0.0")]: {
143
- id: withVersion("generate-malloy-query-from-description", "1.0.0"),
144
- argsSchema: GenerateQueryArgsSchema,
145
- template: generateTemplate,
146
- },
147
- [withVersion("translate-sql-to-malloy", "1.0.0")]: {
148
- id: withVersion("translate-sql-to-malloy", "1.0.0"),
149
- argsSchema: TranslateSqlArgsSchema,
150
- template: translateTemplate,
151
- },
152
- [withVersion("summarize-malloy-model", "1.0.0")]: {
153
- id: withVersion("summarize-malloy-model", "1.0.0"),
154
- argsSchema: SummarizeModelArgsSchema,
155
- template: summarizeTemplate,
156
- },
157
- };
158
-
159
- // Backwards-compat alias until the old handlers are removed (step 4).
160
- export const MALLOY_PROMPTS = PROMPTS;
@@ -1,67 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { logger } from "../../logger";
4
- import { ProjectStore } from "../../service/project_store";
5
- import { promptHandlerMap } from "./handlers";
6
- import { MALLOY_PROMPTS } from "./prompt_definitions";
7
-
8
- /**
9
- * Registers all defined Malloy prompts with the MCP server.
10
- *
11
- * @param mcpServer The McpServer instance.
12
- * @param projectStore The ProjectStore instance for handlers to access project data.
13
- */
14
- export function registerPromptCapability(
15
- mcpServer: McpServer,
16
- projectStore: ProjectStore,
17
- ): void {
18
- logger.info("[MCP Init] Registering prompt capability...");
19
- const startTime = performance.now();
20
- let registeredCount = 0;
21
-
22
- for (const promptKey in MALLOY_PROMPTS) {
23
- const promptDefinition = MALLOY_PROMPTS[promptKey];
24
- const handler = promptHandlerMap[promptDefinition.id];
25
-
26
- if (handler && promptDefinition.argsSchema instanceof z.ZodObject) {
27
- // Prepare a handler that injects the projectStore
28
- const preparedHandler = (
29
- args: z.infer<typeof promptDefinition.argsSchema>,
30
- ) => handler(args, projectStore);
31
-
32
- // Register using prompt ID, the Zod schema shape, and the prepared handler.
33
- mcpServer.prompt(
34
- promptDefinition.id,
35
- (
36
- promptDefinition.argsSchema as z.ZodObject<
37
- z.ZodRawShape,
38
- z.UnknownKeysParam,
39
- z.ZodTypeAny,
40
- object,
41
- object
42
- >
43
- ).shape,
44
- preparedHandler,
45
- );
46
-
47
- registeredCount++;
48
- logger.info(
49
- `[MCP Prompt Reg] Registered prompt: ${promptDefinition.id}`,
50
- );
51
- } else if (!handler) {
52
- logger.warn(
53
- `Handler not found for prompt: ${promptDefinition.id}. It will not be registered.`,
54
- );
55
- } else if (!(promptDefinition.argsSchema instanceof z.ZodObject)) {
56
- logger.warn(
57
- `Prompt definition for ${promptDefinition.id} does not have a ZodObject schema. It will not be registered.`,
58
- );
59
- }
60
- }
61
-
62
- const endTime = performance.now();
63
- logger.info(
64
- `[MCP Init] Finished registering prompts. Registered: ${registeredCount}`,
65
- { duration: endTime - startTime },
66
- );
67
- }
@@ -1,62 +0,0 @@
1
- import { ProjectStore } from "../../service/project_store";
2
- import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
-
4
- // Type alias for compiled model pulled from OpenAPI-generated types.
5
- // Using `any` fallback in case the exact type path changes.
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- export type ApiCompiledModel = any;
8
-
9
- export interface ParsedMalloyUri {
10
- projectName: string;
11
- packageName: string;
12
- modelPath: string;
13
- }
14
-
15
- /**
16
- * Parse a canonical Malloy model URI of the form:
17
- * malloy://project/{project}/package/{package}/models/{modelPath}
18
- *
19
- * Throws an McpError(InvalidParams) if the URI is malformed.
20
- */
21
- export function parseMalloyUri(uri: string): ParsedMalloyUri {
22
- const match = uri.match(
23
- /^malloy:\/\/project\/([^/]+)\/package\/([^/]+)\/models\/(.+)$/,
24
- );
25
-
26
- if (!match) {
27
- throw new McpError(ErrorCode.InvalidParams, `Invalid Malloy URI: ${uri}`);
28
- }
29
-
30
- return {
31
- projectName: match[1],
32
- packageName: match[2],
33
- modelPath: match[3],
34
- };
35
- }
36
-
37
- /**
38
- * Retrieve the compiled model for the given Malloy URI, using the provided
39
- * ProjectStore. Results are memoised in `compiledModelCache`.
40
- */
41
- export async function getCompiledModel(
42
- uri: string,
43
- projectStore: ProjectStore,
44
- ): Promise<ApiCompiledModel> {
45
- const { projectName, packageName, modelPath } = parseMalloyUri(uri);
46
-
47
- // Look up the model via the ProjectStore hierarchy.
48
- const model = await projectStore
49
- .getProject(projectName, false)
50
- .then((p) => p.getPackage(packageName, false))
51
- .then((pkg) => pkg.getModel(modelPath));
52
-
53
- if (!model || model.getModelType() === "notebook") {
54
- throw new McpError(
55
- ErrorCode.InvalidParams,
56
- `Model ${uri} not found or refers to a notebook`,
57
- );
58
- }
59
-
60
- const compiled = await model.getModel();
61
- return compiled;
62
- }