@malloy-publisher/server 0.0.226 → 0.0.228
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.docker.md +8 -8
- package/README.md +2 -11
- package/dist/app/assets/{EnvironmentPage-DvOJ7L_b.js → EnvironmentPage-EW2lbGvb.js} +1 -1
- package/dist/app/assets/{HomePage-CXguJsXS.js → HomePage-Bkwc9Woc.js} +1 -1
- package/dist/app/assets/{LightMode-ZsshUznu.js → LightMode-Bum_KBpN.js} +1 -1
- package/dist/app/assets/{MainPage-BIe0VwBa.js → MainPage-oiEy7TNM.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-BuZ6UJVx.js → MaterializationsPage-C_VJsTgU.js} +1 -1
- package/dist/app/assets/{ModelPage-DsPf-s8B.js → ModelPage-z8REqAmk.js} +1 -1
- package/dist/app/assets/{PackagePage-CEVNAKZa.js → PackagePage-C2Vtt1Ln.js} +1 -1
- package/dist/app/assets/{RouteError-Chn7lL96.js → RouteError-DmJLpLXm.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DWC_FdNU.js → ThemeEditorPage-BywFjC7A.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CGrsFz8p.js → WorkbookPage-DCMizDMR.js} +1 -1
- package/dist/app/assets/{core-vVgoO8IR.es-BD_THWs_.js → core-CEDZMHV1.es-_yGzNgNH.js} +1 -1
- package/dist/app/assets/{index-D6YtyiJ0.js → index-CE9xhdra.js} +1 -1
- package/dist/app/assets/{index-BioohWQj.js → index-CdmFub34.js} +1 -1
- package/dist/app/assets/{index-gEWxu09x.js → index-DDMrjIT3.js} +1 -1
- package/dist/app/assets/{index-DNUZpnaa.js → index-EqslXZ44.js} +4 -4
- package/dist/app/index.html +1 -1
- package/dist/default-publisher.config.json +7 -7
- package/dist/package_load_worker.mjs +86 -24
- package/dist/runtime/publisher.js +5 -0
- package/dist/server.mjs +1415 -7876
- package/package.json +1 -4
- package/publisher.config.example.bigquery.json +7 -7
- package/publisher.config.example.duckdb.json +7 -7
- package/publisher.config.json +7 -11
- package/src/config.spec.ts +2 -2
- package/src/controller/package.controller.ts +62 -31
- package/src/default-publisher.config.json +7 -7
- package/src/health.ts +3 -8
- package/src/mcp/error_messages.ts +8 -37
- package/src/mcp/handler_utils.spec.ts +108 -0
- package/src/mcp/handler_utils.ts +99 -124
- package/src/mcp/mcp_constants.ts +0 -16
- package/src/mcp/server.protocol.spec.ts +138 -0
- package/src/mcp/server.ts +57 -37
- package/src/mcp/skills/build_skills_bundle.ts +1 -1
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/mcp/tools/compile_tool.spec.ts +207 -0
- package/src/mcp/tools/compile_tool.ts +177 -0
- package/src/mcp/tools/docs_search_tool.ts +1 -1
- package/src/mcp/tools/execute_query_tool.spec.ts +143 -0
- package/src/mcp/tools/execute_query_tool.ts +39 -6
- package/src/mcp/tools/get_context_eval.ts +3 -3
- package/src/mcp/tools/get_context_tool.spec.ts +196 -1
- package/src/mcp/tools/get_context_tool.ts +165 -67
- package/src/mcp/tools/reload_package_tool.spec.ts +232 -0
- package/src/mcp/tools/reload_package_tool.ts +158 -0
- package/src/package_load/package_load_pool.ts +3 -0
- package/src/package_load/package_load_worker.ts +68 -24
- package/src/package_load/protocol.ts +16 -0
- package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
- package/src/package_load/rpc_wait_accountant.ts +76 -0
- package/src/package_load_metrics.spec.ts +114 -0
- package/src/package_load_metrics.ts +127 -0
- package/src/pg_helpers.spec.ts +2 -206
- package/src/pg_helpers.ts +4 -120
- package/src/runtime/publisher.js +5 -0
- package/src/server.ts +7 -21
- package/src/service/environment.ts +71 -7
- package/src/service/model.spec.ts +92 -0
- package/src/service/model.ts +58 -7
- package/src/service/package.ts +113 -55
- package/src/service/package_reload_safety.spec.ts +193 -0
- package/src/test_helpers/metrics_harness.ts +40 -0
- package/tests/fixtures/query-givens/data/orders.csv +7 -0
- package/tests/fixtures/query-givens/model.malloy +34 -0
- package/tests/fixtures/query-givens/publisher.json +5 -0
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +22 -22
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
- package/tests/integration/query_givens/query_givens.integration.spec.ts +146 -0
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +121 -0
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +110 -0
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/src/dto/connection.dto.spec.ts +0 -186
- package/src/dto/connection.dto.ts +0 -308
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -27
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/ducklake_version.spec.ts +0 -43
- package/src/ducklake_version.ts +0 -26
- package/src/mcp/agent_server.protocol.spec.ts +0 -78
- package/src/mcp/agent_server.spec.ts +0 -18
- package/src/mcp/agent_server.ts +0 -144
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/environment_resource.ts +0 -187
- package/src/mcp/resources/model_resource.ts +0 -155
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -373
- package/src/mcp/resources/query_resource.ts +0 -122
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/tools/discovery_tools.ts +0 -280
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/StorageManager.mock.ts +0 -50
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mocks.ts +0 -39
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
-
import { URL } from "url";
|
|
7
|
-
import { PackageNotFoundError } from "../../errors";
|
|
8
|
-
import { logger } from "../../logger";
|
|
9
|
-
import { EnvironmentStore } from "../../service/environment_store";
|
|
10
|
-
import {
|
|
11
|
-
getInternalError,
|
|
12
|
-
getNotFoundError,
|
|
13
|
-
type ErrorDetails,
|
|
14
|
-
} from "../error_messages";
|
|
15
|
-
import {
|
|
16
|
-
buildMalloyUri,
|
|
17
|
-
handleResourceGet,
|
|
18
|
-
McpGetResourceError,
|
|
19
|
-
} from "../handler_utils";
|
|
20
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
21
|
-
|
|
22
|
-
// *** Define handleGetPackageContents function ***
|
|
23
|
-
async function handleGetPackageContents(
|
|
24
|
-
uri: URL,
|
|
25
|
-
params: { environmentName?: string; packageName?: string },
|
|
26
|
-
environmentStore: EnvironmentStore,
|
|
27
|
-
) {
|
|
28
|
-
try {
|
|
29
|
-
const { environmentName, packageName } = params;
|
|
30
|
-
if (typeof environmentName !== "string" || environmentName === "") {
|
|
31
|
-
throw new Error("Invalid environment name parameter.");
|
|
32
|
-
}
|
|
33
|
-
if (typeof packageName !== "string" || packageName === "") {
|
|
34
|
-
throw new Error("Invalid package name parameter.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const environment = await environmentStore.getEnvironment(
|
|
38
|
-
environmentName,
|
|
39
|
-
false,
|
|
40
|
-
);
|
|
41
|
-
const packageInstance = await environment.getPackage(packageName, false);
|
|
42
|
-
|
|
43
|
-
// Use listModels() which returns { path: string, type: 'source' | 'notebook' }[]
|
|
44
|
-
const entries = await packageInstance.listModels();
|
|
45
|
-
|
|
46
|
-
// Use a type that includes metadata explicitly
|
|
47
|
-
const resourceDefinitions: { uri: string; metadata: unknown }[] = [];
|
|
48
|
-
|
|
49
|
-
for (const entry of entries) {
|
|
50
|
-
const entryPath = entry.path; // e.g., "flights.malloy", "overview.malloynb"
|
|
51
|
-
// @ts-expect-error TODO: Fix missing type in API
|
|
52
|
-
const entryType = entry.type; // 'source' or 'notebook'
|
|
53
|
-
|
|
54
|
-
if (typeof entryPath !== "string" || entryPath === "") {
|
|
55
|
-
logger.warn(
|
|
56
|
-
`[MCP Server Warning] Skipping entry in package ${packageName} with invalid path:`,
|
|
57
|
-
entry,
|
|
58
|
-
);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Common components for URI building
|
|
63
|
-
const baseUriComponents = {
|
|
64
|
-
environment: environmentName,
|
|
65
|
-
package: packageName,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
switch (entryType) {
|
|
69
|
-
case "notebook": {
|
|
70
|
-
const resourceMetadata = RESOURCE_METADATA.notebook;
|
|
71
|
-
if (!resourceMetadata) {
|
|
72
|
-
logger.warn(
|
|
73
|
-
`[MCP Server Warning] No metadata found for entry type 'notebook' path ${entryPath} in package ${packageName}`,
|
|
74
|
-
);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const notebookUri = buildMalloyUri({
|
|
78
|
-
...baseUriComponents,
|
|
79
|
-
resourceType: "notebooks",
|
|
80
|
-
resourceName: entryPath,
|
|
81
|
-
});
|
|
82
|
-
resourceDefinitions.push({
|
|
83
|
-
uri: notebookUri,
|
|
84
|
-
metadata: resourceMetadata,
|
|
85
|
-
});
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
case "source": {
|
|
89
|
-
// 1. Add the source file itself
|
|
90
|
-
const sourceResourceMetadata = RESOURCE_METADATA.source;
|
|
91
|
-
if (!sourceResourceMetadata) {
|
|
92
|
-
logger.warn(
|
|
93
|
-
`[MCP Server Warning] No metadata found for entry type 'source' path ${entryPath} in package ${packageName}`,
|
|
94
|
-
);
|
|
95
|
-
// Continue processing views even if source metadata is missing
|
|
96
|
-
} else {
|
|
97
|
-
const sourceUri = buildMalloyUri({
|
|
98
|
-
...baseUriComponents,
|
|
99
|
-
resourceType: "sources", // Use 'sources' type for the source file resource
|
|
100
|
-
resourceName: entryPath,
|
|
101
|
-
});
|
|
102
|
-
resourceDefinitions.push({
|
|
103
|
-
uri: sourceUri,
|
|
104
|
-
metadata: sourceResourceMetadata,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 2. Try to load the model to find views within it
|
|
109
|
-
try {
|
|
110
|
-
const model = await packageInstance.getModel(entryPath); // Await the promise
|
|
111
|
-
if (model) {
|
|
112
|
-
// Ensure model exists before proceeding
|
|
113
|
-
const modelSources = model.getSources(); // This should be synchronous if model is loaded
|
|
114
|
-
|
|
115
|
-
if (modelSources && Array.isArray(modelSources)) {
|
|
116
|
-
for (const source of modelSources) {
|
|
117
|
-
if (source.views && Array.isArray(source.views)) {
|
|
118
|
-
for (const view of source.views) {
|
|
119
|
-
const viewResourceMetadata =
|
|
120
|
-
RESOURCE_METADATA.view;
|
|
121
|
-
if (!viewResourceMetadata) {
|
|
122
|
-
logger.warn(
|
|
123
|
-
`[MCP Server Warning] No metadata found for entry type 'view' named '${view.name}' in source '${source.name}'`,
|
|
124
|
-
);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
// Build the nested view URI using the helper
|
|
128
|
-
const viewUri = buildMalloyUri({
|
|
129
|
-
...baseUriComponents,
|
|
130
|
-
resourceType: "models", // Base is the model
|
|
131
|
-
resourceName: entryPath, // Model file path
|
|
132
|
-
subResourceType: "views",
|
|
133
|
-
subResourceName: view.name,
|
|
134
|
-
});
|
|
135
|
-
resourceDefinitions.push({
|
|
136
|
-
uri: viewUri,
|
|
137
|
-
metadata: viewResourceMetadata,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
logger.warn(
|
|
144
|
-
`[MCP Server Warning] Could not retrieve sources or sources is not an array for model ${entryPath}`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
logger.warn(
|
|
149
|
-
`[MCP Server Warning] Could not load model for path ${entryPath} to extract views.`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
} catch (modelLoadError) {
|
|
153
|
-
// Log error if model loading fails, but continue processing other files
|
|
154
|
-
logger.warn(
|
|
155
|
-
`[MCP Server Warning] Failed to load model ${entryPath} to extract views: ${modelLoadError instanceof Error ? modelLoadError.message : String(modelLoadError)}`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
default:
|
|
161
|
-
logger.warn(
|
|
162
|
-
`[MCP Server Warning] Unknown entry type '${entryType}' for path ${entryPath} in package ${packageName}`,
|
|
163
|
-
);
|
|
164
|
-
continue; // Skip unknown types
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return resourceDefinitions; // handleResourceGet will stringify this
|
|
169
|
-
} catch (error) {
|
|
170
|
-
let errorDetails;
|
|
171
|
-
if (error instanceof PackageNotFoundError) {
|
|
172
|
-
errorDetails = getNotFoundError(
|
|
173
|
-
`Package for contents listing matching URI '${uri.href}'`,
|
|
174
|
-
);
|
|
175
|
-
} else if (
|
|
176
|
-
error instanceof Error &&
|
|
177
|
-
(error.message.includes("Invalid package name") ||
|
|
178
|
-
error.message.includes("Invalid environment name"))
|
|
179
|
-
) {
|
|
180
|
-
errorDetails = getNotFoundError(
|
|
181
|
-
`Invalid environment/package identifier in URI '${uri.href}'`,
|
|
182
|
-
);
|
|
183
|
-
} else {
|
|
184
|
-
logger.error(
|
|
185
|
-
`[MCP Server Error] Error getting package contents for ${uri.href}:`,
|
|
186
|
-
{ error },
|
|
187
|
-
);
|
|
188
|
-
errorDetails = getInternalError(
|
|
189
|
-
`GetResource (package contents: ${uri.href})`,
|
|
190
|
-
error,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
throw new McpGetResourceError(errorDetails);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Registers the Malloy Package resource type with the MCP server.
|
|
199
|
-
* Handles listing models/notebooks/sources within packages and getting package details.
|
|
200
|
-
*/
|
|
201
|
-
export function registerPackageResource(
|
|
202
|
-
mcpServer: McpServer,
|
|
203
|
-
environmentStore: EnvironmentStore,
|
|
204
|
-
): void {
|
|
205
|
-
// Define the expected parameter type for the read callback
|
|
206
|
-
type ReadPackageParams = {
|
|
207
|
-
fsPath?: string; // Expect string
|
|
208
|
-
environmentName?: string; // Expect string
|
|
209
|
-
packageName?: string; // Expect string
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
mcpServer.resource(
|
|
213
|
-
"package",
|
|
214
|
-
new ResourceTemplate(
|
|
215
|
-
"malloy://environment/{environmentName}/package/{packageName}",
|
|
216
|
-
{
|
|
217
|
-
list: undefined, // TODO: Implement and add listAllPackages here if needed
|
|
218
|
-
},
|
|
219
|
-
),
|
|
220
|
-
(uri, params) =>
|
|
221
|
-
handleResourceGet(
|
|
222
|
-
uri,
|
|
223
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
224
|
-
params as ReadPackageParams, // Cast params to the updated type
|
|
225
|
-
"package",
|
|
226
|
-
async ({
|
|
227
|
-
environmentName,
|
|
228
|
-
packageName,
|
|
229
|
-
}: {
|
|
230
|
-
// Keep unknown here as it reflects the raw input possibility
|
|
231
|
-
// before validation inside the async function
|
|
232
|
-
environmentName?: unknown;
|
|
233
|
-
packageName?: unknown;
|
|
234
|
-
}) => {
|
|
235
|
-
try {
|
|
236
|
-
if (typeof environmentName !== "string") {
|
|
237
|
-
throw new Error("Invalid environment name parameter.");
|
|
238
|
-
}
|
|
239
|
-
if (typeof packageName !== "string") {
|
|
240
|
-
throw new Error("Invalid package name parameter.");
|
|
241
|
-
}
|
|
242
|
-
const environment = await environmentStore.getEnvironment(
|
|
243
|
-
environmentName,
|
|
244
|
-
false,
|
|
245
|
-
);
|
|
246
|
-
const pkg = await environment.getPackage(packageName, false);
|
|
247
|
-
return pkg.getPackageMetadata();
|
|
248
|
-
} catch (error) {
|
|
249
|
-
let errorDetails;
|
|
250
|
-
if (error instanceof PackageNotFoundError) {
|
|
251
|
-
errorDetails = getNotFoundError(
|
|
252
|
-
`Package resource matching URI '${uri.href}'`,
|
|
253
|
-
);
|
|
254
|
-
} else if (
|
|
255
|
-
error instanceof Error &&
|
|
256
|
-
(error.message.includes("Invalid package name") ||
|
|
257
|
-
error.message.includes("Invalid environment name"))
|
|
258
|
-
) {
|
|
259
|
-
errorDetails = getNotFoundError(
|
|
260
|
-
`Invalid environment/package identifier in URI '${uri.href}'`,
|
|
261
|
-
);
|
|
262
|
-
} else {
|
|
263
|
-
errorDetails = getInternalError(
|
|
264
|
-
`GetResource (package: ${uri.href})`,
|
|
265
|
-
error,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
throw new McpGetResourceError(errorDetails);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
RESOURCE_METADATA.package,
|
|
272
|
-
),
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
// *** Register Package Contents Resource ***
|
|
276
|
-
mcpServer.resource(
|
|
277
|
-
"package-contents",
|
|
278
|
-
new ResourceTemplate(
|
|
279
|
-
"malloy://environment/{environmentName}/package/{packageName}/contents",
|
|
280
|
-
{
|
|
281
|
-
// Contents are listed via GetResource on this specific URI,
|
|
282
|
-
// so no general list callback needed here.
|
|
283
|
-
list: undefined,
|
|
284
|
-
},
|
|
285
|
-
),
|
|
286
|
-
// Custom handler because the response format is just the array, not {definition, metadata}
|
|
287
|
-
async (
|
|
288
|
-
uri: URL,
|
|
289
|
-
params: ReadPackageParams,
|
|
290
|
-
): Promise<ReadResourceResult> => {
|
|
291
|
-
try {
|
|
292
|
-
// Reuse param validation/typing
|
|
293
|
-
const validatedParams = params as ReadPackageParams;
|
|
294
|
-
|
|
295
|
-
// Get the array of resource definitions
|
|
296
|
-
const resourceDefinitions = await handleGetPackageContents(
|
|
297
|
-
uri,
|
|
298
|
-
validatedParams,
|
|
299
|
-
environmentStore,
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
// Return the array directly as JSON content
|
|
303
|
-
return {
|
|
304
|
-
contents: [
|
|
305
|
-
{
|
|
306
|
-
type: "application/json",
|
|
307
|
-
uri: uri.href,
|
|
308
|
-
text: JSON.stringify(resourceDefinitions, null, 2),
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
};
|
|
312
|
-
} catch (error) {
|
|
313
|
-
logger.error(
|
|
314
|
-
`[MCP Server Error] Error reading package contents ${uri.href}:`,
|
|
315
|
-
{ error },
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
let errorDetails: ErrorDetails;
|
|
319
|
-
|
|
320
|
-
// Check if it's the specific error thrown by handleGetPackageContents
|
|
321
|
-
if (error instanceof McpGetResourceError) {
|
|
322
|
-
// Specifically check if it's a 'not found' error from the inner handler
|
|
323
|
-
if (error.message.includes("not found")) {
|
|
324
|
-
// Re-use the not found details directly
|
|
325
|
-
errorDetails = error.details;
|
|
326
|
-
} else {
|
|
327
|
-
// It was a different McpGetResourceError (e.g., internal from inner handler)
|
|
328
|
-
errorDetails = error.details;
|
|
329
|
-
}
|
|
330
|
-
} else if (error instanceof PackageNotFoundError) {
|
|
331
|
-
// Handle PackageNotFoundError specifically if it bubbles up here
|
|
332
|
-
errorDetails = getNotFoundError(
|
|
333
|
-
`Package for contents listing matching URI '${uri.href}'`,
|
|
334
|
-
);
|
|
335
|
-
} else if (
|
|
336
|
-
error instanceof Error &&
|
|
337
|
-
(error.message.includes("Invalid package name") ||
|
|
338
|
-
error.message.includes("Invalid environment name"))
|
|
339
|
-
) {
|
|
340
|
-
// Handle invalid identifier errors specifically
|
|
341
|
-
errorDetails = getNotFoundError(
|
|
342
|
-
`Invalid environment/package identifier in URI '${uri.href}'`,
|
|
343
|
-
);
|
|
344
|
-
} else {
|
|
345
|
-
// Handle other unexpected errors
|
|
346
|
-
errorDetails = getInternalError(
|
|
347
|
-
`GetResource (package contents: ${uri.href})`,
|
|
348
|
-
error,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Format the error response
|
|
353
|
-
return {
|
|
354
|
-
isError: true,
|
|
355
|
-
contents: [
|
|
356
|
-
{
|
|
357
|
-
type: "application/json",
|
|
358
|
-
uri: uri.href,
|
|
359
|
-
text: JSON.stringify(
|
|
360
|
-
{
|
|
361
|
-
error: errorDetails.message,
|
|
362
|
-
suggestions: errorDetails.suggestions,
|
|
363
|
-
},
|
|
364
|
-
null,
|
|
365
|
-
2,
|
|
366
|
-
),
|
|
367
|
-
},
|
|
368
|
-
],
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
);
|
|
373
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { URL } from "url";
|
|
6
|
-
import type { components } from "../../api"; // Need this for Query definition type
|
|
7
|
-
import { ModelCompilationError } from "../../errors";
|
|
8
|
-
import { logger } from "../../logger";
|
|
9
|
-
import { EnvironmentStore } from "../../service/environment_store";
|
|
10
|
-
import {
|
|
11
|
-
getInternalError,
|
|
12
|
-
getMalloyErrorDetails,
|
|
13
|
-
getNotFoundError,
|
|
14
|
-
} from "../error_messages";
|
|
15
|
-
import {
|
|
16
|
-
getModelForQuery,
|
|
17
|
-
handleResourceGet,
|
|
18
|
-
McpGetResourceError,
|
|
19
|
-
} from "../handler_utils";
|
|
20
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
21
|
-
|
|
22
|
-
// Define the expected parameter types
|
|
23
|
-
type QueryParams = {
|
|
24
|
-
environmentName?: unknown;
|
|
25
|
-
packageName?: unknown;
|
|
26
|
-
modelPath?: unknown;
|
|
27
|
-
queryName?: unknown;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Registers the Malloy Query resource type (nested within a Model).
|
|
32
|
-
*/
|
|
33
|
-
export function registerQueryResource(
|
|
34
|
-
mcpServer: McpServer,
|
|
35
|
-
environmentStore: EnvironmentStore,
|
|
36
|
-
): void {
|
|
37
|
-
mcpServer.resource(
|
|
38
|
-
"query",
|
|
39
|
-
new ResourceTemplate(
|
|
40
|
-
"malloy://environment/{environmentName}/package/{packageName}/models/{modelPath}/queries/{queryName}",
|
|
41
|
-
{ list: undefined }, // Listing queries is not supported via this template
|
|
42
|
-
),
|
|
43
|
-
(uri, params) =>
|
|
44
|
-
handleResourceGet(
|
|
45
|
-
uri,
|
|
46
|
-
params as QueryParams,
|
|
47
|
-
"query",
|
|
48
|
-
async (
|
|
49
|
-
{
|
|
50
|
-
environmentName,
|
|
51
|
-
packageName,
|
|
52
|
-
modelPath,
|
|
53
|
-
queryName,
|
|
54
|
-
}: QueryParams,
|
|
55
|
-
uri: URL,
|
|
56
|
-
) => {
|
|
57
|
-
if (
|
|
58
|
-
typeof environmentName !== "string" ||
|
|
59
|
-
typeof packageName !== "string" ||
|
|
60
|
-
typeof modelPath !== "string" ||
|
|
61
|
-
typeof queryName !== "string"
|
|
62
|
-
) {
|
|
63
|
-
throw new Error("Invalid parameters for query resource.");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const modelResult = await getModelForQuery(
|
|
68
|
-
environmentStore,
|
|
69
|
-
environmentName,
|
|
70
|
-
packageName,
|
|
71
|
-
modelPath,
|
|
72
|
-
);
|
|
73
|
-
if ("error" in modelResult) {
|
|
74
|
-
throw new McpGetResourceError(modelResult.error);
|
|
75
|
-
}
|
|
76
|
-
const { model } = modelResult;
|
|
77
|
-
|
|
78
|
-
// Attempt to get the query definition from the model
|
|
79
|
-
const queries = await model.getQueries(); // Assuming this method exists
|
|
80
|
-
if (!queries) {
|
|
81
|
-
throw new Error("Could not retrieve queries from model.");
|
|
82
|
-
}
|
|
83
|
-
const query = queries.find(
|
|
84
|
-
(q: components["schemas"]["Query"]) =>
|
|
85
|
-
q.name === queryName,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
if (!query) {
|
|
89
|
-
// Specific "Query not found" error
|
|
90
|
-
const errorDetails = getNotFoundError(
|
|
91
|
-
`Query '${queryName}' in model '${modelPath}' package '${packageName}' environment '${environmentName}'`,
|
|
92
|
-
);
|
|
93
|
-
throw new McpGetResourceError(errorDetails);
|
|
94
|
-
}
|
|
95
|
-
return query;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error instanceof McpGetResourceError) {
|
|
98
|
-
throw error; // Re-throw already formatted
|
|
99
|
-
}
|
|
100
|
-
if (error instanceof ModelCompilationError) {
|
|
101
|
-
const errorDetails = getMalloyErrorDetails(
|
|
102
|
-
"GetResource (query - model compilation)",
|
|
103
|
-
`${environmentName}/${packageName}/${modelPath}`,
|
|
104
|
-
error,
|
|
105
|
-
);
|
|
106
|
-
throw new McpGetResourceError(errorDetails);
|
|
107
|
-
}
|
|
108
|
-
logger.error(
|
|
109
|
-
`[MCP Server Error] Error fetching query '${queryName}' from ${uri.href}:`,
|
|
110
|
-
{ error },
|
|
111
|
-
);
|
|
112
|
-
const fallbackErrorDetails = getInternalError(
|
|
113
|
-
`GetResource (query: ${uri.href})`,
|
|
114
|
-
error,
|
|
115
|
-
);
|
|
116
|
-
throw new McpGetResourceError(fallbackErrorDetails);
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
RESOURCE_METADATA.query,
|
|
120
|
-
),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { URL } from "url";
|
|
6
|
-
import type { components } from "../../api";
|
|
7
|
-
import { ModelCompilationError } from "../../errors";
|
|
8
|
-
import { logger } from "../../logger";
|
|
9
|
-
import { EnvironmentStore } from "../../service/environment_store";
|
|
10
|
-
import {
|
|
11
|
-
getInternalError,
|
|
12
|
-
getMalloyErrorDetails,
|
|
13
|
-
getNotFoundError,
|
|
14
|
-
} from "../error_messages";
|
|
15
|
-
import {
|
|
16
|
-
getModelForQuery,
|
|
17
|
-
handleResourceGet,
|
|
18
|
-
McpGetResourceError,
|
|
19
|
-
} from "../handler_utils";
|
|
20
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
21
|
-
|
|
22
|
-
// Define the expected parameter types for this resource
|
|
23
|
-
type SourceParams = {
|
|
24
|
-
environmentName?: unknown;
|
|
25
|
-
packageName?: unknown;
|
|
26
|
-
modelPath?: unknown;
|
|
27
|
-
sourceName?: unknown;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Registers the Malloy Source resource type (nested within a Model).
|
|
32
|
-
* Handles getting details for a specific source.
|
|
33
|
-
*/
|
|
34
|
-
export function registerSourceResource(
|
|
35
|
-
mcpServer: McpServer,
|
|
36
|
-
environmentStore: EnvironmentStore,
|
|
37
|
-
): void {
|
|
38
|
-
mcpServer.resource(
|
|
39
|
-
"source",
|
|
40
|
-
new ResourceTemplate(
|
|
41
|
-
"malloy://environment/{environmentName}/package/{packageName}/models/{modelPath}/sources/{sourceName}",
|
|
42
|
-
{ list: undefined }, // Listing sources directly is not supported via this template
|
|
43
|
-
),
|
|
44
|
-
/** Handles GetResource requests for specific Sources within a Model */
|
|
45
|
-
(uri, params) =>
|
|
46
|
-
handleResourceGet(
|
|
47
|
-
uri,
|
|
48
|
-
params as SourceParams, // Cast params
|
|
49
|
-
"source",
|
|
50
|
-
// Define the getData async callback
|
|
51
|
-
async (
|
|
52
|
-
{
|
|
53
|
-
environmentName,
|
|
54
|
-
packageName,
|
|
55
|
-
modelPath,
|
|
56
|
-
sourceName,
|
|
57
|
-
}: SourceParams, // Use defined type
|
|
58
|
-
// Add type for uri
|
|
59
|
-
uri: URL,
|
|
60
|
-
) => {
|
|
61
|
-
// Input validation
|
|
62
|
-
if (
|
|
63
|
-
typeof environmentName !== "string" ||
|
|
64
|
-
typeof packageName !== "string" ||
|
|
65
|
-
typeof modelPath !== "string" ||
|
|
66
|
-
typeof sourceName !== "string"
|
|
67
|
-
) {
|
|
68
|
-
// Throw simple error, handleResourceGet will wrap it
|
|
69
|
-
throw new Error("Invalid parameters for source resource.");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
// Use the imported getModelForQuery
|
|
74
|
-
const modelResult = await getModelForQuery(
|
|
75
|
-
environmentStore,
|
|
76
|
-
environmentName,
|
|
77
|
-
packageName,
|
|
78
|
-
modelPath,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Handle error case from getModelForQuery
|
|
82
|
-
if ("error" in modelResult) {
|
|
83
|
-
throw new McpGetResourceError(modelResult.error);
|
|
84
|
-
}
|
|
85
|
-
const { model } = modelResult;
|
|
86
|
-
|
|
87
|
-
const sources = await model.getSources();
|
|
88
|
-
// Add check for undefined sources
|
|
89
|
-
if (!sources) {
|
|
90
|
-
throw new Error("Could not retrieve sources from model.");
|
|
91
|
-
}
|
|
92
|
-
const source = sources.find(
|
|
93
|
-
(s: components["schemas"]["Source"]) =>
|
|
94
|
-
s.name === sourceName,
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
if (!source) {
|
|
98
|
-
const errorDetails = getNotFoundError(
|
|
99
|
-
`Source '${sourceName}' in model '${modelPath}' package '${packageName}' environment '${environmentName}'`,
|
|
100
|
-
);
|
|
101
|
-
throw new McpGetResourceError(errorDetails);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Strip implicit filters from agent-facing responses
|
|
105
|
-
if (source.filters) {
|
|
106
|
-
source.filters = source.filters.filter((f) => !f.implicit);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return source;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
// Catch errors from getModelForQuery or finding the source
|
|
112
|
-
if (error instanceof McpGetResourceError) {
|
|
113
|
-
throw error; // Re-throw already formatted errors
|
|
114
|
-
}
|
|
115
|
-
// Check if it's a compilation error (likely from getModel call within getModelForQuery)
|
|
116
|
-
if (error instanceof ModelCompilationError) {
|
|
117
|
-
const errorDetails = getMalloyErrorDetails(
|
|
118
|
-
"GetResource (source - model compilation)",
|
|
119
|
-
`${environmentName}/${packageName}/${modelPath}`,
|
|
120
|
-
error,
|
|
121
|
-
);
|
|
122
|
-
throw new McpGetResourceError(errorDetails);
|
|
123
|
-
}
|
|
124
|
-
// Handle other potential errors (e.g., invalid params error from initial check)
|
|
125
|
-
logger.error(
|
|
126
|
-
`[MCP Server Error] Error fetching source '${sourceName}' from ${uri.href}:`,
|
|
127
|
-
{ error },
|
|
128
|
-
);
|
|
129
|
-
// Fallback: Use getInternalError for unexpected issues
|
|
130
|
-
// Or getNotFoundError if it's likely a resource access issue
|
|
131
|
-
const fallbackErrorDetails = getInternalError(
|
|
132
|
-
`GetResource (source: ${uri.href})`,
|
|
133
|
-
error,
|
|
134
|
-
);
|
|
135
|
-
throw new McpGetResourceError(fallbackErrorDetails);
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
RESOURCE_METADATA.source, // Use source-specific metadata
|
|
139
|
-
),
|
|
140
|
-
);
|
|
141
|
-
}
|