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
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { validateConfig, createClient } from "./client.js";
|
|
4
|
+
import { createCoalesceMcpServer } from "./server.js";
|
|
5
|
+
const config = validateConfig();
|
|
6
|
+
const client = createClient(config);
|
|
7
|
+
const server = createCoalesceMcpServer(client);
|
|
8
|
+
// Start server
|
|
9
|
+
const transport = new StdioServerTransport();
|
|
10
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { rmSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { DESTRUCTIVE_ANNOTATIONS, IDEMPOTENT_WRITE_ANNOTATIONS, buildJsonToolResponse, handleToolError, } from "../coalesce/types.js";
|
|
5
|
+
import { getCacheDir, CACHE_DIR_NAME } from "../cache-dir.js";
|
|
6
|
+
import { cacheEnvironmentNodes, cacheOrgUsers, cacheRuns, cacheWorkspaceNodes, } from "../services/cache/snapshots.js";
|
|
7
|
+
const SnapshotPaginationShape = {
|
|
8
|
+
pageSize: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.positive()
|
|
12
|
+
.max(1000)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Optional API page size used while collecting the full snapshot. Defaults to 250."),
|
|
15
|
+
orderBy: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Optional sort field used for paginated collection. Defaults to id."),
|
|
19
|
+
orderByDirection: z
|
|
20
|
+
.enum(["asc", "desc"])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional sort direction used for paginated collection."),
|
|
23
|
+
};
|
|
24
|
+
export function registerCacheTools(server, client) {
|
|
25
|
+
server.tool("cache-workspace-nodes", "Fetch every page of workspace nodes from the Coalesce API, save the collected snapshot to disk, and return only cache metadata. Use this when the full node list would be too large for chat context or will be reused across multiple steps.", {
|
|
26
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
27
|
+
detail: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("When true, fetches expanded node details. Defaults to true."),
|
|
31
|
+
...SnapshotPaginationShape,
|
|
32
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
33
|
+
try {
|
|
34
|
+
const result = await cacheWorkspaceNodes(client, params);
|
|
35
|
+
return buildJsonToolResponse("cache-workspace-nodes", result);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return handleToolError(error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
server.tool("cache-environment-nodes", "Fetch every page of environment nodes from the Coalesce API, save the collected snapshot to disk, and return only cache metadata. Use this when the full node list would be too large for chat context or will be reused across multiple steps.", {
|
|
42
|
+
environmentID: z.string().describe("The environment ID"),
|
|
43
|
+
detail: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("When true, fetches expanded node details. Defaults to true."),
|
|
47
|
+
...SnapshotPaginationShape,
|
|
48
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = await cacheEnvironmentNodes(client, params);
|
|
51
|
+
return buildJsonToolResponse("cache-environment-nodes", result);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return handleToolError(error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
server.tool("cache-runs", "Fetch every page of list-runs results from the Coalesce API, save the collected snapshot to disk, and return only cache metadata. Use this when the run list would be too large for chat context or should be preserved outside the conversation.", {
|
|
58
|
+
runType: z.enum(["deploy", "refresh"]).optional().describe("Optional run type filter"),
|
|
59
|
+
runStatus: z
|
|
60
|
+
.enum(["completed", "failed", "canceled", "running", "waitingToRun"])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Optional run status filter"),
|
|
63
|
+
environmentID: z.string().optional().describe("Optional environment ID filter"),
|
|
64
|
+
detail: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("When true, fetches expanded run details. Defaults to false."),
|
|
68
|
+
...SnapshotPaginationShape,
|
|
69
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
70
|
+
try {
|
|
71
|
+
const result = await cacheRuns(client, params);
|
|
72
|
+
return buildJsonToolResponse("cache-runs", result);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return handleToolError(error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
server.tool("cache-org-users", "Fetch every page of organization users from the Coalesce API, save the collected snapshot to disk, and return only cache metadata. Use this when the user list would be too large for chat context or should be preserved outside the conversation.", SnapshotPaginationShape, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
79
|
+
try {
|
|
80
|
+
const result = await cacheOrgUsers(client, params);
|
|
81
|
+
return buildJsonToolResponse("cache-org-users", result);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return handleToolError(error);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
server.tool("clear_coalesce_transform_mcp_data_cache", "Delete all cached data files created by this MCP server, including snapshots, auto-cached responses, and plan summaries. " +
|
|
88
|
+
"Returns a summary of what was deleted. Use this when the user wants to free disk space or start fresh.", {}, DESTRUCTIVE_ANNOTATIONS, async () => {
|
|
89
|
+
try {
|
|
90
|
+
const cacheDir = getCacheDir();
|
|
91
|
+
if (!existsSync(cacheDir)) {
|
|
92
|
+
return buildJsonToolResponse("clear_coalesce_transform_mcp_data_cache", {
|
|
93
|
+
deleted: false,
|
|
94
|
+
message: `No cache directory found at ${CACHE_DIR_NAME}/`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Count files and measure size before deleting
|
|
98
|
+
let fileCount = 0;
|
|
99
|
+
let totalBytes = 0;
|
|
100
|
+
function countFiles(dir) {
|
|
101
|
+
try {
|
|
102
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
103
|
+
const entryPath = join(dir, entry.name);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
countFiles(entryPath);
|
|
106
|
+
}
|
|
107
|
+
else if (entry.isFile()) {
|
|
108
|
+
fileCount++;
|
|
109
|
+
try {
|
|
110
|
+
totalBytes += statSync(entryPath).size;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// skip unreadable files
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// skip unreadable dirs
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
countFiles(cacheDir);
|
|
123
|
+
rmSync(cacheDir, { recursive: true, force: true });
|
|
124
|
+
const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
|
|
125
|
+
return buildJsonToolResponse("clear_coalesce_transform_mcp_data_cache", {
|
|
126
|
+
deleted: true,
|
|
127
|
+
fileCount,
|
|
128
|
+
totalBytes,
|
|
129
|
+
sizeMB: `${sizeMB} MB`,
|
|
130
|
+
message: `Deleted ${fileCount} files (${sizeMB} MB) from ${CACHE_DIR_NAME}/`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return handleToolError(error);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listEnvironments, getEnvironment, createEnvironment, deleteEnvironment, } from "../coalesce/api/environments.js";
|
|
3
|
+
import { PaginationParams, buildJsonToolResponse, handleToolError, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, } from "../coalesce/types.js";
|
|
4
|
+
export function registerEnvironmentTools(server, client) {
|
|
5
|
+
server.tool("list-environments", "List all available Coalesce environments", PaginationParams.extend({
|
|
6
|
+
detail: z.boolean().optional().describe("When true, returns expanded environment info"),
|
|
7
|
+
}).shape, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await listEnvironments(client, params);
|
|
10
|
+
return buildJsonToolResponse("list-environments", result);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return handleToolError(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
server.tool("get-environment", "Get details of a specific Coalesce environment", {
|
|
17
|
+
environmentID: z.string().describe("The environment ID"),
|
|
18
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = await getEnvironment(client, params);
|
|
21
|
+
return buildJsonToolResponse("get-environment", result);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return handleToolError(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.tool("create-environment", "Create a new Coalesce environment within a project.", {
|
|
28
|
+
project: z.string().describe("The project ID to create the environment in"),
|
|
29
|
+
name: z.string().describe("Name for the new environment"),
|
|
30
|
+
oauthEnabled: z.boolean().optional().describe("Whether OAuth is enabled. Defaults to false."),
|
|
31
|
+
devEnv: z.boolean().optional().describe("Defaults to false."),
|
|
32
|
+
connectionAccount: z.string().optional().describe("Optional connection account identifier"),
|
|
33
|
+
runTimeParameters: z.record(z.unknown()).optional().describe("Optional runtime parameters"),
|
|
34
|
+
tagColors: z
|
|
35
|
+
.object({
|
|
36
|
+
backgroundColor: z.string().optional(),
|
|
37
|
+
textColor: z.string().optional(),
|
|
38
|
+
})
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Optional tag colors for the environment"),
|
|
41
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await createEnvironment(client, params);
|
|
44
|
+
return buildJsonToolResponse("create-environment", result);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return handleToolError(error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
server.tool("delete-environment", "Delete a Coalesce environment. This is a destructive operation — the environment and its configuration will be permanently removed.", {
|
|
51
|
+
environmentID: z.string().describe("The environment ID to delete"),
|
|
52
|
+
}, DESTRUCTIVE_ANNOTATIONS, async (params) => {
|
|
53
|
+
try {
|
|
54
|
+
const result = await deleteEnvironment(client, params);
|
|
55
|
+
return buildJsonToolResponse("delete-environment", result);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return handleToolError(error);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listGitAccounts, getGitAccount, createGitAccount, updateGitAccount, deleteGitAccount, } from "../coalesce/api/git-accounts.js";
|
|
3
|
+
import { buildJsonToolResponse, handleToolError, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, IDEMPOTENT_WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, } from "../coalesce/types.js";
|
|
4
|
+
export function registerGitAccountTools(server, client) {
|
|
5
|
+
const accountOwnerParam = z.string().optional().describe("User ID of the account owner (org admins can manage other users' accounts)");
|
|
6
|
+
server.tool("list-git-accounts", "List all Coalesce git accounts", {
|
|
7
|
+
accountOwner: accountOwnerParam,
|
|
8
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
9
|
+
try {
|
|
10
|
+
const result = await listGitAccounts(client, params);
|
|
11
|
+
return buildJsonToolResponse("list-git-accounts", result);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return handleToolError(error);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
server.tool("get-git-account", "Get details of a specific Coalesce git account", {
|
|
18
|
+
gitAccountID: z.string().describe("The git account ID"),
|
|
19
|
+
accountOwner: accountOwnerParam,
|
|
20
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await getGitAccount(client, params);
|
|
23
|
+
return buildJsonToolResponse("get-git-account", result);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return handleToolError(error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
server.tool("create-git-account", "Create a new Coalesce git account", {
|
|
30
|
+
body: z
|
|
31
|
+
.record(z.unknown())
|
|
32
|
+
.describe("The git account creation request body"),
|
|
33
|
+
accountOwner: accountOwnerParam,
|
|
34
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
35
|
+
try {
|
|
36
|
+
const result = await createGitAccount(client, params);
|
|
37
|
+
return buildJsonToolResponse("create-git-account", result);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return handleToolError(error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
server.tool("update-git-account", "Update an existing Coalesce git account (partial update — only provided fields are changed)", {
|
|
44
|
+
gitAccountID: z.string().describe("The git account ID"),
|
|
45
|
+
body: z
|
|
46
|
+
.record(z.unknown())
|
|
47
|
+
.describe("The git account update request body (partial — only include fields to change)"),
|
|
48
|
+
accountOwner: accountOwnerParam,
|
|
49
|
+
}, IDEMPOTENT_WRITE_ANNOTATIONS, async (params) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = await updateGitAccount(client, params);
|
|
52
|
+
return buildJsonToolResponse("update-git-account", result);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return handleToolError(error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.tool("delete-git-account", "Delete a Coalesce git account", {
|
|
59
|
+
gitAccountID: z.string().describe("The git account ID"),
|
|
60
|
+
accountOwner: accountOwnerParam,
|
|
61
|
+
}, DESTRUCTIVE_ANNOTATIONS, async (params) => {
|
|
62
|
+
try {
|
|
63
|
+
const result = await deleteGitAccount(client, params);
|
|
64
|
+
return buildJsonToolResponse("delete-git-account", result);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return handleToolError(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/mcp/jobs.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listEnvironmentJobs, createWorkspaceJob, getEnvironmentJob, updateWorkspaceJob, deleteWorkspaceJob, } from "../coalesce/api/jobs.js";
|
|
3
|
+
import { PaginationParams, buildJsonToolResponse, handleToolError, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, } from "../coalesce/types.js";
|
|
4
|
+
export function registerJobTools(server, client) {
|
|
5
|
+
server.tool("list-jobs", "List all jobs in a Coalesce environment.", PaginationParams.extend({
|
|
6
|
+
environmentID: z.string().describe("The environment ID"),
|
|
7
|
+
}).shape, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await listEnvironmentJobs(client, params);
|
|
10
|
+
return buildJsonToolResponse("list-jobs", result);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return handleToolError(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
server.tool("create-workspace-job", "Create a job in a Coalesce workspace. A job defines which nodes to include/exclude when running.\n\nThe includeSelector and excludeSelector use the format:\n { location: LOCATION_NAME name: NODE_NAME } OR { location: LOCATION_NAME name: NODE_NAME }\n\nUse locationName from node details (e.g., 'ETL_STAGE', 'ANALYTICS'), NOT database.schema.", {
|
|
17
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
18
|
+
name: z.string().describe("Name for the job"),
|
|
19
|
+
includeSelector: z
|
|
20
|
+
.string()
|
|
21
|
+
.describe("Node selector string. Format: '{ location: LOC name: NAME } OR { location: LOC name: NAME }'. Use empty string to include nothing."),
|
|
22
|
+
excludeSelector: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("Node exclusion selector string. Same format as includeSelector. Use empty string to exclude nothing."),
|
|
25
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
26
|
+
try {
|
|
27
|
+
const result = await createWorkspaceJob(client, params);
|
|
28
|
+
return buildJsonToolResponse("create-workspace-job", result);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return handleToolError(error);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
server.tool("get-job", "Get details of a specific job. Jobs are read via the environment endpoint.", {
|
|
35
|
+
environmentID: z.string().describe("The environment ID"),
|
|
36
|
+
jobID: z.string().describe("The job ID"),
|
|
37
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await getEnvironmentJob(client, params);
|
|
40
|
+
return buildJsonToolResponse("get-job", result);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return handleToolError(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
server.tool("update-workspace-job", "Update a job in a Coalesce workspace. Replaces the job's name, includeSelector, and excludeSelector.\n\nThe includeSelector and excludeSelector use the format:\n { location: LOCATION_NAME name: NODE_NAME } OR { location: LOCATION_NAME name: NODE_NAME }\n\nUse locationName from node details (e.g., 'ETL_STAGE', 'ANALYTICS'), NOT database.schema.", {
|
|
47
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
48
|
+
jobID: z.string().describe("The job ID to update"),
|
|
49
|
+
name: z.string().describe("Updated name for the job"),
|
|
50
|
+
includeSelector: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe("Node selector string. Format: '{ location: LOC name: NAME } OR { location: LOC name: NAME }'."),
|
|
53
|
+
excludeSelector: z
|
|
54
|
+
.string()
|
|
55
|
+
.describe("Node exclusion selector string. Same format as includeSelector. Use empty string to exclude nothing."),
|
|
56
|
+
}, WRITE_ANNOTATIONS, async (params) => {
|
|
57
|
+
try {
|
|
58
|
+
const result = await updateWorkspaceJob(client, params);
|
|
59
|
+
return buildJsonToolResponse("update-workspace-job", result);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return handleToolError(error);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
server.tool("delete-workspace-job", "Delete a job from a Coalesce workspace. This is a destructive operation — the job definition will be permanently removed.", {
|
|
66
|
+
workspaceID: z.string().describe("The workspace ID"),
|
|
67
|
+
jobID: z.string().describe("The job ID to delete"),
|
|
68
|
+
}, DESTRUCTIVE_ANNOTATIONS, async (params) => {
|
|
69
|
+
try {
|
|
70
|
+
const result = await deleteWorkspaceJob(client, params);
|
|
71
|
+
return buildJsonToolResponse("delete-workspace-job", result);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return handleToolError(error);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loadNodeTypeCorpusSnapshot, } from "../services/corpus/loader.js";
|
|
3
|
+
import { summarizeNodeTypeCorpus, searchNodeTypeCorpusVariants, getNodeTypeCorpusVariant, buildVariantSummary, } from "../services/corpus/search.js";
|
|
4
|
+
import { validateVariantTemplateGeneration } from "../services/corpus/templates.js";
|
|
5
|
+
import { buildSetWorkspaceNodeTemplateFromDefinition, compareGeneratedTemplateToWorkspaceNode, renderYaml, } from "../services/templates/nodes.js";
|
|
6
|
+
import { filterSqlOverrideControls, sanitizeNodeDefinitionSqlOverridePolicy, } from "../services/policies/sql-override.js";
|
|
7
|
+
import { getWorkspaceNode } from "../coalesce/api/nodes.js";
|
|
8
|
+
import { buildJsonToolResponse, handleToolError, READ_ONLY_ANNOTATIONS, } from "../coalesce/types.js";
|
|
9
|
+
import { isPlainObject } from "../utils.js";
|
|
10
|
+
function sanitizeVariantForResponse(variant) {
|
|
11
|
+
if (!variant.nodeDefinition) {
|
|
12
|
+
return {
|
|
13
|
+
...variant,
|
|
14
|
+
primitiveSignature: filterSqlOverrideControls(variant.primitiveSignature),
|
|
15
|
+
controlSignature: filterSqlOverrideControls(variant.controlSignature),
|
|
16
|
+
unsupportedPrimitives: filterSqlOverrideControls(variant.unsupportedPrimitives),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const sanitized = sanitizeNodeDefinitionSqlOverridePolicy(variant.nodeDefinition);
|
|
20
|
+
return {
|
|
21
|
+
...variant,
|
|
22
|
+
primitiveSignature: filterSqlOverrideControls(variant.primitiveSignature),
|
|
23
|
+
controlSignature: filterSqlOverrideControls(variant.controlSignature),
|
|
24
|
+
unsupportedPrimitives: filterSqlOverrideControls(variant.unsupportedPrimitives),
|
|
25
|
+
nodeDefinition: sanitized.nodeDefinition,
|
|
26
|
+
nodeMetadataSpec: renderYaml(sanitized.nodeDefinition),
|
|
27
|
+
warnings: sanitized.warnings,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function registerNodeTypeCorpusTools(server, client) {
|
|
31
|
+
server.tool("search-node-type-variants", "Search the generated node-type corpus snapshot by normalized family, package, primitive, or support status. This tool queries the committed snapshot and does not require access to the original external node source repo at runtime.", {
|
|
32
|
+
normalizedFamily: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Case-insensitive exact match against the normalized family name."),
|
|
36
|
+
packageName: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Case-insensitive exact match against one of the package names that carries the variant."),
|
|
40
|
+
primitive: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Case-insensitive match against a primitive used in the node definition, such as tabular or materializationSelector."),
|
|
44
|
+
supportStatus: z
|
|
45
|
+
.enum(["supported", "partial"])
|
|
46
|
+
.or(z.literal("parse_error"))
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Filter by current MCP support classification."),
|
|
49
|
+
limit: z
|
|
50
|
+
.number()
|
|
51
|
+
.int()
|
|
52
|
+
.positive()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Maximum number of matches to return. Defaults to 25, max 200."),
|
|
55
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
56
|
+
try {
|
|
57
|
+
const snapshot = loadNodeTypeCorpusSnapshot();
|
|
58
|
+
const result = {
|
|
59
|
+
summary: summarizeNodeTypeCorpus(snapshot),
|
|
60
|
+
...searchNodeTypeCorpusVariants(snapshot, params),
|
|
61
|
+
};
|
|
62
|
+
return buildJsonToolResponse("search-node-type-variants", result);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return handleToolError(error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
server.tool("get-node-type-variant", "Get one node-type corpus variant from the committed snapshot by variantKey. Use search-node-type-variants first when you need discovery.", {
|
|
69
|
+
variantKey: z.string().describe("The exact node-type corpus variant key."),
|
|
70
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
71
|
+
try {
|
|
72
|
+
const snapshot = loadNodeTypeCorpusSnapshot();
|
|
73
|
+
const variant = getNodeTypeCorpusVariant(snapshot, params.variantKey);
|
|
74
|
+
return buildJsonToolResponse("get-node-type-variant", sanitizeVariantForResponse(variant));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return handleToolError(error);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
server.tool("generate-set-workspace-node-template-from-variant", "Generate a set-workspace-node body template from a node-type corpus variant stored in the committed snapshot. This avoids requiring the original external node source repo at runtime, rejects partial variants unless allowPartial=true, and can optionally compare the inferred template against a live workspace node. SQL override controls are removed from returned templates because they are disallowed in this project.", {
|
|
81
|
+
variantKey: z.string().describe("The exact node-type corpus variant key."),
|
|
82
|
+
nodeName: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Optional node name to inject into the generated template."),
|
|
86
|
+
nodeType: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Optional nodeType override. Defaults to the variant definition capitalized field."),
|
|
90
|
+
locationName: z
|
|
91
|
+
.string()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Optional storage location name to include in the template."),
|
|
94
|
+
database: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Optional database value to include in the template."),
|
|
98
|
+
schema: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Optional schema value to include in the template."),
|
|
102
|
+
allowPartial: z
|
|
103
|
+
.boolean()
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("When true, allow best-effort generation for variants currently marked partial."),
|
|
106
|
+
workspaceID: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Optional workspace ID for comparing inferred mappings to a live workspace node."),
|
|
110
|
+
nodeID: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Optional node ID for comparing inferred mappings to a live workspace node."),
|
|
114
|
+
}, READ_ONLY_ANNOTATIONS, async (params) => {
|
|
115
|
+
try {
|
|
116
|
+
if ((params.workspaceID && !params.nodeID) || (!params.workspaceID && params.nodeID)) {
|
|
117
|
+
throw new Error("workspaceID and nodeID must be provided together when requesting a live node comparison.");
|
|
118
|
+
}
|
|
119
|
+
const snapshot = loadNodeTypeCorpusSnapshot();
|
|
120
|
+
const variant = getNodeTypeCorpusVariant(snapshot, params.variantKey);
|
|
121
|
+
validateVariantTemplateGeneration(variant, {
|
|
122
|
+
allowPartial: params.allowPartial,
|
|
123
|
+
});
|
|
124
|
+
const sanitizedVariant = sanitizeVariantForResponse(variant);
|
|
125
|
+
if (!sanitizedVariant.nodeDefinition || !isPlainObject(sanitizedVariant.nodeDefinition)) {
|
|
126
|
+
throw new Error("Sanitized node definition was not an object.");
|
|
127
|
+
}
|
|
128
|
+
const generated = buildSetWorkspaceNodeTemplateFromDefinition(variant.nodeDefinition, {
|
|
129
|
+
nodeName: params.nodeName,
|
|
130
|
+
nodeType: params.nodeType,
|
|
131
|
+
locationName: params.locationName,
|
|
132
|
+
database: params.database,
|
|
133
|
+
schema: params.schema,
|
|
134
|
+
});
|
|
135
|
+
const filteredUnsupportedPrimitives = filterSqlOverrideControls(variant.unsupportedPrimitives);
|
|
136
|
+
const warnings = Array.from(new Set(variant.supportStatus === "partial"
|
|
137
|
+
? [
|
|
138
|
+
`Best-effort template only. This variant uses unsupported primitives: ${filteredUnsupportedPrimitives.length > 0
|
|
139
|
+
? filteredUnsupportedPrimitives.join(", ")
|
|
140
|
+
: "none after SQL override controls were removed"}.`,
|
|
141
|
+
...generated.warnings,
|
|
142
|
+
]
|
|
143
|
+
: generated.warnings));
|
|
144
|
+
let comparison;
|
|
145
|
+
if (params.workspaceID && params.nodeID) {
|
|
146
|
+
const workspaceNode = await getWorkspaceNode(client, {
|
|
147
|
+
workspaceID: params.workspaceID,
|
|
148
|
+
nodeID: params.nodeID,
|
|
149
|
+
});
|
|
150
|
+
if (!isPlainObject(workspaceNode)) {
|
|
151
|
+
throw new Error("Workspace node comparison target was not an object");
|
|
152
|
+
}
|
|
153
|
+
comparison = {
|
|
154
|
+
workspaceID: params.workspaceID,
|
|
155
|
+
nodeID: params.nodeID,
|
|
156
|
+
result: compareGeneratedTemplateToWorkspaceNode(generated, workspaceNode),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const result = {
|
|
160
|
+
variant: buildVariantSummary(variant),
|
|
161
|
+
nodeMetadataSpecYaml: sanitizedVariant.nodeMetadataSpec,
|
|
162
|
+
...generated,
|
|
163
|
+
warnings,
|
|
164
|
+
setWorkspaceNodeBodyTemplateYaml: renderYaml(generated.setWorkspaceNodeBodyTemplate),
|
|
165
|
+
...(comparison ? { comparison } : {}),
|
|
166
|
+
};
|
|
167
|
+
return buildJsonToolResponse("generate-set-workspace-node-template-from-variant", result);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return handleToolError(error);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|