@malloy-publisher/server 0.0.226 → 0.0.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +2 -11
  2. package/dist/package_load_worker.mjs +86 -24
  3. package/dist/server.mjs +655 -6994
  4. package/package.json +1 -4
  5. package/src/health.ts +3 -8
  6. package/src/mcp/error_messages.ts +8 -37
  7. package/src/mcp/handler_utils.ts +10 -129
  8. package/src/mcp/mcp_constants.ts +0 -16
  9. package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
  10. package/src/mcp/server.ts +29 -37
  11. package/src/mcp/skills/build_skills_bundle.ts +1 -1
  12. package/src/mcp/skills/skills_bundle.json +1 -1
  13. package/src/mcp/tools/docs_search_tool.ts +1 -1
  14. package/src/mcp/tools/execute_query_tool.ts +2 -2
  15. package/src/mcp/tools/get_context_eval.ts +3 -3
  16. package/src/mcp/tools/get_context_tool.spec.ts +196 -1
  17. package/src/mcp/tools/get_context_tool.ts +165 -67
  18. package/src/package_load/package_load_pool.ts +3 -0
  19. package/src/package_load/package_load_worker.ts +68 -24
  20. package/src/package_load/protocol.ts +16 -0
  21. package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
  22. package/src/package_load/rpc_wait_accountant.ts +76 -0
  23. package/src/package_load_metrics.spec.ts +114 -0
  24. package/src/package_load_metrics.ts +127 -0
  25. package/src/pg_helpers.spec.ts +2 -206
  26. package/src/pg_helpers.ts +4 -120
  27. package/src/server.ts +0 -16
  28. package/src/service/environment.ts +1 -1
  29. package/src/service/package.ts +82 -42
  30. package/src/test_helpers/metrics_harness.ts +40 -0
  31. package/tests/harness/mcp_test_setup.ts +1 -1
  32. package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
  33. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
  34. package/dxt/malloy_bridge.py +0 -354
  35. package/dxt/manifest.json +0 -22
  36. package/src/dto/connection.dto.spec.ts +0 -186
  37. package/src/dto/connection.dto.ts +0 -308
  38. package/src/dto/index.ts +0 -2
  39. package/src/dto/package.dto.spec.ts +0 -42
  40. package/src/dto/package.dto.ts +0 -27
  41. package/src/dto/validate.spec.ts +0 -76
  42. package/src/dto/validate.ts +0 -31
  43. package/src/ducklake_version.spec.ts +0 -43
  44. package/src/ducklake_version.ts +0 -26
  45. package/src/mcp/agent_server.spec.ts +0 -18
  46. package/src/mcp/agent_server.ts +0 -144
  47. package/src/mcp/prompts/handlers.ts +0 -84
  48. package/src/mcp/prompts/index.ts +0 -11
  49. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  50. package/src/mcp/prompts/prompt_service.ts +0 -67
  51. package/src/mcp/prompts/utils.ts +0 -62
  52. package/src/mcp/resource_metadata.ts +0 -47
  53. package/src/mcp/resources/environment_resource.ts +0 -187
  54. package/src/mcp/resources/model_resource.ts +0 -155
  55. package/src/mcp/resources/notebook_resource.ts +0 -137
  56. package/src/mcp/resources/package_resource.ts +0 -373
  57. package/src/mcp/resources/query_resource.ts +0 -122
  58. package/src/mcp/resources/source_resource.ts +0 -141
  59. package/src/mcp/resources/view_resource.ts +0 -136
  60. package/src/mcp/tools/discovery_tools.ts +0 -280
  61. package/src/storage/BaseRepository.ts +0 -31
  62. package/src/storage/StorageManager.mock.ts +0 -50
  63. package/tests/harness/e2e.ts +0 -96
  64. package/tests/harness/mocks.ts +0 -39
  65. package/tests/harness/uris.ts +0 -31
  66. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
  67. package/tests/integration/mcp/setup.spec.ts +0 -5
  68. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  69. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
