@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.
Files changed (215) hide show
  1. package/README.md +14 -4
  2. package/connectors/airflow/src/search-filter.ts +2 -15
  3. package/connectors/airflow/src/server.ts +2 -87
  4. package/connectors/airflow/src/tools.ts +74 -0
  5. package/connectors/apple/src/adapters.ts +326 -0
  6. package/connectors/apple/src/apple-mail-core.ts +4 -8
  7. package/connectors/apple/src/calendar-tools.ts +28 -8
  8. package/connectors/apple/src/server.ts +23 -512
  9. package/connectors/apple/src/tools.ts +18 -44
  10. package/connectors/argocd/src/server.ts +25 -38
  11. package/connectors/athena/src/tools.ts +15 -34
  12. package/connectors/aws/src/server.ts +2 -183
  13. package/connectors/aws/src/tools.ts +197 -0
  14. package/connectors/azure/src/server.ts +2 -131
  15. package/connectors/azure/src/tools.ts +133 -0
  16. package/connectors/bigeye/src/search-filter.ts +4 -15
  17. package/connectors/bigeye/src/server.ts +38 -38
  18. package/connectors/bigquery/src/tools.ts +1 -26
  19. package/connectors/bitbucket/src/server.ts +2 -244
  20. package/connectors/bitbucket/src/tools.ts +241 -0
  21. package/connectors/bitrise/src/server.ts +2 -79
  22. package/connectors/bitrise/src/tools.ts +76 -0
  23. package/connectors/canva/src/search-filter.ts +2 -11
  24. package/connectors/canva/src/server.ts +2 -73
  25. package/connectors/canva/src/tools.ts +66 -0
  26. package/connectors/circleci/src/server.ts +2 -176
  27. package/connectors/circleci/src/tools.ts +193 -0
  28. package/connectors/cloud-logging/src/tools.ts +14 -27
  29. package/connectors/cloudwatch/src/tools.ts +15 -34
  30. package/connectors/codemagic/src/server.ts +2 -70
  31. package/connectors/codemagic/src/tools.ts +71 -0
  32. package/connectors/confluence/src/server.ts +2 -301
  33. package/connectors/confluence/src/tools.ts +320 -0
  34. package/connectors/dagster/src/server.ts +2 -214
  35. package/connectors/dagster/src/tools.ts +187 -0
  36. package/connectors/databricks/src/search-filter.ts +7 -7
  37. package/connectors/databricks/src/server.ts +2 -77
  38. package/connectors/databricks/src/tools.ts +66 -0
  39. package/connectors/datadog/src/server.ts +2 -48
  40. package/connectors/datadog/src/tools.ts +48 -0
  41. package/connectors/dataprofile/src/profile.ts +16 -46
  42. package/connectors/dbt/src/server.ts +2 -92
  43. package/connectors/dbt/src/tools.ts +80 -0
  44. package/connectors/dependencytrack/src/server.ts +2 -76
  45. package/connectors/dependencytrack/src/tools.ts +65 -0
  46. package/connectors/discord/src/server.ts +2 -85
  47. package/connectors/discord/src/tools.ts +82 -0
  48. package/connectors/elasticsearch/src/tools.ts +7 -37
  49. package/connectors/fastmail/src/jmap-client.ts +202 -0
  50. package/connectors/fastmail/src/jmap-core.ts +8 -21
  51. package/connectors/fastmail/src/server.ts +2 -187
  52. package/connectors/fastmail/src/tools.ts +4 -23
  53. package/connectors/figma/src/search-filter.ts +2 -11
  54. package/connectors/figma/src/server.ts +2 -113
  55. package/connectors/figma/src/tools.ts +99 -0
  56. package/connectors/firebase/src/server.ts +2 -90
  57. package/connectors/firebase/src/token.ts +2 -5
  58. package/connectors/firebase/src/tools.ts +106 -0
  59. package/connectors/flagsmith/src/server.ts +2 -98
  60. package/connectors/flagsmith/src/tools.ts +95 -0
  61. package/connectors/flux/src/server.ts +52 -53
  62. package/connectors/gcp/src/server.ts +2 -132
  63. package/connectors/gcp/src/tools.ts +123 -0
  64. package/connectors/github/src/server.ts +2 -328
  65. package/connectors/github/src/tools.ts +311 -0
  66. package/connectors/github-actions/src/server.ts +2 -205
  67. package/connectors/github-actions/src/tools.ts +209 -0
  68. package/connectors/gitlab/src/server.ts +2 -384
  69. package/connectors/gitlab/src/tools.ts +392 -0
  70. package/connectors/gmail/src/server.ts +2 -270
  71. package/connectors/gmail/src/tools.ts +209 -0
  72. package/connectors/google-drive/src/server.ts +2 -524
  73. package/connectors/google-drive/src/tools.ts +529 -0
  74. package/connectors/google-meet/src/server.ts +2 -82
  75. package/connectors/google-meet/src/tools.ts +87 -0
  76. package/connectors/google-photos/src/server.ts +2 -132
  77. package/connectors/google-photos/src/tools.ts +138 -0
  78. package/connectors/grafana/src/server.ts +2 -53
  79. package/connectors/grafana/src/tools.ts +53 -0
  80. package/connectors/great-expectations/src/gx-parse.ts +14 -45
  81. package/connectors/greenhouse/src/server.ts +2 -63
  82. package/connectors/greenhouse/src/tools.ts +68 -0
  83. package/connectors/hubspot/src/server.ts +2 -77
  84. package/connectors/hubspot/src/tools.ts +70 -0
  85. package/connectors/iac/src/server.ts +2 -137
  86. package/connectors/iac/src/tools.ts +152 -0
  87. package/connectors/imap/src/imap-core.ts +29 -82
  88. package/connectors/imap/src/server.ts +33 -220
  89. package/connectors/intercom/src/server.ts +2 -61
  90. package/connectors/intercom/src/tools.ts +58 -0
  91. package/connectors/jenkins/src/jenkins-api.ts +2 -6
  92. package/connectors/jenkins/src/server.ts +2 -215
  93. package/connectors/jenkins/src/tools.ts +240 -0
  94. package/connectors/jira/src/server.ts +2 -301
  95. package/connectors/jira/src/tools.ts +325 -0
  96. package/connectors/kubernetes/src/server.ts +2 -155
  97. package/connectors/kubernetes/src/tools.ts +159 -0
  98. package/connectors/launchdarkly/src/server.ts +2 -75
  99. package/connectors/launchdarkly/src/tools.ts +72 -0
  100. package/connectors/lever/src/search-filter.ts +4 -12
  101. package/connectors/lever/src/server.ts +2 -60
  102. package/connectors/lever/src/tools.ts +61 -0
  103. package/connectors/linear/src/server.ts +2 -459
  104. package/connectors/linear/src/tools.ts +482 -0
  105. package/connectors/localdb/src/sql-scan.ts +13 -41
  106. package/connectors/localdb/src/tools.ts +1 -1
  107. package/connectors/looker/src/search-filter.ts +2 -15
  108. package/connectors/looker/src/server.ts +35 -70
  109. package/connectors/mendeley/src/server.ts +2 -55
  110. package/connectors/mendeley/src/tools.ts +54 -0
  111. package/connectors/mercury/src/server.ts +2 -57
  112. package/connectors/mercury/src/tools.ts +50 -0
  113. package/connectors/metabase/src/search-filter.ts +4 -15
  114. package/connectors/metabase/src/server.ts +2 -79
  115. package/connectors/metabase/src/tools.ts +64 -0
  116. package/connectors/miro/src/server.ts +2 -72
  117. package/connectors/miro/src/tools.ts +65 -0
  118. package/connectors/mlflow/src/search-filter.ts +3 -7
  119. package/connectors/mlflow/src/server.ts +20 -40
  120. package/connectors/monte-carlo/src/server.ts +50 -57
  121. package/connectors/netlify/src/search-filter.ts +6 -14
  122. package/connectors/netlify/src/server.ts +2 -60
  123. package/connectors/netlify/src/tools.ts +53 -0
  124. package/connectors/newrelic/src/server.ts +2 -40
  125. package/connectors/newrelic/src/tools.ts +37 -0
  126. package/connectors/notion/src/server.ts +2 -335
  127. package/connectors/notion/src/tools.ts +344 -0
  128. package/connectors/obsidian/src/server.ts +2 -425
  129. package/connectors/obsidian/src/tools.ts +466 -0
  130. package/connectors/onedrive/src/server.ts +2 -249
  131. package/connectors/onedrive/src/tools.ts +253 -0
  132. package/connectors/outlook/src/server.ts +2 -351
  133. package/connectors/outlook/src/tools.ts +357 -0
  134. package/connectors/pagerduty/src/server.ts +2 -132
  135. package/connectors/pagerduty/src/tools.ts +126 -0
  136. package/connectors/pipedrive/src/server.ts +2 -58
  137. package/connectors/pipedrive/src/tools.ts +63 -0
  138. package/connectors/powerbi/src/search-filter.ts +2 -15
  139. package/connectors/powerbi/src/server.ts +43 -68
  140. package/connectors/prefect/src/search-filter.ts +3 -16
  141. package/connectors/prefect/src/server.ts +2 -90
  142. package/connectors/prefect/src/tools.ts +83 -0
  143. package/connectors/protonmail/src/mail-core.ts +16 -75
  144. package/connectors/protonmail/src/server.ts +34 -214
  145. package/connectors/raindrop/src/server.ts +2 -103
  146. package/connectors/raindrop/src/tools.ts +103 -0
  147. package/connectors/ramp/src/server.ts +2 -105
  148. package/connectors/ramp/src/tools.ts +98 -0
  149. package/connectors/readwise/src/server.ts +2 -88
  150. package/connectors/readwise/src/tools.ts +81 -0
  151. package/connectors/sagemaker/src/tools.ts +19 -38
  152. package/connectors/salesforce/src/search-filter.ts +4 -11
  153. package/connectors/salesforce/src/server.ts +2 -90
  154. package/connectors/salesforce/src/tools.ts +75 -0
  155. package/connectors/semgrep/src/search-filter.ts +3 -10
  156. package/connectors/semgrep/src/server.ts +2 -104
  157. package/connectors/semgrep/src/tools.ts +92 -0
  158. package/connectors/sentry/src/server.ts +2 -58
  159. package/connectors/sentry/src/tools.ts +53 -0
  160. package/connectors/slack/src/server.ts +2 -352
  161. package/connectors/slack/src/tools.ts +376 -0
  162. package/connectors/snowflake/src/search-filter.ts +4 -19
  163. package/connectors/snowflake/src/server.ts +6 -25
  164. package/connectors/snyk/src/server.ts +2 -140
  165. package/connectors/snyk/src/tools.ts +136 -0
  166. package/connectors/sonarqube/src/server.ts +2 -111
  167. package/connectors/sonarqube/src/tools.ts +98 -0
  168. package/connectors/stackoverflow/src/server.ts +2 -77
  169. package/connectors/stackoverflow/src/tools.ts +66 -0
  170. package/connectors/storybook/src/storybook-parse.ts +3 -6
  171. package/connectors/stripe/src/server.ts +2 -61
  172. package/connectors/stripe/src/tools.ts +54 -0
  173. package/connectors/superset/src/search-filter.ts +4 -15
  174. package/connectors/superset/src/server.ts +2 -115
  175. package/connectors/superset/src/tools.ts +98 -0
  176. package/connectors/tableau/src/search-filter.ts +2 -15
  177. package/connectors/tableau/src/server.ts +13 -65
  178. package/connectors/teams/src/server.ts +2 -249
  179. package/connectors/teams/src/tools.ts +283 -0
  180. package/connectors/testflight/src/server.ts +2 -70
  181. package/connectors/testflight/src/tools.ts +79 -0
  182. package/connectors/vercel/src/search-filter.ts +2 -10
  183. package/connectors/vercel/src/server.ts +2 -75
  184. package/connectors/vercel/src/tools.ts +73 -0
  185. package/connectors/vertex-ai/src/tools.ts +23 -33
  186. package/connectors/wiz/src/search-filter.ts +6 -30
  187. package/connectors/wiz/src/server.ts +2 -188
  188. package/connectors/wiz/src/tools.ts +168 -0
  189. package/connectors/zendesk/src/server.ts +2 -73
  190. package/connectors/zendesk/src/tools.ts +60 -0
  191. package/connectors/zoom/src/search-filter.ts +1 -7
  192. package/connectors/zoom/src/server.ts +2 -94
  193. package/connectors/zoom/src/tools.ts +93 -0
  194. package/connectors/zotero/src/server.ts +2 -78
  195. package/connectors/zotero/src/tools.ts +67 -0
  196. package/package.json +14 -12
  197. package/shared/access-token-cache.ts +123 -0
  198. package/shared/atlassian-json-fetch.ts +6 -16
  199. package/shared/audit-chain.ts +13 -6
  200. package/shared/cli-json-kit.ts +150 -0
  201. package/shared/collection-tool-kit.ts +143 -0
  202. package/shared/connector-mode.ts +3 -2
  203. package/shared/cursor-list-tool.ts +29 -0
  204. package/shared/env-json-api.ts +209 -0
  205. package/shared/fetch-json-text.ts +27 -0
  206. package/shared/github-rest.ts +62 -0
  207. package/shared/graphql-json.ts +60 -0
  208. package/shared/imap-mail-core.ts +91 -0
  209. package/shared/imap-tool-kit.ts +57 -23
  210. package/shared/imapflow-adapter.ts +417 -0
  211. package/shared/rest-tool-kit.ts +77 -7
  212. package/shared/run-cli-json.ts +38 -16
  213. package/shared/search-filter.ts +58 -18
  214. package/shared/walk-files.ts +118 -0
  215. package/standalone/src/launcher.ts +19 -7
