@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.
- package/README.md +2 -11
- package/dist/app/api-doc.yaml +29 -24
- package/dist/app/assets/{EnvironmentPage-DYTeXDll.js → EnvironmentPage-DvOJ7L_b.js} +1 -1
- package/dist/app/assets/{HomePage-pDK2BPJY.js → HomePage-CXguJsXS.js} +1 -1
- package/dist/app/assets/{LightMode-C2bwGPY1.js → LightMode-ZsshUznu.js} +1 -1
- package/dist/app/assets/{MainPage-WtBulMH_.js → MainPage-BIe0VwBa.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-hMgOtflG.js → MaterializationsPage-BuZ6UJVx.js} +1 -1
- package/dist/app/assets/{ModelPage-B2N5kYII.js → ModelPage-DsPf-s8B.js} +1 -1
- package/dist/app/assets/{PackagePage-CEN90nQG.js → PackagePage-CEVNAKZa.js} +1 -1
- package/dist/app/assets/{RouteError-BG2c5Zf0.js → RouteError-Chn7lL96.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DNfeUwEZ.js → ThemeEditorPage-DWC_FdNU.js} +1 -1
- package/dist/app/assets/{WorkbookPage-NKI1BhFS.js → WorkbookPage-CGrsFz8p.js} +1 -1
- package/dist/app/assets/{core-C6anj5c0.es-DDLHqpzt.js → core-vVgoO8IR.es-BD_THWs_.js} +1 -1
- package/dist/app/assets/{index-DMQtnaf4.js → index-BioohWQj.js} +1 -1
- package/dist/app/assets/{index-CzNqKMVl.js → index-D6YtyiJ0.js} +1 -1
- package/dist/app/assets/{index-C6gZ6sSY.js → index-DNUZpnaa.js} +4 -4
- package/dist/app/assets/{index-JXgvyZna.js → index-gEWxu09x.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/instrumentation.mjs +71 -15
- package/dist/package_load_worker.mjs +169 -40
- package/dist/server.mjs +782 -7050
- package/package.json +1 -4
- package/src/controller/package.controller.spec.ts +17 -17
- package/src/controller/package.controller.ts +7 -6
- package/src/health.ts +3 -8
- package/src/logger.spec.ts +193 -0
- package/src/logger.ts +115 -18
- package/src/mcp/error_messages.ts +8 -37
- package/src/mcp/handler_utils.ts +10 -129
- package/src/mcp/mcp_constants.ts +0 -16
- package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
- package/src/mcp/server.ts +29 -37
- package/src/mcp/skills/build_skills_bundle.ts +1 -1
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/mcp/tools/docs_search_tool.ts +1 -1
- package/src/mcp/tools/execute_query_tool.ts +2 -2
- 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/package_load/package_load_pool.ts +8 -1
- package/src/package_load/package_load_worker.ts +75 -24
- package/src/package_load/protocol.ts +21 -1
- 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/server.ts +0 -16
- package/src/service/build_plan.spec.ts +42 -60
- package/src/service/build_plan.ts +37 -61
- package/src/service/environment.ts +5 -1
- package/src/service/materialization_test_fixtures.ts +6 -8
- package/src/service/package.ts +165 -86
- package/src/service/package_manifest.spec.ts +22 -1
- package/src/service/package_manifest.ts +29 -0
- package/src/service/persistence_policy.spec.ts +234 -0
- package/src/test_helpers/metrics_harness.ts +40 -0
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
- 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.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/service/materialization_cron_gate.spec.ts +0 -181
- 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/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
|
-
}
|
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
|
-
};
|