coalesce-transform-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +304 -0
- package/dist/cache-dir.d.ts +26 -0
- package/dist/cache-dir.js +106 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +212 -0
- package/dist/coalesce/api/environments.d.ts +20 -0
- package/dist/coalesce/api/environments.js +15 -0
- package/dist/coalesce/api/git-accounts.d.ts +21 -0
- package/dist/coalesce/api/git-accounts.js +21 -0
- package/dist/coalesce/api/jobs.d.ts +25 -0
- package/dist/coalesce/api/jobs.js +21 -0
- package/dist/coalesce/api/nodes.d.ts +29 -0
- package/dist/coalesce/api/nodes.js +33 -0
- package/dist/coalesce/api/projects.d.ts +22 -0
- package/dist/coalesce/api/projects.js +25 -0
- package/dist/coalesce/api/runs.d.ts +19 -0
- package/dist/coalesce/api/runs.js +34 -0
- package/dist/coalesce/api/subgraphs.d.ts +20 -0
- package/dist/coalesce/api/subgraphs.js +17 -0
- package/dist/coalesce/api/users.d.ts +30 -0
- package/dist/coalesce/api/users.js +31 -0
- package/dist/coalesce/types.d.ts +298 -0
- package/dist/coalesce/types.js +746 -0
- package/dist/generated/.gitkeep +0 -0
- package/dist/generated/node-type-corpus.json +42656 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/dist/mcp/cache.d.ts +3 -0
- package/dist/mcp/cache.js +137 -0
- package/dist/mcp/environments.d.ts +3 -0
- package/dist/mcp/environments.js +61 -0
- package/dist/mcp/git-accounts.d.ts +3 -0
- package/dist/mcp/git-accounts.js +70 -0
- package/dist/mcp/jobs.d.ts +3 -0
- package/dist/mcp/jobs.js +77 -0
- package/dist/mcp/node-type-corpus.d.ts +3 -0
- package/dist/mcp/node-type-corpus.js +173 -0
- package/dist/mcp/nodes.d.ts +3 -0
- package/dist/mcp/nodes.js +341 -0
- package/dist/mcp/pipelines.d.ts +3 -0
- package/dist/mcp/pipelines.js +342 -0
- package/dist/mcp/projects.d.ts +3 -0
- package/dist/mcp/projects.js +70 -0
- package/dist/mcp/repo-node-types.d.ts +135 -0
- package/dist/mcp/repo-node-types.js +387 -0
- package/dist/mcp/runs.d.ts +3 -0
- package/dist/mcp/runs.js +92 -0
- package/dist/mcp/subgraphs.d.ts +3 -0
- package/dist/mcp/subgraphs.js +60 -0
- package/dist/mcp/users.d.ts +3 -0
- package/dist/mcp/users.js +107 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +58 -0
- package/dist/resources/context/aggregation-patterns.md +145 -0
- package/dist/resources/context/data-engineering-principles.md +183 -0
- package/dist/resources/context/hydrated-metadata.md +92 -0
- package/dist/resources/context/id-discovery.md +64 -0
- package/dist/resources/context/intelligent-node-configuration.md +162 -0
- package/dist/resources/context/node-creation-decision-tree.md +156 -0
- package/dist/resources/context/node-operations.md +316 -0
- package/dist/resources/context/node-payloads.md +114 -0
- package/dist/resources/context/node-type-corpus.md +166 -0
- package/dist/resources/context/node-type-selection-guide.md +96 -0
- package/dist/resources/context/overview.md +135 -0
- package/dist/resources/context/pipeline-workflows.md +355 -0
- package/dist/resources/context/run-operations.md +55 -0
- package/dist/resources/context/sql-bigquery.md +41 -0
- package/dist/resources/context/sql-databricks.md +40 -0
- package/dist/resources/context/sql-platform-selection.md +70 -0
- package/dist/resources/context/sql-snowflake.md +43 -0
- package/dist/resources/context/storage-mappings.md +49 -0
- package/dist/resources/context/tool-usage.md +98 -0
- package/dist/resources/index.d.ts +5 -0
- package/dist/resources/index.js +254 -0
- package/dist/schemas/node-payloads.d.ts +5019 -0
- package/dist/schemas/node-payloads.js +147 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +63 -0
- package/dist/services/cache/snapshots.d.ts +108 -0
- package/dist/services/cache/snapshots.js +275 -0
- package/dist/services/config/context-analyzer.d.ts +14 -0
- package/dist/services/config/context-analyzer.js +76 -0
- package/dist/services/config/field-classifier.d.ts +23 -0
- package/dist/services/config/field-classifier.js +47 -0
- package/dist/services/config/intelligent.d.ts +55 -0
- package/dist/services/config/intelligent.js +306 -0
- package/dist/services/config/rules.d.ts +6 -0
- package/dist/services/config/rules.js +44 -0
- package/dist/services/config/schema-resolver.d.ts +18 -0
- package/dist/services/config/schema-resolver.js +80 -0
- package/dist/services/corpus/loader.d.ts +56 -0
- package/dist/services/corpus/loader.js +25 -0
- package/dist/services/corpus/search.d.ts +49 -0
- package/dist/services/corpus/search.js +69 -0
- package/dist/services/corpus/templates.d.ts +4 -0
- package/dist/services/corpus/templates.js +11 -0
- package/dist/services/pipelines/execution.d.ts +20 -0
- package/dist/services/pipelines/execution.js +290 -0
- package/dist/services/pipelines/node-type-intent.d.ts +96 -0
- package/dist/services/pipelines/node-type-intent.js +356 -0
- package/dist/services/pipelines/node-type-selection.d.ts +66 -0
- package/dist/services/pipelines/node-type-selection.js +758 -0
- package/dist/services/pipelines/planning.d.ts +543 -0
- package/dist/services/pipelines/planning.js +1839 -0
- package/dist/services/policies/sql-override.d.ts +7 -0
- package/dist/services/policies/sql-override.js +109 -0
- package/dist/services/repo/operations.d.ts +6 -0
- package/dist/services/repo/operations.js +10 -0
- package/dist/services/repo/parser.d.ts +70 -0
- package/dist/services/repo/parser.js +365 -0
- package/dist/services/repo/path.d.ts +2 -0
- package/dist/services/repo/path.js +58 -0
- package/dist/services/templates/nodes.d.ts +50 -0
- package/dist/services/templates/nodes.js +336 -0
- package/dist/services/workspace/analysis.d.ts +56 -0
- package/dist/services/workspace/analysis.js +151 -0
- package/dist/services/workspace/mutations.d.ts +150 -0
- package/dist/services/workspace/mutations.js +1718 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/dist/workflows/get-environment-overview.d.ts +9 -0
- package/dist/workflows/get-environment-overview.js +23 -0
- package/dist/workflows/get-run-details.d.ts +10 -0
- package/dist/workflows/get-run-details.js +28 -0
- package/dist/workflows/progress.d.ts +20 -0
- package/dist/workflows/progress.js +54 -0
- package/dist/workflows/retry-and-wait.d.ts +13 -0
- package/dist/workflows/retry-and-wait.js +139 -0
- package/dist/workflows/run-and-wait.d.ts +13 -0
- package/dist/workflows/run-and-wait.js +141 -0
- package/dist/workflows/run-status.d.ts +10 -0
- package/dist/workflows/run-status.js +27 -0
- package/package.json +34 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listEnvironmentNodes, listWorkspaceNodes, getEnvironmentNode, getWorkspaceNode, setWorkspaceNode, deleteWorkspaceNode, } from "../coalesce/api/nodes.js";
|
|
3
|
+
import { updateWorkspaceNode, buildUpdatedWorkspaceNodeBody, replaceWorkspaceNodeColumns, createWorkspaceNodeFromScratch, createWorkspaceNodeFromPredecessor, convertJoinToAggregation, applyJoinCondition, listWorkspaceNodeTypes, } from "../services/workspace/mutations.js";
|
|
4
|
+
import { completeNodeConfiguration } from "../services/config/intelligent.js";
|
|
5
|
+
import { assertNoSqlOverridePayload } from "../services/policies/sql-override.js";
|
|
6
|
+
import { buildWorkspaceProfile } from "../services/workspace/analysis.js";
|
|
7
|
+
import { fetchAllWorkspaceNodes, toNodeSummaries } from "../services/cache/snapshots.js";
|
|
8
|
+
import { NodeConfigInputSchema, StorageLocationInputSchema, WorkspaceNodeColumnInputSchema, WorkspaceNodeMetadataInputSchema, WorkspaceNodeWriteInputSchema, } from "../schemas/node-payloads.js";
|
|
9
|
+
import { PaginationParams, buildJsonToolResponse, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, IDEMPOTENT_WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, handleToolError, } from "../coalesce/types.js";
|
|
10
|
+
export function registerNodeTools(server, client) {
|
|
11
|
+
server.tool("list-environment-nodes", "List all nodes in a Coalesce environment", PaginationParams.extend({
|
|
12
|
+
environmentID: z.string().describe("The environment ID"),
|
|
13
|
+
detail: z.boolean().optional().describe("Include full node details in response"),
|
|
14
|
+
}).shape, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
15
|
+
try {
|
|
16
|
+
const result = await listEnvironmentNodes(client, params);
|
|
17
|
+
return buildJsonToolResponse("list-environment-nodes", result);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return handleToolError(error);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
server.tool("list-workspace-nodes", "List all nodes in a Coalesce workspace. To find workspace IDs, use list-projects or get-project with includeWorkspaces=true.", PaginationParams.extend({
|
|
24
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
25
|
+
detail: z.boolean().optional().describe("Include full node details in response"),
|
|
26
|
+
}).shape, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
27
|
+
try {
|
|
28
|
+
const result = await listWorkspaceNodes(client, params);
|
|
29
|
+
return buildJsonToolResponse("list-workspace-nodes", result);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return handleToolError(error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
server.tool("get-environment-node", "Get details of a specific node in a Coalesce environment", {
|
|
36
|
+
environmentID: z.string().describe("The environment ID"),
|
|
37
|
+
nodeID: z.string().describe("The node ID"),
|
|
38
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
39
|
+
try {
|
|
40
|
+
const result = await getEnvironmentNode(client, params);
|
|
41
|
+
return buildJsonToolResponse("get-environment-node", result);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return handleToolError(error);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
server.tool("get-workspace-node", "Get details of a specific node in a Coalesce workspace. To find workspace IDs, use list-projects or get-project with includeWorkspaces=true.", {
|
|
48
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
49
|
+
nodeID: z.string().describe("The node ID"),
|
|
50
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = await getWorkspaceNode(client, params);
|
|
53
|
+
return buildJsonToolResponse("get-workspace-node", result);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return handleToolError(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
server.tool("create-workspace-node-from-scratch", "Create a workspace node from scratch with NO predecessors. Only use this when the node truly has no upstream nodes — for example, a standalone utility node. If the node has ANY upstream/source nodes, use create-workspace-node-from-predecessor instead.\n\nREQUIRED: Before calling this tool, call plan-pipeline with goal + repoPath to discover the correct nodeType. Do not guess or hardcode node types — the planner ranks all available types and returns the best match.\n\nSPECIALIZED TYPES WARNING: Do NOT use Dynamic Tables, Incremental Load, Materialized View, or other specialized types unless the user explicitly requests that pattern (e.g., 'near-real-time refresh', 'incremental processing'). For standard batch ETL, CTE decomposition, and general transforms, use Stage or Work. The response includes nodeTypeValidation.warning if a specialized pattern was detected without matching context.\n\nDefaults to completionLevel='configured', which REQUIRES both `name` and `metadata.columns` to be provided. If you don't have column definitions yet, set completionLevel to 'created' or 'named' instead.\n\nAUTOMATIC CONFIG: When repoPath is provided, this tool automatically runs intelligent config completion after creation — reading the node type definition, setting node-level config defaults, and applying column-level attributes. The configCompletion result shows what was applied.\n\nDo not use overrideSQL or override.* fields; SQL override is disallowed in this project.", {
|
|
60
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
61
|
+
nodeType: z.string().describe("The type of node to create. IMPORTANT: Call plan-pipeline first to discover and rank available node types — use the nodeType from its result. Format: 'PackageName:::ID' for package types (e.g., 'base-nodes:::Stage') or simple name ('Stage') for built-in types. Always prefer the package-prefixed format returned by plan-pipeline."),
|
|
62
|
+
completionLevel: z
|
|
63
|
+
.enum(["created", "named", "configured"])
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("How complete the node should be before the tool returns. Defaults to configured."),
|
|
66
|
+
name: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Optional node name to apply after creation."),
|
|
70
|
+
description: z
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("Optional node description to apply after creation."),
|
|
74
|
+
storageLocations: z
|
|
75
|
+
.array(StorageLocationInputSchema)
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Optional storageLocations array to apply after creation."),
|
|
78
|
+
config: NodeConfigInputSchema
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Optional config object to apply after creation."),
|
|
81
|
+
metadata: WorkspaceNodeMetadataInputSchema
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Optional metadata object to apply after creation, including metadata.columns."),
|
|
84
|
+
changes: WorkspaceNodeWriteInputSchema
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Optional additional partial fields to merge after the node is created and fetched."),
|
|
87
|
+
repoPath: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Path to local Coalesce repository for automatic config completion after creation."),
|
|
91
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
92
|
+
try {
|
|
93
|
+
const result = await createWorkspaceNodeFromScratch(client, params);
|
|
94
|
+
return buildJsonToolResponse("create-workspace-node-from-scratch", result);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return handleToolError(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
server.tool("set-workspace-node", "Replace all fields of a workspace node (full update). To find workspace IDs, use list-projects or get-project with includeWorkspaces=true.\n\nDo not include overrideSQL or override.* fields; they are auto-preserved from the existing node. SQL override is disallowed in this project.\n\nPrefer update-workspace-node for partial changes. This tool fetches the current node to preserve API-required fields (table, overrideSQL, columnIDs).", {
|
|
101
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
102
|
+
nodeID: z.string().describe("The node ID"),
|
|
103
|
+
body: WorkspaceNodeWriteInputSchema.describe("Complete node data to set. Common fields include name, description, nodeType, table, database, schema, locationName, storageLocations, config, and metadata. Do not include overrideSQL — it is auto-preserved."),
|
|
104
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
105
|
+
try {
|
|
106
|
+
// Deep-check for override fields the agent may have included
|
|
107
|
+
assertNoSqlOverridePayload(params.body, "set-workspace-node body");
|
|
108
|
+
// Fetch current node to preserve API-required fields
|
|
109
|
+
const current = await getWorkspaceNode(client, {
|
|
110
|
+
workspaceID: params.workspaceID,
|
|
111
|
+
nodeID: params.nodeID,
|
|
112
|
+
});
|
|
113
|
+
// Route through the shared merge+validate+clean path that ensures
|
|
114
|
+
// all API-required fields (table, overrideSQL, dataType, columnID,
|
|
115
|
+
// nullable, description, enabledColumnTestIDs) are present.
|
|
116
|
+
const body = buildUpdatedWorkspaceNodeBody(current, params.body);
|
|
117
|
+
// Preserve database/schema from current node if not provided
|
|
118
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) {
|
|
119
|
+
const currentObj = current;
|
|
120
|
+
for (const field of ["database", "schema"]) {
|
|
121
|
+
if (!(field in body) && field in currentObj) {
|
|
122
|
+
body[field] = currentObj[field];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const result = await setWorkspaceNode(client, {
|
|
127
|
+
workspaceID: params.workspaceID,
|
|
128
|
+
nodeID: params.nodeID,
|
|
129
|
+
body,
|
|
130
|
+
});
|
|
131
|
+
return buildJsonToolResponse("set-workspace-node", result);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return handleToolError(error);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
server.tool("update-workspace-node", "Safely update selected fields of a workspace node by fetching the current node, applying partial changes, then writing back the full merged body. Object fields are deep-merged; arrays replace the existing array when provided. To find workspace IDs, use list-projects or get-project with includeWorkspaces=true.\n\nDo not use overrideSQL or override.* fields; SQL override is disallowed in this project.\n\nNOTE: Arrays (like metadata.columns) are replaced, not merged. For complex column transformations (e.g., converting from join to aggregation), consider using replace-workspace-node-columns instead.\n\nFor guidance on SQL platforms and tool usage patterns, see resources: coalesce://context/sql-platform-selection, coalesce://context/tool-usage", {
|
|
138
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
139
|
+
nodeID: z.string().describe("The node ID"),
|
|
140
|
+
changes: WorkspaceNodeWriteInputSchema.describe("Partial node fields to update. Common fields include name, description, database, schema, locationName, storageLocations, config, and metadata. Object fields are deep-merged; arrays replace the existing array when provided."),
|
|
141
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
142
|
+
try {
|
|
143
|
+
const result = await updateWorkspaceNode(client, params);
|
|
144
|
+
return buildJsonToolResponse("update-workspace-node", result);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return handleToolError(error);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
server.tool("replace-workspace-node-columns", "Replace all columns in a workspace node with a new set of columns, optionally applying a WHERE filter and additional changes in a single call.\n\nDo not use overrideSQL or override.* fields; SQL override is disallowed in this project.\n\nUse this when:\n- Applying column transforms (UPPER, LEFT, COALESCE, etc.) after node creation\n- Adding WHERE filters at the same time as column transforms\n- Converting from a simple join to GROUP BY aggregation\n- Completely replacing column definitions with aggregate functions\n\nPrefer this over separate update-workspace-node calls. Combine column replacement + WHERE filter in one call.\n\nExample: Apply transforms and filter in one call:\n{\n columns: [\n { name: 'CUSTOMER_ID', transform: '\"CUSTOMER_LOYALTY\".\"CUSTOMER_ID\"' },\n { name: 'CITY', transform: 'UPPER(\"CUSTOMER_LOYALTY\".\"CITY\")' },\n { name: 'CONTACT_INFO', transform: 'COALESCE(\"CUSTOMER_LOYALTY\".\"E_MAIL\", \"CUSTOMER_LOYALTY\".\"PHONE_NUMBER\")' }\n ],\n whereCondition: '\"CUSTOMER_LOYALTY\".\"CUSTOMER_ID\" IS NOT NULL AND (\"CUSTOMER_LOYALTY\".\"E_MAIL\" IS NOT NULL OR \"CUSTOMER_LOYALTY\".\"PHONE_NUMBER\" IS NOT NULL)'\n}\n\nIMPORTANT: Use whereCondition for WHERE filters — do NOT construct {{ ref() }} syntax yourself. The FROM clause is already set up from node creation. The whereCondition is appended to the existing joinCondition automatically.", {
|
|
151
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
152
|
+
nodeID: z.string().describe("The node ID"),
|
|
153
|
+
columns: z.array(WorkspaceNodeColumnInputSchema).describe("Complete new columns array to replace metadata.columns. Each column should include name and may include transform, dataType, description, nullable, sources, and other hydrated metadata fields."),
|
|
154
|
+
whereCondition: z
|
|
155
|
+
.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("Optional WHERE filter to append to the node's existing joinCondition. Just provide the condition — do NOT include the WHERE keyword or construct {{ ref() }} syntax. The FROM clause is already set from node creation. Example: '\"LOCATION\".\"LOCATION_ID\" IS NOT NULL AND \"LOCATION\".\"LOCATION_ID\" != 0'"),
|
|
158
|
+
additionalChanges: WorkspaceNodeWriteInputSchema
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("Optional additional fields to update, such as name, description, config, or metadata. Object fields are deep-merged; arrays are replaced. Do NOT include metadata.sourceMapping or customSQL — use whereCondition for WHERE filters, apply-join-condition for join setup, or convert-join-to-aggregation for GROUP BY patterns."),
|
|
161
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await replaceWorkspaceNodeColumns(client, params);
|
|
164
|
+
return buildJsonToolResponse("replace-workspace-node-columns", result);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return handleToolError(error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
server.tool("convert-join-to-aggregation", "Convert an existing join node into an aggregated fact table with GROUP BY. This is the REQUIRED follow-up after creating a multi-predecessor node — it completes the join setup.\n\nThis tool automatically:\n- Generates JOIN ON clauses from common columns between predecessors\n- Writes the complete FROM/JOIN/ON/GROUP BY clause to the node's joinCondition (no separate update needed)\n- Replaces columns with GROUP BY dimensions + aggregate measures\n- Infers datatypes from transform functions (COUNT → NUMBER, SUM → NUMBER(38,4), etc.)\n- Sets column-level attributes (isBusinessKey on GROUP BY columns, isChangeTracking on aggregates)\n- Validates that all non-aggregate columns are in GROUP BY\n- Runs intelligent config completion\n\nUse this to transform a simple join (row-level) into an aggregated fact table (summary-level).\n\nExample: Convert order detail join to customer metrics:\n{\n workspaceID: \"1\",\n nodeID: \"fact-node-id\",\n groupByColumns: ['\"STG_ORDER_HEADER\".\"CUSTOMER_ID\"'],\n aggregates: [\n { name: \"TOTAL_ORDERS\", function: \"COUNT\", expression: 'DISTINCT \"STG_ORDER_HEADER\".\"ORDER_ID\"' },\n { name: \"LIFETIME_VALUE\", function: \"SUM\", expression: '\"STG_ORDER_HEADER\".\"ORDER_TOTAL\"' },\n { name: \"AVG_ORDER_VALUE\", function: \"AVG\", expression: '\"STG_ORDER_HEADER\".\"ORDER_TOTAL\"' }\n ],\n joinType: \"INNER JOIN\"\n}\n\nThe response includes:\n- Updated node with new columns and joinCondition already written\n- Generated JOIN SQL with GROUP BY\n- GROUP BY analysis and validation\n- Warnings if GROUP BY is invalid\n- Config completion results", {
|
|
171
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
172
|
+
nodeID: z.string().describe("The node ID of the join to convert"),
|
|
173
|
+
groupByColumns: z
|
|
174
|
+
.array(z.string())
|
|
175
|
+
.describe("Columns to group by (dimensions). Use fully-qualified names like '\"TABLE\".\"COLUMN\"'."),
|
|
176
|
+
aggregates: z
|
|
177
|
+
.array(z.object({
|
|
178
|
+
name: z.string().describe("Column name for the aggregate"),
|
|
179
|
+
function: z.string().describe("Aggregate function: COUNT, SUM, AVG, MIN, MAX, etc."),
|
|
180
|
+
expression: z.string().describe("Expression to aggregate (e.g., 'DISTINCT \"TABLE\".\"COLUMN\"')"),
|
|
181
|
+
description: z.string().optional().describe("Optional column description"),
|
|
182
|
+
}))
|
|
183
|
+
.describe("Aggregate columns with their functions and expressions"),
|
|
184
|
+
joinType: z
|
|
185
|
+
.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL OUTER JOIN"])
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Type of JOIN to use. Defaults to INNER JOIN."),
|
|
188
|
+
maintainJoins: z
|
|
189
|
+
.boolean()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("If true (default), analyzes predecessors, generates JOIN SQL, and writes the joinCondition to the node. If false, only replaces columns with aggregates without generating joins."),
|
|
192
|
+
repoPath: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Optional path to local Coalesce repository for intelligent config completion"),
|
|
196
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
197
|
+
try {
|
|
198
|
+
const result = await convertJoinToAggregation(client, params);
|
|
199
|
+
return buildJsonToolResponse("convert-join-to-aggregation", result);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return handleToolError(error);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
server.tool("apply-join-condition", "Auto-generate and write a FROM/JOIN/ON clause for a multi-predecessor node. Use this for ROW-LEVEL joins (no aggregation). For aggregation joins with GROUP BY, use convert-join-to-aggregation instead.\n\nThis tool automatically:\n- Reads the node to discover its predecessors from sourceMapping dependencies\n- Fetches each predecessor to get locationName and column names\n- Finds common columns between predecessor pairs for JOIN ON clauses\n- Generates FROM/JOIN/ON with proper {{ ref() }} syntax\n- Writes the joinCondition to the node's sourceMapping\n- Returns the generated joinCondition, joinSuggestions, and any warnings\n\nUse after create-workspace-node-from-predecessor when building row-level joins (e.g., enrichment joins, lookups, denormalization).\n\nFor column name mismatches across predecessors, use joinColumnOverrides to map them explicitly.", {
|
|
206
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
207
|
+
nodeID: z.string().describe("The node ID of the multi-predecessor node"),
|
|
208
|
+
joinType: z
|
|
209
|
+
.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL OUTER JOIN"])
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Type of JOIN to use between predecessors. Defaults to INNER JOIN."),
|
|
212
|
+
whereClause: z
|
|
213
|
+
.string()
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Optional WHERE clause to append after the JOIN (without the WHERE keyword)."),
|
|
216
|
+
qualifyClause: z
|
|
217
|
+
.string()
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Optional QUALIFY clause to append (without the QUALIFY keyword)."),
|
|
220
|
+
joinColumnOverrides: z
|
|
221
|
+
.array(z.object({
|
|
222
|
+
leftPredecessor: z.string().describe("Name of the left predecessor node"),
|
|
223
|
+
rightPredecessor: z.string().describe("Name of the right predecessor node"),
|
|
224
|
+
leftColumn: z.string().describe("Column name in the left predecessor"),
|
|
225
|
+
rightColumn: z.string().describe("Column name in the right predecessor"),
|
|
226
|
+
}))
|
|
227
|
+
.optional()
|
|
228
|
+
.describe("Explicit column mappings for joins when column names differ across predecessors. Overrides auto-detected common columns for the specified predecessor pair."),
|
|
229
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
230
|
+
try {
|
|
231
|
+
const result = await applyJoinCondition(client, params);
|
|
232
|
+
return buildJsonToolResponse("apply-join-condition", result);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return handleToolError(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
server.tool("create-workspace-node-from-predecessor", "Create a workspace node from one or more predecessor nodes, fetch it, and verify that columns were auto-populated from those predecessors before applying any optional changes.\n\nSINGLE-CALL WORKFLOW: You can create a node AND apply column transforms, WHERE filters, or aggregation in one call:\n- columns + whereCondition: Replace auto-populated columns with specific transforms and add a WHERE filter — no separate replace-workspace-node-columns needed\n- groupByColumns + aggregates: Convert to an aggregation node with GROUP BY — no separate convert-join-to-aggregation needed\nThese are mutually exclusive: use columns OR groupByColumns+aggregates, not both.\n\nREQUIRED: Before calling this tool, call `plan-pipeline` with `goal`, `sourceNodeIDs`, and `repoPath` to discover and rank available node types. Use the `nodeType` from the plan result — do NOT guess or hardcode node types like 'Stage', 'View', or numeric IDs like '65'. The planner scans all committed node type definitions and scores them against your use case.\n\nSPECIALIZED TYPES WARNING: Do NOT use Dynamic Tables, Incremental Load, Materialized View, or other specialized types unless the user explicitly requests that pattern (e.g., 'near-real-time refresh', 'continuous refresh', 'incremental processing'). For standard batch ETL, CTE decomposition, and general transforms, use Stage or Work. The response includes nodeTypeValidation.warning if a specialized pattern was detected without matching context — always check this field.\n\nJOIN INTELLIGENCE: For multi-predecessor nodes (joins), this tool automatically:\n- Analyzes common columns between each predecessor pair\n- Returns `joinSuggestions` with normalized column names and their left/right counterparts\n- Reports which predecessors are represented in the resulting column references\n- Warns if any predecessor is missing from the auto-populated columns\n\nAUTOMATIC CONFIG: When repoPath is provided, this tool automatically runs intelligent config completion after creation — reading the node type definition, setting node-level config defaults, and applying column-level attributes (isBusinessKey, isChangeTracking, etc.). The configCompletion result shows what was applied.\n\nDo not use overrideSQL or override.* fields; SQL override is disallowed in this project.\n\nFor guidance on node types, storage locations, and SQL patterns, see resources: coalesce://context/data-engineering-principles, coalesce://context/storage-mappings, coalesce://context/sql-platform-selection", {
|
|
239
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
240
|
+
nodeType: z.string().describe("The type of node to create. IMPORTANT: Call plan-pipeline first to discover and rank available node types — use the nodeType from its result. Format: 'PackageName:::ID' for package types (e.g., 'base-nodes:::Stage') or simple name ('Stage') for built-in types. Always prefer the package-prefixed format returned by plan-pipeline."),
|
|
241
|
+
predecessorNodeIDs: z
|
|
242
|
+
.array(z.string())
|
|
243
|
+
.min(1)
|
|
244
|
+
.describe("One or more predecessor node IDs to link to the new node"),
|
|
245
|
+
changes: WorkspaceNodeWriteInputSchema
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Optional partial fields to apply after successful auto-population validation, such as name, description, config, metadata, database, schema, or locationName."),
|
|
248
|
+
columns: z.array(WorkspaceNodeColumnInputSchema)
|
|
249
|
+
.optional()
|
|
250
|
+
.describe("Replace auto-populated columns with these specific columns and transforms. Mutually exclusive with groupByColumns/aggregates."),
|
|
251
|
+
whereCondition: z
|
|
252
|
+
.string()
|
|
253
|
+
.optional()
|
|
254
|
+
.describe("WHERE filter to append to the joinCondition (without the WHERE keyword). Only valid with columns, not with groupByColumns/aggregates."),
|
|
255
|
+
groupByColumns: z
|
|
256
|
+
.array(z.string())
|
|
257
|
+
.optional()
|
|
258
|
+
.describe("GROUP BY columns for aggregation. Must be provided with aggregates. Mutually exclusive with columns."),
|
|
259
|
+
aggregates: z
|
|
260
|
+
.array(z.object({
|
|
261
|
+
name: z.string().describe("Output column name for the aggregate"),
|
|
262
|
+
function: z.string().describe("Aggregate function: COUNT, SUM, AVG, MIN, MAX, etc."),
|
|
263
|
+
expression: z.string().describe("Expression to aggregate"),
|
|
264
|
+
description: z.string().optional().describe("Column description"),
|
|
265
|
+
}))
|
|
266
|
+
.optional()
|
|
267
|
+
.describe("Aggregate columns. Must be provided with groupByColumns. Mutually exclusive with columns."),
|
|
268
|
+
joinType: z
|
|
269
|
+
.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL OUTER JOIN"])
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("JOIN type for multi-predecessor aggregation nodes. Defaults to INNER JOIN."),
|
|
272
|
+
repoPath: z
|
|
273
|
+
.string()
|
|
274
|
+
.optional()
|
|
275
|
+
.describe("Path to local Coalesce repository for automatic config completion after creation."),
|
|
276
|
+
goal: z
|
|
277
|
+
.string()
|
|
278
|
+
.optional()
|
|
279
|
+
.describe("Optional goal/context for this node. Required when using specialized types (Dynamic Table, Incremental Load, etc.) — must include keywords like 'dynamic table', 'incremental', 'near-real-time' to pass validation."),
|
|
280
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
281
|
+
try {
|
|
282
|
+
const result = await createWorkspaceNodeFromPredecessor(client, params);
|
|
283
|
+
return buildJsonToolResponse("create-workspace-node-from-predecessor", result);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return handleToolError(error);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
server.tool("analyze-workspace-patterns", "Analyze workspace node patterns to detect package adoption, pipeline layers, data modeling methodology, and generate recommendations. Results are returned as a workspace profile summary. This tool paginates through the full workspace node list. If you want a reusable local snapshot instead of inline data, use `cache-workspace-nodes`.\n\nThis tool examines existing workspace nodes to understand conventions before creating new nodes.", {
|
|
290
|
+
workspaceID: z.string().describe("The workspace ID to analyze"),
|
|
291
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
292
|
+
try {
|
|
293
|
+
const nodesResponse = await fetchAllWorkspaceNodes(client, {
|
|
294
|
+
workspaceID: params.workspaceID,
|
|
295
|
+
detail: false,
|
|
296
|
+
});
|
|
297
|
+
const nodes = toNodeSummaries(nodesResponse.items);
|
|
298
|
+
const profile = buildWorkspaceProfile(params.workspaceID, nodes);
|
|
299
|
+
return buildJsonToolResponse("analyze-workspace-patterns", profile);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
return handleToolError(error);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
server.tool("list-workspace-node-types", "List distinct node types observed in current workspace nodes. This scans existing nodes only; it is not a true installed-type registry. Use it to inspect current workspace usage.\n\nWARNING: Do NOT use these values directly as the nodeType parameter for create-workspace-node-from-predecessor or create-workspace-node-from-scratch. The observed values may be bare numeric IDs (e.g. '31') that differ from the proper package-prefixed format (e.g. 'base-nodes:::Stage'). Always call plan-pipeline first to discover the correct nodeType — it ranks repo-backed and observed types and returns the properly formatted identifier.", {
|
|
306
|
+
workspaceID: z.string().describe("The workspace ID")
|
|
307
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
308
|
+
try {
|
|
309
|
+
const result = await listWorkspaceNodeTypes(client, params);
|
|
310
|
+
return buildJsonToolResponse("list-workspace-node-types", result);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return handleToolError(error);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
server.tool("delete-workspace-node", "Delete a node from a Coalesce workspace. This is a destructive operation — the node and all its configuration will be permanently removed. To find workspace IDs, use list-projects or get-project with includeWorkspaces=true.", {
|
|
317
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
318
|
+
nodeID: z.string().describe("The node ID to delete"),
|
|
319
|
+
}, DESTRUCTIVE_ANNOTATIONS, async (params) => {
|
|
320
|
+
try {
|
|
321
|
+
const result = await deleteWorkspaceNode(client, params);
|
|
322
|
+
return buildJsonToolResponse("delete-workspace-node", result);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
return handleToolError(error);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
server.tool("complete-node-configuration", "Intelligently complete a node's configuration by analyzing its context, classifying config fields, and applying best-practice rules. Returns updated node with applied config and detailed reasoning.", {
|
|
329
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
330
|
+
nodeID: z.string().describe("The node ID to configure"),
|
|
331
|
+
repoPath: z.string().optional().describe("Optional path to local Coalesce repository for schema resolution"),
|
|
332
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
333
|
+
try {
|
|
334
|
+
const result = await completeNodeConfiguration(client, params);
|
|
335
|
+
return buildJsonToolResponse("complete-node-configuration", result);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return handleToolError(error);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|