@@ -1,19 +1,22 @@
1
1
  import { z } from "zod";
2
+ import {
3
+ cliArg,
4
+ createCliJsonRunner,
5
+ gcloudProjectArgs,
6
+ isRecord,
7
+ strField,
8
+ } from "../../../shared/cli-json-kit.ts";
2
9
  import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
3
10
  import { nimbusSpawn } from "../../../shared/nimbus-spawn.ts";
4
11
  import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
5
12
  import { isSafeCliArg } from "../../../shared/safe-cli-arg.ts";
6
13
 
7
14
  /**
8
- * A value passed to the `gcloud ai` CLI (a Vertex AI region like `us-central1`
9
- * or a model id). Rejected at the schema boundary if it begins with `-` (argv
10
- * flag smuggling) or contains control characters. Exported so the contract test
11
- * can assert the guard rejects a `-`-prefixed value.
15
+ * The argv-injection guard every value passed to the `gcloud ai` CLI (a Vertex AI region like
16
+ * `us-central1`, or a model id) goes through. Re-exported so this connector's own test can assert
17
+ * the guard rejects a `-`-prefixed value.
12
18
  */
13
- export const cliArg = z
14
- .string()
15
- .min(1)
16
- .refine(isSafeCliArg, { message: 'must not start with "-" or contain control characters' });
19
+ export { cliArg };
17
20
 
