@malloy-publisher/server 0.0.226 → 0.0.228
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.docker.md +8 -8
- package/README.md +2 -11
- package/dist/app/assets/{EnvironmentPage-DvOJ7L_b.js → EnvironmentPage-EW2lbGvb.js} +1 -1
- package/dist/app/assets/{HomePage-CXguJsXS.js → HomePage-Bkwc9Woc.js} +1 -1
- package/dist/app/assets/{LightMode-ZsshUznu.js → LightMode-Bum_KBpN.js} +1 -1
- package/dist/app/assets/{MainPage-BIe0VwBa.js → MainPage-oiEy7TNM.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-BuZ6UJVx.js → MaterializationsPage-C_VJsTgU.js} +1 -1
- package/dist/app/assets/{ModelPage-DsPf-s8B.js → ModelPage-z8REqAmk.js} +1 -1
- package/dist/app/assets/{PackagePage-CEVNAKZa.js → PackagePage-C2Vtt1Ln.js} +1 -1
- package/dist/app/assets/{RouteError-Chn7lL96.js → RouteError-DmJLpLXm.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DWC_FdNU.js → ThemeEditorPage-BywFjC7A.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CGrsFz8p.js → WorkbookPage-DCMizDMR.js} +1 -1
- package/dist/app/assets/{core-vVgoO8IR.es-BD_THWs_.js → core-CEDZMHV1.es-_yGzNgNH.js} +1 -1
- package/dist/app/assets/{index-D6YtyiJ0.js → index-CE9xhdra.js} +1 -1
- package/dist/app/assets/{index-BioohWQj.js → index-CdmFub34.js} +1 -1
- package/dist/app/assets/{index-gEWxu09x.js → index-DDMrjIT3.js} +1 -1
- package/dist/app/assets/{index-DNUZpnaa.js → index-EqslXZ44.js} +4 -4
- package/dist/app/index.html +1 -1
- package/dist/default-publisher.config.json +7 -7
- package/dist/package_load_worker.mjs +86 -24
- package/dist/runtime/publisher.js +5 -0
- package/dist/server.mjs +1415 -7876
- package/package.json +1 -4
- package/publisher.config.example.bigquery.json +7 -7
- package/publisher.config.example.duckdb.json +7 -7
- package/publisher.config.json +7 -11
- package/src/config.spec.ts +2 -2
- package/src/controller/package.controller.ts +62 -31
- package/src/default-publisher.config.json +7 -7
- package/src/health.ts +3 -8
- package/src/mcp/error_messages.ts +8 -37
- package/src/mcp/handler_utils.spec.ts +108 -0
- package/src/mcp/handler_utils.ts +99 -124
- package/src/mcp/mcp_constants.ts +0 -16
- package/src/mcp/server.protocol.spec.ts +138 -0
- package/src/mcp/server.ts +57 -37
- package/src/mcp/skills/build_skills_bundle.ts +1 -1
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/mcp/tools/compile_tool.spec.ts +207 -0
- package/src/mcp/tools/compile_tool.ts +177 -0
- package/src/mcp/tools/docs_search_tool.ts +1 -1
- package/src/mcp/tools/execute_query_tool.spec.ts +143 -0
- package/src/mcp/tools/execute_query_tool.ts +39 -6
- package/src/mcp/tools/get_context_eval.ts +3 -3
- package/src/mcp/tools/get_context_tool.spec.ts +196 -1
- package/src/mcp/tools/get_context_tool.ts +165 -67
- package/src/mcp/tools/reload_package_tool.spec.ts +232 -0
- package/src/mcp/tools/reload_package_tool.ts +158 -0
- package/src/package_load/package_load_pool.ts +3 -0
- package/src/package_load/package_load_worker.ts +68 -24
- package/src/package_load/protocol.ts +16 -0
- package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
- package/src/package_load/rpc_wait_accountant.ts +76 -0
- package/src/package_load_metrics.spec.ts +114 -0
- package/src/package_load_metrics.ts +127 -0
- package/src/pg_helpers.spec.ts +2 -206
- package/src/pg_helpers.ts +4 -120
- package/src/runtime/publisher.js +5 -0
- package/src/server.ts +7 -21
- package/src/service/environment.ts +71 -7
- package/src/service/model.spec.ts +92 -0
- package/src/service/model.ts +58 -7
- package/src/service/package.ts +113 -55
- package/src/service/package_reload_safety.spec.ts +193 -0
- package/src/test_helpers/metrics_harness.ts +40 -0
- package/tests/fixtures/query-givens/data/orders.csv +7 -0
- package/tests/fixtures/query-givens/model.malloy +34 -0
- package/tests/fixtures/query-givens/publisher.json +5 -0
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +22 -22
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
- package/tests/integration/query_givens/query_givens.integration.spec.ts +146 -0
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +121 -0
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +110 -0
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/src/dto/connection.dto.spec.ts +0 -186
- package/src/dto/connection.dto.ts +0 -308
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -27
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/ducklake_version.spec.ts +0 -43
- package/src/ducklake_version.ts +0 -26
- package/src/mcp/agent_server.protocol.spec.ts +0 -78
- package/src/mcp/agent_server.spec.ts +0 -18
- package/src/mcp/agent_server.ts +0 -144
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/environment_resource.ts +0 -187
- package/src/mcp/resources/model_resource.ts +0 -155
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -373
- package/src/mcp/resources/query_resource.ts +0 -122
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/tools/discovery_tools.ts +0 -280
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/StorageManager.mock.ts +0 -50
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mocks.ts +0 -39
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
|
@@ -1,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
|
-
});
|
package/src/ducklake_version.ts
DELETED
|
@@ -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
|
-
});
|
package/src/mcp/agent_server.ts
DELETED
|
@@ -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
|
-
>;
|
package/src/mcp/prompts/index.ts
DELETED
|
@@ -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
|
-
}
|