@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.
Files changed (112) hide show
  1. package/README.docker.md +8 -8
  2. package/README.md +2 -11
  3. package/dist/app/assets/{EnvironmentPage-DvOJ7L_b.js → EnvironmentPage-EW2lbGvb.js} +1 -1
  4. package/dist/app/assets/{HomePage-CXguJsXS.js → HomePage-Bkwc9Woc.js} +1 -1
  5. package/dist/app/assets/{LightMode-ZsshUznu.js → LightMode-Bum_KBpN.js} +1 -1
  6. package/dist/app/assets/{MainPage-BIe0VwBa.js → MainPage-oiEy7TNM.js} +1 -1
  7. package/dist/app/assets/{MaterializationsPage-BuZ6UJVx.js → MaterializationsPage-C_VJsTgU.js} +1 -1
  8. package/dist/app/assets/{ModelPage-DsPf-s8B.js → ModelPage-z8REqAmk.js} +1 -1
  9. package/dist/app/assets/{PackagePage-CEVNAKZa.js → PackagePage-C2Vtt1Ln.js} +1 -1
  10. package/dist/app/assets/{RouteError-Chn7lL96.js → RouteError-DmJLpLXm.js} +1 -1
  11. package/dist/app/assets/{ThemeEditorPage-DWC_FdNU.js → ThemeEditorPage-BywFjC7A.js} +1 -1
  12. package/dist/app/assets/{WorkbookPage-CGrsFz8p.js → WorkbookPage-DCMizDMR.js} +1 -1
  13. package/dist/app/assets/{core-vVgoO8IR.es-BD_THWs_.js → core-CEDZMHV1.es-_yGzNgNH.js} +1 -1
  14. package/dist/app/assets/{index-D6YtyiJ0.js → index-CE9xhdra.js} +1 -1
  15. package/dist/app/assets/{index-BioohWQj.js → index-CdmFub34.js} +1 -1
  16. package/dist/app/assets/{index-gEWxu09x.js → index-DDMrjIT3.js} +1 -1
  17. package/dist/app/assets/{index-DNUZpnaa.js → index-EqslXZ44.js} +4 -4
  18. package/dist/app/index.html +1 -1
  19. package/dist/default-publisher.config.json +7 -7
  20. package/dist/package_load_worker.mjs +86 -24
  21. package/dist/runtime/publisher.js +5 -0
  22. package/dist/server.mjs +1415 -7876
  23. package/package.json +1 -4
  24. package/publisher.config.example.bigquery.json +7 -7
  25. package/publisher.config.example.duckdb.json +7 -7
  26. package/publisher.config.json +7 -11
  27. package/src/config.spec.ts +2 -2
  28. package/src/controller/package.controller.ts +62 -31
  29. package/src/default-publisher.config.json +7 -7
  30. package/src/health.ts +3 -8
  31. package/src/mcp/error_messages.ts +8 -37
  32. package/src/mcp/handler_utils.spec.ts +108 -0
  33. package/src/mcp/handler_utils.ts +99 -124
  34. package/src/mcp/mcp_constants.ts +0 -16
  35. package/src/mcp/server.protocol.spec.ts +138 -0
  36. package/src/mcp/server.ts +57 -37
  37. package/src/mcp/skills/build_skills_bundle.ts +1 -1
  38. package/src/mcp/skills/skills_bundle.json +1 -1
  39. package/src/mcp/tools/compile_tool.spec.ts +207 -0
  40. package/src/mcp/tools/compile_tool.ts +177 -0
  41. package/src/mcp/tools/docs_search_tool.ts +1 -1
  42. package/src/mcp/tools/execute_query_tool.spec.ts +143 -0
  43. package/src/mcp/tools/execute_query_tool.ts +39 -6
  44. package/src/mcp/tools/get_context_eval.ts +3 -3
  45. package/src/mcp/tools/get_context_tool.spec.ts +196 -1
  46. package/src/mcp/tools/get_context_tool.ts +165 -67
  47. package/src/mcp/tools/reload_package_tool.spec.ts +232 -0
  48. package/src/mcp/tools/reload_package_tool.ts +158 -0
  49. package/src/package_load/package_load_pool.ts +3 -0
  50. package/src/package_load/package_load_worker.ts +68 -24
  51. package/src/package_load/protocol.ts +16 -0
  52. package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
  53. package/src/package_load/rpc_wait_accountant.ts +76 -0
  54. package/src/package_load_metrics.spec.ts +114 -0
  55. package/src/package_load_metrics.ts +127 -0
  56. package/src/pg_helpers.spec.ts +2 -206
  57. package/src/pg_helpers.ts +4 -120
  58. package/src/runtime/publisher.js +5 -0
  59. package/src/server.ts +7 -21
  60. package/src/service/environment.ts +71 -7
  61. package/src/service/model.spec.ts +92 -0
  62. package/src/service/model.ts +58 -7
  63. package/src/service/package.ts +113 -55
  64. package/src/service/package_reload_safety.spec.ts +193 -0
  65. package/src/test_helpers/metrics_harness.ts +40 -0
  66. package/tests/fixtures/query-givens/data/orders.csv +7 -0
  67. package/tests/fixtures/query-givens/model.malloy +34 -0
  68. package/tests/fixtures/query-givens/publisher.json +5 -0
  69. package/tests/harness/mcp_test_setup.ts +1 -1
  70. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +22 -22
  71. package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
  72. package/tests/integration/query_givens/query_givens.integration.spec.ts +146 -0
  73. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +121 -0
  74. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +110 -0
  75. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
  76. package/dxt/malloy_bridge.py +0 -354
  77. package/dxt/manifest.json +0 -22
  78. package/src/dto/connection.dto.spec.ts +0 -186
  79. package/src/dto/connection.dto.ts +0 -308
  80. package/src/dto/index.ts +0 -2
  81. package/src/dto/package.dto.spec.ts +0 -42
  82. package/src/dto/package.dto.ts +0 -27
  83. package/src/dto/validate.spec.ts +0 -76
  84. package/src/dto/validate.ts +0 -31
  85. package/src/ducklake_version.spec.ts +0 -43
  86. package/src/ducklake_version.ts +0 -26
  87. package/src/mcp/agent_server.protocol.spec.ts +0 -78
  88. package/src/mcp/agent_server.spec.ts +0 -18
  89. package/src/mcp/agent_server.ts +0 -144
  90. package/src/mcp/prompts/handlers.ts +0 -84
  91. package/src/mcp/prompts/index.ts +0 -11
  92. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  93. package/src/mcp/prompts/prompt_service.ts +0 -67
  94. package/src/mcp/prompts/utils.ts +0 -62
  95. package/src/mcp/resource_metadata.ts +0 -47
  96. package/src/mcp/resources/environment_resource.ts +0 -187
  97. package/src/mcp/resources/model_resource.ts +0 -155
  98. package/src/mcp/resources/notebook_resource.ts +0 -137
  99. package/src/mcp/resources/package_resource.ts +0 -373
  100. package/src/mcp/resources/query_resource.ts +0 -122
  101. package/src/mcp/resources/source_resource.ts +0 -141
  102. package/src/mcp/resources/view_resource.ts +0 -136
  103. package/src/mcp/tools/discovery_tools.ts +0 -280
  104. package/src/storage/BaseRepository.ts +0 -31
  105. package/src/storage/StorageManager.mock.ts +0 -50
  106. package/tests/harness/e2e.ts +0 -96
  107. package/tests/harness/mocks.ts +0 -39
  108. package/tests/harness/uris.ts +0 -31
  109. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
  110. package/tests/integration/mcp/setup.spec.ts +0 -5
  111. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  112. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
