@malloy-publisher/server 0.0.195 → 0.0.196
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/dist/app/api-doc.yaml +213 -214
- package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
- package/dist/app/assets/HomePage-DMop21VG.js +1 -0
- package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
- package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
- package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
- package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
- package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
- package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
- package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
- package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
- package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
- package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1976 -1322
- package/package.json +2 -2
- package/publisher.config.json +2 -2
- package/src/config.spec.ts +181 -66
- package/src/config.ts +68 -47
- package/src/controller/compile.controller.ts +10 -7
- package/src/controller/connection.controller.ts +79 -58
- package/src/controller/database.controller.ts +10 -7
- package/src/controller/manifest.controller.ts +23 -14
- package/src/controller/materialization.controller.ts +14 -14
- package/src/controller/model.controller.ts +35 -20
- package/src/controller/package.controller.ts +83 -49
- package/src/controller/query.controller.ts +11 -8
- package/src/controller/watch-mode.controller.ts +35 -29
- package/src/errors.ts +2 -2
- package/src/mcp/error_messages.ts +2 -2
- package/src/mcp/handler_utils.ts +23 -20
- package/src/mcp/mcp_constants.ts +1 -1
- package/src/mcp/prompts/handlers.ts +3 -3
- package/src/mcp/prompts/prompt_service.ts +5 -5
- package/src/mcp/prompts/utils.ts +12 -12
- package/src/mcp/resource_metadata.ts +3 -3
- package/src/mcp/resources/environment_resource.ts +187 -0
- package/src/mcp/resources/model_resource.ts +19 -17
- package/src/mcp/resources/notebook_resource.ts +13 -13
- package/src/mcp/resources/package_resource.ts +30 -27
- package/src/mcp/resources/query_resource.ts +15 -10
- package/src/mcp/resources/source_resource.ts +10 -10
- package/src/mcp/resources/view_resource.ts +11 -11
- package/src/mcp/server.ts +16 -14
- package/src/mcp/tools/discovery_tools.ts +67 -49
- package/src/mcp/tools/execute_query_tool.ts +14 -14
- package/src/server-old.ts +1119 -0
- package/src/server.ts +191 -159
- package/src/service/connection.spec.ts +158 -133
- package/src/service/connection.ts +42 -39
- package/src/service/connection_config.spec.ts +13 -11
- package/src/service/connection_config.ts +28 -19
- package/src/service/connection_service.spec.ts +63 -43
- package/src/service/connection_service.ts +106 -89
- package/src/service/{project.ts → environment.ts} +92 -77
- package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
- package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -83
- package/src/service/{project_store.ts → environment_store.ts} +373 -327
- package/src/service/manifest_service.spec.ts +15 -15
- package/src/service/manifest_service.ts +26 -21
- package/src/service/materialization_service.spec.ts +93 -59
- package/src/service/materialization_service.ts +71 -62
- package/src/service/materialized_table_gc.spec.ts +15 -15
- package/src/service/materialized_table_gc.ts +3 -3
- package/src/service/model.ts +4 -4
- package/src/service/package.spec.ts +2 -2
- package/src/service/package.ts +23 -21
- package/src/service/resolve_environment.ts +15 -0
- package/src/storage/DatabaseInterface.ts +34 -25
- package/src/storage/StorageManager.mock.ts +3 -3
- package/src/storage/StorageManager.ts +64 -28
- package/src/storage/duckdb/ConnectionRepository.ts +13 -11
- package/src/storage/duckdb/DuckDBConnection.ts +1 -1
- package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
- package/src/storage/duckdb/DuckDBRepository.ts +47 -47
- package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
- package/src/storage/duckdb/ManifestRepository.ts +21 -20
- package/src/storage/duckdb/MaterializationRepository.ts +31 -28
- package/src/storage/duckdb/PackageRepository.ts +11 -11
- package/src/storage/duckdb/manifest_store.spec.ts +2 -2
- package/src/storage/duckdb/schema.ts +61 -20
- package/src/storage/ducklake/DuckLakeManifestStore.ts +20 -11
- package/tests/fixtures/publisher.config.json +1 -1
- package/tests/harness/e2e.ts +1 -1
- package/tests/harness/mcp_test_setup.ts +12 -24
- package/tests/harness/mocks.ts +10 -8
- package/tests/harness/rest_e2e.ts +2 -2
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +28 -49
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +39 -47
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +1 -1
- package/tests/unit/duckdb/attached_databases.test.ts +51 -33
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
- package/tests/unit/ducklake/ducklake.test.ts +24 -22
- package/tests/unit/mcp/prompt_happy.test.ts +8 -8
- package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
- package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
- package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
- package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
- package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
- package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/service/resolve_project.ts +0 -13
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
// TODO: Remove this during projects cleanup
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
6
|
+
import fs from "fs/promises";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { DuckDBConnection } from "../../../src/storage/duckdb/DuckDBConnection";
|
|
10
|
+
import { initializeSchema } from "../../../src/storage/duckdb/schema";
|
|
11
|
+
|
|
12
|
+
const TEST_DB_DIR = path.join(os.tmpdir(), "duckdb-legacy-migration-tests");
|
|
13
|
+
|
|
14
|
+
// Seed a pre-rename schema on an *already-open* connection. We deliberately
|
|
15
|
+
// avoid opening, closing, and reopening the same DuckDB file within a test:
|
|
16
|
+
// on Windows runners the second `duckdb.Database(path)` call sometimes fails
|
|
17
|
+
// with `Invalid Error` because the OS hasn't released the file handle yet.
|
|
18
|
+
// Sharing one connection between seed and assertion sidesteps that entirely.
|
|
19
|
+
async function seedLegacySchema(db: DuckDBConnection): Promise<void> {
|
|
20
|
+
// Seed a pre-rename schema: parent table named `projects` and child
|
|
21
|
+
// tables with `project_id` foreign-key columns. Mirrors what an existing
|
|
22
|
+
// installation looked like before the projects→environments rename.
|
|
23
|
+
await db.run(`
|
|
24
|
+
CREATE TABLE projects (
|
|
25
|
+
id VARCHAR PRIMARY KEY,
|
|
26
|
+
name VARCHAR NOT NULL UNIQUE,
|
|
27
|
+
path VARCHAR NOT NULL,
|
|
28
|
+
description VARCHAR,
|
|
29
|
+
metadata JSON,
|
|
30
|
+
created_at TIMESTAMP NOT NULL,
|
|
31
|
+
updated_at TIMESTAMP NOT NULL
|
|
32
|
+
)
|
|
33
|
+
`);
|
|
34
|
+
await db.run(`
|
|
35
|
+
CREATE TABLE packages (
|
|
36
|
+
id VARCHAR PRIMARY KEY,
|
|
37
|
+
project_id VARCHAR NOT NULL,
|
|
38
|
+
name VARCHAR NOT NULL,
|
|
39
|
+
description VARCHAR,
|
|
40
|
+
manifest_path VARCHAR NOT NULL,
|
|
41
|
+
metadata JSON,
|
|
42
|
+
created_at TIMESTAMP NOT NULL,
|
|
43
|
+
updated_at TIMESTAMP NOT NULL,
|
|
44
|
+
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
45
|
+
)
|
|
46
|
+
`);
|
|
47
|
+
await db.run(`
|
|
48
|
+
CREATE TABLE connections (
|
|
49
|
+
id VARCHAR PRIMARY KEY,
|
|
50
|
+
project_id VARCHAR NOT NULL,
|
|
51
|
+
name VARCHAR NOT NULL,
|
|
52
|
+
type VARCHAR NOT NULL,
|
|
53
|
+
config JSON NOT NULL,
|
|
54
|
+
created_at TIMESTAMP NOT NULL,
|
|
55
|
+
updated_at TIMESTAMP NOT NULL,
|
|
56
|
+
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
await db.run(`
|
|
60
|
+
CREATE TABLE materializations (
|
|
61
|
+
id VARCHAR PRIMARY KEY,
|
|
62
|
+
project_id VARCHAR NOT NULL,
|
|
63
|
+
package_name VARCHAR NOT NULL,
|
|
64
|
+
status VARCHAR NOT NULL,
|
|
65
|
+
active_key VARCHAR,
|
|
66
|
+
started_at TIMESTAMP,
|
|
67
|
+
completed_at TIMESTAMP,
|
|
68
|
+
error TEXT,
|
|
69
|
+
metadata JSON,
|
|
70
|
+
created_at TIMESTAMP NOT NULL,
|
|
71
|
+
updated_at TIMESTAMP NOT NULL,
|
|
72
|
+
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
73
|
+
)
|
|
74
|
+
`);
|
|
75
|
+
await db.run(`
|
|
76
|
+
CREATE TABLE build_manifests (
|
|
77
|
+
id VARCHAR PRIMARY KEY,
|
|
78
|
+
project_id VARCHAR NOT NULL,
|
|
79
|
+
package_name VARCHAR NOT NULL,
|
|
80
|
+
build_id VARCHAR NOT NULL,
|
|
81
|
+
table_name VARCHAR NOT NULL,
|
|
82
|
+
source_name VARCHAR NOT NULL,
|
|
83
|
+
connection_name VARCHAR NOT NULL,
|
|
84
|
+
created_at TIMESTAMP NOT NULL,
|
|
85
|
+
updated_at TIMESTAMP NOT NULL,
|
|
86
|
+
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
87
|
+
)
|
|
88
|
+
`);
|
|
89
|
+
|
|
90
|
+
await db.run(
|
|
91
|
+
`INSERT INTO projects VALUES ('p1', 'proj-one', '/p1', 'd1', NULL,
|
|
92
|
+
TIMESTAMP '2024-01-01 00:00:00', TIMESTAMP '2024-01-01 00:00:00')`,
|
|
93
|
+
);
|
|
94
|
+
await db.run(
|
|
95
|
+
`INSERT INTO packages VALUES ('pkg1', 'p1', 'pkg-one', NULL, '/m', NULL,
|
|
96
|
+
TIMESTAMP '2024-01-01 00:00:00', TIMESTAMP '2024-01-01 00:00:00')`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe("DuckDB legacy projects schema cleanup", () => {
|
|
101
|
+
beforeEach(async () => {
|
|
102
|
+
await fs.mkdir(TEST_DB_DIR, { recursive: true });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(async () => {
|
|
106
|
+
try {
|
|
107
|
+
await fs.rm(TEST_DB_DIR, { recursive: true, force: true });
|
|
108
|
+
} catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("drops legacy projects schema and creates the new environments schema cleanly", async () => {
|
|
114
|
+
const dbPath = path.join(TEST_DB_DIR, "legacy.duckdb");
|
|
115
|
+
const db = new DuckDBConnection(dbPath);
|
|
116
|
+
await db.initialize();
|
|
117
|
+
|
|
118
|
+
// Seed the legacy schema on the same connection, then run the
|
|
119
|
+
// production schema-init path. This mirrors a server upgrade
|
|
120
|
+
// (legacy data on disk, new code starting up) without forcing a
|
|
121
|
+
// close+reopen, which is unreliable on Windows runners.
|
|
122
|
+
await seedLegacySchema(db);
|
|
123
|
+
await initializeSchema(db);
|
|
124
|
+
|
|
125
|
+
// Legacy parent table is gone.
|
|
126
|
+
const legacyProjects = await db.all<{ name: string }>(
|
|
127
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
|
|
128
|
+
);
|
|
129
|
+
expect(legacyProjects.length).toBe(0);
|
|
130
|
+
|
|
131
|
+
// New `environments` table exists and is empty (legacy data dropped).
|
|
132
|
+
const envs = await db.all<{ id: string }>("SELECT id FROM environments");
|
|
133
|
+
expect(envs.length).toBe(0);
|
|
134
|
+
|
|
135
|
+
// Child tables are queryable by `environment_id` (the new column),
|
|
136
|
+
// proving they were recreated with the new schema rather than left on
|
|
137
|
+
// the old `project_id` column.
|
|
138
|
+
const pkgs = await db.all<{ id: string }>(
|
|
139
|
+
"SELECT id FROM packages WHERE environment_id = ?",
|
|
140
|
+
["p1"],
|
|
141
|
+
);
|
|
142
|
+
expect(pkgs.length).toBe(0);
|
|
143
|
+
const conns = await db.all<{ id: string }>(
|
|
144
|
+
"SELECT id FROM connections WHERE environment_id = ?",
|
|
145
|
+
["p1"],
|
|
146
|
+
);
|
|
147
|
+
expect(conns.length).toBe(0);
|
|
148
|
+
const mats = await db.all<{ id: string }>(
|
|
149
|
+
"SELECT id FROM materializations WHERE environment_id = ?",
|
|
150
|
+
["p1"],
|
|
151
|
+
);
|
|
152
|
+
expect(mats.length).toBe(0);
|
|
153
|
+
const manifests = await db.all<{ id: string }>(
|
|
154
|
+
"SELECT id FROM build_manifests WHERE environment_id = ?",
|
|
155
|
+
["p1"],
|
|
156
|
+
);
|
|
157
|
+
expect(manifests.length).toBe(0);
|
|
158
|
+
|
|
159
|
+
await db.close();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("is idempotent: running initializeSchema twice on a migrated DB is a no-op", async () => {
|
|
163
|
+
const dbPath = path.join(TEST_DB_DIR, "legacy_idempotent.duckdb");
|
|
164
|
+
const db = new DuckDBConnection(dbPath);
|
|
165
|
+
await db.initialize();
|
|
166
|
+
|
|
167
|
+
await seedLegacySchema(db);
|
|
168
|
+
await initializeSchema(db);
|
|
169
|
+
// Second call should hit the early-return path (isInitialized() === true).
|
|
170
|
+
await initializeSchema(db);
|
|
171
|
+
|
|
172
|
+
const envs = await db.all<{ id: string }>("SELECT id FROM environments");
|
|
173
|
+
expect(envs.length).toBe(0);
|
|
174
|
+
|
|
175
|
+
await db.close();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("creates a fresh schema unchanged when no legacy projects table is present", async () => {
|
|
179
|
+
const dbPath = path.join(TEST_DB_DIR, "fresh.duckdb");
|
|
180
|
+
const db = new DuckDBConnection(dbPath);
|
|
181
|
+
await db.initialize();
|
|
182
|
+
await initializeSchema(db);
|
|
183
|
+
|
|
184
|
+
const envs = await db.all<{ id: string }>("SELECT id FROM environments");
|
|
185
|
+
expect(envs.length).toBe(0);
|
|
186
|
+
|
|
187
|
+
const legacy = await db.all<{ name: string }>(
|
|
188
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
|
|
189
|
+
);
|
|
190
|
+
expect(legacy.length).toBe(0);
|
|
191
|
+
|
|
192
|
+
await db.close();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -4,7 +4,7 @@ import fs from "fs/promises";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { components } from "../../../src/api";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
createEnvironmentConnections,
|
|
8
8
|
deleteDuckLakeConnectionFile,
|
|
9
9
|
testConnectionConfig,
|
|
10
10
|
} from "../../../src/service/connection";
|
|
@@ -143,7 +143,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
const { malloyConnections, apiConnections } =
|
|
146
|
-
await
|
|
146
|
+
await createEnvironmentConnections(
|
|
147
147
|
[ducklakeConnection],
|
|
148
148
|
testProjectPath,
|
|
149
149
|
);
|
|
@@ -212,7 +212,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
212
212
|
},
|
|
213
213
|
};
|
|
214
214
|
|
|
215
|
-
const { malloyConnections } = await
|
|
215
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
216
216
|
[ducklakeConnection],
|
|
217
217
|
testProjectPath,
|
|
218
218
|
);
|
|
@@ -268,7 +268,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
268
268
|
},
|
|
269
269
|
};
|
|
270
270
|
|
|
271
|
-
const { malloyConnections } = await
|
|
271
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
272
272
|
[ducklakeConnection],
|
|
273
273
|
testProjectPath,
|
|
274
274
|
);
|
|
@@ -333,7 +333,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
333
333
|
},
|
|
334
334
|
};
|
|
335
335
|
|
|
336
|
-
const { malloyConnections } = await
|
|
336
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
337
337
|
[ducklakeConnection],
|
|
338
338
|
testProjectPath,
|
|
339
339
|
);
|
|
@@ -400,7 +400,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
400
400
|
},
|
|
401
401
|
};
|
|
402
402
|
|
|
403
|
-
const { malloyConnections } = await
|
|
403
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
404
404
|
[ducklakeConnection],
|
|
405
405
|
testProjectPath,
|
|
406
406
|
);
|
|
@@ -470,7 +470,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
470
470
|
},
|
|
471
471
|
};
|
|
472
472
|
|
|
473
|
-
const { malloyConnections } = await
|
|
473
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
474
474
|
[ducklakeConnection],
|
|
475
475
|
testProjectPath,
|
|
476
476
|
);
|
|
@@ -529,7 +529,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
529
529
|
},
|
|
530
530
|
};
|
|
531
531
|
|
|
532
|
-
const { malloyConnections } = await
|
|
532
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
533
533
|
[ducklakeConnection],
|
|
534
534
|
testProjectPath,
|
|
535
535
|
);
|
|
@@ -553,7 +553,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
553
553
|
describe("Error Handling", () => {
|
|
554
554
|
it("should throw error if DuckLake catalog connection is missing", async () => {
|
|
555
555
|
await expect(
|
|
556
|
-
|
|
556
|
+
createEnvironmentConnections(
|
|
557
557
|
[
|
|
558
558
|
{
|
|
559
559
|
name: "ducklake_no_catalog",
|
|
@@ -576,7 +576,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
576
576
|
|
|
577
577
|
it("should throw error if DuckLake storage bucketUrl is missing", async () => {
|
|
578
578
|
await expect(
|
|
579
|
-
|
|
579
|
+
createEnvironmentConnections(
|
|
580
580
|
[
|
|
581
581
|
{
|
|
582
582
|
name: "ducklake_no_bucket",
|
|
@@ -607,7 +607,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
607
607
|
|
|
608
608
|
it("should throw error if DuckLake connection config is missing", async () => {
|
|
609
609
|
await expect(
|
|
610
|
-
|
|
610
|
+
createEnvironmentConnections(
|
|
611
611
|
[
|
|
612
612
|
{
|
|
613
613
|
name: "ducklake_missing_config",
|
|
@@ -653,19 +653,21 @@ describe("DuckLake Connection Tests", () => {
|
|
|
653
653
|
};
|
|
654
654
|
|
|
655
655
|
// Create connection twice - second should handle already attached gracefully
|
|
656
|
-
const { malloyConnections: conn1 } =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
656
|
+
const { malloyConnections: conn1 } =
|
|
657
|
+
await createEnvironmentConnections(
|
|
658
|
+
[ducklakeConnection],
|
|
659
|
+
testProjectPath,
|
|
660
|
+
);
|
|
660
661
|
const connection1 = conn1.get(
|
|
661
662
|
"ducklake_duplicate_test",
|
|
662
663
|
) as DuckDBConnection;
|
|
663
664
|
createdConnections.push(connection1);
|
|
664
665
|
|
|
665
|
-
const { malloyConnections: conn2 } =
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
const { malloyConnections: conn2 } =
|
|
667
|
+
await createEnvironmentConnections(
|
|
668
|
+
[ducklakeConnection],
|
|
669
|
+
testProjectPath,
|
|
670
|
+
);
|
|
669
671
|
const connection2 = conn2.get(
|
|
670
672
|
"ducklake_duplicate_test",
|
|
671
673
|
) as DuckDBConnection;
|
|
@@ -714,7 +716,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
714
716
|
},
|
|
715
717
|
};
|
|
716
718
|
|
|
717
|
-
const { malloyConnections } = await
|
|
719
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
718
720
|
[ducklakeConnection],
|
|
719
721
|
testProjectPath,
|
|
720
722
|
);
|
|
@@ -771,7 +773,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
771
773
|
},
|
|
772
774
|
};
|
|
773
775
|
|
|
774
|
-
const { malloyConnections } = await
|
|
776
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
775
777
|
[ducklakeConnection],
|
|
776
778
|
testProjectPath,
|
|
777
779
|
);
|
|
@@ -903,7 +905,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
903
905
|
return;
|
|
904
906
|
}
|
|
905
907
|
|
|
906
|
-
const { apiConnections } = await
|
|
908
|
+
const { apiConnections } = await createEnvironmentConnections(
|
|
907
909
|
[
|
|
908
910
|
{
|
|
909
911
|
name: "ducklake_attrs_test",
|
|
@@ -3,13 +3,13 @@ import { describe, it, expect } from "bun:test";
|
|
|
3
3
|
import { promptHandlerMap } from "../../../src/mcp/prompts/handlers";
|
|
4
4
|
|
|
5
5
|
describe("Prompt Handlers – happy paths", () => {
|
|
6
|
-
const
|
|
6
|
+
const environmentStore = undefined; // no EnvironmentStore needed for minimal tests
|
|
7
7
|
|
|
8
8
|
it("explain handler returns text message", async () => {
|
|
9
9
|
const handler = promptHandlerMap["explain-malloy-query@1.0.0"];
|
|
10
10
|
const res = await handler(
|
|
11
11
|
{ query_code: "run: t -> { aggregate: c is count() }" },
|
|
12
|
-
|
|
12
|
+
environmentStore as any,
|
|
13
13
|
);
|
|
14
14
|
expect(res.messages.length).toBe(1);
|
|
15
15
|
expect(res.messages[0].role).toBe("user");
|
|
@@ -21,9 +21,9 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
21
21
|
const res = await handler(
|
|
22
22
|
{
|
|
23
23
|
natural_language_goal: "total sales by day",
|
|
24
|
-
target_model_uri: "malloy://
|
|
24
|
+
target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
|
|
25
25
|
},
|
|
26
|
-
|
|
26
|
+
environmentStore as any,
|
|
27
27
|
);
|
|
28
28
|
expect(res.messages[0].role).toBe("user");
|
|
29
29
|
});
|
|
@@ -33,9 +33,9 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
33
33
|
const res = await handler(
|
|
34
34
|
{
|
|
35
35
|
sql_query: "SELECT 1",
|
|
36
|
-
target_model_uri: "malloy://
|
|
36
|
+
target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
environmentStore as any,
|
|
39
39
|
);
|
|
40
40
|
expect(res.messages[0].content.type).toBe("text");
|
|
41
41
|
});
|
|
@@ -43,8 +43,8 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
43
43
|
it("summarize handler returns message", async () => {
|
|
44
44
|
const handler = promptHandlerMap["summarize-malloy-model@1.0.0"];
|
|
45
45
|
const res = await handler(
|
|
46
|
-
{ model_uri: "malloy://
|
|
47
|
-
|
|
46
|
+
{ model_uri: "malloy://environment/e/pkg/models/m.malloy" },
|
|
47
|
+
environmentStore as any,
|
|
48
48
|
);
|
|
49
49
|
expect(res.messages[0].content.type).toBe("text");
|
|
50
50
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as o,j as a,i as e}from"./index-Bu0ub036.js";function i(){const t=o();return a.jsx(e,{onClickProject:t})}export{i as default};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{u as V,g as F,r as g,R as _,a as X,b as S,c as M,e as A,j as e,s as m,f as w,h as v,k as I,P as Y,m as R,l as J,n as z,B as K,o as N,p as Z,T as U,q,t as oo,d as ro,v as f,C as j,w as eo,x as to,I as ao,M as so,y as $,S as no,z as lo,A as io,O as co,D as po}from"./index-Bu0ub036.js";function uo(o,r,t,a,n){const[s,i]=g.useState(()=>n&&t?t(o).matches:a?a(o).matches:r);return X(()=>{if(!t)return;const p=t(o),u=()=>{i(p.matches)};return u(),p.addEventListener("change",u),()=>{p.removeEventListener("change",u)}},[o,t]),s}const go={..._},L=go.useSyncExternalStore;function xo(o,r,t,a,n){const s=g.useCallback(()=>r,[r]),i=g.useMemo(()=>{if(n&&t)return()=>t(o).matches;if(a!==null){const{matches:c}=a(o);return()=>c}return s},[s,o,a,n,t]),[p,u]=g.useMemo(()=>{if(t===null)return[s,()=>()=>{}];const c=t(o);return[()=>c.matches,l=>(c.addEventListener("change",l),()=>{c.removeEventListener("change",l)})]},[s,t,o]);return L(u,p,i)}function O(o={}){const{themeId:r}=o;return function(a,n={}){let s=V();s&&r&&(s=s[r]||s);const i=typeof window<"u"&&typeof window.matchMedia<"u",{defaultMatches:p=!1,matchMedia:u=i?window.matchMedia:null,ssrMatchMedia:d=null,noSsr:c=!1}=F({name:"MuiUseMediaQuery",props:n,theme:s});let l=typeof a=="function"?a(s):a;return l=l.replace(/^@media( ?)/m,""),l.includes("print")&&console.warn(["MUI: You have provided a `print` query to the `useMediaQuery` hook.","Using the print media query to modify print styles can lead to unexpected results.","Consider using the `displayPrint` field in the `sx` prop instead.","More information about `displayPrint` on our docs: https://mui.com/system/display/#display-in-print."].join(`
|
|
2
|
-
`)),(L!==void 0?xo:uo)(l,p,u,d,c)}}O();function mo(o){return S("MuiAppBar",o)}M("MuiAppBar",["root","positionFixed","positionAbsolute","positionSticky","positionStatic","positionRelative","colorDefault","colorPrimary","colorSecondary","colorInherit","colorTransparent","colorError","colorInfo","colorSuccess","colorWarning"]);const fo=o=>{const{color:r,position:t,classes:a}=o,n={root:["root",`color${v(r)}`,`position${v(t)}`]};return I(n,mo,a)},D=(o,r)=>o?`${o?.replace(")","")}, ${r})`:r,bo=m(Y,{name:"MuiAppBar",slot:"Root",overridesResolver:(o,r)=>{const{ownerState:t}=o;return[r.root,r[`position${v(t.position)}`],r[`color${v(t.color)}`]]}})(R(({theme:o})=>({display:"flex",flexDirection:"column",width:"100%",boxSizing:"border-box",flexShrink:0,variants:[{props:{position:"fixed"},style:{position:"fixed",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0,"@media print":{position:"absolute"}}},{props:{position:"absolute"},style:{position:"absolute",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0}},{props:{position:"sticky"},style:{position:"sticky",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0}},{props:{position:"static"},style:{position:"static"}},{props:{position:"relative"},style:{position:"relative"}},{props:{color:"inherit"},style:{"--AppBar-color":"inherit"}},{props:{color:"default"},style:{"--AppBar-background":o.vars?o.vars.palette.AppBar.defaultBg:o.palette.grey[100],"--AppBar-color":o.vars?o.vars.palette.text.primary:o.palette.getContrastText(o.palette.grey[100]),...o.applyStyles("dark",{"--AppBar-background":o.vars?o.vars.palette.AppBar.defaultBg:o.palette.grey[900],"--AppBar-color":o.vars?o.vars.palette.text.primary:o.palette.getContrastText(o.palette.grey[900])})}},...Object.entries(o.palette).filter(J(["contrastText"])).map(([r])=>({props:{color:r},style:{"--AppBar-background":(o.vars??o).palette[r].main,"--AppBar-color":(o.vars??o).palette[r].contrastText}})),{props:r=>r.enableColorOnDark===!0&&!["inherit","transparent"].includes(r.color),style:{backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)"}},{props:r=>r.enableColorOnDark===!1&&!["inherit","transparent"].includes(r.color),style:{backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)",...o.applyStyles("dark",{backgroundColor:o.vars?D(o.vars.palette.AppBar.darkBg,"var(--AppBar-background)"):null,color:o.vars?D(o.vars.palette.AppBar.darkColor,"var(--AppBar-color)"):null})}},{props:{color:"transparent"},style:{"--AppBar-background":"transparent","--AppBar-color":"inherit",backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)",...o.applyStyles("dark",{backgroundImage:"none"})}}]}))),ho=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiAppBar"}),{className:n,color:s="primary",enableColorOnDark:i=!1,position:p="fixed",...u}=a,d={...a,color:s,position:p,enableColorOnDark:i},c=fo(d);return e.jsx(bo,{square:!0,component:"header",ownerState:d,elevation:4,className:w(c.root,n,p==="fixed"&&"mui-fixed"),ref:t,...u})}),yo=z(e.jsx("path",{d:"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"})),vo=m(K,{name:"MuiBreadcrumbCollapsed"})(R(({theme:o})=>({display:"flex",marginLeft:`calc(${o.spacing(1)} * 0.5)`,marginRight:`calc(${o.spacing(1)} * 0.5)`,...o.palette.mode==="light"?{backgroundColor:o.palette.grey[100],color:o.palette.grey[700]}:{backgroundColor:o.palette.grey[700],color:o.palette.grey[100]},borderRadius:2,"&:hover, &:focus":{...o.palette.mode==="light"?{backgroundColor:o.palette.grey[200]}:{backgroundColor:o.palette.grey[600]}},"&:active":{boxShadow:o.shadows[0],...o.palette.mode==="light"?{backgroundColor:N(o.palette.grey[200],.12)}:{backgroundColor:N(o.palette.grey[600],.12)}}}))),ko=m(yo)({width:24,height:16});function Bo(o){const{slots:r={},slotProps:t={},...a}=o,n=o;return e.jsx("li",{children:e.jsx(vo,{focusRipple:!0,...a,ownerState:n,children:e.jsx(ko,{as:r.CollapsedIcon,ownerState:n,...t.collapsedIcon})})})}function Co(o){return S("MuiBreadcrumbs",o)}const jo=M("MuiBreadcrumbs",["root","ol","li","separator"]),So=o=>{const{classes:r}=o;return I({root:["root"],li:["li"],ol:["ol"],separator:["separator"]},Co,r)},Mo=m(U,{name:"MuiBreadcrumbs",slot:"Root",overridesResolver:(o,r)=>[{[`& .${jo.li}`]:r.li},r.root]})({}),Ao=m("ol",{name:"MuiBreadcrumbs",slot:"Ol"})({display:"flex",flexWrap:"wrap",alignItems:"center",padding:0,margin:0,listStyle:"none"}),wo=m("li",{name:"MuiBreadcrumbs",slot:"Separator"})({display:"flex",userSelect:"none",marginLeft:8,marginRight:8});function Io(o,r,t,a){return o.reduce((n,s,i)=>(i<o.length-1?n=n.concat(s,e.jsx(wo,{"aria-hidden":!0,className:r,ownerState:a,children:t},`separator-${i}`)):n.push(s),n),[])}const Ro=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiBreadcrumbs"}),{children:n,className:s,component:i="nav",slots:p={},slotProps:u={},expandText:d="Show path",itemsAfterCollapse:c=1,itemsBeforeCollapse:l=1,maxItems:h=8,separator:k="/",...Q}=a,[T,W]=g.useState(!1),b={...a,component:i,expanded:T,expandText:d,itemsAfterCollapse:c,itemsBeforeCollapse:l,maxItems:h,separator:k},y=So(b),H=Z({elementType:p.CollapsedIcon,externalSlotProps:u.collapsedIcon,ownerState:b}),P=g.useRef(null),G=x=>{const C=()=>{W(!0);const E=P.current.querySelector("a[href],button,[tabindex]");E&&E.focus()};return l+c>=x.length?x:[...x.slice(0,l),e.jsx(Bo,{"aria-label":d,slots:{CollapsedIcon:p.CollapsedIcon},slotProps:{collapsedIcon:H},onClick:C},"ellipsis"),...x.slice(x.length-c,x.length)]},B=g.Children.toArray(n).filter(x=>g.isValidElement(x)).map((x,C)=>e.jsx("li",{className:y.li,children:x},`child-${C}`));return e.jsx(Mo,{ref:t,component:i,color:"textSecondary",className:w(y.root,s),ownerState:b,...Q,children:e.jsx(Ao,{className:y.ol,ref:P,ownerState:b,children:Io(T||h&&B.length<=h?B:G(B),y.separator,k,b)})})});function zo(o){return S("MuiToolbar",o)}M("MuiToolbar",["root","gutters","regular","dense"]);const To=o=>{const{classes:r,disableGutters:t,variant:a}=o;return I({root:["root",!t&&"gutters",a]},zo,r)},Po=m("div",{name:"MuiToolbar",slot:"Root",overridesResolver:(o,r)=>{const{ownerState:t}=o;return[r.root,!t.disableGutters&&r.gutters,r[t.variant]]}})(R(({theme:o})=>({position:"relative",display:"flex",alignItems:"center",variants:[{props:({ownerState:r})=>!r.disableGutters,style:{paddingLeft:o.spacing(2),paddingRight:o.spacing(2),[o.breakpoints.up("sm")]:{paddingLeft:o.spacing(3),paddingRight:o.spacing(3)}}},{props:{variant:"dense"},style:{minHeight:48}},{props:{variant:"regular"},style:o.mixins.toolbar}]}))),Eo=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiToolbar"}),{className:n,component:s="div",disableGutters:i=!1,variant:p="regular",...u}=a,d={...a,component:s,disableGutters:i,variant:p},c=To(d);return e.jsx(Po,{as:s,className:w(c.root,n),ref:t,ownerState:d,...u})}),No=O({themeId:q}),$o=z(e.jsx("path",{d:"M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),Do=z(e.jsx("path",{d:"M3 18h18v-2H3zm0-5h18v-2H3zm0-7v2h18V6z"}));function Uo(){const o=oo(),r=o["*"],t=ro();return e.jsx(f,{sx:{display:"flex",alignItems:"center"},children:e.jsxs(Ro,{"aria-label":"breadcrumb",separator:e.jsx($o,{sx:{fontSize:14,color:"text.secondary"}}),sx:{"& .MuiBreadcrumbs-separator":{margin:"0 6px"}},children:[o.projectName&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/`,a),label:o.projectName,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"primary.100"}}}),o.packageName&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/${o.packageName}/`,a),label:o.packageName,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"secondary.100"}}}),r&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/${o.packageName}/${r}`,a),label:r,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"grey.200"}}})]})})}function Lo({logoHeader:o,endCap:r}){const t=eo(),a=to(),n=No(a.breakpoints.down("sm")),[s,i]=g.useState(null),p=!!s,u=l=>{i(l.currentTarget)},d=()=>i(null),c=[{label:"Malloy Docs",link:"https://docs.malloydata.dev/documentation/",sx:{color:"primary.main"}},{label:"Publisher Docs",link:"https://github.com/malloydata/publisher/blob/main/README.md",sx:{color:"primary.main"}},{label:"Publisher API",link:"/api-doc.html",sx:{color:"primary.main"}}];return e.jsxs(ho,{position:"sticky",elevation:0,sx:{backgroundColor:"background.paper",borderBottom:"1px solid",borderColor:"divider"},children:[e.jsxs(Eo,{sx:{justifyContent:"space-between",flexWrap:"nowrap",minHeight:44},children:[o||e.jsxs(f,{sx:{display:"flex",alignItems:"center",gap:1,cursor:"pointer"},onClick:()=>t("/"),children:[e.jsx(f,{component:"img",src:"/logo.svg",alt:"Malloy",sx:{width:28,height:28}}),e.jsx(U,{variant:"h6",sx:{color:"text.primary",fontWeight:700,letterSpacing:"-0.025em",fontSize:{xs:"1.1rem",sm:"1.25rem"}},children:"Malloy Publisher"})]}),n?e.jsxs(e.Fragment,{children:[e.jsx(ao,{color:"inherit",onClick:u,children:e.jsx(Do,{})}),e.jsxs(so,{anchorEl:s,open:p,onClose:d,anchorOrigin:{vertical:"bottom",horizontal:"right"},children:[c.map(l=>e.jsx($,{onClick:()=>{d(),window.location.href=l.link},sx:l.sx,children:l.label},l.label)),r&&e.jsx($,{children:r})]})]}):e.jsxs(no,{direction:"row",spacing:2,alignItems:"center",children:[c.map(l=>e.jsx(lo,{href:l.link,sx:l.sx,children:l.label},l.label)),r]})]}),e.jsx(f,{sx:{borderTop:"1px solid white",paddingLeft:"16px",paddingRight:"16px",marginBottom:"1px",overflowX:"auto"},children:e.jsx(Uo,{})})]})}function Qo({headerProps:o}){return e.jsxs(f,{sx:{display:"flex",flexDirection:"column",minHeight:"100vh"},children:[e.jsx(Lo,{...o}),e.jsx(io,{maxWidth:"xl",component:"main",sx:{flex:1,display:"flex",flexDirection:"column",py:2,gap:2},children:e.jsx(f,{sx:{flex:1},children:e.jsx(g.Suspense,{fallback:e.jsx(po,{}),children:e.jsx(co,{})})})})]})}export{Qo as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as n,j as e,E as i,F as t,G as c}from"./index-Bu0ub036.js";function o(){const a=n(),r=a["*"];if(!a.projectName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})});if(!a.packageName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});const s=i({projectName:a.projectName,packageName:a.packageName,modelPath:r});return r?.endsWith(".malloy")?e.jsx(t,{resourceUri:s,runOnDemand:!0,maxResultSize:512*1024}):r?.endsWith(".malloynb")?e.jsx(c,{resourceUri:s,maxResultSize:1024*1024}):e.jsx("div",{children:e.jsxs("h2",{children:["Unrecognized file type: ",r]})})}export{o as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as n,d as c,j as e,E as t,H as o}from"./index-Bu0ub036.js";function l(){const{projectName:s,packageName:a}=n(),r=c();if(s)if(a){const i=t({projectName:s,packageName:a});return e.jsx(o,{onClickPackageFile:r,resourceUri:i})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})})}export{l as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as a,t as n,j as e,E as o,J as c}from"./index-Bu0ub036.js";function u(){const r=a(),{projectName:s}=n();if(s){const t=o({projectName:s});return e.jsx(c,{onSelectPackage:r,resourceUri:t})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})})}export{u as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as a,j as e,E as t,Z as c}from"./index-Bu0ub036.js";function d(){const{workspace:r,workbookPath:s,projectName:i,packageName:n}=a();if(r)if(s)if(i)if(n){const o=t({projectName:i,packageName:n});return e.jsx(c,{workbookPath:{path:s,workspace:r},resourceUri:o},`${s}`)}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing workbook path"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing workspace"})})}export{d as default};
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import type { ListResourcesResult } from "@modelcontextprotocol/sdk/types.js"; // Needed for list return type
|
|
6
|
-
import { logger } from "../../logger";
|
|
7
|
-
import { ProjectStore } from "../../service/project_store";
|
|
8
|
-
import { getInternalError, getNotFoundError } from "../error_messages"; // Needed for error handling in list AND get
|
|
9
|
-
import { handleResourceGet, McpGetResourceError } from "../handler_utils";
|
|
10
|
-
import { RESOURCE_METADATA } from "../resource_metadata";
|
|
11
|
-
|
|
12
|
-
// Define an interface for the package object augmented with project name
|
|
13
|
-
interface PackageWithProject {
|
|
14
|
-
name?: string;
|
|
15
|
-
// Add other relevant package properties if needed
|
|
16
|
-
projectName: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Registers the Malloy Project resource type with the MCP server.
|
|
21
|
-
* Handles getting details for the hardcoded 'home' project and listing packages within it.
|
|
22
|
-
*/
|
|
23
|
-
export function registerProjectResource(
|
|
24
|
-
mcpServer: McpServer,
|
|
25
|
-
projectStore: ProjectStore,
|
|
26
|
-
): void {
|
|
27
|
-
mcpServer.resource(
|
|
28
|
-
"project",
|
|
29
|
-
new ResourceTemplate("malloy://project/{projectName}", {
|
|
30
|
-
/**
|
|
31
|
-
* Handles ListResources requests.
|
|
32
|
-
* If projectName is specified, lists packages for that project (only 'home' supported).
|
|
33
|
-
* If projectName is not specified (general ListResources call), lists packages for the default 'home' project.
|
|
34
|
-
*/
|
|
35
|
-
list: async (/* extra: ListProjectExtra - Deleted */): Promise<ListResourcesResult> => {
|
|
36
|
-
logger.info(
|
|
37
|
-
"[MCP LOG] Entering ListResources (project) handler (listing ALL packages)...",
|
|
38
|
-
);
|
|
39
|
-
// Ignore parameters from 'extra' as URI path params aren't passed to list handlers.
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const allProjects = await projectStore.listProjects();
|
|
43
|
-
logger.info(
|
|
44
|
-
`[MCP LOG] Found ${allProjects.length} projects defined.`,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const packagePromises = allProjects.map(async (proj) => {
|
|
48
|
-
try {
|
|
49
|
-
logger.info(
|
|
50
|
-
`[MCP LOG] Getting project '${proj.name}' to list its packages...`,
|
|
51
|
-
);
|
|
52
|
-
const projectInstance = await projectStore.getProject(
|
|
53
|
-
proj.name!,
|
|
54
|
-
false,
|
|
55
|
-
); // Use proj.name
|
|
56
|
-
const packages = await projectInstance.listPackages();
|
|
57
|
-
logger.info(
|
|
58
|
-
`[MCP LOG] Found ${packages.length} packages in project '${proj.name}'.`,
|
|
59
|
-
);
|
|
60
|
-
// Return packages along with their project name for URI construction
|
|
61
|
-
return packages.map((pkg) => ({
|
|
62
|
-
...pkg,
|
|
63
|
-
projectName: proj.name,
|
|
64
|
-
}));
|
|
65
|
-
} catch (projectError) {
|
|
66
|
-
logger.error(
|
|
67
|
-
`[MCP Server Error] Error getting/listing packages for project ${proj.name}:`,
|
|
68
|
-
{ error: projectError },
|
|
69
|
-
);
|
|
70
|
-
return []; // Return empty array for this project on error
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const results = await Promise.allSettled(packagePromises);
|
|
75
|
-
const allPackagesWithProjectName = results
|
|
76
|
-
.filter((result) => result.status === "fulfilled")
|
|
77
|
-
// Use the specific interface instead of any[]
|
|
78
|
-
.flatMap(
|
|
79
|
-
(result) =>
|
|
80
|
-
(result as PromiseFulfilledResult<PackageWithProject[]>)
|
|
81
|
-
.value,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
logger.info(
|
|
85
|
-
`[MCP LOG] Total packages found across all projects: ${allPackagesWithProjectName.length}`,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const packageMetadata = RESOURCE_METADATA.package;
|
|
89
|
-
const mappedResources = allPackagesWithProjectName.map((pkg) => {
|
|
90
|
-
const name = pkg.name || "unknown";
|
|
91
|
-
// Construct URI using the package's specific projectName
|
|
92
|
-
const uri = `malloy://project/${pkg.projectName}/package/${name}`;
|
|
93
|
-
return {
|
|
94
|
-
uri: uri,
|
|
95
|
-
name: name,
|
|
96
|
-
type: "package",
|
|
97
|
-
description: packageMetadata?.description as
|
|
98
|
-
| string
|
|
99
|
-
| undefined,
|
|
100
|
-
metadata: packageMetadata,
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
logger.info(
|
|
105
|
-
`[MCP LOG] ListResources (project): Returning ${mappedResources.length} package resources.`,
|
|
106
|
-
);
|
|
107
|
-
return {
|
|
108
|
-
resources: mappedResources,
|
|
109
|
-
};
|
|
110
|
-
} catch (error) {
|
|
111
|
-
// Catch errors from projectStore.listProjects() itself
|
|
112
|
-
logger.error(`[MCP Server Error] Error listing projects:`, {
|
|
113
|
-
error,
|
|
114
|
-
});
|
|
115
|
-
const errorDetails = getInternalError(
|
|
116
|
-
`ListResources (project - initial list)`,
|
|
117
|
-
error,
|
|
118
|
-
);
|
|
119
|
-
logger.error("MCP ListResources (project) error:", {
|
|
120
|
-
error: errorDetails.message,
|
|
121
|
-
});
|
|
122
|
-
logger.info(
|
|
123
|
-
"[MCP LOG] ListResources (project): Returning empty on error listing projects.",
|
|
124
|
-
);
|
|
125
|
-
return { resources: [] };
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
}),
|
|
129
|
-
/** Handles GetResource requests for Malloy Projects */
|
|
130
|
-
(uri, params) =>
|
|
131
|
-
handleResourceGet(
|
|
132
|
-
uri,
|
|
133
|
-
params,
|
|
134
|
-
"project",
|
|
135
|
-
async ({ projectName }: { projectName?: unknown }) => {
|
|
136
|
-
logger.info(
|
|
137
|
-
`[MCP LOG] Entering GetResource (project) handler for projectName: ${projectName}`,
|
|
138
|
-
);
|
|
139
|
-
// Validate project name parameter
|
|
140
|
-
if (typeof projectName !== "string") {
|
|
141
|
-
logger.error(
|
|
142
|
-
"[MCP LOG] GetResource (project): Invalid project name param.",
|
|
143
|
-
);
|
|
144
|
-
throw new Error("Invalid project name parameter.");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
logger.info(
|
|
149
|
-
`[MCP LOG] GetResource: Getting project '${projectName}'...`,
|
|
150
|
-
);
|
|
151
|
-
// Get the project instance, but we might not need its metadata directly
|
|
152
|
-
await projectStore.getProject(projectName, false);
|
|
153
|
-
// Construct the definition object expected by the test
|
|
154
|
-
const definition = { name: projectName };
|
|
155
|
-
logger.info(
|
|
156
|
-
`[MCP LOG] GetResource (project): Returning definition for '${projectName}'.`,
|
|
157
|
-
);
|
|
158
|
-
// Return the explicit definition structure
|
|
159
|
-
return definition;
|
|
160
|
-
} catch (error) {
|
|
161
|
-
logger.error(
|
|
162
|
-
`[MCP LOG] GetResource (project): Error caught for '${projectName}':`,
|
|
163
|
-
{ error },
|
|
164
|
-
);
|
|
165
|
-
// Catch expected errors from this specific resource logic
|
|
166
|
-
if (error instanceof Error) {
|
|
167
|
-
// Use getNotFoundError for the specific project not found case
|
|
168
|
-
// or a generic message for the invalid param case.
|
|
169
|
-
const errorDetails = getNotFoundError(
|
|
170
|
-
error.message.includes("not found")
|
|
171
|
-
? `Project '${projectName}'` // More specific context
|
|
172
|
-
: `Invalid project identifier provided for URI ${uri.href}`, // Generic but informative
|
|
173
|
-
);
|
|
174
|
-
// Re-throw structured error for handleResourceGet to catch
|
|
175
|
-
throw new McpGetResourceError(errorDetails);
|
|
176
|
-
}
|
|
177
|
-
// Re-throw unexpected errors to be caught by handleResourceGet's generic handler
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
RESOURCE_METADATA.project,
|
|
182
|
-
),
|
|
183
|
-
);
|
|
184
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ProjectNotFoundError } from "../errors";
|
|
2
|
-
import { ResourceRepository } from "../storage/DatabaseInterface";
|
|
3
|
-
|
|
4
|
-
export async function resolveProjectId(
|
|
5
|
-
repository: ResourceRepository,
|
|
6
|
-
projectName: string,
|
|
7
|
-
): Promise<string> {
|
|
8
|
-
const dbProject = await repository.getProjectByName(projectName);
|
|
9
|
-
if (!dbProject) {
|
|
10
|
-
throw new ProjectNotFoundError(`Project '${projectName}' not found`);
|
|
11
|
-
}
|
|
12
|
-
return dbProject.id;
|
|
13
|
-
}
|