@nimbus-dev/connectors 0.2.1 → 0.2.3
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 +19 -7
- package/connectors/airflow/src/search-filter.ts +2 -15
- package/connectors/airflow/src/server.ts +2 -87
- package/connectors/airflow/src/tools.ts +74 -0
- package/connectors/apple/src/adapters.ts +326 -0
- package/connectors/apple/src/apple-mail-core.ts +4 -8
- package/connectors/apple/src/calendar-tools.ts +28 -8
- package/connectors/apple/src/server.ts +23 -512
- package/connectors/apple/src/tools.ts +18 -44
- package/connectors/argocd/src/server.ts +25 -38
- package/connectors/athena/src/tools.ts +26 -43
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +199 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +134 -0
- package/connectors/bigeye/src/search-filter.ts +4 -15
- package/connectors/bigeye/src/server.ts +38 -38
- package/connectors/bigquery/src/tools.ts +1 -26
- package/connectors/bitbucket/src/server.ts +2 -244
- package/connectors/bitbucket/src/tools.ts +241 -0
- package/connectors/bitrise/src/server.ts +2 -79
- package/connectors/bitrise/src/tools.ts +76 -0
- package/connectors/canva/src/search-filter.ts +2 -11
- package/connectors/canva/src/server.ts +2 -73
- package/connectors/canva/src/tools.ts +66 -0
- package/connectors/circleci/src/server.ts +2 -176
- package/connectors/circleci/src/tools.ts +193 -0
- package/connectors/cloud-logging/src/tools.ts +19 -35
- package/connectors/cloudwatch/src/tools.ts +20 -38
- package/connectors/codemagic/src/server.ts +2 -70
- package/connectors/codemagic/src/tools.ts +71 -0
- package/connectors/confluence/src/server.ts +2 -301
- package/connectors/confluence/src/tools.ts +320 -0
- package/connectors/dagster/src/server.ts +2 -214
- package/connectors/dagster/src/tools.ts +187 -0
- package/connectors/databricks/src/search-filter.ts +7 -7
- package/connectors/databricks/src/server.ts +2 -77
- package/connectors/databricks/src/tools.ts +66 -0
- package/connectors/datadog/src/server.ts +2 -48
- package/connectors/datadog/src/tools.ts +48 -0
- package/connectors/dataprofile/src/profile.ts +16 -46
- package/connectors/dbt/src/server.ts +2 -92
- package/connectors/dbt/src/tools.ts +80 -0
- package/connectors/dependencytrack/src/server.ts +2 -76
- package/connectors/dependencytrack/src/tools.ts +65 -0
- package/connectors/discord/src/server.ts +2 -85
- package/connectors/discord/src/tools.ts +82 -0
- package/connectors/elasticsearch/src/tools.ts +7 -37
- package/connectors/fastmail/src/jmap-client.ts +202 -0
- package/connectors/fastmail/src/jmap-core.ts +8 -21
- package/connectors/fastmail/src/server.ts +2 -187
- package/connectors/fastmail/src/tools.ts +4 -23
- package/connectors/figma/src/search-filter.ts +2 -11
- package/connectors/figma/src/server.ts +2 -113
- package/connectors/figma/src/tools.ts +99 -0
- package/connectors/firebase/src/server.ts +2 -90
- package/connectors/firebase/src/token.ts +2 -5
- package/connectors/firebase/src/tools.ts +106 -0
- package/connectors/flagsmith/src/server.ts +2 -98
- package/connectors/flagsmith/src/tools.ts +95 -0
- package/connectors/flux/src/server.ts +52 -53
- package/connectors/gcp/src/server.ts +2 -132
- package/connectors/gcp/src/tools.ts +123 -0
- package/connectors/github/src/server.ts +2 -328
- package/connectors/github/src/tools.ts +311 -0
- package/connectors/github-actions/src/server.ts +2 -205
- package/connectors/github-actions/src/tools.ts +209 -0
- package/connectors/gitlab/src/server.ts +2 -384
- package/connectors/gitlab/src/tools.ts +392 -0
- package/connectors/gmail/src/server.ts +2 -270
- package/connectors/gmail/src/tools.ts +209 -0
- package/connectors/google-drive/src/server.ts +2 -524
- package/connectors/google-drive/src/tools.ts +529 -0
- package/connectors/google-meet/src/server.ts +2 -82
- package/connectors/google-meet/src/tools.ts +87 -0
- package/connectors/google-photos/src/server.ts +2 -132
- package/connectors/google-photos/src/tools.ts +138 -0
- package/connectors/grafana/src/server.ts +2 -53
- package/connectors/grafana/src/tools.ts +53 -0
- package/connectors/great-expectations/src/gx-parse.ts +14 -45
- package/connectors/greenhouse/src/server.ts +2 -63
- package/connectors/greenhouse/src/tools.ts +68 -0
- package/connectors/hubspot/src/server.ts +2 -77
- package/connectors/hubspot/src/tools.ts +70 -0
- package/connectors/iac/src/server.ts +2 -137
- package/connectors/iac/src/tools.ts +153 -0
- package/connectors/imap/src/imap-core.ts +29 -82
- package/connectors/imap/src/server.ts +33 -220
- package/connectors/intercom/src/server.ts +2 -61
- package/connectors/intercom/src/tools.ts +58 -0
- package/connectors/jenkins/src/jenkins-api.ts +2 -6
- package/connectors/jenkins/src/server.ts +2 -215
- package/connectors/jenkins/src/tools.ts +240 -0
- package/connectors/jira/src/server.ts +2 -301
- package/connectors/jira/src/tools.ts +325 -0
- package/connectors/kubernetes/src/server.ts +2 -155
- package/connectors/kubernetes/src/tools.ts +160 -0
- package/connectors/launchdarkly/src/server.ts +2 -75
- package/connectors/launchdarkly/src/tools.ts +72 -0
- package/connectors/lever/src/search-filter.ts +4 -12
- package/connectors/lever/src/server.ts +2 -60
- package/connectors/lever/src/tools.ts +61 -0
- package/connectors/linear/src/server.ts +2 -459
- package/connectors/linear/src/tools.ts +482 -0
- package/connectors/localdb/src/sql-scan.ts +13 -41
- package/connectors/localdb/src/tools.ts +1 -1
- package/connectors/looker/src/search-filter.ts +2 -15
- package/connectors/looker/src/server.ts +35 -70
- package/connectors/mendeley/src/server.ts +2 -55
- package/connectors/mendeley/src/tools.ts +54 -0
- package/connectors/mercury/src/server.ts +2 -57
- package/connectors/mercury/src/tools.ts +50 -0
- package/connectors/metabase/src/search-filter.ts +4 -15
- package/connectors/metabase/src/server.ts +2 -79
- package/connectors/metabase/src/tools.ts +64 -0
- package/connectors/miro/src/server.ts +2 -72
- package/connectors/miro/src/tools.ts +65 -0
- package/connectors/mlflow/src/search-filter.ts +3 -7
- package/connectors/mlflow/src/server.ts +20 -40
- package/connectors/monte-carlo/src/server.ts +50 -57
- package/connectors/netlify/src/search-filter.ts +6 -14
- package/connectors/netlify/src/server.ts +2 -60
- package/connectors/netlify/src/tools.ts +53 -0
- package/connectors/newrelic/src/server.ts +2 -40
- package/connectors/newrelic/src/tools.ts +37 -0
- package/connectors/notion/src/server.ts +2 -335
- package/connectors/notion/src/tools.ts +344 -0
- package/connectors/obsidian/src/server.ts +2 -425
- package/connectors/obsidian/src/tools.ts +466 -0
- package/connectors/onedrive/src/server.ts +2 -249
- package/connectors/onedrive/src/tools.ts +253 -0
- package/connectors/outlook/src/server.ts +2 -351
- package/connectors/outlook/src/tools.ts +357 -0
- package/connectors/pagerduty/src/server.ts +2 -132
- package/connectors/pagerduty/src/tools.ts +126 -0
- package/connectors/pipedrive/src/server.ts +2 -58
- package/connectors/pipedrive/src/tools.ts +63 -0
- package/connectors/powerbi/src/search-filter.ts +2 -15
- package/connectors/powerbi/src/server.ts +43 -68
- package/connectors/prefect/src/search-filter.ts +3 -16
- package/connectors/prefect/src/server.ts +2 -90
- package/connectors/prefect/src/tools.ts +83 -0
- package/connectors/protonmail/src/mail-core.ts +16 -75
- package/connectors/protonmail/src/server.ts +34 -214
- package/connectors/raindrop/src/server.ts +2 -103
- package/connectors/raindrop/src/tools.ts +103 -0
- package/connectors/ramp/src/server.ts +2 -105
- package/connectors/ramp/src/tools.ts +98 -0
- package/connectors/readwise/src/server.ts +2 -88
- package/connectors/readwise/src/tools.ts +81 -0
- package/connectors/sagemaker/src/tools.ts +21 -40
- package/connectors/salesforce/src/search-filter.ts +4 -11
- package/connectors/salesforce/src/server.ts +2 -90
- package/connectors/salesforce/src/tools.ts +75 -0
- package/connectors/semgrep/src/search-filter.ts +3 -10
- package/connectors/semgrep/src/server.ts +2 -104
- package/connectors/semgrep/src/tools.ts +92 -0
- package/connectors/sentry/src/server.ts +2 -58
- package/connectors/sentry/src/tools.ts +53 -0
- package/connectors/slack/src/server.ts +2 -352
- package/connectors/slack/src/tools.ts +376 -0
- package/connectors/snowflake/src/search-filter.ts +4 -19
- package/connectors/snowflake/src/server.ts +6 -25
- package/connectors/snyk/src/server.ts +2 -140
- package/connectors/snyk/src/tools.ts +136 -0
- package/connectors/sonarqube/src/server.ts +2 -111
- package/connectors/sonarqube/src/tools.ts +98 -0
- package/connectors/stackoverflow/src/server.ts +2 -77
- package/connectors/stackoverflow/src/tools.ts +66 -0
- package/connectors/storybook/src/storybook-parse.ts +3 -6
- package/connectors/stripe/src/server.ts +2 -61
- package/connectors/stripe/src/tools.ts +54 -0
- package/connectors/superset/src/search-filter.ts +4 -15
- package/connectors/superset/src/server.ts +2 -115
- package/connectors/superset/src/tools.ts +98 -0
- package/connectors/tableau/src/search-filter.ts +2 -15
- package/connectors/tableau/src/server.ts +13 -65
- package/connectors/teams/src/server.ts +2 -249
- package/connectors/teams/src/tools.ts +283 -0
- package/connectors/testflight/src/server.ts +2 -70
- package/connectors/testflight/src/tools.ts +79 -0
- package/connectors/vercel/src/search-filter.ts +2 -10
- package/connectors/vercel/src/server.ts +2 -75
- package/connectors/vercel/src/tools.ts +73 -0
- package/connectors/vertex-ai/src/tools.ts +23 -33
- package/connectors/wiz/src/search-filter.ts +6 -30
- package/connectors/wiz/src/server.ts +2 -188
- package/connectors/wiz/src/tools.ts +168 -0
- package/connectors/zendesk/src/server.ts +2 -73
- package/connectors/zendesk/src/tools.ts +60 -0
- package/connectors/zoom/src/search-filter.ts +1 -7
- package/connectors/zoom/src/server.ts +2 -94
- package/connectors/zoom/src/tools.ts +93 -0
- package/connectors/zotero/src/server.ts +2 -78
- package/connectors/zotero/src/tools.ts +67 -0
- package/package.json +14 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +601 -34
- package/shared/cli-json-kit.ts +191 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/consent-kit.ts +81 -26
- package/shared/cursor-list-tool.ts +29 -0
- package/shared/env-json-api.ts +209 -0
- package/shared/fetch-json-text.ts +27 -0
- package/shared/github-rest.ts +62 -0
- package/shared/graphql-json.ts +60 -0
- package/shared/imap-mail-core.ts +91 -0
- package/shared/imap-tool-kit.ts +57 -23
- package/shared/imapflow-adapter.ts +417 -0
- package/shared/nimbus-spawn.ts +84 -27
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/safe-cli-arg.ts +139 -17
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/shared/windows-batch-args.ts +273 -0
- package/shared/write-budget.ts +100 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { postGraphql } from "../../../shared/graphql-json.ts";
|
|
4
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
5
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
6
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
7
|
+
import { asRecord } from "../../../shared/search-filter.ts";
|
|
8
|
+
import { filterDagsterJobs } from "./search-filter.ts";
|
|
9
|
+
|
|
10
|
+
function apiToken(): string {
|
|
11
|
+
// Required for Dagster Cloud; self-hosted OSS may use a placeholder. The
|
|
12
|
+
// gateway injects it regardless to keep spawn wiring uniform.
|
|
13
|
+
return requiredEnv("DAGSTER_API_TOKEN");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const JOBS_QUERY = `
|
|
17
|
+
query NimbusJobs {
|
|
18
|
+
repositoriesOrError {
|
|
19
|
+
__typename
|
|
20
|
+
... on RepositoryConnection {
|
|
21
|
+
nodes {
|
|
22
|
+
name
|
|
23
|
+
location { name }
|
|
24
|
+
pipelines { id name description isJob tags { key value } }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
... on PythonError { message }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
async function dagsterGraphql<T>(query: string): Promise<T> {
|
|
33
|
+
return postGraphql<T>({
|
|
34
|
+
url: `${requiredBaseUrl("DAGSTER_BASE_URL")}/graphql`,
|
|
35
|
+
label: "Dagster",
|
|
36
|
+
headers: { "Dagster-Cloud-Api-Token": apiToken() },
|
|
37
|
+
query,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface FlatJob {
|
|
42
|
+
name: string;
|
|
43
|
+
repository: string;
|
|
44
|
+
location: string | null;
|
|
45
|
+
id: string | null;
|
|
46
|
+
description: string | null;
|
|
47
|
+
isJob: boolean;
|
|
48
|
+
tags: Array<{ key: string; value: string }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function str(row: Record<string, unknown>, key: string): string | null {
|
|
52
|
+
const v = row[key];
|
|
53
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tagPairs(raw: unknown): Array<{ key: string; value: string }> {
|
|
57
|
+
if (!Array.isArray(raw)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const out: Array<{ key: string; value: string }> = [];
|
|
61
|
+
for (const t of raw) {
|
|
62
|
+
const tr = asRecord(t);
|
|
63
|
+
if (tr === undefined) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const key = str(tr, "key");
|
|
67
|
+
if (key === null) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out.push({ key, value: str(tr, "value") ?? "" });
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pipelinesOf(node: unknown): {
|
|
76
|
+
repository: string;
|
|
77
|
+
location: string | null;
|
|
78
|
+
rows: unknown[];
|
|
79
|
+
} {
|
|
80
|
+
const repo = asRecord(node);
|
|
81
|
+
if (repo === undefined) {
|
|
82
|
+
return { repository: "", location: null, rows: [] };
|
|
83
|
+
}
|
|
84
|
+
const locationRow = asRecord(repo["location"]);
|
|
85
|
+
const pipelines = repo["pipelines"];
|
|
86
|
+
return {
|
|
87
|
+
repository: str(repo, "name") ?? "",
|
|
88
|
+
location: locationRow === undefined ? null : str(locationRow, "name"),
|
|
89
|
+
rows: Array.isArray(pipelines) ? pipelines : [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function pipelineToJob(p: unknown, repository: string, location: string | null): FlatJob | null {
|
|
94
|
+
const pr = asRecord(p);
|
|
95
|
+
if (pr === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const name = str(pr, "name");
|
|
99
|
+
if (name === null) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
name,
|
|
104
|
+
repository,
|
|
105
|
+
location,
|
|
106
|
+
id: str(pr, "id"),
|
|
107
|
+
description: str(pr, "description"),
|
|
108
|
+
isJob: pr["isJob"] === true,
|
|
109
|
+
tags: tagPairs(pr["tags"]),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function flattenJobs(data: unknown): FlatJob[] {
|
|
114
|
+
const root = asRecord(data) ?? {};
|
|
115
|
+
const repos = asRecord(root["repositoriesOrError"]) ?? {};
|
|
116
|
+
if (repos["__typename"] === "PythonError") {
|
|
117
|
+
const message = str(repos, "message") ?? "unknown error";
|
|
118
|
+
throw new Error(`Dagster repositoriesOrError PythonError: ${message}`);
|
|
119
|
+
}
|
|
120
|
+
const nodes = repos["nodes"];
|
|
121
|
+
if (!Array.isArray(nodes)) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const out: FlatJob[] = [];
|
|
125
|
+
for (const node of nodes) {
|
|
126
|
+
const { repository, location, rows } = pipelinesOf(node);
|
|
127
|
+
for (const p of rows) {
|
|
128
|
+
const job = pipelineToJob(p, repository, location);
|
|
129
|
+
if (job !== null) {
|
|
130
|
+
out.push(job);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function listJobs(): Promise<FlatJob[]> {
|
|
138
|
+
return flattenJobs(await dagsterGraphql<unknown>(JOBS_QUERY));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
142
|
+
export const DAGSTER_TOOL_NAMES = ["dagster_get", "dagster_list", "dagster_search"] as const;
|
|
143
|
+
|
|
144
|
+
export function registerDagsterTools(reg: ZodToolRegistrar): void {
|
|
145
|
+
reg(
|
|
146
|
+
"dagster_list",
|
|
147
|
+
"List Dagster jobs. Walks the repositories catalog via a single GraphQL query (repositoriesOrError → nodes[].pipelines[]) against POST <base_url>/graphql and flattens to jobs, each carrying its repository and code location. Returns an { items: [...] } envelope.",
|
|
148
|
+
z.object({
|
|
149
|
+
limit: z.number().int().min(1).max(2000).optional(),
|
|
150
|
+
}),
|
|
151
|
+
async (p) => {
|
|
152
|
+
const jobs = await listJobs();
|
|
153
|
+
const limited = p.limit === undefined ? jobs : jobs.slice(0, p.limit);
|
|
154
|
+
return jsonResult({ items: limited });
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
reg(
|
|
159
|
+
"dagster_get",
|
|
160
|
+
"Fetch one Dagster job by its stable id. The id is either the full location:repository:jobName triple or just the bare jobName (the first match wins when only a name is given). Throws when no job matches.",
|
|
161
|
+
z.object({
|
|
162
|
+
id: z.string().min(1),
|
|
163
|
+
}),
|
|
164
|
+
async (p) => {
|
|
165
|
+
const jobs = await listJobs();
|
|
166
|
+
const match = jobs.find((j) => {
|
|
167
|
+
const triple = `${j.location ?? "_"}:${j.repository}:${j.name}`;
|
|
168
|
+
return triple === p.id || j.name === p.id;
|
|
169
|
+
});
|
|
170
|
+
if (match === undefined) {
|
|
171
|
+
throw new Error(`Dagster: job ${p.id} not found`);
|
|
172
|
+
}
|
|
173
|
+
return jsonResult(match);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
reg(
|
|
178
|
+
"dagster_search",
|
|
179
|
+
"Substring search across Dagster jobs. Matches the query (case-insensitive) against the job name, repository, code location, description, and tag keys/values. Returns a { matches: [...] } envelope.",
|
|
180
|
+
searchToolInputSchema(2000),
|
|
181
|
+
async (p) => {
|
|
182
|
+
const jobs = await listJobs();
|
|
183
|
+
const matches = filterDagsterJobs(jobs, { query: p.query, limit: p.limit });
|
|
184
|
+
return jsonResult({ matches });
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
asRecord,
|
|
3
3
|
makeQueryFilter,
|
|
4
|
+
nestedString,
|
|
4
5
|
type SearchMatchOptions,
|
|
6
|
+
stringField,
|
|
5
7
|
} from "../../../shared/search-filter.ts";
|
|
6
8
|
|
|
7
9
|
export type DatabricksSearchMatchOptions = SearchMatchOptions;
|
|
8
10
|
|
|
9
|
-
function stringAt(row: Record<string, unknown>, key: string): string {
|
|
10
|
-
const v = row[key];
|
|
11
|
-
return typeof v === "string" ? v : "";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
11
|
function numberAt(row: Record<string, unknown>, key: string): string {
|
|
15
12
|
const v = row[key];
|
|
16
13
|
return typeof v === "number" && Number.isFinite(v) ? String(v) : "";
|
|
@@ -21,8 +18,11 @@ function fieldsOf(item: unknown): readonly string[] | null {
|
|
|
21
18
|
if (row === undefined) {
|
|
22
19
|
return null;
|
|
23
20
|
}
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
return [
|
|
22
|
+
nestedString(row, ["settings", "name"]),
|
|
23
|
+
stringField(row, "creator_user_name"),
|
|
24
|
+
numberAt(row, "job_id"),
|
|
25
|
+
];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export const filterDatabricksJobs = makeQueryFilter(fieldsOf);
|
|
@@ -1,81 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
-
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
function trimTrailingSlash(s: string): string {
|
|
8
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function apiBase(): string {
|
|
12
|
-
const v = process.env["DATABRICKS_HOST"]?.trim();
|
|
13
|
-
if (v === undefined || v === "") {
|
|
14
|
-
throw new Error("DATABRICKS_HOST is not set");
|
|
15
|
-
}
|
|
16
|
-
return trimTrailingSlash(v);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function authHeader(): Record<string, string> {
|
|
20
|
-
const k = process.env["DATABRICKS_TOKEN"]?.trim();
|
|
21
|
-
if (k === undefined || k === "") {
|
|
22
|
-
throw new Error("DATABRICKS_TOKEN is not set");
|
|
23
|
-
}
|
|
24
|
-
return { Authorization: `Bearer ${k}`, Accept: "application/json" };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function dbGet(path: string): Promise<unknown> {
|
|
28
|
-
const res = await fetch(`${apiBase()}${path}`, { headers: authHeader() });
|
|
29
|
-
const text = await res.text();
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
throw new Error(`Databricks ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
32
|
-
}
|
|
33
|
-
return JSON.parse(text) as unknown;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function jobsFrom(root: unknown): unknown[] {
|
|
37
|
-
const jobs = (root as { jobs?: unknown } | null)?.jobs;
|
|
38
|
-
return Array.isArray(jobs) ? jobs : [];
|
|
39
|
-
}
|
|
2
|
+
import { registerDatabricksTools } from "./tools.ts";
|
|
40
3
|
|
|
41
4
|
await runReadOnlyMcpConnector("nimbus-databricks", (reg) => {
|
|
42
|
-
reg
|
|
43
|
-
"databricks_list",
|
|
44
|
-
"List Databricks jobs (`GET /api/2.1/jobs/list`). Returns a single page; `limit` (1..100, default 25) caps the page size. Returns the raw `{ jobs, has_more, next_page_token }` envelope.",
|
|
45
|
-
z.object({
|
|
46
|
-
limit: z.number().int().min(1).max(100).optional(),
|
|
47
|
-
}),
|
|
48
|
-
async (p) => {
|
|
49
|
-
const limit = p.limit ?? 25;
|
|
50
|
-
const params = new URLSearchParams({ limit: String(limit) });
|
|
51
|
-
return jsonResult(await dbGet(`/api/2.1/jobs/list?${params.toString()}`));
|
|
52
|
-
},
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
reg(
|
|
56
|
-
"databricks_get",
|
|
57
|
-
"Fetch one Databricks job by numeric `jobId` (`GET /api/2.1/jobs/get?job_id={jobId}`). Throws when no job with that id exists.",
|
|
58
|
-
z.object({
|
|
59
|
-
jobId: z.number().int(),
|
|
60
|
-
}),
|
|
61
|
-
async (p) => {
|
|
62
|
-
const params = new URLSearchParams({ job_id: String(p.jobId) });
|
|
63
|
-
return jsonResult(await dbGet(`/api/2.1/jobs/get?${params.toString()}`));
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
reg(
|
|
68
|
-
"databricks_search",
|
|
69
|
-
"Substring search across Databricks jobs. Matches the query (case-insensitive) against the job's `settings.name`, `creator_user_name`, and `job_id`. Returns a `{ matches: [...] }` envelope.",
|
|
70
|
-
searchToolInputSchema(100),
|
|
71
|
-
async (p) => {
|
|
72
|
-
const params = new URLSearchParams({ limit: "100" });
|
|
73
|
-
const root = await dbGet(`/api/2.1/jobs/list?${params.toString()}`);
|
|
74
|
-
const matches = filterDatabricksJobs(jobsFrom(root), {
|
|
75
|
-
query: p.query,
|
|
76
|
-
limit: p.limit,
|
|
77
|
-
});
|
|
78
|
-
return jsonResult({ matches });
|
|
79
|
-
},
|
|
80
|
-
);
|
|
5
|
+
registerDatabricksTools(reg);
|
|
81
6
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders, requiredBaseUrl } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterDatabricksJobs } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const dbGet = createJsonGetter({
|
|
9
|
+
base: () => requiredBaseUrl("DATABRICKS_HOST"),
|
|
10
|
+
label: "Databricks",
|
|
11
|
+
headers: envAuthHeaders({ env: "DATABRICKS_TOKEN" }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function jobsFrom(root: unknown): unknown[] {
|
|
15
|
+
const jobs = (root as { jobs?: unknown } | null)?.jobs;
|
|
16
|
+
return Array.isArray(jobs) ? jobs : [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
20
|
+
export const DATABRICKS_TOOL_NAMES = [
|
|
21
|
+
"databricks_list",
|
|
22
|
+
"databricks_get",
|
|
23
|
+
"databricks_search",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export function registerDatabricksTools(reg: ZodToolRegistrar): void {
|
|
27
|
+
reg(
|
|
28
|
+
"databricks_list",
|
|
29
|
+
"List Databricks jobs (`GET /api/2.1/jobs/list`). Returns a single page; `limit` (1..100, default 25) caps the page size. Returns the raw `{ jobs, has_more, next_page_token }` envelope.",
|
|
30
|
+
z.object({
|
|
31
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
32
|
+
}),
|
|
33
|
+
async (p) => {
|
|
34
|
+
const limit = p.limit ?? 25;
|
|
35
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
36
|
+
return jsonResult(await dbGet(`/api/2.1/jobs/list?${params.toString()}`));
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
reg(
|
|
41
|
+
"databricks_get",
|
|
42
|
+
"Fetch one Databricks job by numeric `jobId` (`GET /api/2.1/jobs/get?job_id={jobId}`). Throws when no job with that id exists.",
|
|
43
|
+
z.object({
|
|
44
|
+
jobId: z.number().int(),
|
|
45
|
+
}),
|
|
46
|
+
async (p) => {
|
|
47
|
+
const params = new URLSearchParams({ job_id: String(p.jobId) });
|
|
48
|
+
return jsonResult(await dbGet(`/api/2.1/jobs/get?${params.toString()}`));
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
reg(
|
|
53
|
+
"databricks_search",
|
|
54
|
+
"Substring search across Databricks jobs. Matches the query (case-insensitive) against the job's `settings.name`, `creator_user_name`, and `job_id`. Returns a `{ matches: [...] }` envelope.",
|
|
55
|
+
searchToolInputSchema(100),
|
|
56
|
+
async (p) => {
|
|
57
|
+
const params = new URLSearchParams({ limit: "100" });
|
|
58
|
+
const root = await dbGet(`/api/2.1/jobs/list?${params.toString()}`);
|
|
59
|
+
const matches = filterDatabricksJobs(jobsFrom(root), {
|
|
60
|
+
query: p.query,
|
|
61
|
+
limit: p.limit,
|
|
62
|
+
});
|
|
63
|
+
return jsonResult({ matches });
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,56 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createRegisterSimpleTool,
|
|
7
|
-
createZodToolRegistrar,
|
|
8
|
-
mcpJsonResult as jsonResult,
|
|
9
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
10
|
-
|
|
11
|
-
function siteHost(): string {
|
|
12
|
-
const s = process.env["DD_SITE"]?.trim() || "datadoghq.com";
|
|
13
|
-
return `api.${s}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function headers(): Record<string, string> {
|
|
17
|
-
const ak = process.env["DD_API_KEY"]?.trim();
|
|
18
|
-
const app = process.env["DD_APP_KEY"]?.trim();
|
|
19
|
-
if (ak === undefined || ak === "" || app === undefined || app === "") {
|
|
20
|
-
throw new Error("DD_API_KEY and DD_APP_KEY must be set");
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
"DD-API-KEY": ak,
|
|
24
|
-
"DD-APPLICATION-KEY": app,
|
|
25
|
-
Accept: "application/json",
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function ddGet(path: string): Promise<unknown> {
|
|
30
|
-
const res = await fetch(`https://${siteHost()}${path}`, { headers: headers() });
|
|
31
|
-
const text = await res.text();
|
|
32
|
-
if (!res.ok) {
|
|
33
|
-
throw new Error(`Datadog ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
34
|
-
}
|
|
35
|
-
return JSON.parse(text) as unknown;
|
|
36
|
-
}
|
|
3
|
+
import { registerDatadogTools } from "./tools.ts";
|
|
37
4
|
|
|
38
5
|
const mcp = new McpServer({ name: "nimbus-datadog", version: "0.1.0" });
|
|
39
|
-
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
40
|
-
|
|
41
|
-
reg("datadog_monitor_list", "List monitors.", z.object({}), async () =>
|
|
42
|
-
jsonResult(await ddGet("/api/v1/monitor")),
|
|
43
|
-
);
|
|
44
6
|
|
|
45
|
-
|
|
46
|
-
"datadog_incident_list",
|
|
47
|
-
"List incidents (v2).",
|
|
48
|
-
z.object({ limit: z.number().int().min(1).max(50).optional() }),
|
|
49
|
-
async (p) => {
|
|
50
|
-
const lim = p.limit ?? 10;
|
|
51
|
-
return jsonResult(await ddGet(`/api/v2/incidents?page[size]=${String(lim)}`));
|
|
52
|
-
},
|
|
53
|
-
);
|
|
7
|
+
registerDatadogTools(mcp);
|
|
54
8
|
|
|
55
9
|
const transport = new StdioServerTransport();
|
|
56
10
|
await mcp.connect(transport);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, optionalEnv } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import {
|
|
4
|
+
createRegisterSimpleTool,
|
|
5
|
+
createZodToolRegistrar,
|
|
6
|
+
mcpJsonResult as jsonResult,
|
|
7
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
8
|
+
|
|
9
|
+
/** Both keys or neither: one alone authenticates nothing, so the refusal names the pair. */
|
|
10
|
+
function headers(): Record<string, string> {
|
|
11
|
+
const ak = process.env["DD_API_KEY"]?.trim();
|
|
12
|
+
const app = process.env["DD_APP_KEY"]?.trim();
|
|
13
|
+
if (ak === undefined || ak === "" || app === undefined || app === "") {
|
|
14
|
+
throw new Error("DD_API_KEY and DD_APP_KEY must be set");
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
"DD-API-KEY": ak,
|
|
18
|
+
"DD-APPLICATION-KEY": app,
|
|
19
|
+
Accept: "application/json",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ddGet = createJsonGetter({
|
|
24
|
+
base: () => `https://api.${optionalEnv("DD_SITE", "datadoghq.com")}`,
|
|
25
|
+
label: "Datadog",
|
|
26
|
+
headers,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
30
|
+
export const DATADOG_TOOL_NAMES = ["datadog_incident_list", "datadog_monitor_list"] as const;
|
|
31
|
+
|
|
32
|
+
export function registerDatadogTools(server: { tool: (...args: never) => unknown }): void {
|
|
33
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
34
|
+
|
|
35
|
+
reg("datadog_monitor_list", "List monitors.", z.object({}), async () =>
|
|
36
|
+
jsonResult(await ddGet("/api/v1/monitor")),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
reg(
|
|
40
|
+
"datadog_incident_list",
|
|
41
|
+
"List incidents (v2).",
|
|
42
|
+
z.object({ limit: z.number().int().min(1).max(50).optional() }),
|
|
43
|
+
async (p) => {
|
|
44
|
+
const lim = p.limit ?? 10;
|
|
45
|
+
return jsonResult(await ddGet(`/api/v2/incidents?page[size]=${String(lim)}`));
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
|
-
import { open
|
|
3
|
-
import {
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
type DataColumn,
|
|
6
6
|
firstLineAndRows,
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
parseJsonColumns,
|
|
11
11
|
parseJsonlColumns,
|
|
12
12
|
} from "@nimbus-dev/sdk";
|
|
13
|
+
import { requiredEnv } from "../../../shared/env-json-api.ts";
|
|
14
|
+
import { assertWithinDir, walkFiles } from "../../../shared/walk-files.ts";
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Local data profiling reader (Tier-5, no-row-data). The MCP-server-side
|
|
@@ -53,21 +55,11 @@ export interface DataModel {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
export function dataDir(): string {
|
|
56
|
-
|
|
57
|
-
if (dir === undefined || dir === "") {
|
|
58
|
-
throw new Error("DATAPROFILE_DIR is not set");
|
|
59
|
-
}
|
|
60
|
-
return resolve(dir);
|
|
58
|
+
return resolve(requiredEnv("DATAPROFILE_DIR"));
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
export function assertWithinDataDir(candidate: string, root: string): void {
|
|
64
|
-
|
|
65
|
-
if (rel === "") {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (rel.startsWith("..") || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
69
|
-
throw new Error("path escapes the configured data-profile dir");
|
|
70
|
-
}
|
|
62
|
+
assertWithinDir(candidate, root, "the configured data-profile dir");
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
function extOf(name: string): string {
|
|
@@ -218,37 +210,15 @@ async function profileFile(
|
|
|
218
210
|
};
|
|
219
211
|
}
|
|
220
212
|
|
|
221
|
-
|
|
222
|
-
root
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
232
|
-
} catch {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
for (const entry of entries) {
|
|
236
|
-
if (found.length >= MAX_FILES) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const full = join(dir, entry.name);
|
|
240
|
-
if (entry.isDirectory()) {
|
|
241
|
-
await walk(full, depth + 1);
|
|
242
|
-
} else if (entry.isFile()) {
|
|
243
|
-
const format = EXT_FORMAT[extOf(entry.name)];
|
|
244
|
-
if (format !== undefined) {
|
|
245
|
-
found.push({ path: full, format });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
await walk(root, 0);
|
|
251
|
-
return found;
|
|
213
|
+
function collectDataFiles(root: string): Promise<Array<{ path: string; format: DataFileFormat }>> {
|
|
214
|
+
return walkFiles(root, {
|
|
215
|
+
maxFiles: MAX_FILES,
|
|
216
|
+
maxDepth: MAX_WALK_DEPTH,
|
|
217
|
+
select: (entry, full) => {
|
|
218
|
+
const format = EXT_FORMAT[extOf(entry.name)];
|
|
219
|
+
return format === undefined ? undefined : { path: full, format };
|
|
220
|
+
},
|
|
221
|
+
});
|
|
252
222
|
}
|
|
253
223
|
|
|
254
224
|
/** Profile all data files under the configured dir into schema-only `DataModel` views. */
|
|
@@ -260,7 +230,7 @@ export async function listDataModels(
|
|
|
260
230
|
const out: DataModel[] = [];
|
|
261
231
|
for (const { path, format } of files) {
|
|
262
232
|
assertWithinDataDir(path, root);
|
|
263
|
-
const model = await profileFile(path, root, format, readParquet);
|
|
233
|
+
const model = await profileFile(path, root, format, readParquet); // NOSONAR S9382: one file at a time on purpose — up to MAX_FILES (2000) files, a text file read whole up to 64 MiB, so a fan-out could hold gigabytes and thousands of open handles at once.
|
|
264
234
|
if (model !== null) {
|
|
265
235
|
out.push(model);
|
|
266
236
|
}
|
|
@@ -1,96 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const DEFAULT_API_BASE = "https://cloud.getdbt.com";
|
|
7
|
-
|
|
8
|
-
function trimTrailingSlash(s: string): string {
|
|
9
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function apiBase(): string {
|
|
13
|
-
const v = process.env["DBT_API_BASE"]?.trim();
|
|
14
|
-
return v === undefined || v === "" ? DEFAULT_API_BASE : trimTrailingSlash(v);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function authHeader(): Record<string, string> {
|
|
18
|
-
const t = process.env["DBT_TOKEN"]?.trim();
|
|
19
|
-
if (t === undefined || t === "") {
|
|
20
|
-
throw new Error("DBT_TOKEN is not set");
|
|
21
|
-
}
|
|
22
|
-
return { Authorization: `Token ${t}`, Accept: "application/json" };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function dbtGet(path: string): Promise<unknown> {
|
|
26
|
-
const res = await fetch(`${apiBase()}/api/v2${path}`, { headers: authHeader() });
|
|
27
|
-
const text = await res.text();
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
throw new Error(`dbt Cloud ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
30
|
-
}
|
|
31
|
-
return JSON.parse(text) as unknown;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function dataFrom(root: unknown): unknown[] {
|
|
35
|
-
if (Array.isArray(root)) {
|
|
36
|
-
return root;
|
|
37
|
-
}
|
|
38
|
-
const data = (root as { data?: unknown } | null)?.data;
|
|
39
|
-
return Array.isArray(data) ? data : [];
|
|
40
|
-
}
|
|
2
|
+
import { registerDbtTools } from "./tools.ts";
|
|
41
3
|
|
|
42
4
|
await runReadOnlyMcpConnector("nimbus-dbt", (reg) => {
|
|
43
|
-
reg
|
|
44
|
-
"dbt_list",
|
|
45
|
-
"List dbt Cloud accounts, or jobs for an account. Without `accountId`, returns the account's accounts (`/accounts/`). With `accountId`, returns that account's jobs (`/accounts/{accountId}/jobs/`), capped at `limit` (default 100). Returns the raw dbt Cloud envelope (`{ data: [...] }`).",
|
|
46
|
-
z.object({
|
|
47
|
-
accountId: z.number().int().optional(),
|
|
48
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
49
|
-
}),
|
|
50
|
-
async (p) => {
|
|
51
|
-
if (p.accountId === undefined) {
|
|
52
|
-
return jsonResult(await dbtGet("/accounts/"));
|
|
53
|
-
}
|
|
54
|
-
const search = new URLSearchParams({ limit: String(p.limit ?? 100) });
|
|
55
|
-
return jsonResult(
|
|
56
|
-
await dbtGet(
|
|
57
|
-
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/?${search.toString()}`,
|
|
58
|
-
),
|
|
59
|
-
);
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
reg(
|
|
64
|
-
"dbt_get",
|
|
65
|
-
"Fetch one dbt Cloud job by `accountId` + `jobId` (`/accounts/{accountId}/jobs/{jobId}/`). Returns the raw dbt Cloud envelope. Throws when no such job exists.",
|
|
66
|
-
z.object({
|
|
67
|
-
accountId: z.number().int(),
|
|
68
|
-
jobId: z.number().int(),
|
|
69
|
-
}),
|
|
70
|
-
async (p) => {
|
|
71
|
-
return jsonResult(
|
|
72
|
-
await dbtGet(
|
|
73
|
-
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/${encodeURIComponent(String(p.jobId))}/`,
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
reg(
|
|
80
|
-
"dbt_search",
|
|
81
|
-
"Substring search across an account's dbt Cloud jobs. Matches the query against job name, dbt version, and id (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
82
|
-
z.object({
|
|
83
|
-
accountId: z.number().int(),
|
|
84
|
-
query: z.string().min(1),
|
|
85
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
86
|
-
}),
|
|
87
|
-
async (p) => {
|
|
88
|
-
const search = new URLSearchParams({ limit: "500" });
|
|
89
|
-
const root = await dbtGet(
|
|
90
|
-
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/?${search.toString()}`,
|
|
91
|
-
);
|
|
92
|
-
const matches = filterDbtJobs(dataFrom(root), { query: p.query, limit: p.limit });
|
|
93
|
-
return jsonResult({ matches });
|
|
94
|
-
},
|
|
95
|
-
);
|
|
5
|
+
registerDbtTools(reg);
|
|
96
6
|
});
|