18
21
  /**
19
22
  * GCP Vertex AI (Tier-3, no-row-data) MCP tool surface. ALL tools index Vertex
@@ -32,25 +35,10 @@ export const VERTEX_AI_TOOL_NAMES = [
32
35
 
33
36
  const DEFAULT_REGION = "us-central1";
34
37
 
35
- function isRecord(v: unknown): v is Record<string, unknown> {
36
- return v !== null && typeof v === "object" && !Array.isArray(v);
37
- }
38
-
39
- function strField(r: Record<string, unknown>, key: string): string {
40
- const v = r[key];
41
- return typeof v === "string" ? v : "";
42
- }
43
-
44
38
  function asArray(parsed: unknown): unknown[] {
45
39
  return Array.isArray(parsed) ? parsed : [];
46
40
  }
47
41
 
48
- /** The configured GCP project id, if `GOOGLE_CLOUD_PROJECT` is set at spawn. */
49
- function projectArgs(): string[] {
50
- const project = process.env["GOOGLE_CLOUD_PROJECT"]?.trim();
51
- return project !== undefined && project !== "" ? ["--project", project] : [];
52
- }
53
-
54
42
  /**
55
43
  * Resolve the Vertex AI region: an explicit tool argument wins, else the
56
44
  * `VERTEX_AI_REGION` env injected at spawn, else `us-central1`. The result is a
@@ -69,21 +57,23 @@ function resolveRegion(explicit?: string): string {
69
57
  * spawn time. The `<region>` is `isSafeCliArg`-guarded before use (argv flag
70
58
  * smuggling). Throws on non-zero exit.
71
59
  */
