@malloy-publisher/server 0.0.225 → 0.0.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +2 -11
  2. package/dist/app/api-doc.yaml +29 -24
  3. package/dist/app/assets/{EnvironmentPage-DYTeXDll.js → EnvironmentPage-DvOJ7L_b.js} +1 -1
  4. package/dist/app/assets/{HomePage-pDK2BPJY.js → HomePage-CXguJsXS.js} +1 -1
  5. package/dist/app/assets/{LightMode-C2bwGPY1.js → LightMode-ZsshUznu.js} +1 -1
  6. package/dist/app/assets/{MainPage-WtBulMH_.js → MainPage-BIe0VwBa.js} +2 -2
  7. package/dist/app/assets/{MaterializationsPage-hMgOtflG.js → MaterializationsPage-BuZ6UJVx.js} +1 -1
  8. package/dist/app/assets/{ModelPage-B2N5kYII.js → ModelPage-DsPf-s8B.js} +1 -1
  9. package/dist/app/assets/{PackagePage-CEN90nQG.js → PackagePage-CEVNAKZa.js} +1 -1
  10. package/dist/app/assets/{RouteError-BG2c5Zf0.js → RouteError-Chn7lL96.js} +1 -1
  11. package/dist/app/assets/{ThemeEditorPage-DNfeUwEZ.js → ThemeEditorPage-DWC_FdNU.js} +1 -1
  12. package/dist/app/assets/{WorkbookPage-NKI1BhFS.js → WorkbookPage-CGrsFz8p.js} +1 -1
  13. package/dist/app/assets/{core-C6anj5c0.es-DDLHqpzt.js → core-vVgoO8IR.es-BD_THWs_.js} +1 -1
  14. package/dist/app/assets/{index-DMQtnaf4.js → index-BioohWQj.js} +1 -1
  15. package/dist/app/assets/{index-CzNqKMVl.js → index-D6YtyiJ0.js} +1 -1
  16. package/dist/app/assets/{index-C6gZ6sSY.js → index-DNUZpnaa.js} +4 -4
  17. package/dist/app/assets/{index-JXgvyZna.js → index-gEWxu09x.js} +1 -1
  18. package/dist/app/index.html +1 -1
  19. package/dist/instrumentation.mjs +71 -15
  20. package/dist/package_load_worker.mjs +169 -40
  21. package/dist/server.mjs +782 -7050
  22. package/package.json +1 -4
  23. package/src/controller/package.controller.spec.ts +17 -17
  24. package/src/controller/package.controller.ts +7 -6
  25. package/src/health.ts +3 -8
  26. package/src/logger.spec.ts +193 -0
  27. package/src/logger.ts +115 -18
  28. package/src/mcp/error_messages.ts +8 -37
  29. package/src/mcp/handler_utils.ts +10 -129
  30. package/src/mcp/mcp_constants.ts +0 -16
  31. package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
  32. package/src/mcp/server.ts +29 -37
  33. package/src/mcp/skills/build_skills_bundle.ts +1 -1
  34. package/src/mcp/skills/skills_bundle.json +1 -1
  35. package/src/mcp/tools/docs_search_tool.ts +1 -1
  36. package/src/mcp/tools/execute_query_tool.ts +2 -2
  37. package/src/mcp/tools/get_context_eval.ts +3 -3
  38. package/src/mcp/tools/get_context_tool.spec.ts +196 -1
  39. package/src/mcp/tools/get_context_tool.ts +165 -67
  40. package/src/package_load/package_load_pool.ts +8 -1
  41. package/src/package_load/package_load_worker.ts +75 -24
  42. package/src/package_load/protocol.ts +21 -1
  43. package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
  44. package/src/package_load/rpc_wait_accountant.ts +76 -0
  45. package/src/package_load_metrics.spec.ts +114 -0
  46. package/src/package_load_metrics.ts +127 -0
  47. package/src/pg_helpers.spec.ts +2 -206
  48. package/src/pg_helpers.ts +4 -120
  49. package/src/server.ts +0 -16
  50. package/src/service/build_plan.spec.ts +42 -60
  51. package/src/service/build_plan.ts +37 -61
  52. package/src/service/environment.ts +5 -1
  53. package/src/service/materialization_test_fixtures.ts +6 -8
  54. package/src/service/package.ts +165 -86
  55. package/src/service/package_manifest.spec.ts +22 -1
  56. package/src/service/package_manifest.ts +29 -0
  57. package/src/service/persistence_policy.spec.ts +234 -0
  58. package/src/test_helpers/metrics_harness.ts +40 -0
  59. package/tests/harness/mcp_test_setup.ts +1 -1
  60. package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
  61. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
  62. package/dxt/malloy_bridge.py +0 -354
  63. package/dxt/manifest.json +0 -22
  64. package/src/dto/connection.dto.spec.ts +0 -186
  65. package/src/dto/connection.dto.ts +0 -308
  66. package/src/dto/index.ts +0 -2
  67. package/src/dto/package.dto.spec.ts +0 -42
  68. package/src/dto/package.dto.ts +0 -27
  69. package/src/dto/validate.spec.ts +0 -76
  70. package/src/dto/validate.ts +0 -31
  71. package/src/ducklake_version.spec.ts +0 -43
  72. package/src/ducklake_version.ts +0 -26
  73. package/src/mcp/agent_server.spec.ts +0 -18
  74. package/src/mcp/agent_server.ts +0 -144
  75. package/src/mcp/prompts/handlers.ts +0 -84
  76. package/src/mcp/prompts/index.ts +0 -11
  77. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  78. package/src/mcp/prompts/prompt_service.ts +0 -67
  79. package/src/mcp/prompts/utils.ts +0 -62
  80. package/src/mcp/resource_metadata.ts +0 -47
  81. package/src/mcp/resources/environment_resource.ts +0 -187
  82. package/src/mcp/resources/model_resource.ts +0 -155
  83. package/src/mcp/resources/notebook_resource.ts +0 -137
  84. package/src/mcp/resources/package_resource.ts +0 -373
  85. package/src/mcp/resources/query_resource.ts +0 -122
  86. package/src/mcp/resources/source_resource.ts +0 -141
  87. package/src/mcp/resources/view_resource.ts +0 -136
  88. package/src/mcp/tools/discovery_tools.ts +0 -280
  89. package/src/service/materialization_cron_gate.spec.ts +0 -181
  90. package/src/storage/BaseRepository.ts +0 -31
  91. package/src/storage/StorageManager.mock.ts +0 -50
  92. package/tests/harness/e2e.ts +0 -96
  93. package/tests/harness/mocks.ts +0 -39
  94. package/tests/harness/uris.ts +0 -31
  95. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
  96. package/tests/integration/mcp/setup.spec.ts +0 -5
  97. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  98. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
