@nimbus-dev/connectors 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- 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 +15 -34
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +197 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +133 -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 +14 -27
- package/connectors/cloudwatch/src/tools.ts +15 -34
- 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 +152 -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 +159 -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 +19 -38
- 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 +13 -6
- package/shared/cli-json-kit.ts +150 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- 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/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -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
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders, optionalBaseUrl } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterDbtJobs } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_API_BASE = "https://cloud.getdbt.com";
|
|
8
|
+
|
|
9
|
+
const dbtGet = createJsonGetter({
|
|
10
|
+
base: () => `${optionalBaseUrl("DBT_API_BASE", DEFAULT_API_BASE)}/api/v2`,
|
|
11
|
+
label: "dbt Cloud",
|
|
12
|
+
headers: envAuthHeaders({ env: "DBT_TOKEN", scheme: "Token" }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function dataFrom(root: unknown): unknown[] {
|
|
16
|
+
if (Array.isArray(root)) {
|
|
17
|
+
return root;
|
|
18
|
+
}
|
|
19
|
+
const data = (root as { data?: unknown } | null)?.data;
|
|
20
|
+
return Array.isArray(data) ? data : [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
24
|
+
export const DBT_TOOL_NAMES = ["dbt_get", "dbt_list", "dbt_search"] as const;
|
|
25
|
+
|
|
26
|
+
export function registerDbtTools(reg: ZodToolRegistrar): void {
|
|
27
|
+
reg(
|
|
28
|
+
"dbt_list",
|
|
29
|
+
"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: [...] }`).",
|
|
30
|
+
z.object({
|
|
31
|
+
accountId: z.number().int().optional(),
|
|
32
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
33
|
+
}),
|
|
34
|
+
async (p) => {
|
|
35
|
+
if (p.accountId === undefined) {
|
|
36
|
+
return jsonResult(await dbtGet("/accounts/"));
|
|
37
|
+
}
|
|
38
|
+
const search = new URLSearchParams({ limit: String(p.limit ?? 100) });
|
|
39
|
+
return jsonResult(
|
|
40
|
+
await dbtGet(
|
|
41
|
+
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/?${search.toString()}`,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
reg(
|
|
48
|
+
"dbt_get",
|
|
49
|
+
"Fetch one dbt Cloud job by `accountId` + `jobId` (`/accounts/{accountId}/jobs/{jobId}/`). Returns the raw dbt Cloud envelope. Throws when no such job exists.",
|
|
50
|
+
z.object({
|
|
51
|
+
accountId: z.number().int(),
|
|
52
|
+
jobId: z.number().int(),
|
|
53
|
+
}),
|
|
54
|
+
async (p) => {
|
|
55
|
+
return jsonResult(
|
|
56
|
+
await dbtGet(
|
|
57
|
+
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/${encodeURIComponent(String(p.jobId))}/`,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
reg(
|
|
64
|
+
"dbt_search",
|
|
65
|
+
"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.",
|
|
66
|
+
z.object({
|
|
67
|
+
accountId: z.number().int(),
|
|
68
|
+
query: z.string().min(1),
|
|
69
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
70
|
+
}),
|
|
71
|
+
async (p) => {
|
|
72
|
+
const search = new URLSearchParams({ limit: "500" });
|
|
73
|
+
const root = await dbtGet(
|
|
74
|
+
`/accounts/${encodeURIComponent(String(p.accountId))}/jobs/?${search.toString()}`,
|
|
75
|
+
);
|
|
76
|
+
const matches = filterDbtJobs(dataFrom(root), { query: p.query, limit: p.limit });
|
|
77
|
+
return jsonResult({ matches });
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -1,80 +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["DEPENDENCYTRACK_URL"]?.trim();
|
|
13
|
-
if (v === undefined || v === "") {
|
|
14
|
-
throw new Error("DEPENDENCYTRACK_URL is not set");
|
|
15
|
-
}
|
|
16
|
-
return trimTrailingSlash(v);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function authHeader(): Record<string, string> {
|
|
20
|
-
const k = process.env["DEPENDENCYTRACK_API_KEY"]?.trim();
|
|
21
|
-
if (k === undefined || k === "") {
|
|
22
|
-
throw new Error("DEPENDENCYTRACK_API_KEY is not set");
|
|
23
|
-
}
|
|
24
|
-
return { "X-Api-Key": k, Accept: "application/json" };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function dtGet(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(`Dependency-Track ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
32
|
-
}
|
|
33
|
-
return JSON.parse(text) as unknown;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function projectsFrom(root: unknown): unknown[] {
|
|
37
|
-
return Array.isArray(root) ? root : [];
|
|
38
|
-
}
|
|
2
|
+
import { registerDependencytrackTools } from "./tools.ts";
|
|
39
3
|
|
|
40
4
|
await runReadOnlyMcpConnector("nimbus-dependencytrack", (reg) => {
|
|
41
|
-
reg
|
|
42
|
-
"dependencytrack_list",
|
|
43
|
-
"List Dependency-Track projects (GET /api/v1/project?pageSize=100&pageNumber=1&excludeInactive=false). Dependency-Track returns a JSON array of project objects, each embedding its current vulnerability metrics. The optional pageNumber arg (default 1) selects the page; pageSize is fixed at 100.",
|
|
44
|
-
z.object({
|
|
45
|
-
pageNumber: z.number().int().min(1).optional(),
|
|
46
|
-
}),
|
|
47
|
-
async (p) => {
|
|
48
|
-
const page = p.pageNumber ?? 1;
|
|
49
|
-
const root = await dtGet(
|
|
50
|
-
`/api/v1/project?pageSize=100&pageNumber=${encodeURIComponent(String(page))}&excludeInactive=false`,
|
|
51
|
-
);
|
|
52
|
-
return jsonResult({ items: projectsFrom(root) });
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
reg(
|
|
57
|
-
"dependencytrack_get",
|
|
58
|
-
"Fetch one Dependency-Track project by its UUID (GET /api/v1/project/{uuid}). Returns the project object directly (NOT wrapped in an array). Throws when no project with that UUID exists.",
|
|
59
|
-
z.object({
|
|
60
|
-
uuid: z.string().min(1),
|
|
61
|
-
}),
|
|
62
|
-
async (p) => {
|
|
63
|
-
return jsonResult(await dtGet(`/api/v1/project/${encodeURIComponent(p.uuid)}`));
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
reg(
|
|
68
|
-
"dependencytrack_search",
|
|
69
|
-
"Substring search across the first page of Dependency-Track projects. Matches the query (case-insensitive) against the project name, version, classifier, and tag names. Returns a matches envelope.",
|
|
70
|
-
searchToolInputSchema(100),
|
|
71
|
-
async (p) => {
|
|
72
|
-
const root = await dtGet("/api/v1/project?pageSize=100&pageNumber=1&excludeInactive=false");
|
|
73
|
-
const matches = filterDependencyTrackProjects(projectsFrom(root), {
|
|
74
|
-
query: p.query,
|
|
75
|
-
limit: p.limit,
|
|
76
|
-
});
|
|
77
|
-
return jsonResult({ matches });
|
|
78
|
-
},
|
|
79
|
-
);
|
|
5
|
+
registerDependencytrackTools(reg);
|
|
80
6
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
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 { filterDependencyTrackProjects } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const dtGet = createJsonGetter({
|
|
9
|
+
base: () => requiredBaseUrl("DEPENDENCYTRACK_URL"),
|
|
10
|
+
label: "Dependency-Track",
|
|
11
|
+
headers: envAuthHeaders({ env: "DEPENDENCYTRACK_API_KEY", scheme: "", header: "X-Api-Key" }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function projectsFrom(root: unknown): unknown[] {
|
|
15
|
+
return Array.isArray(root) ? root : [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
19
|
+
export const DEPENDENCYTRACK_TOOL_NAMES = [
|
|
20
|
+
"dependencytrack_list",
|
|
21
|
+
"dependencytrack_get",
|
|
22
|
+
"dependencytrack_search",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export function registerDependencytrackTools(reg: ZodToolRegistrar): void {
|
|
26
|
+
reg(
|
|
27
|
+
"dependencytrack_list",
|
|
28
|
+
"List Dependency-Track projects (GET /api/v1/project?pageSize=100&pageNumber=1&excludeInactive=false). Dependency-Track returns a JSON array of project objects, each embedding its current vulnerability metrics. The optional pageNumber arg (default 1) selects the page; pageSize is fixed at 100.",
|
|
29
|
+
z.object({
|
|
30
|
+
pageNumber: z.number().int().min(1).optional(),
|
|
31
|
+
}),
|
|
32
|
+
async (p) => {
|
|
33
|
+
const page = p.pageNumber ?? 1;
|
|
34
|
+
const root = await dtGet(
|
|
35
|
+
`/api/v1/project?pageSize=100&pageNumber=${encodeURIComponent(String(page))}&excludeInactive=false`,
|
|
36
|
+
);
|
|
37
|
+
return jsonResult({ items: projectsFrom(root) });
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
reg(
|
|
42
|
+
"dependencytrack_get",
|
|
43
|
+
"Fetch one Dependency-Track project by its UUID (GET /api/v1/project/{uuid}). Returns the project object directly (NOT wrapped in an array). Throws when no project with that UUID exists.",
|
|
44
|
+
z.object({
|
|
45
|
+
uuid: z.string().min(1),
|
|
46
|
+
}),
|
|
47
|
+
async (p) => {
|
|
48
|
+
return jsonResult(await dtGet(`/api/v1/project/${encodeURIComponent(p.uuid)}`));
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
reg(
|
|
53
|
+
"dependencytrack_search",
|
|
54
|
+
"Substring search across the first page of Dependency-Track projects. Matches the query (case-insensitive) against the project name, version, classifier, and tag names. Returns a matches envelope.",
|
|
55
|
+
searchToolInputSchema(100),
|
|
56
|
+
async (p) => {
|
|
57
|
+
const root = await dtGet("/api/v1/project?pageSize=100&pageNumber=1&excludeInactive=false");
|
|
58
|
+
const matches = filterDependencyTrackProjects(projectsFrom(root), {
|
|
59
|
+
query: p.query,
|
|
60
|
+
limit: p.limit,
|
|
61
|
+
});
|
|
62
|
+
return jsonResult({ matches });
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -1,93 +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 { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
|
|
6
|
-
import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
7
|
-
|
|
8
|
-
const DISCORD_API = "https://discord.com/api/v10";
|
|
9
|
-
|
|
10
|
-
async function discordFetch(
|
|
11
|
-
token: string,
|
|
12
|
-
path: string,
|
|
13
|
-
init?: RequestInit,
|
|
14
|
-
): Promise<{
|
|
15
|
-
ok: boolean;
|
|
16
|
-
status: number;
|
|
17
|
-
json: unknown;
|
|
18
|
-
text: string;
|
|
19
|
-
}> {
|
|
20
|
-
const url = path.startsWith("http") ? path : `${DISCORD_API}${path}`;
|
|
21
|
-
const res = await fetch(url, {
|
|
22
|
-
...init,
|
|
23
|
-
headers: {
|
|
24
|
-
Authorization: `Bot ${token}`,
|
|
25
|
-
"User-Agent": "NimbusMCP (https://github.com/nimbus-dev/nimbus)",
|
|
26
|
-
...(init?.headers as Record<string, string> | undefined),
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
const text = await res.text();
|
|
30
|
-
let json: unknown;
|
|
31
|
-
try {
|
|
32
|
-
json = JSON.parse(text) as unknown;
|
|
33
|
-
} catch {
|
|
34
|
-
json = null;
|
|
35
|
-
}
|
|
36
|
-
return { ok: res.ok, status: res.status, json, text };
|
|
37
|
-
}
|
|
3
|
+
import { registerDiscordTools } from "./tools.ts";
|
|
38
4
|
|
|
39
5
|
const server = new McpServer({ name: "nimbus-discord", version: "0.1.0" });
|
|
40
6
|
|
|
41
|
-
|
|
42
|
-
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
43
|
-
|
|
44
|
-
/** Standard Discord read tool: token → discordFetch(buildPath) → mcpJsonResultIfOk("Discord"). */
|
|
45
|
-
const registerDiscordTool = makeRestToolRegistrar({
|
|
46
|
-
registrar: reg,
|
|
47
|
-
tokenEnv: "DISCORD_BOT_TOKEN",
|
|
48
|
-
serviceLabel: "Discord",
|
|
49
|
-
fetch: discordFetch,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
registerDiscordTool(
|
|
53
|
-
"discord_guild_list",
|
|
54
|
-
"List guilds the bot is a member of.",
|
|
55
|
-
z.object({}),
|
|
56
|
-
() => "/users/@me/guilds",
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
registerDiscordTool(
|
|
60
|
-
"discord_channel_list",
|
|
61
|
-
"List channels in a guild (id, type, name).",
|
|
62
|
-
z.object({ guildId: z.string().min(1) }),
|
|
63
|
-
(parsed) => `/guilds/${encodeURIComponent(parsed.guildId)}/channels`,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
registerDiscordTool(
|
|
67
|
-
"discord_channel_messages",
|
|
68
|
-
"List recent messages in a channel (newest first). Optional `after` snowflake for incremental fetch.",
|
|
69
|
-
z.object({
|
|
70
|
-
channelId: z.string().min(1),
|
|
71
|
-
limit: z.number().int().min(1).max(100).optional(),
|
|
72
|
-
after: z.string().optional(),
|
|
73
|
-
}),
|
|
74
|
-
(parsed) => {
|
|
75
|
-
const lim = parsed.limit ?? 50;
|
|
76
|
-
const u = new URL(`${DISCORD_API}/channels/${encodeURIComponent(parsed.channelId)}/messages`);
|
|
77
|
-
u.searchParams.set("limit", String(lim));
|
|
78
|
-
if (parsed.after !== undefined && parsed.after !== "") {
|
|
79
|
-
u.searchParams.set("after", parsed.after);
|
|
80
|
-
}
|
|
81
|
-
return `${u.pathname}${u.search}`;
|
|
82
|
-
},
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
registerDiscordTool(
|
|
86
|
-
"discord_thread_list",
|
|
87
|
-
"List active threads in a guild (includes public threads the bot can see).",
|
|
88
|
-
z.object({ guildId: z.string().min(1) }),
|
|
89
|
-
(parsed) => `/guilds/${encodeURIComponent(parsed.guildId)}/threads/active`,
|
|
90
|
-
);
|
|
7
|
+
registerDiscordTools(server);
|
|
91
8
|
|
|
92
9
|
const transport = new StdioServerTransport();
|
|
93
10
|
await server.connect(transport);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
|
|
3
|
+
import {
|
|
4
|
+
makeRestToolRegistrar,
|
|
5
|
+
type RestFetchResult,
|
|
6
|
+
toRestFetchResult,
|
|
7
|
+
} from "../../../shared/rest-tool-kit.ts";
|
|
8
|
+
|
|
9
|
+
const DISCORD_API = "https://discord.com/api/v10";
|
|
10
|
+
|
|
11
|
+
async function discordFetch(
|
|
12
|
+
token: string,
|
|
13
|
+
path: string,
|
|
14
|
+
init?: RequestInit,
|
|
15
|
+
): Promise<RestFetchResult> {
|
|
16
|
+
const url = path.startsWith("http") ? path : `${DISCORD_API}${path}`;
|
|
17
|
+
const res = await fetch(url, {
|
|
18
|
+
...init,
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bot ${token}`,
|
|
21
|
+
"User-Agent": "NimbusMCP (https://github.com/nimbus-dev/nimbus)",
|
|
22
|
+
...(init?.headers as Record<string, string> | undefined),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return toRestFetchResult(res);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerDiscordTools(server: { tool: (...args: never) => unknown }): void {
|
|
29
|
+
const registerSimpleTool = createRegisterSimpleTool(server);
|
|
30
|
+
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
31
|
+
|
|
32
|
+
/** Standard Discord read tool: token → discordFetch(buildPath) → mcpJsonResultIfOk("Discord"). */
|
|
33
|
+
const registerDiscordTool = makeRestToolRegistrar({
|
|
34
|
+
registrar: reg,
|
|
35
|
+
tokenEnv: "DISCORD_BOT_TOKEN",
|
|
36
|
+
serviceLabel: "Discord",
|
|
37
|
+
fetch: discordFetch,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
registerDiscordTool(
|
|
41
|
+
"discord_guild_list",
|
|
42
|
+
"List guilds the bot is a member of.",
|
|
43
|
+
z.object({}),
|
|
44
|
+
() => "/users/@me/guilds",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
registerDiscordTool(
|
|
48
|
+
"discord_channel_list",
|
|
49
|
+
"List channels in a guild (id, type, name).",
|
|
50
|
+
z.object({ guildId: z.string().min(1) }),
|
|
51
|
+
(parsed) => `/guilds/${encodeURIComponent(parsed.guildId)}/channels`,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
registerDiscordTool(
|
|
55
|
+
"discord_channel_messages",
|
|
56
|
+
"List recent messages in a channel (newest first). Optional `after` snowflake for incremental fetch.",
|
|
57
|
+
z.object({
|
|
58
|
+
channelId: z.string().min(1),
|
|
59
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
60
|
+
after: z.string().optional(),
|
|
61
|
+
}),
|
|
62
|
+
(parsed) => {
|
|
63
|
+
const lim = parsed.limit ?? 50;
|
|
64
|
+
const path = `/channels/${encodeURIComponent(parsed.channelId)}/messages`;
|
|
65
|
+
const u = new URL(`${DISCORD_API}${path}`);
|
|
66
|
+
u.searchParams.set("limit", String(lim));
|
|
67
|
+
if (parsed.after !== undefined && parsed.after !== "") {
|
|
68
|
+
u.searchParams.set("after", parsed.after);
|
|
69
|
+
}
|
|
70
|
+
// Relative to DISCORD_API, like every other tool here: the fetcher prefixes the base, which
|
|
71
|
+
// already ends in /api/v10, so returning `u.pathname` requested /api/v10/api/v10/….
|
|
72
|
+
return `${path}${u.search}`;
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
registerDiscordTool(
|
|
77
|
+
"discord_thread_list",
|
|
78
|
+
"List active threads in a guild (includes public threads the bot can see).",
|
|
79
|
+
z.object({ guildId: z.string().min(1) }),
|
|
80
|
+
(parsed) => `/guilds/${encodeURIComponent(parsed.guildId)}/threads/active`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { isRecord, strField } from "../../../shared/cli-json-kit.ts";
|
|
3
|
+
import { createJsonGetter, envAuthHeaders, requiredBaseUrl } from "../../../shared/env-json-api.ts";
|
|
2
4
|
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
5
|
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
4
6
|
|
|
@@ -18,43 +20,11 @@ export const ELASTICSEARCH_TOOL_NAMES = [
|
|
|
18
20
|
"elasticsearch_search",
|
|
19
21
|
] as const;
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const v = process.env["ELASTICSEARCH_URL"]?.trim();
|
|
27
|
-
if (v === undefined || v === "") {
|
|
28
|
-
throw new Error("ELASTICSEARCH_URL is not set");
|
|
29
|
-
}
|
|
30
|
-
return trimTrailingSlash(v);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function authHeaders(): Record<string, string> {
|
|
34
|
-
const k = process.env["ELASTICSEARCH_API_KEY"]?.trim();
|
|
35
|
-
if (k === undefined || k === "") {
|
|
36
|
-
throw new Error("ELASTICSEARCH_API_KEY is not set");
|
|
37
|
-
}
|
|
38
|
-
return { Authorization: `ApiKey ${k}`, Accept: "application/json" };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function esGet(path: string): Promise<unknown> {
|
|
42
|
-
const res = await fetch(`${baseUrl()}${path}`, { headers: authHeaders() });
|
|
43
|
-
const text = await res.text();
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
throw new Error(`Elasticsearch ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
46
|
-
}
|
|
47
|
-
return JSON.parse(text) as unknown;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
51
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function strField(r: Record<string, unknown>, key: string): string {
|
|
55
|
-
const v = r[key];
|
|
56
|
-
return typeof v === "string" ? v : "";
|
|
57
|
-
}
|
|
23
|
+
const esGet = createJsonGetter({
|
|
24
|
+
base: () => requiredBaseUrl("ELASTICSEARCH_URL"),
|
|
25
|
+
label: "Elasticsearch",
|
|
26
|
+
headers: envAuthHeaders({ env: "ELASTICSEARCH_API_KEY", scheme: "ApiKey" }),
|
|
27
|
+
});
|
|
58
28
|
|
|
59
29
|
function indexEntries(parsed: unknown): unknown[] {
|
|
60
30
|
return Array.isArray(parsed) ? parsed : [];
|