coalesce-transform-mcp 0.3.0 → 0.4.2
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 +74 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6 -2
- package/dist/client.js.map +1 -1
- package/dist/coalesce/api/environments.d.ts +0 -12
- package/dist/coalesce/api/environments.d.ts.map +1 -1
- package/dist/coalesce/api/environments.js +0 -4
- package/dist/coalesce/api/environments.js.map +1 -1
- package/dist/coalesce/api/jobs.d.ts +3 -5
- package/dist/coalesce/api/jobs.d.ts.map +1 -1
- package/dist/coalesce/api/jobs.js +3 -6
- package/dist/coalesce/api/jobs.js.map +1 -1
- package/dist/coalesce/api/nodes.d.ts +3 -3
- package/dist/coalesce/api/nodes.d.ts.map +1 -1
- package/dist/coalesce/api/nodes.js +6 -4
- package/dist/coalesce/api/nodes.js.map +1 -1
- package/dist/coalesce/api/runs.d.ts.map +1 -1
- package/dist/coalesce/api/runs.js +11 -1
- package/dist/coalesce/api/runs.js.map +1 -1
- package/dist/coalesce/api/scan.d.ts +14 -0
- package/dist/coalesce/api/scan.d.ts.map +1 -0
- package/dist/coalesce/api/scan.js +64 -0
- package/dist/coalesce/api/scan.js.map +1 -0
- package/dist/coalesce/api/subgraphs.d.ts +3 -2
- package/dist/coalesce/api/subgraphs.d.ts.map +1 -1
- package/dist/coalesce/api/subgraphs.js +3 -2
- package/dist/coalesce/api/subgraphs.js.map +1 -1
- package/dist/coalesce/run-schemas.d.ts.map +1 -1
- package/dist/coalesce/run-schemas.js +26 -16
- package/dist/coalesce/run-schemas.js.map +1 -1
- package/dist/coalesce/tool-response.d.ts +1 -13
- package/dist/coalesce/tool-response.d.ts.map +1 -1
- package/dist/coalesce/tool-response.js +20 -6
- package/dist/coalesce/tool-response.js.map +1 -1
- package/dist/coalesce/tool-schemas.d.ts +1 -2
- package/dist/coalesce/tool-schemas.d.ts.map +1 -1
- package/dist/coalesce/tool-schemas.js +368 -5
- package/dist/coalesce/tool-schemas.js.map +1 -1
- package/dist/coalesce/types.d.ts +8 -0
- package/dist/coalesce/types.d.ts.map +1 -1
- package/dist/coalesce/types.js +3 -1
- package/dist/coalesce/types.js.map +1 -1
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +21 -0
- package/dist/constants.js.map +1 -0
- package/dist/mcp/cache.d.ts +2 -1
- package/dist/mcp/cache.d.ts.map +1 -1
- package/dist/mcp/cache.js +122 -138
- package/dist/mcp/cache.js.map +1 -1
- package/dist/mcp/environments.d.ts +2 -1
- package/dist/mcp/environments.d.ts.map +1 -1
- package/dist/mcp/environments.js +56 -112
- package/dist/mcp/environments.js.map +1 -1
- package/dist/mcp/git-accounts.d.ts +2 -1
- package/dist/mcp/git-accounts.d.ts.map +1 -1
- package/dist/mcp/git-accounts.js +74 -96
- package/dist/mcp/git-accounts.js.map +1 -1
- package/dist/mcp/jobs.d.ts +2 -1
- package/dist/mcp/jobs.d.ts.map +1 -1
- package/dist/mcp/jobs.js +68 -122
- package/dist/mcp/jobs.js.map +1 -1
- package/dist/mcp/lineage.d.ts +5 -0
- package/dist/mcp/lineage.d.ts.map +1 -0
- package/dist/mcp/lineage.js +410 -0
- package/dist/mcp/lineage.js.map +1 -0
- package/dist/mcp/node-type-corpus.d.ts +2 -1
- package/dist/mcp/node-type-corpus.d.ts.map +1 -1
- package/dist/mcp/node-type-corpus.js +148 -151
- package/dist/mcp/node-type-corpus.js.map +1 -1
- package/dist/mcp/nodes.d.ts +2 -1
- package/dist/mcp/nodes.d.ts.map +1 -1
- package/dist/mcp/nodes.js +358 -464
- package/dist/mcp/nodes.js.map +1 -1
- package/dist/mcp/pipelines.d.ts +2 -1
- package/dist/mcp/pipelines.d.ts.map +1 -1
- package/dist/mcp/pipelines.js +514 -314
- package/dist/mcp/pipelines.js.map +1 -1
- package/dist/mcp/projects.d.ts +2 -1
- package/dist/mcp/projects.d.ts.map +1 -1
- package/dist/mcp/projects.js +66 -100
- package/dist/mcp/projects.js.map +1 -1
- package/dist/mcp/repo-node-types.d.ts +2 -1
- package/dist/mcp/repo-node-types.d.ts.map +1 -1
- package/dist/mcp/repo-node-types.js +92 -121
- package/dist/mcp/repo-node-types.js.map +1 -1
- package/dist/mcp/runs.d.ts +3 -2
- package/dist/mcp/runs.d.ts.map +1 -1
- package/dist/mcp/runs.js +93 -148
- package/dist/mcp/runs.js.map +1 -1
- package/dist/mcp/skills.d.ts +13 -0
- package/dist/mcp/skills.d.ts.map +1 -0
- package/dist/mcp/skills.js +85 -0
- package/dist/mcp/skills.js.map +1 -0
- package/dist/mcp/subgraphs.d.ts +2 -1
- package/dist/mcp/subgraphs.d.ts.map +1 -1
- package/dist/mcp/subgraphs.js +61 -98
- package/dist/mcp/subgraphs.js.map +1 -1
- package/dist/mcp/tool-helpers.d.ts +37 -0
- package/dist/mcp/tool-helpers.d.ts.map +1 -0
- package/dist/mcp/tool-helpers.js +82 -0
- package/dist/mcp/tool-helpers.js.map +1 -0
- package/dist/mcp/users.d.ts +2 -1
- package/dist/mcp/users.d.ts.map +1 -1
- package/dist/mcp/users.js +92 -145
- package/dist/mcp/users.js.map +1 -1
- package/dist/mcp/workshop.d.ts +2 -1
- package/dist/mcp/workshop.d.ts.map +1 -1
- package/dist/mcp/workshop.js +66 -101
- package/dist/mcp/workshop.js.map +1 -1
- package/dist/mcp/workspaces.d.ts +2 -1
- package/dist/mcp/workspaces.d.ts.map +1 -1
- package/dist/mcp/workspaces.js +19 -34
- package/dist/mcp/workspaces.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +85 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/context/pipeline-workshop-guide.md +1 -1
- package/dist/resources/context/tool-usage.md +7 -0
- package/dist/resources/index.d.ts +13 -0
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +105 -5
- package/dist/resources/index.js.map +1 -1
- package/dist/schemas/node-payloads.d.ts +2 -2
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +158 -41
- package/dist/server.js.map +1 -1
- package/dist/services/cache/snapshots.d.ts.map +1 -1
- package/dist/services/cache/snapshots.js +9 -5
- package/dist/services/cache/snapshots.js.map +1 -1
- package/dist/services/config/schema-resolver.d.ts.map +1 -1
- package/dist/services/config/schema-resolver.js +3 -6
- package/dist/services/config/schema-resolver.js.map +1 -1
- package/dist/services/lineage/lineage-cache.d.ts +53 -0
- package/dist/services/lineage/lineage-cache.d.ts.map +1 -0
- package/dist/services/lineage/lineage-cache.js +335 -0
- package/dist/services/lineage/lineage-cache.js.map +1 -0
- package/dist/services/lineage/lineage-documentation.d.ts +29 -0
- package/dist/services/lineage/lineage-documentation.d.ts.map +1 -0
- package/dist/services/lineage/lineage-documentation.js +80 -0
- package/dist/services/lineage/lineage-documentation.js.map +1 -0
- package/dist/services/lineage/lineage-propagation.d.ts +47 -0
- package/dist/services/lineage/lineage-propagation.d.ts.map +1 -0
- package/dist/services/lineage/lineage-propagation.js +176 -0
- package/dist/services/lineage/lineage-propagation.js.map +1 -0
- package/dist/services/lineage/lineage-search.d.ts +33 -0
- package/dist/services/lineage/lineage-search.d.ts.map +1 -0
- package/dist/services/lineage/lineage-search.js +133 -0
- package/dist/services/lineage/lineage-search.js.map +1 -0
- package/dist/services/lineage/lineage-traversal.d.ts +34 -0
- package/dist/services/lineage/lineage-traversal.d.ts.map +1 -0
- package/dist/services/lineage/lineage-traversal.js +283 -0
- package/dist/services/lineage/lineage-traversal.js.map +1 -0
- package/dist/services/pipelines/clause-extraction.d.ts +3 -0
- package/dist/services/pipelines/clause-extraction.d.ts.map +1 -0
- package/dist/services/pipelines/clause-extraction.js +27 -0
- package/dist/services/pipelines/clause-extraction.js.map +1 -0
- package/dist/services/pipelines/column-helpers.d.ts +8 -0
- package/dist/services/pipelines/column-helpers.d.ts.map +1 -0
- package/dist/services/pipelines/column-helpers.js +125 -0
- package/dist/services/pipelines/column-helpers.js.map +1 -0
- package/dist/services/pipelines/cte-parsing.d.ts +29 -0
- package/dist/services/pipelines/cte-parsing.d.ts.map +1 -0
- package/dist/services/pipelines/cte-parsing.js +160 -0
- package/dist/services/pipelines/cte-parsing.js.map +1 -0
- package/dist/services/pipelines/cte-planning.d.ts +22 -0
- package/dist/services/pipelines/cte-planning.d.ts.map +1 -0
- package/dist/services/pipelines/cte-planning.js +206 -0
- package/dist/services/pipelines/cte-planning.js.map +1 -0
- package/dist/services/pipelines/execution.d.ts.map +1 -1
- package/dist/services/pipelines/execution.js +0 -1
- package/dist/services/pipelines/execution.js.map +1 -1
- package/dist/services/pipelines/intent-parsing.d.ts +24 -0
- package/dist/services/pipelines/intent-parsing.d.ts.map +1 -0
- package/dist/services/pipelines/intent-parsing.js +245 -0
- package/dist/services/pipelines/intent-parsing.js.map +1 -0
- package/dist/services/pipelines/intent-resolution.d.ts +24 -0
- package/dist/services/pipelines/intent-resolution.d.ts.map +1 -0
- package/dist/services/pipelines/intent-resolution.js +141 -0
- package/dist/services/pipelines/intent-resolution.js.map +1 -0
- package/dist/services/pipelines/intent.d.ts +4 -45
- package/dist/services/pipelines/intent.d.ts.map +1 -1
- package/dist/services/pipelines/intent.js +14 -408
- package/dist/services/pipelines/intent.js.map +1 -1
- package/dist/services/pipelines/node-type-candidates.d.ts +6 -0
- package/dist/services/pipelines/node-type-candidates.d.ts.map +1 -0
- package/dist/services/pipelines/node-type-candidates.js +165 -0
- package/dist/services/pipelines/node-type-candidates.js.map +1 -0
- package/dist/services/pipelines/node-type-intent.d.ts +1 -5
- package/dist/services/pipelines/node-type-intent.d.ts.map +1 -1
- package/dist/services/pipelines/node-type-intent.js +1 -5
- package/dist/services/pipelines/node-type-intent.js.map +1 -1
- package/dist/services/pipelines/node-type-scoring.d.ts +13 -0
- package/dist/services/pipelines/node-type-scoring.d.ts.map +1 -0
- package/dist/services/pipelines/node-type-scoring.js +322 -0
- package/dist/services/pipelines/node-type-scoring.js.map +1 -0
- package/dist/services/pipelines/node-type-selection.d.ts +22 -2
- package/dist/services/pipelines/node-type-selection.d.ts.map +1 -1
- package/dist/services/pipelines/node-type-selection.js +16 -538
- package/dist/services/pipelines/node-type-selection.js.map +1 -1
- package/dist/services/pipelines/plan-builder.d.ts +33 -0
- package/dist/services/pipelines/plan-builder.d.ts.map +1 -0
- package/dist/services/pipelines/plan-builder.js +224 -0
- package/dist/services/pipelines/plan-builder.js.map +1 -0
- package/dist/services/pipelines/planning-types.d.ts +543 -0
- package/dist/services/pipelines/planning-types.d.ts.map +1 -0
- package/dist/services/pipelines/planning-types.js +85 -0
- package/dist/services/pipelines/planning-types.js.map +1 -0
- package/dist/services/pipelines/planning.d.ts +8 -537
- package/dist/services/pipelines/planning.d.ts.map +1 -1
- package/dist/services/pipelines/planning.js +10 -1956
- package/dist/services/pipelines/planning.js.map +1 -1
- package/dist/services/pipelines/review.d.ts.map +1 -1
- package/dist/services/pipelines/review.js +3 -8
- package/dist/services/pipelines/review.js.map +1 -1
- package/dist/services/pipelines/select-parsing.d.ts +7 -0
- package/dist/services/pipelines/select-parsing.d.ts.map +1 -0
- package/dist/services/pipelines/select-parsing.js +185 -0
- package/dist/services/pipelines/select-parsing.js.map +1 -0
- package/dist/services/pipelines/source-parsing.d.ts +8 -0
- package/dist/services/pipelines/source-parsing.d.ts.map +1 -0
- package/dist/services/pipelines/source-parsing.js +151 -0
- package/dist/services/pipelines/source-parsing.js.map +1 -0
- package/dist/services/pipelines/sql-parsing.d.ts +8 -0
- package/dist/services/pipelines/sql-parsing.d.ts.map +1 -0
- package/dist/services/pipelines/sql-parsing.js +9 -0
- package/dist/services/pipelines/sql-parsing.js.map +1 -0
- package/dist/services/pipelines/sql-tokenizer.d.ts +42 -0
- package/dist/services/pipelines/sql-tokenizer.d.ts.map +1 -0
- package/dist/services/pipelines/sql-tokenizer.js +493 -0
- package/dist/services/pipelines/sql-tokenizer.js.map +1 -0
- package/dist/services/pipelines/sql-utils.d.ts +30 -0
- package/dist/services/pipelines/sql-utils.d.ts.map +1 -0
- package/dist/services/pipelines/sql-utils.js +62 -0
- package/dist/services/pipelines/sql-utils.js.map +1 -0
- package/dist/services/pipelines/workshop.d.ts.map +1 -1
- package/dist/services/pipelines/workshop.js +53 -25
- package/dist/services/pipelines/workshop.js.map +1 -1
- package/dist/services/pipelines/workspace-resolution.d.ts +18 -0
- package/dist/services/pipelines/workspace-resolution.d.ts.map +1 -0
- package/dist/services/pipelines/workspace-resolution.js +279 -0
- package/dist/services/pipelines/workspace-resolution.js.map +1 -0
- package/dist/services/runs/diagnostics.d.ts.map +1 -1
- package/dist/services/runs/diagnostics.js +3 -8
- package/dist/services/runs/diagnostics.js.map +1 -1
- package/dist/services/shared/elicitation.d.ts +14 -0
- package/dist/services/shared/elicitation.d.ts.map +1 -0
- package/dist/services/shared/elicitation.js +56 -0
- package/dist/services/shared/elicitation.js.map +1 -0
- package/dist/services/workspace/node-creation.d.ts.map +1 -1
- package/dist/services/workspace/node-creation.js +5 -1
- package/dist/services/workspace/node-creation.js.map +1 -1
- package/dist/services/workspace/node-update-helpers.d.ts.map +1 -1
- package/dist/services/workspace/node-update-helpers.js +3 -8
- package/dist/services/workspace/node-update-helpers.js.map +1 -1
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +20 -1
- package/dist/utils.js.map +1 -1
- package/dist/workflows/get-environment-health.d.ts +49 -0
- package/dist/workflows/get-environment-health.d.ts.map +1 -0
- package/dist/workflows/get-environment-health.js +310 -0
- package/dist/workflows/get-environment-health.js.map +1 -0
- package/dist/workflows/get-environment-overview.d.ts +2 -1
- package/dist/workflows/get-environment-overview.d.ts.map +1 -1
- package/dist/workflows/get-environment-overview.js +13 -19
- package/dist/workflows/get-environment-overview.js.map +1 -1
- package/dist/workflows/get-run-details.d.ts +2 -2
- package/dist/workflows/get-run-details.d.ts.map +1 -1
- package/dist/workflows/get-run-details.js +14 -19
- package/dist/workflows/get-run-details.js.map +1 -1
- package/dist/workflows/retry-and-wait.d.ts.map +1 -1
- package/dist/workflows/retry-and-wait.js +3 -2
- package/dist/workflows/retry-and-wait.js.map +1 -1
- package/dist/workflows/run-and-wait.d.ts.map +1 -1
- package/dist/workflows/run-and-wait.js +3 -2
- package/dist/workflows/run-and-wait.js.map +1 -1
- package/package.json +2 -2
package/dist/mcp/pipelines.js
CHANGED
|
@@ -4,11 +4,12 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFile
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { CACHE_DIR_NAME } from "../cache-dir.js";
|
|
6
6
|
import { buildPlanConfirmationToken, sortJsonValue } from "../services/pipelines/confirmation.js";
|
|
7
|
-
import { PipelinePlanSchema, planPipeline, } from "../services/pipelines/planning.js";
|
|
7
|
+
import { PipelinePlanSchema, planPipeline, extractCtes, parseSqlSourceRefs, parseSqlSelectItems, getWorkspaceNodeTypeInventory, escapeRegExp, } from "../services/pipelines/planning.js";
|
|
8
|
+
import { selectPipelineNodeType, } from "../services/pipelines/node-type-selection.js";
|
|
8
9
|
import { createPipelineFromPlan, } from "../services/pipelines/execution.js";
|
|
9
10
|
import { NodeConfigInputSchema } from "../schemas/node-payloads.js";
|
|
10
11
|
import { buildJsonToolResponse, getToolOutputSchema, handleToolError, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, validatePathSegment, } from "../coalesce/types.js";
|
|
11
|
-
import { isPlainObject } from "../utils.js";
|
|
12
|
+
import { isPlainObject, sanitizeForFilename } from "../utils.js";
|
|
12
13
|
import { buildPipelinePlanFromIntent, } from "../services/pipelines/intent.js";
|
|
13
14
|
import { reviewPipeline, } from "../services/pipelines/review.js";
|
|
14
15
|
export { buildPlanConfirmationToken };
|
|
@@ -76,7 +77,7 @@ function findCachedPlanSummary(workspaceID, fingerprint) {
|
|
|
76
77
|
const dir = getPlanSummaryDir();
|
|
77
78
|
if (!existsSync(dir))
|
|
78
79
|
return null;
|
|
79
|
-
const safeID = workspaceID
|
|
80
|
+
const safeID = sanitizeForFilename(workspaceID);
|
|
80
81
|
const prefix = `plan-${safeID}-`;
|
|
81
82
|
const files = readdirSync(dir)
|
|
82
83
|
.filter((f) => f.startsWith(prefix) && f.endsWith(".md"))
|
|
@@ -95,8 +96,7 @@ function writePlanSummary(plan, fingerprint) {
|
|
|
95
96
|
if (!isPlainObject(plan))
|
|
96
97
|
return null;
|
|
97
98
|
const rawWorkspaceID = typeof plan.workspaceID === "string" ? plan.workspaceID : "unknown";
|
|
98
|
-
|
|
99
|
-
const workspaceID = rawWorkspaceID.replace(/[^a-zA-Z0-9_\-]/g, "_");
|
|
99
|
+
const workspaceID = sanitizeForFilename(rawWorkspaceID);
|
|
100
100
|
const selection = isPlainObject(plan.nodeTypeSelection) ? plan.nodeTypeSelection : null;
|
|
101
101
|
const consideredNodeTypes = Array.isArray(selection?.consideredNodeTypes)
|
|
102
102
|
? selection.consideredNodeTypes.filter(isPlainObject)
|
|
@@ -161,14 +161,24 @@ function writePlanSummary(plan, fingerprint) {
|
|
|
161
161
|
}
|
|
162
162
|
function cleanupOldPlanFiles(dir, workspaceID, maxToKeep) {
|
|
163
163
|
try {
|
|
164
|
-
const safeID = workspaceID
|
|
164
|
+
const safeID = sanitizeForFilename(workspaceID);
|
|
165
165
|
const prefix = `plan-${safeID}-`;
|
|
166
166
|
const files = readdirSync(dir)
|
|
167
167
|
.filter((f) => f.startsWith(prefix) && f.endsWith(".md"))
|
|
168
168
|
.sort()
|
|
169
169
|
.reverse(); // most recent first (timestamp in filename)
|
|
170
170
|
for (const file of files.slice(maxToKeep)) {
|
|
171
|
-
|
|
171
|
+
try {
|
|
172
|
+
unlinkSync(join(dir, file));
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
// Ignore ENOENT — another process may have already deleted this file.
|
|
176
|
+
// Log anything else so permission/disk errors don't go unnoticed.
|
|
177
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT")
|
|
178
|
+
continue;
|
|
179
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
180
|
+
process.stderr.write(`[coalesce-transform-mcp] plan file delete failed for ${file}: ${reason}\n`);
|
|
181
|
+
}
|
|
172
182
|
}
|
|
173
183
|
}
|
|
174
184
|
catch (error) {
|
|
@@ -271,322 +281,512 @@ async function requirePipelineCreationApproval(server, toolName, plan, confirmed
|
|
|
271
281
|
}
|
|
272
282
|
return null;
|
|
273
283
|
}
|
|
274
|
-
export function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
284
|
+
export function definePipelineTools(server, client) {
|
|
285
|
+
return [
|
|
286
|
+
[
|
|
287
|
+
"plan_pipeline",
|
|
288
|
+
{
|
|
289
|
+
title: "Plan Pipeline",
|
|
290
|
+
description: "Plan a Coalesce pipeline by discovering and ranking all available node types from the repo. ALWAYS call this before creating nodes to get the correct node type.\n\nThe planner scans the repo for all committed node type definitions, scores them against your use case, and returns ranked candidates. When available, it also returns a cached `planSummaryUri` MCP resource for the ranked node type summary so you can reuse that guidance throughout the pipeline without calling the planner again.\n\nIMPORTANT — DO NOT WRITE SQL: The `sql` parameter is ONLY for converting SQL that the USER provided (pasted or typed). If you are building a pipeline yourself, provide `goal` + `sourceNodeIDs` instead.\n\nPREREQUISITE: Before calling this tool, use list_workspace_nodes to discover available source/upstream nodes and their IDs in the workspace.\n\nPreferred approach: Provide `goal` AND `sourceNodeIDs`. The planner selects the best node type and scaffolds the pipeline. Without sourceNodeIDs, the planner returns clarification questions.\n\nUser-provided SQL: When a user pastes SQL, pass it in `sql`. The planner parses refs and column projections.\n\nConsult coalesce://context/node-type-corpus for node type patterns and metadata structures.",
|
|
291
|
+
inputSchema: z.object({
|
|
292
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
293
|
+
goal: z.string().optional().describe("Optional natural-language pipeline goal"),
|
|
294
|
+
sql: z.string().optional().describe("The user's EXACT SQL, copied verbatim. It may use raw table names or existing Coalesce {{ ref() }} syntax. Do NOT rewrite between SQL styles or modify the query. If you are building a pipeline yourself, do NOT write SQL — use goal + sourceNodeIDs instead."),
|
|
295
|
+
targetName: z.string().optional().describe("Optional target node name override"),
|
|
296
|
+
targetNodeType: z
|
|
297
|
+
.string()
|
|
298
|
+
.optional()
|
|
299
|
+
.describe("Optional node type override. When omitted, the planner ranks repo-backed and observed workspace node types for the use case."),
|
|
300
|
+
description: z.string().optional().describe("Optional node description"),
|
|
301
|
+
configOverrides: NodeConfigInputSchema
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Optional config overrides to merge into the planned node body."),
|
|
304
|
+
locationName: z.string().optional().describe("Optional target locationName"),
|
|
305
|
+
database: z.string().optional().describe("Optional target database"),
|
|
306
|
+
schema: z.string().optional().describe("Optional target schema"),
|
|
307
|
+
repoPath: z
|
|
308
|
+
.string()
|
|
309
|
+
.optional()
|
|
310
|
+
.describe("Optional local committed Coalesce repo path for repo-first node-type ranking. Falls back to COALESCE_REPO_PATH when omitted."),
|
|
311
|
+
sourceNodeIDs: z
|
|
312
|
+
.array(z.string())
|
|
313
|
+
.optional()
|
|
314
|
+
.describe("Optional upstream node IDs when planning from a non-SQL goal."),
|
|
315
|
+
}),
|
|
316
|
+
outputSchema: getToolOutputSchema("plan_pipeline"),
|
|
317
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
318
|
+
},
|
|
319
|
+
async (params) => {
|
|
320
|
+
try {
|
|
321
|
+
const result = await planPipeline(client, params);
|
|
322
|
+
// Build fingerprint from the actual ranked node-type output used in the summary.
|
|
323
|
+
const selection = isPlainObject(result.nodeTypeSelection) ? result.nodeTypeSelection : null;
|
|
324
|
+
const fingerprint = buildPlanFingerprint(params.workspaceID, selection, Array.isArray(result.supportedNodeTypes)
|
|
325
|
+
? result.supportedNodeTypes.filter((value) => typeof value === "string")
|
|
326
|
+
: [], {
|
|
327
|
+
goal: params.goal,
|
|
328
|
+
sql: params.sql,
|
|
329
|
+
sourceNodeIDs: params.sourceNodeIDs,
|
|
330
|
+
targetNodeType: params.targetNodeType,
|
|
331
|
+
});
|
|
332
|
+
// Check for a cached plan with the same fingerprint
|
|
333
|
+
const cached = findCachedPlanSummary(params.workspaceID, fingerprint);
|
|
334
|
+
const summaryPath = cached?.path ?? writePlanSummary(result, fingerprint);
|
|
335
|
+
// Extract the recommended nodeType and put it at the top level
|
|
336
|
+
// so the agent can't miss it.
|
|
337
|
+
const selectedNodeType = typeof selection?.selectedNodeType === "string"
|
|
338
|
+
? selection.selectedNodeType
|
|
339
|
+
: null;
|
|
340
|
+
const selectedDisplayName = typeof selection?.selectedDisplayName === "string"
|
|
341
|
+
? selection.selectedDisplayName
|
|
342
|
+
: null;
|
|
343
|
+
const response = summaryPath
|
|
344
|
+
? {
|
|
345
|
+
// Put the recommended type FIRST so it's the most visible field
|
|
346
|
+
...(selectedNodeType ? {
|
|
347
|
+
USE_THIS_NODE_TYPE: selectedNodeType,
|
|
348
|
+
...(selectedDisplayName ? { nodeTypeDisplayName: selectedDisplayName } : {}),
|
|
349
|
+
nodeTypeInstruction: `Use nodeType "${selectedNodeType}" when calling create_workspace_node_from_predecessor or create_workspace_node_from_scratch. Do NOT use "Source" or any other type unless the plan explicitly recommends it.`,
|
|
350
|
+
} : {}),
|
|
351
|
+
...result,
|
|
352
|
+
planSummaryUri: summaryPath,
|
|
353
|
+
planCached: !!cached,
|
|
354
|
+
instruction: cached
|
|
355
|
+
? `Cached node type rankings found at planSummaryUri (ranking fingerprint unchanged). Reference this resource for all subsequent node creations — no need to call plan_pipeline again unless repo-backed ranking inputs or workspace node types change enough to alter the planner's ranking.`
|
|
356
|
+
: `Node type rankings saved to planSummaryUri. Reference this resource for all subsequent node creations in this pipeline. The cache auto-invalidates when repo-backed ranking inputs or workspace node types change enough to alter the planner's ranking.`,
|
|
357
|
+
}
|
|
358
|
+
: {
|
|
359
|
+
...(selectedNodeType ? {
|
|
360
|
+
USE_THIS_NODE_TYPE: selectedNodeType,
|
|
361
|
+
...(selectedDisplayName ? { nodeTypeDisplayName: selectedDisplayName } : {}),
|
|
362
|
+
nodeTypeInstruction: `Use nodeType "${selectedNodeType}" when calling create_workspace_node_from_predecessor or create_workspace_node_from_scratch. Do NOT use "Source" or any other type unless the plan explicitly recommends it.`,
|
|
363
|
+
} : {}),
|
|
364
|
+
...result,
|
|
365
|
+
};
|
|
366
|
+
return buildJsonToolResponse("plan_pipeline", response);
|
|
343
367
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
USE_THIS_NODE_TYPE: selectedNodeType,
|
|
347
|
-
...(selectedDisplayName ? { nodeTypeDisplayName: selectedDisplayName } : {}),
|
|
348
|
-
nodeTypeInstruction: `Use nodeType "${selectedNodeType}" when calling create_workspace_node_from_predecessor or create_workspace_node_from_scratch. Do NOT use "Source" or any other type unless the plan explicitly recommends it.`,
|
|
349
|
-
} : {}),
|
|
350
|
-
...result,
|
|
351
|
-
};
|
|
352
|
-
return buildJsonToolResponse("plan_pipeline", response);
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
return handleToolError(error);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
server.registerTool("create_pipeline_from_plan", {
|
|
359
|
-
title: "Create Pipeline from Plan",
|
|
360
|
-
description: "Create a Coalesce pipeline from a previously approved plan. Pass the exact plan object returned by plan_pipeline. Projection-capable node types execute by creating predecessor-based nodes first and then persisting the final full node body via set_workspace_node.\n\nArgs:\n - workspaceID (string, required): The workspace ID\n - plan (object, required): The exact plan object returned by plan_pipeline\n - confirmed (boolean, optional): Set to true after user approves the plan. Must be paired with confirmationToken\n - confirmationToken (string, optional): Token from prior STOP_AND_CONFIRM response. Required when confirmed=true\n - dryRun (boolean, optional): When true, validate without creating nodes\n\nReturns:\n { created: boolean, nodes?: CreatedNode[], warnings?: string[] }",
|
|
361
|
-
inputSchema: z.object({
|
|
362
|
-
workspaceID: z.string().describe("The workspace ID"),
|
|
363
|
-
plan: PipelinePlanSchema.describe("The plan object returned by plan_pipeline."),
|
|
364
|
-
confirmed: z
|
|
365
|
-
.boolean()
|
|
366
|
-
.optional()
|
|
367
|
-
.describe("Set to true only after presenting the plan to the user and receiving explicit approval. Must be paired with the confirmationToken returned by the prior STOP_AND_CONFIRM response."),
|
|
368
|
-
confirmationToken: z
|
|
369
|
-
.string()
|
|
370
|
-
.optional()
|
|
371
|
-
.describe("The token returned in the STOP_AND_CONFIRM response. Required when confirmed=true to prove the plan was presented to the user."),
|
|
372
|
-
dryRun: z
|
|
373
|
-
.boolean()
|
|
374
|
-
.optional()
|
|
375
|
-
.describe("When true, validate the plan and return it without creating any nodes."),
|
|
376
|
-
}),
|
|
377
|
-
outputSchema: getToolOutputSchema("create_pipeline_from_plan"),
|
|
378
|
-
annotations: WRITE_ANNOTATIONS,
|
|
379
|
-
}, async (params) => {
|
|
380
|
-
try {
|
|
381
|
-
if (!params.dryRun) {
|
|
382
|
-
const approvalResponse = await requirePipelineCreationApproval(server, "create_pipeline_from_plan", params.plan, params.confirmed, params.confirmationToken, { plan: params.plan });
|
|
383
|
-
if (approvalResponse) {
|
|
384
|
-
return approvalResponse;
|
|
368
|
+
catch (error) {
|
|
369
|
+
return handleToolError(error);
|
|
385
370
|
}
|
|
386
371
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
.string()
|
|
419
|
-
.optional()
|
|
420
|
-
.describe("Optional local committed Coalesce repo path for repo-first node-type ranking. Falls back to COALESCE_REPO_PATH when omitted."),
|
|
421
|
-
confirmed: z
|
|
422
|
-
.boolean()
|
|
423
|
-
.optional()
|
|
424
|
-
.describe("Set to true only after presenting the ready plan to the user and receiving explicit approval. Must be paired with the confirmationToken returned by the prior STOP_AND_CONFIRM response."),
|
|
425
|
-
confirmationToken: z
|
|
426
|
-
.string()
|
|
427
|
-
.optional()
|
|
428
|
-
.describe("The token returned in the STOP_AND_CONFIRM response. Required when confirmed=true to prove the plan was presented to the user."),
|
|
429
|
-
dryRun: z
|
|
430
|
-
.boolean()
|
|
431
|
-
.optional()
|
|
432
|
-
.describe("When true, return the generated plan without creating nodes."),
|
|
433
|
-
}),
|
|
434
|
-
outputSchema: getToolOutputSchema("create_pipeline_from_sql"),
|
|
435
|
-
annotations: WRITE_ANNOTATIONS,
|
|
436
|
-
}, async (params) => {
|
|
437
|
-
try {
|
|
438
|
-
const plan = await planPipeline(client, params);
|
|
439
|
-
if (params.dryRun || plan.status !== "ready") {
|
|
440
|
-
return buildJsonToolResponse("create_pipeline_from_sql", {
|
|
441
|
-
created: false,
|
|
442
|
-
...(params.dryRun ? { dryRun: true } : {}),
|
|
443
|
-
plan,
|
|
444
|
-
...(plan.status !== "ready"
|
|
445
|
-
? {
|
|
446
|
-
warning: "SQL was planned but still needs clarification before creation. Review openQuestions and warnings. Present the plan to the user and wait for approval.",
|
|
372
|
+
],
|
|
373
|
+
[
|
|
374
|
+
"create_pipeline_from_plan",
|
|
375
|
+
{
|
|
376
|
+
title: "Create Pipeline from Plan",
|
|
377
|
+
description: "Create a Coalesce pipeline from a previously approved plan. Pass the exact plan object returned by plan_pipeline. Projection-capable node types execute by creating predecessor-based nodes first and then persisting the final full node body via set_workspace_node.\n\nArgs:\n - workspaceID (string, required): The workspace ID\n - plan (object, required): The exact plan object returned by plan_pipeline\n - confirmed (boolean, optional): Set to true after user approves the plan. Must be paired with confirmationToken\n - confirmationToken (string, optional): Token from prior STOP_AND_CONFIRM response. Required when confirmed=true\n - dryRun (boolean, optional): When true, validate without creating nodes\n\nReturns:\n { created: boolean, nodes?: CreatedNode[], warnings?: string[] }",
|
|
378
|
+
inputSchema: z.object({
|
|
379
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
380
|
+
plan: PipelinePlanSchema.describe("The plan object returned by plan_pipeline."),
|
|
381
|
+
confirmed: z
|
|
382
|
+
.boolean()
|
|
383
|
+
.optional()
|
|
384
|
+
.describe("Set to true only after presenting the plan to the user and receiving explicit approval. Must be paired with the confirmationToken returned by the prior STOP_AND_CONFIRM response."),
|
|
385
|
+
confirmationToken: z
|
|
386
|
+
.string()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe("The token returned in the STOP_AND_CONFIRM response. Required when confirmed=true to prove the plan was presented to the user."),
|
|
389
|
+
dryRun: z
|
|
390
|
+
.boolean()
|
|
391
|
+
.optional()
|
|
392
|
+
.describe("When true, validate the plan and return it without creating any nodes."),
|
|
393
|
+
}),
|
|
394
|
+
outputSchema: getToolOutputSchema("create_pipeline_from_plan"),
|
|
395
|
+
annotations: WRITE_ANNOTATIONS,
|
|
396
|
+
},
|
|
397
|
+
async (params) => {
|
|
398
|
+
try {
|
|
399
|
+
if (!params.dryRun) {
|
|
400
|
+
const approvalResponse = await requirePipelineCreationApproval(server, "create_pipeline_from_plan", params.plan, params.confirmed, params.confirmationToken, { plan: params.plan });
|
|
401
|
+
if (approvalResponse) {
|
|
402
|
+
return approvalResponse;
|
|
447
403
|
}
|
|
448
|
-
|
|
449
|
-
|
|
404
|
+
}
|
|
405
|
+
const result = await createPipelineFromPlan(client, params);
|
|
406
|
+
const response = buildJsonToolResponse("create_pipeline_from_plan", result);
|
|
407
|
+
if (isPlainObject(result) && result.isError) {
|
|
408
|
+
return { ...response, isError: true };
|
|
409
|
+
}
|
|
410
|
+
return response;
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
return handleToolError(error);
|
|
414
|
+
}
|
|
450
415
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
416
|
+
],
|
|
417
|
+
[
|
|
418
|
+
"create_pipeline_from_sql",
|
|
419
|
+
{
|
|
420
|
+
title: "Create Pipeline from SQL",
|
|
421
|
+
description: "Plan and create a Coalesce pipeline from user-provided SQL. Pass the user's EXACT SQL unchanged. The SQL may use raw table names or already contain Coalesce {{ ref() }} syntax if that is what the user provided. Do NOT rewrite between styles or otherwise modify the query. The planner resolves workspace sources automatically and generates a Coalesce-compatible joinCondition for the final node.\n\nIf you are building a pipeline yourself, use declarative tools directly: create_workspace_node_from_predecessor → convert_join_to_aggregation → replace_workspace_node_columns.\n\nThis tool validates candidate node types against currently observed workspace nodes. If a selected type is not observed, the plan will include a warning asking the user to confirm installation in Coalesce.\n\nConsult coalesce://context/node-type-corpus for node type patterns and metadata structures.",
|
|
422
|
+
inputSchema: z.object({
|
|
423
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
424
|
+
sql: z.string().describe("The user's EXACT SQL, copied verbatim. It may use raw table names or existing Coalesce {{ ref() }} syntax. Do NOT rewrite between SQL styles or modify it in any way. Pass it exactly as the user provided it."),
|
|
425
|
+
goal: z.string().optional().describe("Optional business goal or context for the SQL"),
|
|
426
|
+
targetName: z.string().optional().describe("Optional target node name override"),
|
|
427
|
+
targetNodeType: z
|
|
428
|
+
.string()
|
|
429
|
+
.optional()
|
|
430
|
+
.describe("Optional node type override. When omitted, the planner ranks repo-backed and observed workspace node types for the use case."),
|
|
431
|
+
description: z.string().optional().describe("Optional node description"),
|
|
432
|
+
configOverrides: NodeConfigInputSchema
|
|
433
|
+
.optional()
|
|
434
|
+
.describe("Optional config overrides to merge into the final node body."),
|
|
435
|
+
locationName: z.string().optional().describe("Optional target locationName"),
|
|
436
|
+
database: z.string().optional().describe("Optional target database"),
|
|
437
|
+
schema: z.string().optional().describe("Optional target schema"),
|
|
438
|
+
repoPath: z
|
|
439
|
+
.string()
|
|
440
|
+
.optional()
|
|
441
|
+
.describe("Optional local committed Coalesce repo path for repo-first node-type ranking. Falls back to COALESCE_REPO_PATH when omitted."),
|
|
442
|
+
confirmed: z
|
|
443
|
+
.boolean()
|
|
444
|
+
.optional()
|
|
445
|
+
.describe("Set to true only after presenting the ready plan to the user and receiving explicit approval. Must be paired with the confirmationToken returned by the prior STOP_AND_CONFIRM response."),
|
|
446
|
+
confirmationToken: z
|
|
447
|
+
.string()
|
|
448
|
+
.optional()
|
|
449
|
+
.describe("The token returned in the STOP_AND_CONFIRM response. Required when confirmed=true to prove the plan was presented to the user."),
|
|
450
|
+
dryRun: z
|
|
451
|
+
.boolean()
|
|
452
|
+
.optional()
|
|
453
|
+
.describe("When true, return the generated plan without creating nodes."),
|
|
454
|
+
}),
|
|
455
|
+
outputSchema: getToolOutputSchema("create_pipeline_from_sql"),
|
|
456
|
+
annotations: WRITE_ANNOTATIONS,
|
|
457
|
+
},
|
|
458
|
+
async (params) => {
|
|
459
|
+
try {
|
|
460
|
+
const plan = await planPipeline(client, params);
|
|
461
|
+
if (params.dryRun || plan.status !== "ready") {
|
|
462
|
+
return buildJsonToolResponse("create_pipeline_from_sql", {
|
|
463
|
+
created: false,
|
|
464
|
+
...(params.dryRun ? { dryRun: true } : {}),
|
|
465
|
+
plan,
|
|
466
|
+
...(plan.status !== "ready"
|
|
467
|
+
? {
|
|
468
|
+
warning: "SQL was planned but still needs clarification before creation. Review openQuestions and warnings. Present the plan to the user and wait for approval.",
|
|
469
|
+
}
|
|
470
|
+
: {}),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
const approvalResponse = await requirePipelineCreationApproval(server, "create_pipeline_from_sql", plan, params.confirmed, params.confirmationToken, { plan });
|
|
474
|
+
if (approvalResponse) {
|
|
475
|
+
return approvalResponse;
|
|
476
|
+
}
|
|
477
|
+
const execution = await createPipelineFromPlan(client, {
|
|
478
|
+
workspaceID: params.workspaceID,
|
|
479
|
+
plan,
|
|
480
|
+
});
|
|
481
|
+
const result = {
|
|
482
|
+
plan,
|
|
483
|
+
...(isPlainObject(execution) ? execution : { execution }),
|
|
484
|
+
};
|
|
485
|
+
const response = buildJsonToolResponse("create_pipeline_from_sql", result);
|
|
486
|
+
if (isPlainObject(execution) && execution.isError) {
|
|
487
|
+
return { ...response, isError: true };
|
|
488
|
+
}
|
|
489
|
+
return response;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
return handleToolError(error);
|
|
493
|
+
}
|
|
454
494
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
plan,
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
495
|
+
],
|
|
496
|
+
[
|
|
497
|
+
"build_pipeline_from_intent",
|
|
498
|
+
{
|
|
499
|
+
title: "Build Pipeline from Intent",
|
|
500
|
+
description: "Build a Coalesce pipeline from a natural language description. Describe what you want in plain English and this tool resolves workspace nodes, selects node types, and creates the pipeline nodes.\n\nExamples:\n- \"combine customers and orders by customer_id, aggregate total revenue by region\"\n- \"stage the raw payments table\"\n- \"join products with inventory on product_id\"\n\nThe tool parses the intent, fuzzy-matches entity names to existing workspace nodes, and selects appropriate node types. When confirmed, it creates the pipeline nodes directly. Alternatively, set dryRun=true to get the plan without creating nodes, then pass it to create_pipeline_from_plan.\n\nIf entity names cannot be resolved or the intent is ambiguous, the tool returns clarification questions instead of a plan.",
|
|
501
|
+
inputSchema: z.object({
|
|
502
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
503
|
+
intent: z
|
|
504
|
+
.string()
|
|
505
|
+
.describe("Natural language description of the pipeline to build. Mention table/node names, join keys, aggregations, and filters."),
|
|
506
|
+
targetName: z.string().optional().describe("Optional target node name override"),
|
|
507
|
+
targetNodeType: z
|
|
508
|
+
.string()
|
|
509
|
+
.optional()
|
|
510
|
+
.describe("Optional node type override. When omitted, the planner selects the best type automatically."),
|
|
511
|
+
repoPath: z
|
|
512
|
+
.string()
|
|
513
|
+
.optional()
|
|
514
|
+
.describe("Optional local committed Coalesce repo path for repo-first node-type ranking. Falls back to COALESCE_REPO_PATH when omitted."),
|
|
515
|
+
locationName: z.string().optional().describe("Optional target locationName"),
|
|
516
|
+
database: z.string().optional().describe("Optional target database"),
|
|
517
|
+
schema: z.string().optional().describe("Optional target schema"),
|
|
518
|
+
confirmed: z
|
|
519
|
+
.boolean()
|
|
520
|
+
.optional()
|
|
521
|
+
.describe("Set to true only after presenting the plan to the user and receiving explicit approval."),
|
|
522
|
+
confirmationToken: z
|
|
523
|
+
.string()
|
|
524
|
+
.optional()
|
|
525
|
+
.describe("The token returned in the STOP_AND_CONFIRM response. Required when confirmed=true."),
|
|
526
|
+
dryRun: z
|
|
527
|
+
.boolean()
|
|
528
|
+
.optional()
|
|
529
|
+
.describe("When true, return the generated plan without creating nodes."),
|
|
530
|
+
}),
|
|
531
|
+
outputSchema: getToolOutputSchema("build_pipeline_from_intent"),
|
|
532
|
+
annotations: WRITE_ANNOTATIONS,
|
|
533
|
+
},
|
|
534
|
+
async (params) => {
|
|
535
|
+
try {
|
|
536
|
+
const result = await buildPipelinePlanFromIntent(client, {
|
|
537
|
+
workspaceID: params.workspaceID,
|
|
538
|
+
intent: params.intent,
|
|
539
|
+
targetName: params.targetName,
|
|
540
|
+
targetNodeType: params.targetNodeType,
|
|
541
|
+
repoPath: params.repoPath,
|
|
542
|
+
locationName: params.locationName,
|
|
543
|
+
database: params.database,
|
|
544
|
+
schema: params.schema,
|
|
545
|
+
});
|
|
546
|
+
if (result.status !== "ready" ||
|
|
547
|
+
!result.plan ||
|
|
548
|
+
params.dryRun) {
|
|
549
|
+
return buildJsonToolResponse("build_pipeline_from_intent", {
|
|
550
|
+
created: false,
|
|
551
|
+
...(params.dryRun ? { dryRun: true } : {}),
|
|
552
|
+
...result,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
// Plan is ready — require confirmation before creating
|
|
556
|
+
const approvalResponse = await requirePipelineCreationApproval(server, "build_pipeline_from_intent", result.plan, params.confirmed, params.confirmationToken, { ...result });
|
|
557
|
+
if (approvalResponse) {
|
|
558
|
+
return approvalResponse;
|
|
559
|
+
}
|
|
560
|
+
// Confirmed — execute the plan
|
|
561
|
+
const execution = await createPipelineFromPlan(client, {
|
|
562
|
+
workspaceID: params.workspaceID,
|
|
563
|
+
plan: result.plan,
|
|
564
|
+
});
|
|
565
|
+
const response = {
|
|
566
|
+
...result,
|
|
567
|
+
...(isPlainObject(execution) ? execution : { execution }),
|
|
568
|
+
};
|
|
569
|
+
const toolResponse = buildJsonToolResponse("build_pipeline_from_intent", response);
|
|
570
|
+
if (isPlainObject(execution) && execution.isError) {
|
|
571
|
+
return { ...toolResponse, isError: true };
|
|
572
|
+
}
|
|
573
|
+
return toolResponse;
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
return handleToolError(error);
|
|
577
|
+
}
|
|
466
578
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}, async (params) => {
|
|
509
|
-
try {
|
|
510
|
-
const result = await buildPipelinePlanFromIntent(client, {
|
|
511
|
-
workspaceID: params.workspaceID,
|
|
512
|
-
intent: params.intent,
|
|
513
|
-
targetName: params.targetName,
|
|
514
|
-
targetNodeType: params.targetNodeType,
|
|
515
|
-
repoPath: params.repoPath,
|
|
516
|
-
locationName: params.locationName,
|
|
517
|
-
database: params.database,
|
|
518
|
-
schema: params.schema,
|
|
519
|
-
});
|
|
520
|
-
if (result.status !== "ready" ||
|
|
521
|
-
!result.plan ||
|
|
522
|
-
params.dryRun) {
|
|
523
|
-
return buildJsonToolResponse("build_pipeline_from_intent", {
|
|
524
|
-
created: false,
|
|
525
|
-
...(params.dryRun ? { dryRun: true } : {}),
|
|
526
|
-
...result,
|
|
527
|
-
});
|
|
579
|
+
],
|
|
580
|
+
[
|
|
581
|
+
"review_pipeline",
|
|
582
|
+
{
|
|
583
|
+
title: "Review Pipeline",
|
|
584
|
+
description: "Analyze an existing pipeline in a Coalesce workspace and suggest improvements. " +
|
|
585
|
+
"Walks the node DAG, inspects column transforms, join conditions, node types, naming conventions, " +
|
|
586
|
+
"and layer architecture to identify issues and optimization opportunities.\n\n" +
|
|
587
|
+
"Returns findings sorted by severity (critical → warning → suggestion) with actionable fix suggestions.\n\n" +
|
|
588
|
+
"Checks for:\n" +
|
|
589
|
+
"- Redundant passthrough nodes (no transforms added)\n" +
|
|
590
|
+
"- Missing join conditions (multi-predecessor nodes without FROM/JOIN)\n" +
|
|
591
|
+
"- Layer violations (skipping staging/intermediate layers)\n" +
|
|
592
|
+
"- Node type mismatches (View used for joins, Dimension in staging layer)\n" +
|
|
593
|
+
"- Orphan nodes (disconnected from the pipeline)\n" +
|
|
594
|
+
"- Deep chains (8+ nodes deep)\n" +
|
|
595
|
+
"- High fan-out risk (10+ downstream dependents)\n" +
|
|
596
|
+
"- Naming inconsistencies\n" +
|
|
597
|
+
"- Unused columns (>50% not referenced downstream)\n\n" +
|
|
598
|
+
"Use nodeIDs to scope the review to a specific pipeline section (e.g., from a subgraph).",
|
|
599
|
+
inputSchema: z.object({
|
|
600
|
+
workspaceID: z.string().describe("The workspace ID to review"),
|
|
601
|
+
nodeIDs: z
|
|
602
|
+
.array(z.string())
|
|
603
|
+
.optional()
|
|
604
|
+
.describe("Optional list of node IDs to scope the review. If omitted, reviews the entire workspace (up to 50 nodes in detail)."),
|
|
605
|
+
}),
|
|
606
|
+
outputSchema: getToolOutputSchema("review_pipeline"),
|
|
607
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
608
|
+
},
|
|
609
|
+
async (params) => {
|
|
610
|
+
try {
|
|
611
|
+
const result = await reviewPipeline(client, {
|
|
612
|
+
workspaceID: validatePathSegment(params.workspaceID, "workspaceID"),
|
|
613
|
+
nodeIDs: params.nodeIDs,
|
|
614
|
+
});
|
|
615
|
+
return buildJsonToolResponse("review_pipeline", result);
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
return handleToolError(error);
|
|
619
|
+
}
|
|
528
620
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
621
|
+
],
|
|
622
|
+
[
|
|
623
|
+
"parse_sql_structure",
|
|
624
|
+
{
|
|
625
|
+
title: "Parse SQL Structure",
|
|
626
|
+
description: "Parse a SQL statement into its structural components without touching the workspace. " +
|
|
627
|
+
"Returns CTEs (if any), source table references (FROM/JOIN), and projected columns (SELECT list). " +
|
|
628
|
+
"Use this as the first step in a multi-step pipeline creation workflow to inspect " +
|
|
629
|
+
"the SQL decomposition before choosing node types or building a plan.\n\n" +
|
|
630
|
+
"For CTE-based SQL, each CTE is returned with its columns (with transform detection), " +
|
|
631
|
+
"WHERE filters, source tables, structural flags (hasJoin, hasGroupBy), and inter-CTE dependency references.\n\n" +
|
|
632
|
+
"For non-CTE SQL, returns the parsed source refs and SELECT items with column/expression classification.\n\n" +
|
|
633
|
+
"This tool is pure parsing — no workspace reads, no node type selection, no mutations.",
|
|
634
|
+
inputSchema: z.object({
|
|
635
|
+
sql: z.string().describe("The SQL statement to parse. Pass it exactly as provided — do not rewrite or modify it."),
|
|
636
|
+
}),
|
|
637
|
+
outputSchema: getToolOutputSchema("parse_sql_structure"),
|
|
638
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
639
|
+
},
|
|
640
|
+
async (params) => {
|
|
641
|
+
try {
|
|
642
|
+
const sql = params.sql.trim();
|
|
643
|
+
if (sql.length === 0) {
|
|
644
|
+
return {
|
|
645
|
+
...buildJsonToolResponse("parse_sql_structure", {
|
|
646
|
+
error: "SQL parameter must not be empty.",
|
|
647
|
+
}),
|
|
648
|
+
isError: true,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const ctes = extractCtes(sql);
|
|
652
|
+
if (ctes.length > 0) {
|
|
653
|
+
// CTE-based SQL — return the decomposed CTEs plus the final SELECT
|
|
654
|
+
const cteNames = ctes.map((cte) => cte.name);
|
|
655
|
+
return buildJsonToolResponse("parse_sql_structure", {
|
|
656
|
+
hasCtes: true,
|
|
657
|
+
cteCount: ctes.length,
|
|
658
|
+
ctes: ctes.map((cte) => ({
|
|
659
|
+
name: cte.name,
|
|
660
|
+
sourceTable: cte.sourceTable,
|
|
661
|
+
hasJoin: cte.hasJoin,
|
|
662
|
+
hasGroupBy: cte.hasGroupBy,
|
|
663
|
+
whereClause: cte.whereClause,
|
|
664
|
+
columnCount: cte.columns.length,
|
|
665
|
+
columns: cte.columns.map((col) => ({
|
|
666
|
+
outputName: col.outputName,
|
|
667
|
+
expression: col.expression,
|
|
668
|
+
isTransform: col.isTransform,
|
|
669
|
+
})),
|
|
670
|
+
transformCount: cte.columns.filter((col) => col.isTransform).length,
|
|
671
|
+
passthroughCount: cte.columns.filter((col) => !col.isTransform).length,
|
|
672
|
+
dependsOnCtes: cteNames.filter((name) => name !== cte.name && new RegExp(`\\b${escapeRegExp(name)}\\b`, "iu").test(cte.body)),
|
|
673
|
+
})),
|
|
674
|
+
guidance: "Each CTE should become a separate Coalesce node — Coalesce does not support CTEs. " +
|
|
675
|
+
"Use select_pipeline_node_type for each CTE to determine the best node type based on its pattern " +
|
|
676
|
+
"(staging vs join vs aggregation). Then use plan_pipeline or create_pipeline_from_plan to build the pipeline.",
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
// Non-CTE SQL — parse source refs and SELECT items
|
|
680
|
+
const sourceResult = parseSqlSourceRefs(sql);
|
|
681
|
+
const parseResult = parseSqlSelectItems(sql, sourceResult.refs);
|
|
682
|
+
return buildJsonToolResponse("parse_sql_structure", {
|
|
683
|
+
hasCtes: false,
|
|
684
|
+
sourceRefs: parseResult.refs.map((ref) => ({
|
|
685
|
+
locationName: ref.locationName,
|
|
686
|
+
nodeName: ref.nodeName,
|
|
687
|
+
alias: ref.alias,
|
|
688
|
+
sourceStyle: ref.sourceStyle,
|
|
689
|
+
})),
|
|
690
|
+
sourceCount: parseResult.refs.length,
|
|
691
|
+
hasJoin: parseResult.refs.length > 1,
|
|
692
|
+
selectItems: parseResult.selectItems.map((item) => ({
|
|
693
|
+
expression: item.expression,
|
|
694
|
+
outputName: item.outputName,
|
|
695
|
+
sourceNodeAlias: item.sourceNodeAlias,
|
|
696
|
+
sourceColumnName: item.sourceColumnName,
|
|
697
|
+
kind: item.kind,
|
|
698
|
+
supported: item.supported,
|
|
699
|
+
...(item.reason ? { reason: item.reason } : {}),
|
|
700
|
+
})),
|
|
701
|
+
selectItemCount: parseResult.selectItems.length,
|
|
702
|
+
supportedSelectCount: parseResult.selectItems.filter((item) => item.supported).length,
|
|
703
|
+
unsupportedSelectCount: parseResult.selectItems.filter((item) => !item.supported).length,
|
|
704
|
+
warnings: parseResult.warnings,
|
|
705
|
+
guidance: "Use select_pipeline_node_type with the structural hints from this result " +
|
|
706
|
+
"(sourceCount, hasJoin) to determine the best node type. " +
|
|
707
|
+
"Then use plan_pipeline or create_pipeline_from_plan to build the pipeline.",
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
return handleToolError(error);
|
|
712
|
+
}
|
|
533
713
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
714
|
+
],
|
|
715
|
+
[
|
|
716
|
+
"select_pipeline_node_type",
|
|
717
|
+
{
|
|
718
|
+
title: "Select Pipeline Node Type",
|
|
719
|
+
description: "Rank and select the best Coalesce node type for a specific pipeline step. " +
|
|
720
|
+
"When repoPath or COALESCE_REPO_PATH is configured, scans the local repo for committed node type definitions. " +
|
|
721
|
+
"Otherwise, falls back to workspace-observed node types. Scores candidates against the provided context " +
|
|
722
|
+
"and returns ranked candidates with confidence levels.\n\n" +
|
|
723
|
+
"Use this after parse_sql_structure to select node types for each structural piece " +
|
|
724
|
+
"(e.g., each CTE, or the main query). Provide structural hints like sourceCount, hasJoin, hasGroupBy " +
|
|
725
|
+
"for more accurate selection.\n\n" +
|
|
726
|
+
"The tool runs the full deliberative selection loop: score all candidates, challenge the top pick " +
|
|
727
|
+
"against the intent corpus, disqualify if challenged, re-rank, and challenge again (2 rounds max).\n\n" +
|
|
728
|
+
"Returns the selected node type, confidence level, full ranking with scores, and any warnings. " +
|
|
729
|
+
"Use the selectedNodeType in subsequent plan_pipeline, create_workspace_node_from_predecessor, or create_workspace_node_from_scratch calls.\n\n" +
|
|
730
|
+
"This tool reads workspace node type inventory for ranking but does not mutate anything.",
|
|
731
|
+
inputSchema: z.object({
|
|
732
|
+
workspaceID: z.string().describe("The workspace ID — used to fetch observed node types for ranking context."),
|
|
733
|
+
goal: z.string().optional().describe("Natural-language description of what this pipeline step does. Be specific — " +
|
|
734
|
+
"'staging layer for raw customer data' is better than 'stage'. " +
|
|
735
|
+
"Include the transform pattern (join, aggregate, filter, rename, etc.)."),
|
|
736
|
+
targetName: z.string().optional().describe("Optional target node name — used for naming-signal scoring."),
|
|
737
|
+
sql: z.string().optional().describe("Optional SQL for this step — used for structural analysis during scoring."),
|
|
738
|
+
sourceCount: z.number().describe("Number of source/predecessor nodes feeding into this step."),
|
|
739
|
+
hasJoin: z.boolean().optional().describe("Does this step involve a JOIN? Prevents view-family selection and provides structural context for ranking."),
|
|
740
|
+
hasGroupBy: z.boolean().optional().describe("Does this step involve a GROUP BY? Influences selection toward aggregation-capable types."),
|
|
741
|
+
hasBusinessKeys: z.boolean().optional().describe("Are business keys explicitly defined? Influences dimensional/data-vault type selection."),
|
|
742
|
+
targetNodeType: z.string().optional().describe("Optional explicit node type override — bypasses ranking and validates this specific type."),
|
|
743
|
+
repoPath: z.string().optional().describe("Optional local committed Coalesce repo path. Falls back to COALESCE_REPO_PATH when omitted."),
|
|
744
|
+
}),
|
|
745
|
+
outputSchema: getToolOutputSchema("select_pipeline_node_type"),
|
|
746
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
747
|
+
},
|
|
748
|
+
async (params) => {
|
|
749
|
+
try {
|
|
750
|
+
const inventory = await getWorkspaceNodeTypeInventory(client, validatePathSegment(params.workspaceID, "workspaceID"));
|
|
751
|
+
const result = selectPipelineNodeType({
|
|
752
|
+
explicitNodeType: params.targetNodeType,
|
|
753
|
+
goal: params.goal,
|
|
754
|
+
targetName: params.targetName,
|
|
755
|
+
sql: params.sql,
|
|
756
|
+
sourceCount: params.sourceCount,
|
|
757
|
+
hasJoin: params.hasJoin,
|
|
758
|
+
hasGroupBy: params.hasGroupBy,
|
|
759
|
+
hasBusinessKeys: params.hasBusinessKeys,
|
|
760
|
+
workspaceNodeTypes: inventory.nodeTypes,
|
|
761
|
+
workspaceNodeTypeCounts: inventory.counts,
|
|
762
|
+
repoPath: params.repoPath,
|
|
763
|
+
});
|
|
764
|
+
const candidate = result.selectedCandidate;
|
|
765
|
+
const inventoryDegraded = inventory.nodeTypes.length === 0 && inventory.warnings.length > 0;
|
|
766
|
+
const response = {
|
|
767
|
+
...(candidate ? {
|
|
768
|
+
selectedNodeType: candidate.nodeType,
|
|
769
|
+
selectedDisplayName: candidate.displayName,
|
|
770
|
+
selectedFamily: candidate.family,
|
|
771
|
+
autoExecutable: candidate.autoExecutable,
|
|
772
|
+
...(candidate.semanticSignals.length > 0 ? { semanticSignals: candidate.semanticSignals } : {}),
|
|
773
|
+
...(candidate.missingDefaultFields.length > 0 ? { missingDefaultFields: candidate.missingDefaultFields } : {}),
|
|
774
|
+
...(candidate.templateWarnings.length > 0 ? { templateWarnings: candidate.templateWarnings } : {}),
|
|
775
|
+
} : {
|
|
776
|
+
selectedNodeType: null,
|
|
777
|
+
warning: "No suitable node type found. Review the ranked candidates or specify an explicit targetNodeType.",
|
|
778
|
+
}),
|
|
779
|
+
...(inventoryDegraded ? { selectionDegraded: true } : {}),
|
|
780
|
+
selection: result.selection,
|
|
781
|
+
warnings: [...result.warnings, ...inventory.warnings],
|
|
782
|
+
};
|
|
783
|
+
return buildJsonToolResponse("select_pipeline_node_type", response);
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
return handleToolError(error);
|
|
787
|
+
}
|
|
546
788
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
catch (error) {
|
|
550
|
-
return handleToolError(error);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
server.registerTool("review_pipeline", {
|
|
554
|
-
title: "Review Pipeline",
|
|
555
|
-
description: "Analyze an existing pipeline in a Coalesce workspace and suggest improvements. " +
|
|
556
|
-
"Walks the node DAG, inspects column transforms, join conditions, node types, naming conventions, " +
|
|
557
|
-
"and layer architecture to identify issues and optimization opportunities.\n\n" +
|
|
558
|
-
"Returns findings sorted by severity (critical → warning → suggestion) with actionable fix suggestions.\n\n" +
|
|
559
|
-
"Checks for:\n" +
|
|
560
|
-
"- Redundant passthrough nodes (no transforms added)\n" +
|
|
561
|
-
"- Missing join conditions (multi-predecessor nodes without FROM/JOIN)\n" +
|
|
562
|
-
"- Layer violations (skipping staging/intermediate layers)\n" +
|
|
563
|
-
"- Node type mismatches (View used for joins, Dimension in staging layer)\n" +
|
|
564
|
-
"- Orphan nodes (disconnected from the pipeline)\n" +
|
|
565
|
-
"- Deep chains (8+ nodes deep)\n" +
|
|
566
|
-
"- High fan-out risk (10+ downstream dependents)\n" +
|
|
567
|
-
"- Naming inconsistencies\n" +
|
|
568
|
-
"- Unused columns (>50% not referenced downstream)\n\n" +
|
|
569
|
-
"Use nodeIDs to scope the review to a specific pipeline section (e.g., from a subgraph).",
|
|
570
|
-
inputSchema: z.object({
|
|
571
|
-
workspaceID: z.string().describe("The workspace ID to review"),
|
|
572
|
-
nodeIDs: z
|
|
573
|
-
.array(z.string())
|
|
574
|
-
.optional()
|
|
575
|
-
.describe("Optional list of node IDs to scope the review. If omitted, reviews the entire workspace (up to 50 nodes in detail)."),
|
|
576
|
-
}),
|
|
577
|
-
outputSchema: getToolOutputSchema("review_pipeline"),
|
|
578
|
-
annotations: READ_ONLY_ANNOTATIONS,
|
|
579
|
-
}, async (params) => {
|
|
580
|
-
try {
|
|
581
|
-
const result = await reviewPipeline(client, {
|
|
582
|
-
workspaceID: validatePathSegment(params.workspaceID, "workspaceID"),
|
|
583
|
-
nodeIDs: params.nodeIDs,
|
|
584
|
-
});
|
|
585
|
-
return buildJsonToolResponse("review_pipeline", result);
|
|
586
|
-
}
|
|
587
|
-
catch (error) {
|
|
588
|
-
return handleToolError(error);
|
|
589
|
-
}
|
|
590
|
-
});
|
|
789
|
+
],
|
|
790
|
+
];
|
|
591
791
|
}
|
|
592
792
|
//# sourceMappingURL=pipelines.js.map
|