60
+ const runGcloudAi = createCliJsonRunner(
61
+ {
62
+ // The region has already been guarded by the caller below; everything else
63
+ // is a literal or a schema-validated argument.
64
+ argv: (args) => ["gcloud", "ai", ...args],
65
+ label: "gcloud ai",
66
+ emptyResult: [],
67
+ },
68
+ nimbusSpawn,
69
+ );
70
+
72
71
  async function gcloudAi(args: string[], region: string): Promise<unknown> {
73
72
  const regionPreview = region.slice(0, 64);
74
73
  if (!isSafeCliArg(region)) {
75
74
  throw new Error(`Invalid region: ${regionPreview}`);
76
75
  }
77
- const { code, stdout, stderr } = await nimbusSpawn(
78
- ["gcloud", "ai", ...args, "--region", region, ...projectArgs(), "--format", "json"],
79
- {},
80
- );
81
- const out = stdout.trim();
82
- const err = stderr.trim();
83
- if (code !== 0) {
84
- throw new Error(`gcloud ai ${args[0] ?? ""} failed: ${err.slice(0, 400)}`);
85
- }
86
- return out === "" ? [] : (JSON.parse(out) as unknown);
76
+ return runGcloudAi([...args, "--region", region, ...gcloudProjectArgs(), "--format", "json"]);
87
77
  }
88
78
 