@@ -1,308 +0,0 @@
1
- import { Type } from "class-transformer";
2
- import {
3
- IsDefined,
4
- IsEnum,
5
- IsArray,
6
- IsNumber,
7
- IsOptional,
8
- IsString,
9
- ValidateIf,
10
- ValidateNested,
11
- } from "class-validator";
12
- import "reflect-metadata";
13
- import { components } from "../api";
14
- import { ApiConnection } from "../service/model";
15
-
16
- type AttachedDatabase = components["schemas"]["AttachedDatabase"];
17
-
18
- export class PostgresConnectionDto {
19
- @IsOptional()
20
- @IsString()
21
- host?: string;
22
-
23
- @IsOptional()
24
- @IsNumber()
25
- port?: number;
26
-
27
- @IsOptional()
28
- @IsString()
29
- databaseName?: string;
30
-
31
- @IsOptional()
32
- @IsString()
33
- userName?: string;
34
-
35
- @IsOptional()
36
- @IsString()
37
- password?: string;
38
-
39
- @IsOptional()
40
- @IsString()
41
- connectionString?: string;
42
- }
43
-
44
- export class MysqlConnectionDto {
45
- @IsOptional()
46
- @IsString()
47
- host?: string;
48
-
49
- @IsOptional()
50
- @IsNumber()
51
- port?: number;
52
-
53
- @IsOptional()
54
- @IsString()
55
- database?: string;
56
-
57
- @IsOptional()
58
- @IsString()
59
- user?: string;
60
-
61
- @IsOptional()
62
- @IsString()
63
- password?: string;
64
- }
65
-
66
- export class BigqueryConnectionDto {
67
- @IsOptional()
68
- @IsString()
69
- defaultProjectId?: string;
70
-
71
- @IsOptional()
72
- @IsString()
73
- billingProjectId?: string;
74
-
75
- @IsOptional()
76
- @IsString()
77
- location?: string;
78
-
79
- @IsOptional()
80
- @IsString()
81
- serviceAccountKeyJson?: string;
82
-
83
- @IsOptional()
84
- @IsString()
85
- maximumBytesBilled?: string;
86
-
87
- @IsOptional()
88
- @IsString()
89
- queryTimeoutMilliseconds?: string;
90
- }
91
-
92
- export class SnowflakeConnectionDto {
93
- @IsOptional()
94
- @IsString()
95
- account?: string;
96
-
97
- @IsOptional()
98
- @IsString()
99
- username?: string;
100
-
101
- @IsOptional()
102
- @IsString()
103
- password?: string;
104
-
105
- @IsOptional()
106
- @IsString()
107
- privateKey?: string;
108
-
109
- @IsOptional()
110
- @IsString()
111
- privateKeyPass?: string;
112
-
113
- @IsOptional()
114
- @IsString()
115
- warehouse?: string;
116
-
117
- @IsOptional()
118
- @IsString()
119
- database?: string;
120
-
121
- @IsOptional()
122
- @IsString()
123
- schema?: string;
124
-
125
- @IsOptional()
126
- @IsString()
127
- role?: string;
128
-
129
- @IsOptional()
130
- @IsNumber()
131
- responseTimeoutMilliseconds?: number;
132
- }
133
-
134
- export class DatabricksConnectionDto {
135
- @IsOptional()
136
- @IsString()
137
- host?: string;
138
-
139
- @IsOptional()
140
- @IsString()
141
- path?: string;
142
-
143
- @IsOptional()
144
- @IsString()
145
- token?: string;
146
-
147
- @IsOptional()
148
- @IsString()
149
- oauthClientId?: string;
150
-
151
- @IsOptional()
152
- @IsString()
153
- oauthClientSecret?: string;
154
-
155
- @IsOptional()
156
- @IsString()
157
- defaultCatalog?: string;
158
-
159
- @IsOptional()
160
- @IsString()
161
- defaultSchema?: string;
162
-
163
- @IsOptional()
164
- @IsString()
165
- setupSQL?: string;
166
- }
167
-
168
- export class TrinoConnectionDto {
169
- @IsOptional()
170
- @IsString()
171
- server?: string;
172
-
173
- @IsOptional()
174
- @IsString()
175
- port?: number;
176
-
177
- @IsOptional()
178
- @IsString()
179
- catalog?: string;
180
-
181
- @IsOptional()
182
- @IsString()
183
- schema?: string;
184
-
185
- @IsOptional()
186
- @IsString()
187
- user?: string;
188
-
189
- @IsOptional()
190
- @IsString()
191
- password?: string;
192
-
193
- @IsOptional()
194
- @IsString()
195
- peakaKey?: string;
196
- }
197
-
198
- export class DuckdbConnectionDto {
199
- @IsOptional()
200
- @IsArray()
201
- attachedDatabases?: AttachedDatabase[];
202
- }
203
-
204
- export class SshProxyConfigDto {
205
- @IsString()
206
- host!: string;
207
-
208
- @IsOptional()
209
- @IsNumber()
210
- port?: number;
211
-
212
- @IsString()
213
- username!: string;
214
-
215
- @IsString()
216
- privateKey!: string;
217
-
218
- @IsOptional()
219
- @IsString()
220
- privateKeyPass?: string;
221
-
222
- @IsOptional()
223
- @IsString()
224
- hostKey?: string;
225
- }
226
-
227
- export class ConnectionProxyDto {
228
- @IsEnum(["ssh"])
229
- type!: "ssh";
230
-
231
- // ssh config is required for the ssh proxy type (the only type today); keep
232
- // the conditional so a future proxy type isn't forced to supply `ssh`.
233
- @ValidateIf((o) => o.type === "ssh")
234
- @IsDefined()
235
- @ValidateNested()
236
- @Type(() => SshProxyConfigDto)
237
- ssh?: SshProxyConfigDto;
238
- }
239
-
240
- export class ConnectionDto implements ApiConnection {
241
- @IsOptional()
242
- @IsString()
243
- name?: string;
244
-
245
- @IsOptional()
246
- @IsEnum([
247
- "postgres",
248
- "bigquery",
249
- "snowflake",
250
- "trino",
251
- "databricks",
252
- "mysql",
253
- "duckdb",
254
- "motherduck",
255
- "ducklake",
256
- ])
257
- type?:
258
- | "postgres"
259
- | "bigquery"
260
- | "snowflake"
261
- | "trino"
262
- | "databricks"
263
- | "mysql"
264
- | "duckdb"
265
- | "motherduck"
266
- | "ducklake";
267
-
268
- // Opaque, secret-free data-identity token; carried verbatim (never parsed
269
- // or re-hashed) into content-addressed source ids. See api-doc.yaml.
270
- @IsOptional()
271
- @IsString()
272
- fingerprint?: string;
273
-
274
- @IsOptional()
275
- @ValidateNested()
276
- @Type(() => PostgresConnectionDto)
277
- postgresConnection?: PostgresConnectionDto;
278
-
279
- @IsOptional()
280
- @ValidateNested()
281
- @Type(() => BigqueryConnectionDto)
282
- bigqueryConnection?: BigqueryConnectionDto;
283
-
284
- @IsOptional()
285
- @ValidateNested()
286
- @Type(() => SnowflakeConnectionDto)
287
- snowflakeConnection?: SnowflakeConnectionDto;
288
-
289
- @IsOptional()
290
- @ValidateNested()
291
- @Type(() => TrinoConnectionDto)
292
- TrinoConnection?: TrinoConnectionDto;
293
-
294
- @IsOptional()
295
- @ValidateNested()
296
- @Type(() => DatabricksConnectionDto)
297
- databricksConnection?: DatabricksConnectionDto;
298
-
299
- @IsOptional()
300
- @ValidateNested()
301
- @Type(() => DuckdbConnectionDto)
302
- duckdbConnection?: DuckdbConnectionDto;
303
-
304
- @IsOptional()
305
- @ValidateNested()
306
- @Type(() => ConnectionProxyDto)
307
- proxy?: ConnectionProxyDto;
308
- }
package/src/dto/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from "./package.dto";
2
- export * from "./connection.dto";
@@ -1,42 +0,0 @@
1
- import { expect, it, describe } from "bun:test";
2
-
3
- import { plainToInstance } from "class-transformer";
4
- import { validate } from "class-validator";
5
- import { PackageDto } from "./package.dto";
6
- import { faker } from "@faker-js/faker";
7
-
8
- describe("dto/package", () => {
9
- it("should not throw when valid object is provided", async () => {
10
- const errors = await validate(
11
- plainToInstance(PackageDto, {
12
- name: faker.person.firstName(),
13
- description: faker.person.lastName(),
14
- }),
15
- );
16
-
17
- expect(errors).toHaveLength(0);
18
- });
19
-
20
- it("should throw when no name is specified", async () => {
21
- const errors = await validate(
22
- plainToInstance(PackageDto, {
23
- name: "",
24
- description: faker.person.lastName(),
25
- }),
26
- );
27
-
28
- expect(errors.length).toBeGreaterThan(0);
29
- expect(errors).toHaveLength(1);
30
- });
31
-
32
- it("should throw when no description is specified", async () => {
33
- const errors = await validate(
34
- plainToInstance(PackageDto, {
35
- name: faker.person.firstName(),
36
- }),
37
- );
38
-
39
- expect(errors.length).toBeGreaterThan(0);
40
- expect(errors).toHaveLength(1);
41
- });
42
- });
@@ -1,27 +0,0 @@
1
- import {
2
- IsString,
3
- IsNotEmpty,
4
- IsOptional,
5
- IsArray,
6
- IsIn,
7
- } from "class-validator";
8
- import { ApiPackage } from "../service/package";
9
-
10
- export class PackageDto implements ApiPackage {
11
- @IsString()
12
- @IsNotEmpty()
13
- name: string;
14
-
15
- @IsString()
16
- @IsNotEmpty()
17
- description: string;
18
-
19
- @IsOptional()
20
- @IsArray()
21
- @IsString({ each: true })
22
- explores?: string[];
23
-
24
- @IsOptional()
25
- @IsIn(["declared", "all"])
26
- queryableSources?: "declared" | "all";
27
- }
@@ -1,76 +0,0 @@
1
- import { expect, it, describe } from "bun:test";
2
- import request from "supertest";
3
- import express from "express";
4
- import { validateSchema } from "./validate";
5
- import { PackageDto } from "./package.dto";
6
- import { faker } from "@faker-js/faker";
7
-
8
- const app = express();
9
- app.use(express.json());
10
-
11
- app.post("/packages", validateSchema(PackageDto), (req, res) => {
12
- res.status(201).json({ message: "Package created", data: req.body });
13
- });
14
-
15
- describe("dto/validate", () => {
16
- describe("validateSchema", () => {
17
- it("should pass validation for a valid request", async () => {
18
- const dummyPayload = {
19
- name: faker.person.firstName(),
20
- description: faker.person.lastName(),
21
- };
22
-
23
- const response = await request(app)
24
- .post("/packages")
25
- .send(dummyPayload);
26
-
27
- expect(response.status).toBe(201);
28
- expect(response.body.message).toBe("Package created");
29
- expect(response.body.data).toEqual(dummyPayload);
30
- });
31
-
32
- it("should return 400 for missing required fields", async () => {
33
- const response = await request(app).post("/packages").send({
34
- name: "",
35
- description: "",
36
- });
37
-
38
- expect(response.status).toBe(400);
39
- expect(Array.isArray(response.body.errors)).toBe(true);
40
-
41
- const expectedErrors = [
42
- {
43
- property: "name",
44
- constraints: { isNotEmpty: "name should not be empty" },
45
- },
46
- {
47
- property: "description",
48
- constraints: {
49
- isNotEmpty: "description should not be empty",
50
- },
51
- },
52
- ];
53
- expect(response.body.errors).toEqual(
54
- expect.arrayContaining(expectedErrors),
55
- );
56
- });
57
-
58
- it("should return 400 for invalid description format", async () => {
59
- const response = await request(app).post("/packages").send({
60
- name: faker.person.firstName(),
61
- description: false,
62
- });
63
-
64
- expect(response.status).toBe(400);
65
- const expectedErrors = [
66
- {
67
- property: "description",
68
- constraints: { isString: "description must be a string" },
69
- },
70
- ];
71
- expect(response.body.errors).toEqual(
72
- expect.arrayContaining(expectedErrors),
73
- );
74
- });
75
- });
76
- });
@@ -1,31 +0,0 @@
1
- import { Request, Response, NextFunction } from "express";
2
- import { plainToInstance, ClassConstructor } from "class-transformer";
3
- import { validate } from "class-validator";
4
-
5
- /**
6
- * @description validates incoming requests based on the provided dto schema
7
- * @param dtoClass to validate against
8
- * @example
9
- * app.post('/users', validateSchema(UserDto), (req, res) => {
10
- * res.status(201).json({ message: 'User created', data: req.body });
11
- * });
12
- */
13
- export function validateSchema<T extends object>(
14
- dtoClass: ClassConstructor<T>,
15
- ) {
16
- return async (req: Request, res: Response, next: NextFunction) => {
17
- const dtoInstance = plainToInstance(dtoClass, req.body);
18
- const errors = await validate(dtoInstance);
19
-
20
- if (errors.length > 0) {
21
- const formattedErrors = errors.map((err) => ({
22
- property: err.property,
23
- constraints: err.constraints,
24
- }));
25
-
26
- return res.status(400).json({ errors: formattedErrors });
27
- }
28
-
29
- next();
30
- };
31
- }
@@ -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,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
- });