@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.
- package/README.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3929 -534
- package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
- package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
- package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
- package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
- package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
- package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
- package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
- package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
- package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
- package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-C5ClsnS7.js +1774 -0
- package/dist/app/assets/index-DF9tVqwN.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DgVRz2wm.js +2 -0
- package/dist/app/assets/index-P-pF8nU5.js +632 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +141276 -74705
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- 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/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
|
@@ -1,370 +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 { ProjectStore } from "../../service/project_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: { projectName?: string; packageName?: string },
|
|
26
|
-
projectStore: ProjectStore,
|
|
27
|
-
) {
|
|
28
|
-
try {
|
|
29
|
-
const { projectName, packageName } = params;
|
|
30
|
-
if (typeof projectName !== "string" || projectName === "") {
|
|
31
|
-
throw new Error("Invalid project name parameter.");
|
|
32
|
-
}
|
|
33
|
-
if (typeof packageName !== "string" || packageName === "") {
|
|
34
|
-
throw new Error("Invalid package name parameter.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const project = await projectStore.getProject(projectName, false);
|
|
38
|
-
const packageInstance = await project.getPackage(packageName, false);
|
|
39
|
-
|
|
40
|
-
// Use listModels() which returns { path: string, type: 'source' | 'notebook' }[]
|
|
41
|
-
const entries = await packageInstance.listModels();
|
|
42
|
-
|
|
43
|
-
// Use a type that includes metadata explicitly
|
|
44
|
-
const resourceDefinitions: { uri: string; metadata: unknown }[] = [];
|
|
45
|
-
|
|
46
|
-
for (const entry of entries) {
|
|
47
|
-
const entryPath = entry.path; // e.g., "flights.malloy", "overview.malloynb"
|
|
48
|
-
// @ts-expect-error TODO: Fix missing type in API
|
|
49
|
-
const entryType = entry.type; // 'source' or 'notebook'
|
|
50
|
-
|
|
51
|
-
if (typeof entryPath !== "string" || entryPath === "") {
|
|
52
|
-
logger.warn(
|
|
53
|
-
`[MCP Server Warning] Skipping entry in package ${packageName} with invalid path:`,
|
|
54
|
-
entry,
|
|
55
|
-
);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Common components for URI building
|
|
60
|
-
const baseUriComponents = {
|
|
61
|
-
project: projectName,
|
|
62
|
-
package: packageName,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
switch (entryType) {
|
|
66
|
-
case "notebook": {
|
|
67
|
-
const resourceMetadata = RESOURCE_METADATA.notebook;
|
|
68
|
-
if (!resourceMetadata) {
|
|
69
|
-
logger.warn(
|
|
70
|
-
`[MCP Server Warning] No metadata found for entry type 'notebook' path ${entryPath} in package ${packageName}`,
|
|
71
|
-
);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const notebookUri = buildMalloyUri({
|
|
75
|
-
...baseUriComponents,
|
|
76
|
-
resourceType: "notebooks",
|
|
77
|
-
resourceName: entryPath,
|
|
78
|
-
});
|
|
79
|
-
resourceDefinitions.push({
|
|
80
|
-
uri: notebookUri,
|
|
81
|
-
metadata: resourceMetadata,
|
|
82
|
-
});
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
case "source": {
|
|
86
|
-
// 1. Add the source file itself
|
|
87
|
-
const sourceResourceMetadata = RESOURCE_METADATA.source;
|
|
88
|
-
if (!sourceResourceMetadata) {
|
|
89
|
-
logger.warn(
|
|
90
|
-
`[MCP Server Warning] No metadata found for entry type 'source' path ${entryPath} in package ${packageName}`,
|
|
91
|
-
);
|
|
92
|
-
// Continue processing views even if source metadata is missing
|
|
93
|
-
} else {
|
|
94
|
-
const sourceUri = buildMalloyUri({
|
|
95
|
-
...baseUriComponents,
|
|
96
|
-
resourceType: "sources", // Use 'sources' type for the source file resource
|
|
97
|
-
resourceName: entryPath,
|
|
98
|
-
});
|
|
99
|
-
resourceDefinitions.push({
|
|
100
|
-
uri: sourceUri,
|
|
101
|
-
metadata: sourceResourceMetadata,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 2. Try to load the model to find views within it
|
|
106
|
-
try {
|
|
107
|
-
const model = await packageInstance.getModel(entryPath); // Await the promise
|
|
108
|
-
if (model) {
|
|
109
|
-
// Ensure model exists before proceeding
|
|
110
|
-
const modelSources = model.getSources(); // This should be synchronous if model is loaded
|
|
111
|
-
|
|
112
|
-
if (modelSources && Array.isArray(modelSources)) {
|
|
113
|
-
for (const source of modelSources) {
|
|
114
|
-
if (source.views && Array.isArray(source.views)) {
|
|
115
|
-
for (const view of source.views) {
|
|
116
|
-
const viewResourceMetadata =
|
|
117
|
-
RESOURCE_METADATA.view;
|
|
118
|
-
if (!viewResourceMetadata) {
|
|
119
|
-
logger.warn(
|
|
120
|
-
`[MCP Server Warning] No metadata found for entry type 'view' named '${view.name}' in source '${source.name}'`,
|
|
121
|
-
);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
// Build the nested view URI using the helper
|
|
125
|
-
const viewUri = buildMalloyUri({
|
|
126
|
-
...baseUriComponents,
|
|
127
|
-
resourceType: "models", // Base is the model
|
|
128
|
-
resourceName: entryPath, // Model file path
|
|
129
|
-
subResourceType: "views",
|
|
130
|
-
subResourceName: view.name,
|
|
131
|
-
});
|
|
132
|
-
resourceDefinitions.push({
|
|
133
|
-
uri: viewUri,
|
|
134
|
-
metadata: viewResourceMetadata,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
logger.warn(
|
|
141
|
-
`[MCP Server Warning] Could not retrieve sources or sources is not an array for model ${entryPath}`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
logger.warn(
|
|
146
|
-
`[MCP Server Warning] Could not load model for path ${entryPath} to extract views.`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
} catch (modelLoadError) {
|
|
150
|
-
// Log error if model loading fails, but continue processing other files
|
|
151
|
-
logger.warn(
|
|
152
|
-
`[MCP Server Warning] Failed to load model ${entryPath} to extract views: ${modelLoadError instanceof Error ? modelLoadError.message : String(modelLoadError)}`,
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
default:
|
|
158
|
-
logger.warn(
|
|
159
|
-
`[MCP Server Warning] Unknown entry type '${entryType}' for path ${entryPath} in package ${packageName}`,
|
|
160
|
-
);
|
|
161
|
-
continue; // Skip unknown types
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return resourceDefinitions; // handleResourceGet will stringify this
|
|
166
|
-
} catch (error) {
|
|
167
|
-
let errorDetails;
|
|
168
|
-
if (error instanceof PackageNotFoundError) {
|
|
169
|
-
errorDetails = getNotFoundError(
|
|
170
|
-
`Package for contents listing matching URI '${uri.href}'`,
|
|
171
|
-
);
|
|
172
|
-
} else if (
|
|
173
|
-
error instanceof Error &&
|
|
174
|
-
(error.message.includes("Invalid package name") ||
|
|
175
|
-
error.message.includes("Invalid project name"))
|
|
176
|
-
) {
|
|
177
|
-
errorDetails = getNotFoundError(
|
|
178
|
-
`Invalid project/package identifier in URI '${uri.href}'`,
|
|
179
|
-
);
|
|
180
|
-
} else {
|
|
181
|
-
logger.error(
|
|
182
|
-
`[MCP Server Error] Error getting package contents for ${uri.href}:`,
|
|
183
|
-
{ error },
|
|
184
|
-
);
|
|
185
|
-
errorDetails = getInternalError(
|
|
186
|
-
`GetResource (package contents: ${uri.href})`,
|
|
187
|
-
error,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
throw new McpGetResourceError(errorDetails);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Registers the Malloy Package resource type with the MCP server.
|
|
196
|
-
* Handles listing models/notebooks/sources within packages and getting package details.
|
|
197
|
-
*/
|
|
198
|
-
export function registerPackageResource(
|
|
199
|
-
mcpServer: McpServer,
|
|
200
|
-
projectStore: ProjectStore,
|
|
201
|
-
): void {
|
|
202
|
-
// Define the expected parameter type for the read callback
|
|
203
|
-
type ReadPackageParams = {
|
|
204
|
-
fsPath?: string; // Expect string
|
|
205
|
-
projectName?: string; // Expect string
|
|
206
|
-
packageName?: string; // Expect string
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
mcpServer.resource(
|
|
210
|
-
"package",
|
|
211
|
-
new ResourceTemplate(
|
|
212
|
-
"malloy://project/{projectName}/package/{packageName}",
|
|
213
|
-
{
|
|
214
|
-
list: undefined, // TODO: Implement and add listAllPackages here if needed
|
|
215
|
-
},
|
|
216
|
-
),
|
|
217
|
-
(uri, params) =>
|
|
218
|
-
handleResourceGet(
|
|
219
|
-
uri,
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
-
params as ReadPackageParams, // Cast params to the updated type
|
|
222
|
-
"package",
|
|
223
|
-
async ({
|
|
224
|
-
projectName,
|
|
225
|
-
packageName,
|
|
226
|
-
}: {
|
|
227
|
-
// Keep unknown here as it reflects the raw input possibility
|
|
228
|
-
// before validation inside the async function
|
|
229
|
-
projectName?: unknown;
|
|
230
|
-
packageName?: unknown;
|
|
231
|
-
}) => {
|
|
232
|
-
try {
|
|
233
|
-
if (typeof projectName !== "string") {
|
|
234
|
-
throw new Error("Invalid project name parameter.");
|
|
235
|
-
}
|
|
236
|
-
if (typeof packageName !== "string") {
|
|
237
|
-
throw new Error("Invalid package name parameter.");
|
|
238
|
-
}
|
|
239
|
-
const project = await projectStore.getProject(
|
|
240
|
-
projectName,
|
|
241
|
-
false,
|
|
242
|
-
);
|
|
243
|
-
const pkg = await project.getPackage(packageName, false);
|
|
244
|
-
return pkg.getPackageMetadata();
|
|
245
|
-
} catch (error) {
|
|
246
|
-
let errorDetails;
|
|
247
|
-
if (error instanceof PackageNotFoundError) {
|
|
248
|
-
errorDetails = getNotFoundError(
|
|
249
|
-
`Package resource matching URI '${uri.href}'`,
|
|
250
|
-
);
|
|
251
|
-
} else if (
|
|
252
|
-
error instanceof Error &&
|
|
253
|
-
(error.message.includes("Invalid package name") ||
|
|
254
|
-
error.message.includes("Invalid project name"))
|
|
255
|
-
) {
|
|
256
|
-
errorDetails = getNotFoundError(
|
|
257
|
-
`Invalid project/package identifier in URI '${uri.href}'`,
|
|
258
|
-
);
|
|
259
|
-
} else {
|
|
260
|
-
errorDetails = getInternalError(
|
|
261
|
-
`GetResource (package: ${uri.href})`,
|
|
262
|
-
error,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
throw new McpGetResourceError(errorDetails);
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
RESOURCE_METADATA.package,
|
|
269
|
-
),
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
// *** Register Package Contents Resource ***
|
|
273
|
-
mcpServer.resource(
|
|
274
|
-
"package-contents",
|
|
275
|
-
new ResourceTemplate(
|
|
276
|
-
"malloy://project/{projectName}/package/{packageName}/contents",
|
|
277
|
-
{
|
|
278
|
-
// Contents are listed via GetResource on this specific URI,
|
|
279
|
-
// so no general list callback needed here.
|
|
280
|
-
list: undefined,
|
|
281
|
-
},
|
|
282
|
-
),
|
|
283
|
-
// Custom handler because the response format is just the array, not {definition, metadata}
|
|
284
|
-
async (
|
|
285
|
-
uri: URL,
|
|
286
|
-
params: ReadPackageParams,
|
|
287
|
-
): Promise<ReadResourceResult> => {
|
|
288
|
-
try {
|
|
289
|
-
// Reuse param validation/typing
|
|
290
|
-
const validatedParams = params as ReadPackageParams;
|
|
291
|
-
|
|
292
|
-
// Get the array of resource definitions
|
|
293
|
-
const resourceDefinitions = await handleGetPackageContents(
|
|
294
|
-
uri,
|
|
295
|
-
validatedParams,
|
|
296
|
-
projectStore,
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
// Return the array directly as JSON content
|
|
300
|
-
return {
|
|
301
|
-
contents: [
|
|
302
|
-
{
|
|
303
|
-
type: "application/json",
|
|
304
|
-
uri: uri.href,
|
|
305
|
-
text: JSON.stringify(resourceDefinitions, null, 2),
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
};
|
|
309
|
-
} catch (error) {
|
|
310
|
-
logger.error(
|
|
311
|
-
`[MCP Server Error] Error reading package contents ${uri.href}:`,
|
|
312
|
-
{ error },
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
let errorDetails: ErrorDetails;
|
|
316
|
-
|
|
317
|
-
// Check if it's the specific error thrown by handleGetPackageContents
|
|
318
|
-
if (error instanceof McpGetResourceError) {
|
|
319
|
-
// Specifically check if it's a 'not found' error from the inner handler
|
|
320
|
-
if (error.message.includes("not found")) {
|
|
321
|
-
// Re-use the not found details directly
|
|
322
|
-
errorDetails = error.details;
|
|
323
|
-
} else {
|
|
324
|
-
// It was a different McpGetResourceError (e.g., internal from inner handler)
|
|
325
|
-
errorDetails = error.details;
|
|
326
|
-
}
|
|
327
|
-
} else if (error instanceof PackageNotFoundError) {
|
|
328
|
-
// Handle PackageNotFoundError specifically if it bubbles up here
|
|
329
|
-
errorDetails = getNotFoundError(
|
|
330
|
-
`Package for contents listing matching URI '${uri.href}'`,
|
|
331
|
-
);
|
|
332
|
-
} else if (
|
|
333
|
-
error instanceof Error &&
|
|
334
|
-
(error.message.includes("Invalid package name") ||
|
|
335
|
-
error.message.includes("Invalid project name"))
|
|
336
|
-
) {
|
|
337
|
-
// Handle invalid identifier errors specifically
|
|
338
|
-
errorDetails = getNotFoundError(
|
|
339
|
-
`Invalid project/package identifier in URI '${uri.href}'`,
|
|
340
|
-
);
|
|
341
|
-
} else {
|
|
342
|
-
// Handle other unexpected errors
|
|
343
|
-
errorDetails = getInternalError(
|
|
344
|
-
`GetResource (package contents: ${uri.href})`,
|
|
345
|
-
error,
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Format the error response
|
|
350
|
-
return {
|
|
351
|
-
isError: true,
|
|
352
|
-
contents: [
|
|
353
|
-
{
|
|
354
|
-
type: "application/json",
|
|
355
|
-
uri: uri.href,
|
|
356
|
-
text: JSON.stringify(
|
|
357
|
-
{
|
|
358
|
-
error: errorDetails.message,
|
|
359
|
-
suggestions: errorDetails.suggestions,
|
|
360
|
-
},
|
|
361
|
-
null,
|
|
362
|
-
2,
|
|
363
|
-
),
|
|
364
|
-
},
|
|
365
|
-
],
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
},
|
|
369
|
-
);
|
|
370
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import type { ListResourcesResult } from "@modelcontextprotocol/sdk/types.js"; // Needed for list return type
|
|
6
|
-
import { logger } from "../../logger";
|
|
7
|
-
import { ProjectStore } from "../../service/project_store";
|
|
8
|
-
import { getInternalError, getNotFoundError } from "../error_messages"; // Needed for error handling in list AND get
|
|
9
|
-
import { handleResourceGet, McpGetResourceError } from "../handler_utils";
|
|
10
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
11
|
-
|
|
12
|
-
// Define an interface for the package object augmented with project name
|
|
13
|
-
interface PackageWithProject {
|
|
14
|
-
name?: string;
|
|
15
|
-
// Add other relevant package properties if needed
|
|
16
|
-
projectName: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Registers the Malloy Project resource type with the MCP server.
|
|
21
|
-
* Handles getting details for the hardcoded 'home' project and listing packages within it.
|
|
22
|
-
*/
|
|
23
|
-
export function registerProjectResource(
|
|
24
|
-
mcpServer: McpServer,
|
|
25
|
-
projectStore: ProjectStore,
|
|
26
|
-
): void {
|
|
27
|
-
mcpServer.resource(
|
|
28
|
-
"project",
|
|
29
|
-
new ResourceTemplate("malloy://project/{projectName}", {
|
|
30
|
-
/**
|
|
31
|
-
* Handles ListResources requests.
|
|
32
|
-
* If projectName is specified, lists packages for that project (only 'home' supported).
|
|
33
|
-
* If projectName is not specified (general ListResources call), lists packages for the default 'home' project.
|
|
34
|
-
*/
|
|
35
|
-
list: async (/* extra: ListProjectExtra - Deleted */): Promise<ListResourcesResult> => {
|
|
36
|
-
logger.info(
|
|
37
|
-
"[MCP LOG] Entering ListResources (project) handler (listing ALL packages)...",
|
|
38
|
-
);
|
|
39
|
-
// Ignore parameters from 'extra' as URI path params aren't passed to list handlers.
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const allProjects = await projectStore.listProjects();
|
|
43
|
-
logger.info(
|
|
44
|
-
`[MCP LOG] Found ${allProjects.length} projects defined.`,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const packagePromises = allProjects.map(async (proj) => {
|
|
48
|
-
try {
|
|
49
|
-
logger.info(
|
|
50
|
-
`[MCP LOG] Getting project '${proj.name}' to list its packages...`,
|
|
51
|
-
);
|
|
52
|
-
const projectInstance = await projectStore.getProject(
|
|
53
|
-
proj.name!,
|
|
54
|
-
false,
|
|
55
|
-
); // Use proj.name
|
|
56
|
-
const packages = await projectInstance.listPackages();
|
|
57
|
-
logger.info(
|
|
58
|
-
`[MCP LOG] Found ${packages.length} packages in project '${proj.name}'.`,
|
|
59
|
-
);
|
|
60
|
-
// Return packages along with their project name for URI construction
|
|
61
|
-
return packages.map((pkg) => ({
|
|
62
|
-
...pkg,
|
|
63
|
-
projectName: proj.name,
|
|
64
|
-
}));
|
|
65
|
-
} catch (projectError) {
|
|
66
|
-
logger.error(
|
|
67
|
-
`[MCP Server Error] Error getting/listing packages for project ${proj.name}:`,
|
|
68
|
-
{ error: projectError },
|
|
69
|
-
);
|
|
70
|
-
return []; // Return empty array for this project on error
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const results = await Promise.allSettled(packagePromises);
|
|
75
|
-
const allPackagesWithProjectName = results
|
|
76
|
-
.filter((result) => result.status === "fulfilled")
|
|
77
|
-
// Use the specific interface instead of any[]
|
|
78
|
-
.flatMap(
|
|
79
|
-
(result) =>
|
|
80
|
-
(result as PromiseFulfilledResult<PackageWithProject[]>)
|
|
81
|
-
.value,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
logger.info(
|
|
85
|
-
`[MCP LOG] Total packages found across all projects: ${allPackagesWithProjectName.length}`,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const packageMetadata = RESOURCE_METADATA.package;
|
|
89
|
-
const mappedResources = allPackagesWithProjectName.map((pkg) => {
|
|
90
|
-
const name = pkg.name || "unknown";
|
|
91
|
-
// Construct URI using the package's specific projectName
|
|
92
|
-
const uri = `malloy://project/${pkg.projectName}/package/${name}`;
|
|
93
|
-
return {
|
|
94
|
-
uri: uri,
|
|
95
|
-
name: name,
|
|
96
|
-
type: "package",
|
|
97
|
-
description: packageMetadata?.description as
|
|
98
|
-
| string
|
|
99
|
-
| undefined,
|
|
100
|
-
metadata: packageMetadata,
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
logger.info(
|
|
105
|
-
`[MCP LOG] ListResources (project): Returning ${mappedResources.length} package resources.`,
|
|
106
|
-
);
|
|
107
|
-
return {
|
|
108
|
-
resources: mappedResources,
|
|
109
|
-
};
|
|
110
|
-
} catch (error) {
|
|
111
|
-
// Catch errors from projectStore.listProjects() itself
|
|
112
|
-
logger.error(`[MCP Server Error] Error listing projects:`, {
|
|
113
|
-
error,
|
|
114
|
-
});
|
|
115
|
-
const errorDetails = getInternalError(
|
|
116
|
-
`ListResources (project - initial list)`,
|
|
117
|
-
error,
|
|
118
|
-
);
|
|
119
|
-
logger.error("MCP ListResources (project) error:", {
|
|
120
|
-
error: errorDetails.message,
|
|
121
|
-
});
|
|
122
|
-
logger.info(
|
|
123
|
-
"[MCP LOG] ListResources (project): Returning empty on error listing projects.",
|
|
124
|
-
);
|
|
125
|
-
return { resources: [] };
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
}),
|
|
129
|
-
/** Handles GetResource requests for Malloy Projects */
|
|
130
|
-
(uri, params) =>
|
|
131
|
-
handleResourceGet(
|
|
132
|
-
uri,
|
|
133
|
-
params,
|
|
134
|
-
"project",
|
|
135
|
-
async ({ projectName }: { projectName?: unknown }) => {
|
|
136
|
-
logger.info(
|
|
137
|
-
`[MCP LOG] Entering GetResource (project) handler for projectName: ${projectName}`,
|
|
138
|
-
);
|
|
139
|
-
// Validate project name parameter
|
|
140
|
-
if (typeof projectName !== "string") {
|
|
141
|
-
logger.error(
|
|
142
|
-
"[MCP LOG] GetResource (project): Invalid project name param.",
|
|
143
|
-
);
|
|
144
|
-
throw new Error("Invalid project name parameter.");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
logger.info(
|
|
149
|
-
`[MCP LOG] GetResource: Getting project '${projectName}'...`,
|
|
150
|
-
);
|
|
151
|
-
// Get the project instance, but we might not need its metadata directly
|
|
152
|
-
await projectStore.getProject(projectName, false);
|
|
153
|
-
// Construct the definition object expected by the test
|
|
154
|
-
const definition = { name: projectName };
|
|
155
|
-
logger.info(
|
|
156
|
-
`[MCP LOG] GetResource (project): Returning definition for '${projectName}'.`,
|
|
157
|
-
);
|
|
158
|
-
// Return the explicit definition structure
|
|
159
|
-
return definition;
|
|
160
|
-
} catch (error) {
|
|
161
|
-
logger.error(
|
|
162
|
-
`[MCP LOG] GetResource (project): Error caught for '${projectName}':`,
|
|
163
|
-
{ error },
|
|
164
|
-
);
|
|
165
|
-
// Catch expected errors from this specific resource logic
|
|
166
|
-
if (error instanceof Error) {
|
|
167
|
-
// Use getNotFoundError for the specific project not found case
|
|
168
|
-
// or a generic message for the invalid param case.
|
|
169
|
-
const errorDetails = getNotFoundError(
|
|
170
|
-
error.message.includes("not found")
|
|
171
|
-
? `Project '${projectName}'` // More specific context
|
|
172
|
-
: `Invalid project identifier provided for URI ${uri.href}`, // Generic but informative
|
|
173
|
-
);
|
|
174
|
-
// Re-throw structured error for handleResourceGet to catch
|
|
175
|
-
throw new McpGetResourceError(errorDetails);
|
|
176
|
-
}
|
|
177
|
-
// Re-throw unexpected errors to be caught by handleResourceGet's generic handler
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
RESOURCE_METADATA.project,
|
|
182
|
-
),
|
|
183
|
-
);
|
|
184
|
-
}
|
|
@@ -1,117 +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 { ProjectStore } from "../../service/project_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
|
-
projectName?: 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
|
-
projectStore: ProjectStore,
|
|
36
|
-
): void {
|
|
37
|
-
mcpServer.resource(
|
|
38
|
-
"query",
|
|
39
|
-
new ResourceTemplate(
|
|
40
|
-
"malloy://project/{projectName}/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
|
-
{ projectName, packageName, modelPath, queryName }: QueryParams,
|
|
50
|
-
uri: URL,
|
|
51
|
-
) => {
|
|
52
|
-
if (
|
|
53
|
-
typeof projectName !== "string" ||
|
|
54
|
-
typeof packageName !== "string" ||
|
|
55
|
-
typeof modelPath !== "string" ||
|
|
56
|
-
typeof queryName !== "string"
|
|
57
|
-
) {
|
|
58
|
-
throw new Error("Invalid parameters for query resource.");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const modelResult = await getModelForQuery(
|
|
63
|
-
projectStore,
|
|
64
|
-
projectName,
|
|
65
|
-
packageName,
|
|
66
|
-
modelPath,
|
|
67
|
-
);
|
|
68
|
-
if ("error" in modelResult) {
|
|
69
|
-
throw new McpGetResourceError(modelResult.error);
|
|
70
|
-
}
|
|
71
|
-
const { model } = modelResult;
|
|
72
|
-
|
|
73
|
-
// Attempt to get the query definition from the model
|
|
74
|
-
const queries = await model.getQueries(); // Assuming this method exists
|
|
75
|
-
if (!queries) {
|
|
76
|
-
throw new Error("Could not retrieve queries from model.");
|
|
77
|
-
}
|
|
78
|
-
const query = queries.find(
|
|
79
|
-
(q: components["schemas"]["Query"]) =>
|
|
80
|
-
q.name === queryName,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if (!query) {
|
|
84
|
-
// Specific "Query not found" error
|
|
85
|
-
const errorDetails = getNotFoundError(
|
|
86
|
-
`Query '${queryName}' in model '${modelPath}' package '${packageName}' project '${projectName}'`,
|
|
87
|
-
);
|
|
88
|
-
throw new McpGetResourceError(errorDetails);
|
|
89
|
-
}
|
|
90
|
-
return query;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (error instanceof McpGetResourceError) {
|
|
93
|
-
throw error; // Re-throw already formatted
|
|
94
|
-
}
|
|
95
|
-
if (error instanceof ModelCompilationError) {
|
|
96
|
-
const errorDetails = getMalloyErrorDetails(
|
|
97
|
-
"GetResource (query - model compilation)",
|
|
98
|
-
`${projectName}/${packageName}/${modelPath}`,
|
|
99
|
-
error,
|
|
100
|
-
);
|
|
101
|
-
throw new McpGetResourceError(errorDetails);
|
|
102
|
-
}
|
|
103
|
-
logger.error(
|
|
104
|
-
`[MCP Server Error] Error fetching query '${queryName}' from ${uri.href}:`,
|
|
105
|
-
{ error },
|
|
106
|
-
);
|
|
107
|
-
const fallbackErrorDetails = getInternalError(
|
|
108
|
-
`GetResource (query: ${uri.href})`,
|
|
109
|
-
error,
|
|
110
|
-
);
|
|
111
|
-
throw new McpGetResourceError(fallbackErrorDetails);
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
RESOURCE_METADATA.query,
|
|
115
|
-
),
|
|
116
|
-
);
|
|
117
|
-
}
|