89
79
  function modelMatches(entry: unknown, q: string): boolean {
@@ -1,49 +1,25 @@
1
1
  import {
2
2
  asObjectish,
3
3
  makeQueryFilter,
4
+ nestedString,
5
+ objectNamesText,
4
6
  type SearchMatchOptions,
5
7
  stringField,
6
8
  } from "../../../shared/search-filter.ts";
7
9
 
8
10
  export type WizSearchMatchOptions = SearchMatchOptions;
9
11
 
10
- function nestedString(row: Record<string, unknown>, parent: string, key: string): string {
11
- const p = row[parent];
12
- if (p === null || typeof p !== "object") {
13
- return "";
14
- }
15
- return stringField(p as Record<string, unknown>, key);
16
- }
17
-
18
- function projectNames(row: Record<string, unknown>): string {
19
- const projects = row["projects"];
20
- if (!Array.isArray(projects)) {
21
- return "";
22
- }
23
- const names: string[] = [];
24
- for (const p of projects) {
25
- if (p === null || typeof p !== "object") {
26
- continue;
27
- }
28
- const name = stringField(p as Record<string, unknown>, "name");
29
- if (name !== "") {
30
- names.push(name);
31
- }
32
- }
33
- return names.join(" ");
34
- }
35
-
36
12
  function fieldsOf(item: unknown): readonly string[] | null {
37
13
  const row = asObjectish(item);
38
14
  if (row === undefined) {
39
15
  return null;
40
16
  }
41
17
  return [
42
- nestedString(row, "sourceRule", "name"),
18
+ nestedString(row, ["sourceRule", "name"]),
43
19
  stringField(row, "description"),
44
- nestedString(row, "entity", "name"),
45
- nestedString(row, "entity", "type"),
46
- projectNames(row),
20
+ nestedString(row, ["entity", "name"]),
21
+ nestedString(row, ["entity", "type"]),
22
+ objectNamesText(row, "projects"),
47
23
  ];
48
24
  }
49
25
 
@@ -1,192 +1,6 @@
1
- import { z } from "zod";
2
-
3
- import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
4
- import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
5
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
6
- import { filterWizIssues } from "./search-filter.ts";
7
-
8
- const DEFAULT_API = "https://api.app.wiz.io/graphql";
9
- const DEFAULT_AUTH = "https://auth.app.wiz.io/oauth/token";
10
- const SEVERITIES = ["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
11
- const STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] as const;
12
-
13
- function apiUrl(): string {
14
- const v = process.env["WIZ_API_URL"]?.trim();
15
- return v === undefined || v === "" ? DEFAULT_API : v;
16
- }
17
-
18
- function authUrl(): string {
19
- const v = process.env["WIZ_AUTH_URL"]?.trim();
20
- return v === undefined || v === "" ? DEFAULT_AUTH : v;
21
- }
22
-
23
- async function fetchAccessToken(): Promise<string> {
24
- const clientId = process.env["WIZ_CLIENT_ID"]?.trim();
25
- const clientSecret = process.env["WIZ_CLIENT_SECRET"]?.trim();
26
- if (clientId === undefined || clientId === "") {
27
- throw new Error("WIZ_CLIENT_ID is not set");
28
- }
29
- if (clientSecret === undefined || clientSecret === "") {
30
- throw new Error("WIZ_CLIENT_SECRET is not set");
31
- }
32
- const body = new URLSearchParams({
33
- grant_type: "client_credentials",
34
- client_id: clientId,
35
- client_secret: clientSecret,
36
- audience: "wiz-api",
37
- });
38
- const res = await fetch(authUrl(), {
39
- method: "POST",
40
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
41
- body: body.toString(),
42
- });
43
- const text = await res.text();
44
- if (!res.ok) {
45
- throw new Error(`Wiz auth ${String(res.status)}: ${text.slice(0, 400)}`);
46
- }
47
- const parsed = JSON.parse(text) as { access_token?: string };
48
- if (typeof parsed.access_token !== "string" || parsed.access_token === "") {
49
- throw new Error("Wiz auth: missing access_token in response");
50
- }
51
- return parsed.access_token;
52
- }
53
-
54
- let cachedToken: string | undefined;
55
- async function getToken(): Promise<string> {
56
- cachedToken ??= await fetchAccessToken();
57
- return cachedToken;
58
- }
59
-
60
- async function wizGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
61
- const token = await getToken();
62
- const res = await fetch(apiUrl(), {
63
- method: "POST",
64
- headers: {
65
- Authorization: `Bearer ${token}`,
66
- "Content-Type": "application/json",
67
- Accept: "application/json",
68
- },
69
- body: JSON.stringify({ query, variables }),
70
- });
71
- const text = await res.text();
72
- if (!res.ok) {
73
- throw new Error(`Wiz ${String(res.status)}: ${text.slice(0, 400)}`);
74
- }
75
- const parsed = JSON.parse(text) as { data?: T; errors?: unknown };
76
- if (parsed.errors !== undefined) {
77
- throw new Error(`Wiz GraphQL error: ${JSON.stringify(parsed.errors).slice(0, 400)}`);
78
- }
79
- if (parsed.data === undefined) {
80
- throw new Error("Wiz GraphQL: response missing `data` field");
81
- }
82
- return parsed.data;
83
- }
84
-
85
- const ISSUES_QUERY = `
86
- query Issues($first: Int!, $after: String, $filterBy: IssueFilters) {
87
- issues(first: $first, after: $after, filterBy: $filterBy) {
88
- nodes {
89
- id
90
- sourceRule { id name }
91
- severity
92
- status
93
- type
94
- createdAt
95
- updatedAt
96
- resolvedAt
97
- description
98
- remediation
99
- entity { id name type }
100
- projects { id name slug }
101
- }
102
- pageInfo { hasNextPage endCursor }
103
- }
104
- }
105
- `;
106
-
107
- const SINGLE_ISSUE_QUERY = `
108
- query Issue($id: ID!) {
109
- issue(id: $id) {
110
- id
111
- sourceRule { id name }
112
- severity
113
- status
114
- type
115
- createdAt
116
- updatedAt
117
- resolvedAt
118
- description
119
- remediation
120
- entity { id name type }
121
- projects { id name slug }
122
- }
123
- }
124
- `;
125
-
126
- interface IssuesPage {
127
- readonly issues: {
128
- readonly nodes: readonly unknown[];
129
- readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
130
- };
131
- }
2
+ import { registerWizTools } from "./tools.ts";
132
3
 