@@ -1,6 +1,3 @@
1
- import { URL } from "url";
2
- import type { ResourceMetadata } from "@modelcontextprotocol/sdk/server/mcp";
3
- import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
4
1
  import { EnvironmentStore } from "../service/environment_store";
5
2
  import {
6
3
  AccessDeniedError,
@@ -19,105 +16,6 @@ import {
19
16
  import type { Model } from "../service/model";
20
17
  import { logger } from "../logger";
21
18
 
22
- // Custom error to wrap specific GetResource application errors
23
- export class McpGetResourceError extends Error {
24
- details: ErrorDetails;
25
-
26
- constructor(details: ErrorDetails) {
27
- super(details.message); // Pass message to the base Error constructor
28
- this.name = "McpGetResourceError"; // Custom error name
29
- this.details = details; // Store the structured details
30
-
31
- // Maintain stack trace (important for V8)
32
- if (Error.captureStackTrace) {
33
- Error.captureStackTrace(this, McpGetResourceError);
34
- }
35
- }
36
- }
37
-
38
- // Helper type for the data fetching logic within a resource handler
39
- type GetDataLogic<TParams, TDefinition> = (
40
- params: TParams,
41
- uri: URL,
42
- ) => Promise<TDefinition>;
43
-
44
- /**
45
- * Handles the common logic for GetResource handlers, fetching data and formatting the response or error.
46
- */
47
- export async function handleResourceGet<
48
- TParams extends Record<string, unknown>,
49
- TDefinition,
50
- >(
51
- uri: URL,
52
- params: TParams,
53
- resourceType: string, // e.g., 'environment', 'package' for logging/errors
54
- getData: GetDataLogic<TParams, TDefinition>,
55
- resourceMetadata: ResourceMetadata | undefined,
56
- ): Promise<ReadResourceResult> {
57
- try {
58
- const definition = await getData(params, uri);
59
-
60
- // Combine definition and metadata
61
- const responsePayload = JSON.stringify(
62
- { definition: definition, metadata: resourceMetadata },
63
- null,
64
- 2,
65
- );
66
-
67
- return {
68
- contents: [
69
- {
70
- type: "application/json",
71
- uri: uri.href,
72
- text: responsePayload,
73
- },
74
- ],
75
- };
76
- } catch (error) {
77
- logger.error(
78
- `[MCP Server Error] Error reading ${resourceType} ${uri.href}:`,
79
- { error },
80
- );
81
-
82
- let errorDetails: ErrorDetails;
83
-
84
- // Determine the correct error details based on the error type
85
- if (error instanceof McpGetResourceError) {
86
- // The getData function already caught, formatted, and wrapped the error
87
- errorDetails = error.details;
88
- } else {
89
- // Catch-all for truly unexpected errors not handled by the specific getData logic
90
- logger.error(
91
- "[MCP Server Error] Unexpected error type caught in handleResourceGet:",
92
- { error },
93
- );
94
- errorDetails = getInternalError(
95
- `GetResource (${resourceType})`,
96
- error,
97
- );
98
- }
99
-
100
- // Format the error response consistently
101
- return {
102
- isError: true,
103
- contents: [
104
- {
105
- type: "application/json", // Keep JSON for structured error
106
- uri: uri.href,
107
- text: JSON.stringify(
108
- {
109
- error: errorDetails.message,
110
- suggestions: errorDetails.suggestions,
111
- },
112
- null,
113
- 2,
114
- ),
115
- },
116
- ],
117
- };
118
- }
119
- }
120
-
121
19
  /**
122
20
  * Fetches and validates the Package and Model instances needed for query execution.
123
21
  * Handles errors related to package/model access and initial compilation.
@@ -202,28 +100,20 @@ export async function getModelForQuery(
202
100
  }
203
101
 
204
102
  /**
205
- * Constructs a valid malloy:// URI string from its components.
206
- * Handles encoding of path segments.
103
+ * Constructs a malloy:// URI string identifying the model a tool response came
104
+ * from. Attached as `uri` metadata on tool results (get-context, execute-query,
105
+ * docs-search); it is not a fetchable MCP resource.
207
106
  *
208
- * @param components An object containing the URI parts (environment, package, model, etc.)
209
- * @param fragment Optional fragment identifier (e.g., #queryResult)
210
- * @returns A valid malloy:// URI string.
107
+ * @param components The URI parts (environment, package, and optionally the model).
108
+ * @param fragment Optional fragment identifier (e.g., "result", "get-context").
109
+ * @returns A malloy:// URI string.
211
110
  */
212
111
  export function buildMalloyUri(
213
112
  components: {
214
113
  environment?: string;
215
114
  package?: string;
216
- model?: string;
217
- resourceType?:
218
- | "models"
219
- | "packages"
220
- | "notebooks"
221
- | "sources"
222
- | "queries"
223
- | "views"; // Type of resource list or specific resource
224
- resourceName?: string; // Specific name for model, query, etc.
225
- subResourceType?: "views"; // For views within sources
226
- subResourceName?: string; // Name of the view
115
+ resourceType?: "models";
116
+ resourceName?: string;
227
117
  },
228
118
  fragment?: string,
229
119
  ): string {
@@ -244,20 +134,11 @@ export function buildMalloyUri(
244
134
  path += "/" + components.resourceType;
245
135
  if (components.resourceName) {
246
136
  path += "/" + encodeURIComponent(components.resourceName);
247
-
248
- if (components.subResourceType && components.subResourceName) {
249
- path +=
250
- "/" +
251
- components.subResourceType +
252
- "/" +
253
- encodeURIComponent(components.subResourceName);
254
- }
255
137
  }
256
138
  }
257
139
 
258
- // The URL constructor seems to normalize malloy://path to malloy:///path
259
- // which breaks the tests expecting malloy://environment/...
260
- // We manually construct the string instead.
140
+ // The URL constructor normalizes malloy://path to malloy:///path, so we
141
+ // build the string manually to keep the malloy://environment/... shape.
261
142
  let uriString = "malloy:/" + path; // Start with one slash after scheme
262
143
 
263
144
  if (fragment) {
@@ -3,20 +3,4 @@ export const MCP_ERROR_MESSAGES = {
3
3
  MISSING_REQUIRED_PARAMS:
4
4
  "Either 'query' or both 'sourceName' and 'queryName' must be provided",
5
5
  MUTUALLY_EXCLUSIVE_PARAMS: "Cannot provide both 'query' and 'queryName'",
6
- INVALID_ARGUMENTS: (toolName: string, errors: string[]) =>
7
- `Invalid arguments for tool ${toolName}: ${errors.join(", ")}`,
8
-
9
- // Application-level errors (runtime)
10
- PROJECT_NOT_FOUND: (projectName: string) =>
11
- `Environment '${projectName}' is not available or does not exist.`,
12
- PACKAGE_NOT_FOUND: (packageName: string) =>
13
- `Package manifest for ${packageName} does not exist.`,
14
- MODEL_NOT_FOUND: (packageName: string, modelPath: string) =>
15
- `Model '${modelPath}' not found in package '${packageName}'`,
16
- QUERY_NOT_FOUND: (queryName: string) => `'${queryName}' is not defined`,
17
- COMPILATION_ERROR: (error: string) => `Error(s) compiling model:\n${error}`,
18
-
19
- // Error message prefixes
20
- ERROR_EXECUTING_QUERY: (packagePath: string) =>
21
- `Error executing query on '${packagePath}':`,
22
6
  } as const;
@@ -1,16 +1,17 @@
1
1
  import { beforeAll, describe, expect, it } from "bun:test";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
3
  import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
4
- import { initializeAgentMcpServer } from "./agent_server";
4
+ import { initializeMcpServer } from "./server";
5
5
  import type { EnvironmentStore } from "../service/environment_store";
6
6
 
7
7
  /**
8
- * End-to-end coverage of the agent MCP server over the real MCP protocol, using
9
- * the SDK's in-memory transport (no HTTP, no network, no DuckDB). Exercises tool
10
- * registration, the dual-channel prompts, a real tool call (searchDocs over the
11
- * bundled index), and the getContext error path.
8
+ * End-to-end coverage of the unified MCP server over the real MCP protocol,
9
+ * using the SDK's in-memory transport (no HTTP, no network, no DuckDB).
10
+ * Exercises tool registration (including the agent retrieval tools that now
11
+ * live on the same server), the dual-channel skill prompts, a real tool call
12
+ * (searchDocs over the bundled index), and the getContext error path.
12
13
  */
13
- describe("agent MCP server over the MCP protocol (in-memory)", () => {
14
+ describe("MCP server over the MCP protocol (in-memory)", () => {
14
15
  let client: Client;
15
16
 
16
17
  beforeAll(async () => {
@@ -22,19 +23,20 @@ describe("agent MCP server over the MCP protocol (in-memory)", () => {
22
23
  },
23
24
  } as unknown as EnvironmentStore;
24
25
 
25
- const server = initializeAgentMcpServer(stubStore);
26
+ const server = initializeMcpServer(stubStore);
26
27
  const [clientTransport, serverTransport] =
27
28
  InMemoryTransport.createLinkedPair();
28
29
  await server.connect(serverTransport);
29
- client = new Client({ name: "agent-protocol-test", version: "0.0.0" });
30
+ client = new Client({ name: "mcp-protocol-test", version: "0.0.0" });
30
31
  await client.connect(clientTransport);
31
32
  });
32
33
 
33
- it("exposes exactly the two agent tools", async () => {
34
+ it("exposes the agent retrieval tools alongside the core tools", async () => {
34
35
  const { tools } = await client.listTools();
35
- expect(new Set(tools.map((t) => t.name))).toEqual(
36
- new Set(["malloy_getContext", "malloy_searchDocs"]),
37
- );
36
+ const names = new Set(tools.map((t) => t.name));
37
+ expect(names.has("malloy_getContext")).toBe(true);
38
+ expect(names.has("malloy_searchDocs")).toBe(true);
39
+ expect(names.has("malloy_executeQuery")).toBe(true);
38
40
  });
39
41
 
40
42
  it("exposes the skill set as dual-channel prompts", async () => {
package/src/mcp/server.ts CHANGED
@@ -2,16 +2,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { EnvironmentStore } from "../service/environment_store";
3
3
 
4
4
  import { formatDuration, logger } from "../logger";
5
- import { registerPromptCapability } from "./prompts/prompt_service.js";
6
- import { registerEnvironmentResource } from "./resources/environment_resource";
7
- import { registerModelResource } from "./resources/model_resource";
8
- import { registerNotebookResource } from "./resources/notebook_resource";
9
- import { registerPackageResource } from "./resources/package_resource";
10
- import { registerQueryResource } from "./resources/query_resource";
11
- import { registerSourceResource } from "./resources/source_resource";
12
- import { registerViewResource } from "./resources/view_resource";
13
- import { registerTools } from "./tools/discovery_tools";
5
+ import { registerDocsSearchTool } from "./tools/docs_search_tool";
14
6
  import { registerExecuteQueryTool } from "./tools/execute_query_tool";
7
+ import { registerGetContextTool } from "./tools/get_context_tool";
8
+ import skillsBundle from "./skills/skills_bundle.json";
15
9
 
16
10
  export const testServerInfo = {
17
11
  name: "malloy-publisher-mcp-server",
@@ -20,6 +14,14 @@ export const testServerInfo = {
20
14
  description: "Provides access to Malloy models and query execution via MCP.",
21
15
  };
22
16
 
17
+ // Build-time bundle of the agent skills (see skills/build_skills_bundle.ts),
18
+ // exposed as MCP prompts for the dual-channel delivery below.
19
+ const AGENT_SKILLS = (
20
+ skillsBundle as {
21
+ skills: { name: string; description: string; body: string }[];
22
+ }
23
+ ).skills;
24
+
23
25
  export function initializeMcpServer(
24
26
  environmentStore: EnvironmentStore,
25
27
  ): McpServer {
@@ -28,35 +30,25 @@ export function initializeMcpServer(
28
30
 
29
31
  const mcpServer = new McpServer(testServerInfo);
30
32
 
31
- logger.info("[MCP Init] Registering environment resource...");
32
- registerEnvironmentResource(mcpServer, environmentStore);
33
- logger.info("[MCP Init] Registering package resource...");
34
- registerPackageResource(mcpServer, environmentStore);
35
-
36
- // Register more specific templates first
37
- logger.info("[MCP Init] Registering notebook resource...");
38
- registerNotebookResource(mcpServer, environmentStore);
39
- logger.info("[MCP Init] Registering source resource...");
40
- registerSourceResource(mcpServer, environmentStore);
41
- logger.info("[MCP Init] Registering query resource...");
42
- registerQueryResource(mcpServer, environmentStore);
43
- logger.info("[MCP Init] Registering view resource...");
44
- registerViewResource(mcpServer, environmentStore);
45
-
46
- // Register the general model template last among resource types
47
- logger.info("[MCP Init] Registering model resource...");
48
- registerModelResource(mcpServer, environmentStore);
49
-
50
- logger.info("[MCP Init] Registering executeQuery tool...");
51
33
  registerExecuteQueryTool(mcpServer, environmentStore);
52
-
53
- registerTools(mcpServer, environmentStore);
54
-
55
- // Note: malloy_getContext and malloy_searchDocs are intentionally NOT registered
56
- // here. They run on a separate, isolated agent MCP server (see mcp/agent_server.ts).
57
-
58
- logger.info("[MCP Init] Registering prompt capability...");
59
- registerPromptCapability(mcpServer, environmentStore);
34
+ registerGetContextTool(mcpServer, environmentStore);
35
+ registerDocsSearchTool(mcpServer, environmentStore);
36
+
37
+ // Dual-channel: also expose each skill as an MCP prompt, so hosts that ingest
38
+ // MCP but do not load skill files (e.g. Codex, ChatGPT, Cursor) can pull the
39
+ // same guidance. Skill-aware hosts (Claude Code/Desktop) use the native skill
40
+ // files. Note: MCP prompts are on-demand, so always-on "prevention" skills
41
+ // become on-demand here; see docs/agent-skills/design-principles.md.
42
+ for (const skill of AGENT_SKILLS) {
43
+ mcpServer.prompt(skill.name, skill.description, () => ({
44
+ messages: [
45
+ {
46
+ role: "user" as const,
47
+ content: { type: "text" as const, text: skill.body },
48
+ },
49
+ ],
50
+ }));
51
+ }
60
52
 
61
53
  const endTime = performance.now();
62
54
  logger.info(`[MCP Init] Finished initializeMcpServer`, {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Build a bundled JSON of the agent skills so the agent MCP server can expose them as
2
+ * Build a bundled JSON of the agent skills so the MCP server can expose them as
3
3
  * MCP prompts (dual-channel): skill-aware hosts read the skill files directly, while
4
4
  * hosts that only ingest MCP get the same guidance through these prompts.
5
5
  *