@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
package/src/mcp/prompts/utils.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { EnvironmentStore } from "../../service/environment_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
|
-
environmentName: string;
|
|
11
|
-
packageName: string;
|
|
12
|
-
modelPath: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parse a canonical Malloy model URI of the form:
|
|
17
|
-
* malloy://environment/{environment}/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:\/\/environment\/([^/]+)\/package\/([^/]+)\/models\/(.+)$/,
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
if (!match) {
|
|
27
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid Malloy URI: ${uri}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
environmentName: 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
|
-
* EnvironmentStore. Results are memoised in `compiledModelCache`.
|
|
40
|
-
*/
|
|
41
|
-
export async function getCompiledModel(
|
|
42
|
-
uri: string,
|
|
43
|
-
environmentStore: EnvironmentStore,
|
|
44
|
-
): Promise<ApiCompiledModel> {
|
|
45
|
-
const { environmentName, packageName, modelPath } = parseMalloyUri(uri);
|
|
46
|
-
|
|
47
|
-
// Look up the model via the EnvironmentStore hierarchy.
|
|
48
|
-
const model = await environmentStore
|
|
49
|
-
.getEnvironment(environmentName, false)
|
|
50
|
-
.then((e) => e.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
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { ResourceMetadata as SdkResourceMetadata } from "@modelcontextprotocol/sdk/server/mcp";
|
|
2
|
-
|
|
3
|
-
// Define the specific resource types expected as keys
|
|
4
|
-
type McpResourceType = "model" | "source" | "view" | "query" | "notebook";
|
|
5
|
-
|
|
6
|
-
// Descriptions and usage guidance for different Malloy resource types.
|
|
7
|
-
// Sourced from documentation and adapted for MCP client context.
|
|
8
|
-
// Type assertion ensures compatibility with the SDK's ResourceMetadata,
|
|
9
|
-
// which might include optional fields or an index signature.
|
|
10
|
-
export const RESOURCE_METADATA: Record<
|
|
11
|
-
McpResourceType | "package" | "environment",
|
|
12
|
-
SdkResourceMetadata // Use the imported SDK type here
|
|
13
|
-
> = {
|
|
14
|
-
model: {
|
|
15
|
-
description:
|
|
16
|
-
"A blueprint organizing your data tables, their connections, and reusable calculations (like 'revenue'). Models contain Sources, Views, and saved Queries.",
|
|
17
|
-
usage: "This is your main map. Use its `definition` to see the available Sources, Views, and Queries inside (identified by their names or URIs). You can then fetch details for those specific items using `GetResource`. Use the `malloy_executeQuery` tool with queries defined here to get results, typically as structured data tables (JSON). The results might also include styling information suggesting visualizations.",
|
|
18
|
-
},
|
|
19
|
-
source: {
|
|
20
|
-
description:
|
|
21
|
-
"Represents a table or dataset (like 'orders'), including its columns and any attached calculations (dimensions/measures) or Views.",
|
|
22
|
-
usage: "Examine the `definition` to understand the structure of this data and see available Views. Use `GetResource` on a View's URI to inspect it, or use the `malloy_executeQuery` tool (referencing the source and potentially a view) to get data results (JSON).",
|
|
23
|
-
},
|
|
24
|
-
view: {
|
|
25
|
-
description:
|
|
26
|
-
"A saved recipe within a Source for a specific analysis (like 'Top 10 Products').",
|
|
27
|
-
usage: "Run this predefined analysis using the `malloy_executeQuery` tool (referencing the parent source/model and this view's name) to get results, typically structured data tables (JSON), sometimes with visualization hints.",
|
|
28
|
-
},
|
|
29
|
-
query: {
|
|
30
|
-
description:
|
|
31
|
-
"Similar to a View, a saved recipe for an analysis, usually stored directly in a Model.",
|
|
32
|
-
usage: "Run this standard report using the `malloy_executeQuery` tool (referencing the parent model and this query's name) to get results, typically structured data tables (JSON), sometimes with visualization hints.",
|
|
33
|
-
},
|
|
34
|
-
notebook: {
|
|
35
|
-
description:
|
|
36
|
-
"An interactive document combining notes (Markdown text), Malloy code, and potentially saved queries or results.",
|
|
37
|
-
usage: "Read the `definition` to explore the analysis steps, notes (Markdown), and code. Identify queries within the notebook cells that you can run using the `malloy_executeQuery` tool to get results (JSON data, visualization hints).",
|
|
38
|
-
},
|
|
39
|
-
package: {
|
|
40
|
-
description: "A folder grouping related Models and Notebooks.",
|
|
41
|
-
usage: "Use `ListResources` on this Package's URI to discover the Models and Notebooks it contains. You can then use `GetResource` on those items' URIs to explore them further.",
|
|
42
|
-
},
|
|
43
|
-
environment: {
|
|
44
|
-
description: "The main workspace folder holding Packages, Models, etc.",
|
|
45
|
-
usage: "Use `ListResources` on this environment's URI to see the Packages inside and begin navigating your data assets.",
|
|
46
|
-
},
|
|
47
|
-
};
|
|
@@ -1,187 +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 { EnvironmentStore } from "../../service/environment_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 environment name
|
|
13
|
-
interface PackageWithEnvironment {
|
|
14
|
-
name?: string;
|
|
15
|
-
// Add other relevant package properties if needed
|
|
16
|
-
environmentName: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Registers the Malloy environment resource type with the MCP server (URI scheme malloy://environment/...).
|
|
21
|
-
* Lists packages across environments and resolves environment metadata on read.
|
|
22
|
-
*/
|
|
23
|
-
export function registerEnvironmentResource(
|
|
24
|
-
mcpServer: McpServer,
|
|
25
|
-
environmentStore: EnvironmentStore,
|
|
26
|
-
): void {
|
|
27
|
-
mcpServer.resource(
|
|
28
|
-
"environment",
|
|
29
|
-
new ResourceTemplate("malloy://environment/{environmentName}", {
|
|
30
|
-
/**
|
|
31
|
-
* Handles ListResources requests.
|
|
32
|
-
* If environmentName is specified, lists packages for that environment (only 'home' supported).
|
|
33
|
-
* If environmentName is not specified (general ListResources call), lists packages for the default 'home' environment.
|
|
34
|
-
*/
|
|
35
|
-
list: async (/* extra: ListEnvironmentExtra - Deleted */): Promise<ListResourcesResult> => {
|
|
36
|
-
logger.info(
|
|
37
|
-
"[MCP LOG] Entering ListResources (environment) 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 allEnvironments =
|
|
43
|
-
await environmentStore.listEnvironments();
|
|
44
|
-
logger.info(
|
|
45
|
-
`[MCP LOG] Found ${allEnvironments.length} environments defined.`,
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const packagePromises = allEnvironments.map(async (env) => {
|
|
49
|
-
try {
|
|
50
|
-
logger.info(
|
|
51
|
-
`[MCP LOG] Getting environment '${env.name}' to list its packages...`,
|
|
52
|
-
);
|
|
53
|
-
const environmentInstance =
|
|
54
|
-
await environmentStore.getEnvironment(env.name!, false);
|
|
55
|
-
const packages = await environmentInstance.listPackages();
|
|
56
|
-
logger.info(
|
|
57
|
-
`[MCP LOG] Found ${packages.length} packages in environment '${env.name}'.`,
|
|
58
|
-
);
|
|
59
|
-
// Return packages along with their environment name for URI construction
|
|
60
|
-
return packages.map((pkg) => ({
|
|
61
|
-
...pkg,
|
|
62
|
-
environmentName: env.name,
|
|
63
|
-
}));
|
|
64
|
-
} catch (environmentError) {
|
|
65
|
-
logger.error(
|
|
66
|
-
`[MCP Server Error] Error getting/listing packages for environment ${env.name}:`,
|
|
67
|
-
{ error: environmentError },
|
|
68
|
-
);
|
|
69
|
-
return []; // Return empty array for this environment on error
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const results = await Promise.allSettled(packagePromises);
|
|
74
|
-
const allPackagesWithEnvironmentName = results
|
|
75
|
-
.filter((result) => result.status === "fulfilled")
|
|
76
|
-
.flatMap(
|
|
77
|
-
(result) =>
|
|
78
|
-
(
|
|
79
|
-
result as PromiseFulfilledResult<
|
|
80
|
-
PackageWithEnvironment[]
|
|
81
|
-
>
|
|
82
|
-
).value,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
logger.info(
|
|
86
|
-
`[MCP LOG] Total packages found across all environments: ${allPackagesWithEnvironmentName.length}`,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const packageMetadata = RESOURCE_METADATA.package;
|
|
90
|
-
const mappedResources = allPackagesWithEnvironmentName.map(
|
|
91
|
-
(pkg) => {
|
|
92
|
-
const name = pkg.name || "unknown";
|
|
93
|
-
// Construct URI using the package's specific environmentName
|
|
94
|
-
const uri = `malloy://environment/${pkg.environmentName}/package/${name}`;
|
|
95
|
-
return {
|
|
96
|
-
uri: uri,
|
|
97
|
-
name: name,
|
|
98
|
-
type: "package",
|
|
99
|
-
description: packageMetadata?.description as
|
|
100
|
-
| string
|
|
101
|
-
| undefined,
|
|
102
|
-
metadata: packageMetadata,
|
|
103
|
-
};
|
|
104
|
-
},
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
logger.info(
|
|
108
|
-
`[MCP LOG] ListResources (environment): Returning ${mappedResources.length} package resources.`,
|
|
109
|
-
);
|
|
110
|
-
return {
|
|
111
|
-
resources: mappedResources,
|
|
112
|
-
};
|
|
113
|
-
} catch (error) {
|
|
114
|
-
// Catch errors from environmentStore.listEnvironments() itself
|
|
115
|
-
logger.error(`[MCP Server Error] Error listing environments:`, {
|
|
116
|
-
error,
|
|
117
|
-
});
|
|
118
|
-
const errorDetails = getInternalError(
|
|
119
|
-
`ListResources (environment - initial list)`,
|
|
120
|
-
error,
|
|
121
|
-
);
|
|
122
|
-
logger.error("MCP ListResources (environment) error:", {
|
|
123
|
-
error: errorDetails.message,
|
|
124
|
-
});
|
|
125
|
-
logger.info(
|
|
126
|
-
"[MCP LOG] ListResources (environment): Returning empty on error listing environments.",
|
|
127
|
-
);
|
|
128
|
-
return { resources: [] };
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
}),
|
|
132
|
-
/** Handles GetResource requests for Malloy environments */
|
|
133
|
-
(uri, params) =>
|
|
134
|
-
handleResourceGet(
|
|
135
|
-
uri,
|
|
136
|
-
params,
|
|
137
|
-
"environment",
|
|
138
|
-
async ({ environmentName }: { environmentName?: unknown }) => {
|
|
139
|
-
logger.info(
|
|
140
|
-
`[MCP LOG] Entering GetResource (environment) handler for environmentName: ${environmentName}`,
|
|
141
|
-
);
|
|
142
|
-
// Validate environment name parameter
|
|
143
|
-
if (typeof environmentName !== "string") {
|
|
144
|
-
logger.error(
|
|
145
|
-
"[MCP LOG] GetResource (environment): Invalid environment name param.",
|
|
146
|
-
);
|
|
147
|
-
throw new Error("Invalid environment name parameter.");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
logger.info(
|
|
152
|
-
`[MCP LOG] GetResource: Getting environment '${environmentName}'...`,
|
|
153
|
-
);
|
|
154
|
-
// Get the environment instance, but we might not need its metadata directly
|
|
155
|
-
await environmentStore.getEnvironment(environmentName, false);
|
|
156
|
-
// Construct the definition object expected by the test
|
|
157
|
-
const definition = { name: environmentName };
|
|
158
|
-
logger.info(
|
|
159
|
-
`[MCP LOG] GetResource (environment): Returning definition for '${environmentName}'.`,
|
|
160
|
-
);
|
|
161
|
-
// Return the explicit definition structure
|
|
162
|
-
return definition;
|
|
163
|
-
} catch (error) {
|
|
164
|
-
logger.error(
|
|
165
|
-
`[MCP LOG] GetResource (environment): Error caught for '${environmentName}':`,
|
|
166
|
-
{ error },
|
|
167
|
-
);
|
|
168
|
-
// Catch expected errors from this specific resource logic
|
|
169
|
-
if (error instanceof Error) {
|
|
170
|
-
// Use getNotFoundError for the specific environment not found case
|
|
171
|
-
// or a generic message for the invalid param case.
|
|
172
|
-
const errorDetails = getNotFoundError(
|
|
173
|
-
error.message.includes("not found")
|
|
174
|
-
? `Environment '${environmentName}'` // More specific context
|
|
175
|
-
: `Invalid environment identifier provided for URI ${uri.href}`, // Generic but informative
|
|
176
|
-
);
|
|
177
|
-
// Re-throw structured error for handleResourceGet to catch
|
|
178
|
-
throw new McpGetResourceError(errorDetails);
|
|
179
|
-
}
|
|
180
|
-
// Re-throw unexpected errors to be caught by handleResourceGet's generic handler
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
RESOURCE_METADATA.environment,
|
|
185
|
-
),
|
|
186
|
-
);
|
|
187
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { EnvironmentStore } from "../../service/environment_store";
|
|
6
|
-
import {
|
|
7
|
-
ModelNotFoundError,
|
|
8
|
-
PackageNotFoundError,
|
|
9
|
-
ModelCompilationError,
|
|
10
|
-
} from "../../errors";
|
|
11
|
-
import { handleResourceGet, McpGetResourceError } from "../handler_utils";
|
|
12
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
13
|
-
import {
|
|
14
|
-
getNotFoundError,
|
|
15
|
-
getMalloyErrorDetails,
|
|
16
|
-
getInternalError,
|
|
17
|
-
} from "../error_messages";
|
|
18
|
-
import type { components } from "../../api"; // Import the components type
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Registers the Malloy Model resource type with the MCP server.
|
|
22
|
-
* Handles getting details for a specific model.
|
|
23
|
-
*/
|
|
24
|
-
export function registerModelResource(
|
|
25
|
-
mcpServer: McpServer,
|
|
26
|
-
environmentStore: EnvironmentStore,
|
|
27
|
-
): void {
|
|
28
|
-
mcpServer.resource(
|
|
29
|
-
"model",
|
|
30
|
-
new ResourceTemplate(
|
|
31
|
-
"malloy://environment/{environmentName}/package/{packageName}/models/{modelPath}",
|
|
32
|
-
{ list: undefined }, // No list handler for individual models
|
|
33
|
-
),
|
|
34
|
-
/** Handles GetResource requests for specific Malloy Models */
|
|
35
|
-
(uri, params) =>
|
|
36
|
-
handleResourceGet(
|
|
37
|
-
uri,
|
|
38
|
-
params,
|
|
39
|
-
"model",
|
|
40
|
-
async ({
|
|
41
|
-
environmentName,
|
|
42
|
-
packageName,
|
|
43
|
-
modelPath,
|
|
44
|
-
}: {
|
|
45
|
-
environmentName?: unknown;
|
|
46
|
-
packageName?: unknown;
|
|
47
|
-
modelPath?: unknown;
|
|
48
|
-
}) => {
|
|
49
|
-
try {
|
|
50
|
-
// Validate all parameters
|
|
51
|
-
if (typeof environmentName !== "string") {
|
|
52
|
-
throw new Error("Invalid environment name parameter.");
|
|
53
|
-
}
|
|
54
|
-
if (typeof packageName !== "string") {
|
|
55
|
-
throw new Error("Invalid package name parameter.");
|
|
56
|
-
}
|
|
57
|
-
if (typeof modelPath !== "string") {
|
|
58
|
-
throw new Error("Invalid model path parameter.");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// *** UPDATED LOGIC using EnvironmentStore ***
|
|
62
|
-
// getEnvironment can throw EnvironmentNotFoundError (though unlikely if name is 'home')
|
|
63
|
-
const environment = await environmentStore.getEnvironment(
|
|
64
|
-
environmentName,
|
|
65
|
-
false,
|
|
66
|
-
);
|
|
67
|
-
// getPackage can throw PackageNotFoundError
|
|
68
|
-
const pkg = await environment.getPackage(packageName, false);
|
|
69
|
-
// getModel is SYNCHRONOUS
|
|
70
|
-
const modelInstance = pkg.getModel(modelPath);
|
|
71
|
-
// *** END UPDATED LOGIC ***
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
!modelInstance ||
|
|
75
|
-
modelInstance.getModelType() === "notebook"
|
|
76
|
-
) {
|
|
77
|
-
// Explicitly throw the expected error if not found or wrong type
|
|
78
|
-
throw new ModelNotFoundError(modelPath);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// modelInstance.getModel() is ASYNC and can throw ModelCompilationError
|
|
82
|
-
const compiledModelDefinition: components["schemas"]["CompiledModel"] =
|
|
83
|
-
await modelInstance.getModel();
|
|
84
|
-
|
|
85
|
-
// Strip implicit filters from agent-facing responses
|
|
86
|
-
if (compiledModelDefinition.sources) {
|
|
87
|
-
for (const source of compiledModelDefinition.sources) {
|
|
88
|
-
if (source.filters) {
|
|
89
|
-
source.filters = source.filters.filter(
|
|
90
|
-
(f) => !f.implicit,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return compiledModelDefinition;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
let errorDetails;
|
|
99
|
-
// Provide specific context for error messages
|
|
100
|
-
// Use validated string parameters here
|
|
101
|
-
const safeEnvironmentName =
|
|
102
|
-
typeof environmentName === "string"
|
|
103
|
-
? environmentName
|
|
104
|
-
: "unknown";
|
|
105
|
-
const safePackageName =
|
|
106
|
-
typeof packageName === "string" ? packageName : "unknown";
|
|
107
|
-
const safeModelPath =
|
|
108
|
-
typeof modelPath === "string" ? modelPath : "unknown";
|
|
109
|
-
|
|
110
|
-
const notFoundContext = `Model '${safeModelPath}' in package '${safePackageName}' for environment '${safeEnvironmentName}'`;
|
|
111
|
-
const malloyErrorContext = `${safeEnvironmentName}/${safePackageName}/${safeModelPath}`;
|
|
112
|
-
|
|
113
|
-
if (error instanceof PackageNotFoundError) {
|
|
114
|
-
// Package not found during getPackage call
|
|
115
|
-
errorDetails = getNotFoundError(
|
|
116
|
-
`Package '${safePackageName}' in environment '${safeEnvironmentName}'`,
|
|
117
|
-
);
|
|
118
|
-
} else if (error instanceof ModelNotFoundError) {
|
|
119
|
-
// Model not found (either from getModel or type check)
|
|
120
|
-
errorDetails = getNotFoundError(notFoundContext);
|
|
121
|
-
} else if (error instanceof ModelCompilationError) {
|
|
122
|
-
// Compilation error when fetching model definition
|
|
123
|
-
errorDetails = getMalloyErrorDetails(
|
|
124
|
-
"GetResource (model)",
|
|
125
|
-
malloyErrorContext,
|
|
126
|
-
error,
|
|
127
|
-
);
|
|
128
|
-
} else if (error instanceof Error) {
|
|
129
|
-
// Catch invalid param errors or other generic errors
|
|
130
|
-
// Provide a clearer message differentiating param errors
|
|
131
|
-
if (error.message.includes("parameter")) {
|
|
132
|
-
errorDetails = getNotFoundError(
|
|
133
|
-
`Invalid identifier parameter provided for URI ${uri.href}: ${error.message}`,
|
|
134
|
-
);
|
|
135
|
-
} else {
|
|
136
|
-
errorDetails = getInternalError(
|
|
137
|
-
"GetResource (model) - Unexpected Error",
|
|
138
|
-
error,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
// Fallback for truly unexpected non-Error throws
|
|
143
|
-
errorDetails = getInternalError(
|
|
144
|
-
"GetResource (model)",
|
|
145
|
-
error,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
// Wrap and re-throw for handleResourceGet
|
|
149
|
-
throw new McpGetResourceError(errorDetails);
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
RESOURCE_METADATA.model,
|
|
153
|
-
),
|
|
154
|
-
);
|
|
155
|
-
}
|
|
@@ -1,137 +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 CompiledModel type
|
|
7
|
-
import {
|
|
8
|
-
ModelCompilationError,
|
|
9
|
-
ModelNotFoundError,
|
|
10
|
-
PackageNotFoundError,
|
|
11
|
-
} from "../../errors";
|
|
12
|
-
import { logger } from "../../logger";
|
|
13
|
-
import { EnvironmentStore } from "../../service/environment_store";
|
|
14
|
-
import {
|
|
15
|
-
getInternalError,
|
|
16
|
-
getMalloyErrorDetails,
|
|
17
|
-
getNotFoundError,
|
|
18
|
-
} from "../error_messages";
|
|
19
|
-
import { handleResourceGet, McpGetResourceError } from "../handler_utils";
|
|
20
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
21
|
-
|
|
22
|
-
// Define the expected parameter types
|
|
23
|
-
type NotebookParams = {
|
|
24
|
-
environmentName?: unknown;
|
|
25
|
-
packageName?: unknown;
|
|
26
|
-
notebookName?: unknown;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Registers the Malloy Notebook resource type.
|
|
31
|
-
* Handles getting details for a specific notebook.
|
|
32
|
-
*/
|
|
33
|
-
export function registerNotebookResource(
|
|
34
|
-
mcpServer: McpServer,
|
|
35
|
-
environmentStore: EnvironmentStore,
|
|
36
|
-
): void {
|
|
37
|
-
mcpServer.resource(
|
|
38
|
-
"notebook",
|
|
39
|
-
new ResourceTemplate(
|
|
40
|
-
"malloy://environment/{environmentName}/package/{packageName}/notebooks/{notebookName}",
|
|
41
|
-
{ list: undefined }, // Listing notebooks is not supported via this template
|
|
42
|
-
),
|
|
43
|
-
(uri, params) =>
|
|
44
|
-
handleResourceGet(
|
|
45
|
-
uri,
|
|
46
|
-
params as NotebookParams,
|
|
47
|
-
"notebook",
|
|
48
|
-
async (
|
|
49
|
-
{ environmentName, packageName, notebookName }: NotebookParams,
|
|
50
|
-
uri: URL,
|
|
51
|
-
) => {
|
|
52
|
-
if (
|
|
53
|
-
typeof environmentName !== "string" ||
|
|
54
|
-
typeof packageName !== "string" ||
|
|
55
|
-
typeof notebookName !== "string"
|
|
56
|
-
) {
|
|
57
|
-
throw new Error("Invalid parameters for notebook resource.");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let modelInstance;
|
|
61
|
-
try {
|
|
62
|
-
const environment = await environmentStore.getEnvironment(
|
|
63
|
-
environmentName,
|
|
64
|
-
false,
|
|
65
|
-
);
|
|
66
|
-
const pkg = await environment.getPackage(packageName, false);
|
|
67
|
-
// Get the model instance using the notebookName as the path
|
|
68
|
-
modelInstance = pkg.getModel(notebookName);
|
|
69
|
-
|
|
70
|
-
// Check if it exists and is actually a notebook
|
|
71
|
-
if (
|
|
72
|
-
!modelInstance ||
|
|
73
|
-
modelInstance.getModelType() !== "notebook"
|
|
74
|
-
) {
|
|
75
|
-
const isNotebookError =
|
|
76
|
-
modelInstance?.getModelType() !== "notebook";
|
|
77
|
-
const errorDetails = getNotFoundError(
|
|
78
|
-
`Notebook '${notebookName}' in package '${packageName}' environment '${environmentName}'${
|
|
79
|
-
isNotebookError ? " (not a .malloynb file)" : ""
|
|
80
|
-
}`,
|
|
81
|
-
);
|
|
82
|
-
throw new McpGetResourceError(errorDetails);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Now try to get the actual notebook content
|
|
86
|
-
const notebookContent: components["schemas"]["RawNotebook"] =
|
|
87
|
-
await modelInstance.getNotebook();
|
|
88
|
-
return notebookContent;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (error instanceof McpGetResourceError) {
|
|
91
|
-
throw error; // Re-throw if already formatted by checks above
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Handle specific errors like PackageNotFoundError or ModelCompilationError
|
|
95
|
-
if (error instanceof PackageNotFoundError) {
|
|
96
|
-
throw new McpGetResourceError(
|
|
97
|
-
getNotFoundError(
|
|
98
|
-
`Package '${packageName}' in environment '${environmentName}'`,
|
|
99
|
-
),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
// Handle ModelCompilationError from modelInstance.getModel()
|
|
103
|
-
if (error instanceof ModelCompilationError) {
|
|
104
|
-
const malloyErrorContext = `${environmentName}/${packageName}/${notebookName}`;
|
|
105
|
-
throw new McpGetResourceError(
|
|
106
|
-
getMalloyErrorDetails(
|
|
107
|
-
"GetResource (notebook compilation)",
|
|
108
|
-
malloyErrorContext,
|
|
109
|
-
error,
|
|
110
|
-
),
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
// Handle ModelNotFoundError specifically from pkg.getModel()
|
|
114
|
-
if (error instanceof ModelNotFoundError) {
|
|
115
|
-
throw new McpGetResourceError(
|
|
116
|
-
getNotFoundError(
|
|
117
|
-
`Notebook '${notebookName}' not found in package '${packageName}' environment '${environmentName}'`,
|
|
118
|
-
),
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Handle other unexpected errors
|
|
123
|
-
logger.error(
|
|
124
|
-
`[MCP Server Error] Error fetching notebook '${notebookName}' from ${uri.href}:`,
|
|
125
|
-
{ error },
|
|
126
|
-
);
|
|
127
|
-
const fallbackErrorDetails = getInternalError(
|
|
128
|
-
`GetResource (notebook: ${uri.href})`,
|
|
129
|
-
error,
|
|
130
|
-
);
|
|
131
|
-
throw new McpGetResourceError(fallbackErrorDetails);
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
RESOURCE_METADATA.notebook,
|
|
135
|
-
),
|
|
136
|
-
);
|
|
137
|
-
}
|