133
4
  await runReadOnlyMcpConnector("nimbus-wiz", (reg) => {
134
- reg(
135
- "wiz_list",
136
- "List Wiz issues. Filters map to the `IssueFilters` GraphQL input — severity, status, and entity type are all optional. Defaults to open issues across all severities, capped at 100.",
137
- z.object({
138
- severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
139
- status: z.array(z.enum(STATUSES)).min(1).optional(),
140
- entityType: z.string().min(1).optional(),
141
- limit: z.number().int().min(1).max(500).optional(),
142
- }),
143
- async (p) => {
144
- const filterBy: Record<string, unknown> = {};
145
- filterBy["status"] = p.status ?? ["OPEN"];
146
- if (p.severity !== undefined && p.severity.length > 0) {
147
- filterBy["severity"] = p.severity;
148
- }
149
- if (p.entityType !== undefined) {
150
- filterBy["entityType"] = p.entityType;
151
- }
152
- const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
153
- first: p.limit ?? 100,
154
- after: null,
155
- filterBy,
156
- });
157
- return jsonResult(data.issues);
158
- },
159
- );
160
-
161
- reg(
162
- "wiz_get",
163
- "Fetch one Wiz issue by its id. Throws when no match is found.",
164
- z.object({
165
- issueId: z.string().min(1),
166
- }),
167
- async (p) => {
168
- const data = await wizGraphql<{ issue: unknown }>(SINGLE_ISSUE_QUERY, {
169
- id: p.issueId,
170
- });
171
- if (data.issue === null) {
172
- throw new Error(`Wiz: issue ${p.issueId} not found`);
173
- }
174
- return jsonResult(data.issue);
175
- },
176
- );
177
-
178
- reg(
179
- "wiz_search",
180
- "Substring search across open issues. Matches the query against `sourceRule.name`, `description`, `entity.name`, `entity.type`, and project names (case-insensitive). Returns a `{ matches: [...] }` envelope.",
181
- searchToolInputSchema(200),
182
- async (p) => {
183
- const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
184
- first: 500,
185
- after: null,
186
- filterBy: { status: ["OPEN"] },
187
- });
188
- const matches = filterWizIssues(data.issues.nodes, { query: p.query, limit: p.limit });
189
- return jsonResult({ matches });
190
- },
191
- );
5
+ registerWizTools(reg);
192
6
  });