@@ -1,43 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- isCatalogVersionSupported,
4
- SUPPORTED_CATALOG_VERSIONS,
5
- } from "./ducklake_version";
6
-
7
- describe("ducklake_version", () => {
8
- describe("SUPPORTED_CATALOG_VERSIONS", () => {
9
- it("matches the 0.3-line DuckLake extension's internal list", () => {
10
- expect([...SUPPORTED_CATALOG_VERSIONS]).toEqual([
11
- "0.1",
12
- "0.2",
13
- "0.3-dev1",
14
- "0.3",
15
- ]);
16
- });
17
- });
18
-
19
- describe("isCatalogVersionSupported", () => {
20
- it("accepts each documented 0.3-line version", () => {
21
- for (const v of SUPPORTED_CATALOG_VERSIONS) {
22
- expect(isCatalogVersionSupported(v)).toBe(true);
23
- }
24
- });
25
-
26
- it("rejects 1.0-line versions", () => {
27
- expect(isCatalogVersionSupported("1.0")).toBe(false);
28
- expect(isCatalogVersionSupported("1.0.0")).toBe(false);
29
- expect(isCatalogVersionSupported("1.1.0")).toBe(false);
30
- });
31
-
32
- it("rejects newer pre-1.0 versions not in the supported list", () => {
33
- expect(isCatalogVersionSupported("0.4")).toBe(false);
34
- expect(isCatalogVersionSupported("0.3-dev2")).toBe(false);
35
- });
36
-
37
- it("rejects empty and malformed input without throwing", () => {
38
- expect(isCatalogVersionSupported("")).toBe(false);
39
- expect(isCatalogVersionSupported("not a version")).toBe(false);
40
- expect(isCatalogVersionSupported("0.3 ")).toBe(false);
41
- });
42
- });
43
- });
@@ -1,26 +0,0 @@
1
- // DuckLake catalog format compatibility check. Lives at `src/` root so
2
- // both `service/` (user-facing connections) and `storage/` (materialization
3
- // catalog) can pre-flight a catalog's recorded version without bringing in
4
- // the other layer's helpers. See CLAUDE.md's "Two parallel DuckLake/PG
5
- // attach paths" note.
6
- //
7
- // The Publisher Docker image bakes a specific DuckLake extension version at
8
- // build time (see Dockerfile). If a catalog's on-disk format version is
9
- // newer than what the baked extension supports, ATTACH fails deep inside
10
- // DuckDB with a generic 500. The preflight catches that case and surfaces
11
- // it as a non-retryable 422 instead.
12
-
13
- // Versions the currently-baked DuckLake extension (0.3 line, paired with
14
- // duckdb@1.4.4) can read. When the Publisher upgrades to the DuckLake 1.0
15
- // line (separate PR, requires duckdb@1.5+), extend this list to include
16
- // "1.0" and later format versions.
17
- export const SUPPORTED_CATALOG_VERSIONS: readonly string[] = [
18
- "0.1",
19
- "0.2",
20
- "0.3-dev1",
21
- "0.3",
22
- ];
23
-
24
- export function isCatalogVersionSupported(version: string): boolean {
25
- return SUPPORTED_CATALOG_VERSIONS.includes(version);
26
- }
@@ -1,78 +0,0 @@
1
- import { beforeAll, describe, expect, it } from "bun:test";
2
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
4
- import { initializeAgentMcpServer } from "./agent_server";
5
- import type { EnvironmentStore } from "../service/environment_store";
6
-
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.
12
- */
13
- describe("agent MCP server over the MCP protocol (in-memory)", () => {
14
- let client: Client;
15
-
16
- beforeAll(async () => {
17
- // searchDocs does not touch the store; getContext's error path only needs
18
- // getEnvironment to reject, so a throwing stub is sufficient here.
19
- const stubStore = {
20
- getEnvironment: async (name: string): Promise<never> => {
21
- throw new Error(`Environment not found: ${name}`);
22
- },
23
- } as unknown as EnvironmentStore;
24
-
25
- const server = initializeAgentMcpServer(stubStore);
26
- const [clientTransport, serverTransport] =
27
- InMemoryTransport.createLinkedPair();
28
- await server.connect(serverTransport);
29
- client = new Client({ name: "agent-protocol-test", version: "0.0.0" });
30
- await client.connect(clientTransport);
31
- });
32
-
33
- it("exposes exactly the two agent tools", async () => {
34
- const { tools } = await client.listTools();
35
- expect(new Set(tools.map((t) => t.name))).toEqual(
36
- new Set(["malloy_getContext", "malloy_searchDocs"]),
37
- );
38
- });
39
-
40
- it("exposes the skill set as dual-channel prompts", async () => {
41
- const { prompts } = await client.listPrompts();
42
- expect(prompts.length).toBeGreaterThanOrEqual(24);
43
- expect(prompts.some((p) => p.name === "malloy-analysis")).toBe(true);
44
- });
45
-
46
- it("malloy_searchDocs returns relevant docs over the protocol", async () => {
47
- const res = await client.callTool({
48
- name: "malloy_searchDocs",
49
- arguments: { query: "window functions" },
50
- });
51
- const content = res.content as Array<{ resource?: { text?: string } }>;
52
- const results = JSON.parse(
53
- content?.[0]?.resource?.text ?? "[]",
54
- ) as Array<{
55
- url: string;
56
- }>;
57
- expect(results.length).toBeGreaterThan(0);
58
- expect(results[0].url.length).toBeGreaterThan(0);
59
- });
60
-
61
- it("malloy_getContext fails gracefully (isError) for an unknown environment", async () => {
62
- const res = await client.callTool({
63
- name: "malloy_getContext",
64
- arguments: {
65
- environmentName: "nope",
66
- packageName: "nope",
67
- query: "x",
68
- },
69
- });
70
- expect(res.isError).toBe(true);
71
- });
72
-
73
- it("a skill prompt returns its body", async () => {
74
- const res = await client.getPrompt({ name: "malloy-analysis" });
75
- const content = res.messages[0].content as { text?: string };
76
- expect((content.text ?? "").length).toBeGreaterThan(200);
77
- });
78
- });
@@ -1,18 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { agentServerInfo, initializeAgentMcpServer } from "./agent_server";
3
- import type { EnvironmentStore } from "../service/environment_store";
4
-
5
- describe("agent MCP server", () => {
6
- it("advertises a distinct server name from the core MCP server", () => {
7
- expect(agentServerInfo.name).toBe("malloy-publisher-agent-mcp-server");
8
- });
9
-
10
- it("initializes and registers its tools and skill prompts without throwing", () => {
11
- // The store is captured for later handler calls, not used during
12
- // registration, so a placeholder is sufficient for this smoke test.
13
- const server = initializeAgentMcpServer(
14
- {} as unknown as EnvironmentStore,
15
- );
16
- expect(server).toBeTruthy();
17
- });
18
- });
@@ -1,144 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
- import cors from "cors";
4
- import express from "express";
5
- import * as http from "http";
6
- import { registerHealthEndpoints } from "../health";
7
- import { logger } from "../logger";
8
- import { EnvironmentStore } from "../service/environment_store";
9
- import { registerDocsSearchTool } from "./tools/docs_search_tool";
10
- import { registerGetContextTool } from "./tools/get_context_tool";
11
- import skillsBundle from "./skills/skills_bundle.json";
12
-
13
- const AGENT_MCP_ENDPOINT = "/mcp";
14
-
15
- // Build-time bundle of the agent skills (see skills/build_skills_bundle.ts), exposed
16
- // as MCP prompts for the dual-channel delivery below.
17
- const AGENT_SKILLS = (
18
- skillsBundle as {
19
- skills: { name: string; description: string; body: string }[];
20
- }
21
- ).skills;
22
-
23
- export const agentServerInfo = {
24
- name: "malloy-publisher-agent-mcp-server",
25
- version: "0.0.1",
26
- displayName: "Malloy Publisher Agent MCP Server",
27
- description:
28
- "Agent retrieval tools (semantic model context and docs search) for Malloy, served on a separate MCP endpoint from the core Publisher MCP tools.",
29
- };
30
-
31
- /**
32
- * Builds an MCP server exposing ONLY the agent retrieval tools (malloy_getContext,
33
- * malloy_searchDocs). Kept deliberately separate from the core initializeMcpServer so
34
- * these tools and their tests stay isolated from the existing MCP tool surface.
35
- */
36
- export function initializeAgentMcpServer(
37
- environmentStore: EnvironmentStore,
38
- ): McpServer {
39
- const mcpServer = new McpServer(agentServerInfo);
40
- registerGetContextTool(mcpServer, environmentStore);
41
- registerDocsSearchTool(mcpServer, environmentStore);
42
-
43
- // Dual-channel: also expose each skill as an MCP prompt, so hosts that ingest MCP
44
- // but do not load skill files (e.g. Codex, ChatGPT, Cursor) can pull the same
45
- // guidance. Skill-aware hosts (Claude Code/Desktop) use the native skill files.
46
- // Note: MCP prompts are on-demand, so always-on "prevention" skills become
47
- // on-demand here; see docs/agent-skills/design-principles.md.
48
- for (const skill of AGENT_SKILLS) {
49
- mcpServer.prompt(skill.name, skill.description, () => ({
50
- messages: [
51
- {
52
- role: "user" as const,
53
- content: { type: "text" as const, text: skill.body },
54
- },
55
- ],
56
- }));
57
- }
58
-
59
- return mcpServer;
60
- }
61
-
62
- /**
63
- * Starts the agent MCP server on its own HTTP listener (a third Publisher listener),
64
- * using the same stateless StreamableHTTP transport pattern as the core MCP server.
65
- * Returns the http.Server so the caller can manage shutdown.
66
- */
67
- export function startAgentMcpServer(
68
- environmentStore: EnvironmentStore,
69
- host: string,
70
- port: number,
71
- ): http.Server {
72
- const agentMcpApp = express();
73
- registerHealthEndpoints(agentMcpApp);
74
- agentMcpApp.use(AGENT_MCP_ENDPOINT, express.json());
75
- agentMcpApp.use(AGENT_MCP_ENDPOINT, cors());
76
-
77
- agentMcpApp.all(AGENT_MCP_ENDPOINT, async (req, res) => {
78
- if (req.method !== "POST") {
79
- res.setHeader("Allow", "POST");
80
- res.status(405).json({
81
- jsonrpc: "2.0",
82
- error: {
83
- code: -32601,
84
- message: "Method Not Allowed in Stateless Mode",
85
- },
86
- id: null,
87
- });
88
- return;
89
- }
90
-
91
- try {
92
- const transport = new StreamableHTTPServerTransport({
93
- sessionIdGenerator: undefined,
94
- });
95
- transport.onerror = (err: Error) => {
96
- logger.error("[Agent MCP Transport Error]", {
97
- error: err instanceof Error ? err.message : String(err),
98
- });
99
- };
100
- const requestMcpServer = initializeAgentMcpServer(environmentStore);
101
- await requestMcpServer.connect(transport);
102
- res.on("close", () => {
103
- transport.close().catch((err) => {
104
- logger.error("[Agent MCP Transport Error] close failed", {
105
- error: err instanceof Error ? err.message : String(err),
106
- });
107
- });
108
- });
109
- await transport.handleRequest(req, res, req.body);
110
- } catch (err) {
111
- logger.error("[Agent MCP] request handler error", {
112
- error: err instanceof Error ? err.message : String(err),
113
- });
114
- if (!res.headersSent) {
115
- res.status(500).json({
116
- jsonrpc: "2.0",
117
- error: { code: -32603, message: "Internal server error" },
118
- id: null,
119
- });
120
- }
121
- }
122
- });
123
-
124
- const agentMcpServer = agentMcpApp.listen(port, host, () => {
125
- logger.info(
126
- `Agent MCP server listening at http://${host}:${port}${AGENT_MCP_ENDPOINT}`,
127
- );
128
- });
129
-
130
- // The agent MCP server is auxiliary: a bind failure here (e.g. EADDRINUSE)
131
- // must not take down the main API or the core MCP server. Without this
132
- // handler the listener's 'error' event would surface as an uncaught
133
- // exception and crash the whole process at boot. Log and degrade instead.
134
- agentMcpServer.on("error", (err: NodeJS.ErrnoException) => {
135
- logger.error("Agent MCP server failed to start; continuing without it", {
136
- host,
137
- port,
138
- code: err.code,
139
- error: err.message,
140
- });
141
- });
142
-
143
- return agentMcpServer;
144
- }
@@ -1,84 +0,0 @@
1
- import Handlebars from "handlebars";
2
- import { z } from "zod";
3
- import {
4
- PromptMessageSchema,
5
- GetPromptResultSchema,
6
- } from "@modelcontextprotocol/sdk/types.js";
7
- import { PROMPTS } from "./prompt_definitions";
8
- import { getCompiledModel } from "./utils";
9
- import { EnvironmentStore } from "../../service/environment_store";
10
-
11
- /* eslint-disable @typescript-eslint/no-explicit-any */
12
-
13
- /* --------------------------------------------------------------------------
14
- * Helper – create a simple text prompt message
15
- * ----------------------------------------------------------------------- */
16
-
17
- const textMsg = (text: string): z.infer<typeof PromptMessageSchema> => ({
18
- role: "user",
19
- content: { type: "text", text },
20
- });
21
-
22
- /* --------------------------------------------------------------------------
23
- * makePromptHandler – higher-order factory
24
- * ----------------------------------------------------------------------- */
25
-
26
- export function makePromptHandler(id: string) {
27
- const def = PROMPTS[id];
28
- if (!def) throw new Error(`Unknown prompt id: ${id}`);
29
-
30
- // pre-compile Handlebars template
31
- const templateFn = Handlebars.compile(def.template, { noEscape: true });
32
-
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- return async (params: any, ps: EnvironmentStore) => {
35
- const data: Record<string, unknown> = { ...params };
36
-
37
- // Dynamically add model content / schema context if the args include URIs
38
- if (ps) {
39
- if (typeof params?.model_uri === "string") {
40
- const compiled = await getCompiledModel(params.model_uri, ps);
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- (data as any).model_def_content = (compiled as any).modelDef;
43
- }
44
- if (typeof params?.target_model_uri === "string") {
45
- const compiled = await getCompiledModel(
46
- params.target_model_uri,
47
- ps,
48
- );
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- (data as any).schema_context = (compiled as any).modelInfo;
51
- }
52
- if (typeof params?.model_context_uri === "string") {
53
- const compiled = await getCompiledModel(
54
- params.model_context_uri,
55
- ps,
56
- );
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- (data as any).schema_context = (compiled as any).modelInfo;
59
- }
60
- }
61
-
62
- const rendered = templateFn(data);
63
-
64
- return {
65
- messages: [textMsg(rendered)],
66
- // Ensure shape matches SDK schema
67
- } as z.infer<typeof GetPromptResultSchema>;
68
- };
69
- }
70
-
71
- /* --------------------------------------------------------------------------
72
- * Registry – id -> handler
73
- * ----------------------------------------------------------------------- */
74
-
75
- export const promptHandlerMap = Object.fromEntries(
76
- Object.keys(PROMPTS).map((id) => [id, makePromptHandler(id)]),
77
- ) as Record<
78
- string,
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- (
81
- params: any,
82
- ps: EnvironmentStore,
83
- ) => Promise<z.infer<typeof GetPromptResultSchema>>
84
- >;
@@ -1,11 +0,0 @@
1
- export {
2
- withVersion,
3
- PROMPTS,
4
- MALLOY_PROMPTS,
5
- ExplainArgsSchema,
6
- GenerateQueryArgsSchema,
7
- TranslateSqlArgsSchema,
8
- SummarizeModelArgsSchema,
9
- } from "./prompt_definitions";
10
- export { promptHandlerMap, makePromptHandler } from "./handlers";
11
- export { registerPromptCapability } from "./prompt_service";
@@ -1,160 +0,0 @@
1
- import { z } from "zod";
2
-
3
- /**
4
- * Helper to build fully-qualified prompt IDs combining name + semver.
5
- */
6
- export const withVersion = (name: string, v: string) => `${name}@${v}`;
7
-
8
- /* --------------------------------------------------------------------------
9
- * Argument Schemas (Zod) – one per prompt
10
- * ----------------------------------------------------------------------- */
11
-
12
- export const ExplainArgsSchema = z
13
- .object({
14
- query_code: z.string(),
15
- model_context_uri: z.string().url().optional(),
16
- })
17
- .describe(
18
- "Explain a Malloy query: purpose, transformations, output structure.",
19
- );
20
-
21
- export const GenerateQueryArgsSchema = z
22
- .object({
23
- natural_language_goal: z.string(),
24
- target_model_uri: z.string().url(),
25
- data_source_hints: z.string().optional(),
26
- })
27
- .describe(
28
- "Generate a Malloy query from a natural-language goal and model context.",
29
- );
30
-
31
- export const TranslateSqlArgsSchema = z
32
- .object({
33
- sql_query: z.string(),
34
- target_model_uri: z.string().url(),
35
- sql_dialect: z.string().default("standard_sql"),
36
- })
37
- .describe("Translate a SQL query to Malloy using model schema context.");
38
-
39
- export const SummarizeModelArgsSchema = z
40
- .object({
41
- model_uri: z.string().url(),
42
- })
43
- .describe("Summarize a Malloy model's purpose, sources, and joins.");
44
-
45
- /* --------------------------------------------------------------------------
46
- * PromptDefinition
47
- * ----------------------------------------------------------------------- */
48
-
49
- export interface PromptDefinition {
50
- id: string;
51
- argsSchema: z.ZodTypeAny;
52
- template: string; // Handlebars syntax
53
- }
54
-
55
- /* --------------------------------------------------------------------------
56
- * Prompt Templates (Handlebars strings)
57
- * ----------------------------------------------------------------------- */
58
-
59
- const explainTemplate = `
60
- <instructions>
61
- You are an expert Malloy data analyst.
62
- Explain the following Malloy query. Provide details on:
63
- • Purpose and intended insight
64
- • Key sources involved
65
- • Main transformations / calculations
66
- • Expected result structure (dimensions/measures)
67
- </instructions>
68
-
69
- <malloy_query>
70
- {{query_code}}
71
- </malloy_query>
72
-
73
- {{#if schema_context}}
74
- <model_schema_context uri="{{model_context_uri}}">
75
- {{schema_context}}
76
- </model_schema_context>
77
- {{/if}}
78
- `;
79
-
80
- const generateTemplate = `
81
- <instructions>
82
- You are an expert Malloy modeler and query writer.
83
- Generate a Malloy query that satisfies the goal below using the model at {{target_model_uri}}.
84
-
85
- Goal: {{natural_language_goal}}
86
-
87
- {{#if data_source_hints}}
88
- Hints to consider: {{data_source_hints}}
89
- {{/if}}
90
-
91
- Return ONLY the Malloy query code.
92
- </instructions>
93
-
94
- <malloy_model_context uri="{{target_model_uri}}" />
95
- {{#if schema_context}}
96
- <fetched_model_schema_context>
97
- {{schema_context}}
98
- </fetched_model_schema_context>
99
- {{/if}}
100
- `;
101
-
102
- const translateTemplate = `
103
- <instructions>
104
- You are fluent in both {{sql_dialect}} SQL and Malloy.
105
- Translate the SQL below into Malloy using the model at {{target_model_uri}} for schema context. Return ONLY Malloy code.
106
- </instructions>
107
-
108
- <sql_query dialect="{{sql_dialect}}">{{sql_query}}</sql_query>
109
-
110
- <malloy_model_context uri="{{target_model_uri}}" />
111
- {{#if schema_context}}
112
- <fetched_model_schema_context>
113
- {{schema_context}}
114
- </fetched_model_schema_context>
115
- {{/if}}
116
- `;
117
-
118
- const summarizeTemplate = `
119
- <instructions>
120
- You are an expert Malloy modeler.
121
- Provide a concise summary of the model at {{model_uri}} covering purpose, key sources, dimensions, measures & joins.
122
- </instructions>
123
-
124
- <malloy_model_content uri="{{model_uri}}" />
125
- {{#if model_def_content}}
126
- <fetched_model_definition>
127
- {{model_def_content}}
128
- </fetched_model_definition>
129
- {{/if}}
130
- `;
131
-
132
- /* --------------------------------------------------------------------------
133
- * PROMPTS registry
134
- * ----------------------------------------------------------------------- */
135
-
136
- export const PROMPTS: Record<string, PromptDefinition> = {
137
- [withVersion("explain-malloy-query", "1.0.0")]: {
138
- id: withVersion("explain-malloy-query", "1.0.0"),
139
- argsSchema: ExplainArgsSchema,
140
- template: explainTemplate,
141
- },
142
- [withVersion("generate-malloy-query-from-description", "1.0.0")]: {
143
- id: withVersion("generate-malloy-query-from-description", "1.0.0"),
144
- argsSchema: GenerateQueryArgsSchema,
145
- template: generateTemplate,
146
- },
147
- [withVersion("translate-sql-to-malloy", "1.0.0")]: {
148
- id: withVersion("translate-sql-to-malloy", "1.0.0"),
149
- argsSchema: TranslateSqlArgsSchema,
150
- template: translateTemplate,
151
- },
152
- [withVersion("summarize-malloy-model", "1.0.0")]: {
153
- id: withVersion("summarize-malloy-model", "1.0.0"),
154
- argsSchema: SummarizeModelArgsSchema,
155
- template: summarizeTemplate,
156
- },
157
- };
158
-
159
- // Backwards-compat alias until the old handlers are removed (step 4).
160
- export const MALLOY_PROMPTS = PROMPTS;
@@ -1,67 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { logger } from "../../logger";
4
- import { EnvironmentStore } from "../../service/environment_store";
5
- import { promptHandlerMap } from "./handlers";
6
- import { MALLOY_PROMPTS } from "./prompt_definitions";
7
-
8
- /**
9
- * Registers all defined Malloy prompts with the MCP server.
10
- *
11
- * @param mcpServer The McpServer instance.
12
- * @param environmentStore The EnvironmentStore instance for handlers to access environment data.
13
- */
14
- export function registerPromptCapability(
15
- mcpServer: McpServer,
16
- environmentStore: EnvironmentStore,
17
- ): void {
18
- logger.info("[MCP Init] Registering prompt capability...");
19
- const startTime = performance.now();
20
- let registeredCount = 0;
21
-
22
- for (const promptKey in MALLOY_PROMPTS) {
23
- const promptDefinition = MALLOY_PROMPTS[promptKey];
24
- const handler = promptHandlerMap[promptDefinition.id];
25
-
26
- if (handler && promptDefinition.argsSchema instanceof z.ZodObject) {
27
- // Prepare a handler that injects the environmentStore
28
- const preparedHandler = (
29
- args: z.infer<typeof promptDefinition.argsSchema>,
30
- ) => handler(args, environmentStore);
31
-
32
- // Register using prompt ID, the Zod schema shape, and the prepared handler.
33
- mcpServer.prompt(
34
- promptDefinition.id,
35
- (
36
- promptDefinition.argsSchema as z.ZodObject<
37
- z.ZodRawShape,
38
- z.UnknownKeysParam,
39
- z.ZodTypeAny,
40
- object,
41
- object
42
- >
43
- ).shape,
44
- preparedHandler,
45
- );
46
-
47
- registeredCount++;
48
- logger.info(
49
- `[MCP Prompt Reg] Registered prompt: ${promptDefinition.id}`,
50
- );
51
- } else if (!handler) {
52
- logger.warn(
53
- `Handler not found for prompt: ${promptDefinition.id}. It will not be registered.`,
54
- );
55
- } else if (!(promptDefinition.argsSchema instanceof z.ZodObject)) {
56
- logger.warn(
57
- `Prompt definition for ${promptDefinition.id} does not have a ZodObject schema. It will not be registered.`,
58
- );
59
- }
60
- }
61
-
62
- const endTime = performance.now();
63
- logger.info(
64
- `[MCP Init] Finished registering prompts. Registered: ${registeredCount}`,
65
- { duration: endTime - startTime },
66
- );
67
- }