@@ -0,0 +1,168 @@
1
+ import { z } from "zod";
2
+ import { createAccessTokenCache } from "../../../shared/access-token-cache.ts";
3
+ import { optionalEnv, requiredEnv } from "../../../shared/env-json-api.ts";
4
+ import { postGraphql } from "../../../shared/graphql-json.ts";
5
+ import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
6
+ import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
7
+ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
8
+ import { filterWizIssues } from "./search-filter.ts";
9
+
10
+ const DEFAULT_API = "https://api.app.wiz.io/graphql";
11
+ const DEFAULT_AUTH = "https://auth.app.wiz.io/oauth/token";
12
+ const SEVERITIES = ["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
13
+ const STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] as const;
14
+
15
+ function apiUrl(): string {
16
+ return optionalEnv("WIZ_API_URL", DEFAULT_API);
17
+ }
18
+
19
+ function authUrl(): string {
20
+ return optionalEnv("WIZ_AUTH_URL", DEFAULT_AUTH);
21
+ }
22
+
23
+ /** OAuth2 client-credentials against the Wiz auth endpoint. */
24
+ const getToken = createAccessTokenCache({
25
+ label: "Wiz auth",
26
+ exchange: async () => {
27
+ const body = new URLSearchParams({
28
+ grant_type: "client_credentials",
29
+ client_id: requiredEnv("WIZ_CLIENT_ID"),
30
+ client_secret: requiredEnv("WIZ_CLIENT_SECRET"),
31
+ audience: "wiz-api",
32
+ });
33
+ const res = await fetch(authUrl(), {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
36
+ body: body.toString(),
37
+ });
38
+ return { ok: res.ok, status: res.status, text: await res.text() };
39
+ },
40
+ });
41
+
42
+ /** Forget the exchanged token. Tests only: the cache otherwise outlives the test that filled it. */
43
+ export function __resetWizTokenForTests(): void {
44
+ getToken.clear();
45
+ }
46
+
47
+ async function wizGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
48
+ const token = await getToken();
49
+ return postGraphql<T>({
50
+ url: apiUrl(),
51
+ label: "Wiz",
52
+ headers: { Authorization: `Bearer ${token}` },
53
+ query,
54
+ variables,
55
+ });
56
+ }
57
+
58
+ const ISSUES_QUERY = `
59
+ query Issues($first: Int!, $after: String, $filterBy: IssueFilters) {
60
+ issues(first: $first, after: $after, filterBy: $filterBy) {
61
+ nodes {
62
+ id
63
+ sourceRule { id name }
64
+ severity
65
+ status
66
+ type
67
+ createdAt
68
+ updatedAt
69
+ resolvedAt
70
+ description
71
+ remediation
72
+ entity { id name type }
73
+ projects { id name slug }
74
+ }
75
+ pageInfo { hasNextPage endCursor }
76
+ }
77
+ }
78
+ `;
79
+
80
+ const SINGLE_ISSUE_QUERY = `
81
+ query Issue($id: ID!) {
82
+ issue(id: $id) {
83
+ id
84
+ sourceRule { id name }
85
+ severity
86
+ status
87
+ type
88
+ createdAt
89
+ updatedAt
90
+ resolvedAt
91
+ description
92
+ remediation
93
+ entity { id name type }
94
+ projects { id name slug }
95
+ }
96
+ }
97
+ `;
98
+
99
+ interface IssuesPage {
100
+ readonly issues: {
101
+ readonly nodes: readonly unknown[];
102
+ readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
103
+ };
104
+ }
105
+
106
+ /** Tool names exposed by this connector — for contract/introspection tests. */
107
+ export const WIZ_TOOL_NAMES = ["wiz_get", "wiz_list", "wiz_search"] as const;
108
+
109
+ export function registerWizTools(reg: ZodToolRegistrar): void {
110
+ reg(
111
+ "wiz_list",
112
+ "List Wiz issues. Filters map to the `IssueFilters` GraphQL input — severity, status, and entity type are all optional. Defaults to open issues across all severities, capped at 100.",
113
+ z.object({
114
+ severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
115
+ status: z.array(z.enum(STATUSES)).min(1).optional(),
116
+ entityType: z.string().min(1).optional(),
117
+ limit: z.number().int().min(1).max(500).optional(),
118
+ }),
119
+ async (p) => {
120
+ const filterBy: Record<string, unknown> = {};
121
+ filterBy["status"] = p.status ?? ["OPEN"];
122
+ if (p.severity !== undefined && p.severity.length > 0) {
123
+ filterBy["severity"] = p.severity;
124
+ }
125
+ if (p.entityType !== undefined) {
126
+ filterBy["entityType"] = p.entityType;
127
+ }
128
+ const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
129
+ first: p.limit ?? 100,
130
+ after: null,
131
+ filterBy,
132
+ });
133
+ return jsonResult(data.issues);
134
+ },
135
+ );
136
+
137
+ reg(
138
+ "wiz_get",
139
+ "Fetch one Wiz issue by its id. Throws when no match is found.",
140
+ z.object({
141
+ issueId: z.string().min(1),
142
+ }),
143
+ async (p) => {
144
+ const data = await wizGraphql<{ issue: unknown }>(SINGLE_ISSUE_QUERY, {
145
+ id: p.issueId,
146
+ });
147
+ if (data.issue === null) {
148
+ throw new Error(`Wiz: issue ${p.issueId} not found`);
149
+ }
150
+ return jsonResult(data.issue);
151
+ },
152
+ );
153
+
154
+ reg(
155
+ "wiz_search",
156
+ "Substring search across open issues. Matches the query against `sourceRule.name`, `description`, `entity.name`, `entity.type`, and project names (case-insensitive). Returns a `{ matches: [...] }` envelope.",
157
+ searchToolInputSchema(200),
158
+ async (p) => {
159
+ const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
160
+ first: 500,
161
+ after: null,
162
+ filterBy: { status: ["OPEN"] },
163
+ });
164
+ const matches = filterWizIssues(data.issues.nodes, { query: p.query, limit: p.limit });
165
+ return jsonResult({ matches });
166
+ },
167
+ );
168
+ }
@@ -1,77 +1,6 @@
1
- import { z } from "zod";
2
- import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
3
- import {
4
- encodeBasicAuthHeader,
5
- mcpJsonResult as jsonResult,
6
- } from "../../../shared/mcp-tool-kit.ts";
7
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
8
- import { filterZendeskTickets } from "./search-filter.ts";
9
-
10
- function trimTrailingSlash(s: string): string {
11
- return s.endsWith("/") ? s.slice(0, -1) : s;
12
- }
13
-
14
- function baseUrl(): string {
15
- const v = process.env["ZENDESK_URL"]?.trim();
16
- if (v === undefined || v === "") {
17
- throw new Error("ZENDESK_URL is not set");
18
- }
19
- return trimTrailingSlash(v);
20
- }
21
-
22
- function authHeader(): Record<string, string> {
23
- const email = process.env["ZENDESK_EMAIL"]?.trim();
24
- if (email === undefined || email === "") {
25
- throw new Error("ZENDESK_EMAIL is not set");
26
- }
27
- const token = process.env["ZENDESK_API_TOKEN"]?.trim();
28
- if (token === undefined || token === "") {
29
- throw new Error("ZENDESK_API_TOKEN is not set");
30
- }
31
- return {
32
- Authorization: encodeBasicAuthHeader(`${email}/token`, token),
33
- Accept: "application/json",
34
- };
35
- }
36
-
37
- async function zendeskGet(path: string): Promise<unknown> {
38
- const res = await fetch(`${baseUrl()}${path}`, { headers: authHeader() });
39
- const text = await res.text();
40
- if (!res.ok) {
41
- throw new Error(`Zendesk ${String(res.status)}: ${text.slice(0, 400)}`);
42
- }
43
- return JSON.parse(text) as unknown;
44
- }
2
+ import { registerZendeskTools } from "./tools.ts";
45
3
 
46
4
  await runReadOnlyMcpConnector("nimbus-zendesk", (reg) => {
47
- reg(
48
- "zendesk_list",
49
- "List the user's Zendesk tickets (`GET /api/v2/tickets.json?page[size]=100`). Returns the cursor-pagination envelope `{ tickets: [...], meta: { has_more, after_cursor }, links: { next } }` — `tickets` holds the ticket objects.",
50
- z.object({}),
51
- async () => {
52
- return jsonResult(await zendeskGet(`/api/v2/tickets.json?page[size]=100`));
53
- },
54
- );
55
-
56
- reg(
57
- "zendesk_get",
58
- "Fetch one Zendesk ticket by its numeric id (`GET /api/v2/tickets/{id}.json`). Returns the `{ ticket: {...} }` envelope. Throws when no match is found.",
59
- z.object({
60
- id: z.string().min(1),
61
- }),
62
- async (p) => {
63
- return jsonResult(await zendeskGet(`/api/v2/tickets/${encodeURIComponent(p.id)}.json`));
64
- },
65
- );
66
-
67
- reg(
68
- "zendesk_search",
69
- "Substring search across the user's Zendesk tickets (first page only). Matches the query against the ticket subject, description, status, priority, type, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
70
- searchToolInputSchema(100),
71
- async (p) => {
72
- const root = await zendeskGet(`/api/v2/tickets.json?page[size]=100`);
73
- const tickets = (root as { tickets?: unknown[] } | null)?.tickets;
74
- return matchesResult(tickets, filterZendeskTickets, p);
75
- },
76
- );
5
+ registerZendeskTools(reg);
77
6
  });
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { createJsonGetter, requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
3
+ import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
4
+ import {
5
+ encodeBasicAuthHeader,
6
+ mcpJsonResult as jsonResult,
7
+ } from "../../../shared/mcp-tool-kit.ts";
8
+ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
9
+ import { filterZendeskTickets } from "./search-filter.ts";
10
+
11
+ function authHeader(): Record<string, string> {
12
+ const email = requiredEnv("ZENDESK_EMAIL");
13
+ const token = requiredEnv("ZENDESK_API_TOKEN");
14
+ return {
15
+ Authorization: encodeBasicAuthHeader(`${email}/token`, token),
16
+ Accept: "application/json",
17
+ };
18
+ }
19
+
20
+ const zendeskGet = createJsonGetter({
21
+ base: () => requiredBaseUrl("ZENDESK_URL"),
22
+ label: "Zendesk",
23
+ headers: authHeader,
24
+ });
25
+
26
+ /** Tool names exposed by this connector — for contract/introspection tests. */
27
+ export const ZENDESK_TOOL_NAMES = ["zendesk_get", "zendesk_list", "zendesk_search"] as const;
28
+
29
+ export function registerZendeskTools(reg: ZodToolRegistrar): void {
30
+ reg(
31
+ "zendesk_list",
32
+ "List the user's Zendesk tickets (`GET /api/v2/tickets.json?page[size]=100`). Returns the cursor-pagination envelope `{ tickets: [...], meta: { has_more, after_cursor }, links: { next } }` — `tickets` holds the ticket objects.",
33
+ z.object({}),
34
+ async () => {
35
+ return jsonResult(await zendeskGet(`/api/v2/tickets.json?page[size]=100`));
36
+ },
37
+ );
38
+
39
+ reg(
40
+ "zendesk_get",
41
+ "Fetch one Zendesk ticket by its numeric id (`GET /api/v2/tickets/{id}.json`). Returns the `{ ticket: {...} }` envelope. Throws when no match is found.",
42
+ z.object({
43
+ id: z.string().min(1),
44
+ }),
45
+ async (p) => {
46
+ return jsonResult(await zendeskGet(`/api/v2/tickets/${encodeURIComponent(p.id)}.json`));
47
+ },
48
+ );
49
+
50
+ reg(
51
+ "zendesk_search",
52
+ "Substring search across the user's Zendesk tickets (first page only). Matches the query against the ticket subject, description, status, priority, type, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
53
+ searchToolInputSchema(100),
54
+ async (p) => {
55
+ const root = await zendeskGet(`/api/v2/tickets.json?page[size]=100`);
56
+ const tickets = (root as { tickets?: unknown[] } | null)?.tickets;
57
+ return matchesResult(tickets, filterZendeskTickets, p);
58
+ },
59
+ );
60
+ }
@@ -1,16 +1,10 @@
1
- import { stringField } from "../../../shared/search-filter.ts";
1
+ import { asRecord, stringField } from "../../../shared/search-filter.ts";
2
2
 
3
3
  export interface ZoomSearchOptions {
4
4
  readonly query: string;
5
5
  readonly limit?: number;
6
6
  }
7
7
 
8
- function asRecord(v: unknown): Record<string, unknown> | undefined {
9
- return v !== null && typeof v === "object" && !Array.isArray(v)
10
- ? (v as Record<string, unknown>)
11
- : undefined;
12
- }
13
-
14
8
  export function filterZoomMeetings(
15
9
  meetings: readonly unknown[],
16
10
  options: ZoomSearchOptions,