@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,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 { filterHubspotDeals } from "./search-filter.ts";
6
-
7
- const BASE = "https://api.hubapi.com";
8
- const DEAL_PROPERTIES =
9
- "dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate";
10
-
11
- function apiToken(): string {
12
- const t = process.env["HUBSPOT_TOKEN"]?.trim();
13
- if (t === undefined || t === "") {
14
- throw new Error("HUBSPOT_TOKEN is not set");
15
- }
16
- return t;
17
- }
18
-
19
- function authHeader(): Record<string, string> {
20
- return { Authorization: `Bearer ${apiToken()}`, Accept: "application/json" };
21
- }
22
-
23
- async function hubspotGet(path: string): Promise<unknown> {
24
- const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
25
- const text = await res.text();
26
- if (!res.ok) {
27
- throw new Error(`HubSpot ${String(res.status)}: ${text.slice(0, 400)}`);
28
- }
29
- return JSON.parse(text) as unknown;
30
- }
31
-
32
- function dealsFrom(root: unknown): unknown[] {
33
- const results = (root as { results?: unknown } | null)?.results;
34
- return Array.isArray(results) ? results : [];
35
- }
2
+ import { registerHubspotTools } from "./tools.ts";
36
3
 
37
4
  await runReadOnlyMcpConnector("nimbus-hubspot", (reg) => {
38
- reg(
39
- "hubspot_list",
40
- "List the authenticated portal's HubSpot deals (GET /crm/v3/objects/deals?limit=N&properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the { results: [...], paging?: { next?: { after } } } envelope — `results` holds the deal objects, each shaped { id, properties: { dealname, amount, dealstage, pipeline, closedate, createdate, hs_lastmodifieddate }, createdAt, updatedAt }. `limit` (default 100, max 100) caps page_size of the single first-page request.",
41
- z.object({
42
- limit: z.number().int().min(1).max(100).optional(),
43
- }),
44
- async (p) => {
45
- const params = new URLSearchParams({
46
- limit: String(p.limit ?? 100),
47
- properties: DEAL_PROPERTIES,
48
- });
49
- return jsonResult(await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`));
50
- },
51
- );
52
-
53
- reg(
54
- "hubspot_get",
55
- "Fetch one HubSpot deal by its id (GET /crm/v3/objects/deals/{dealId}?properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the deal object directly (NOT wrapped in `results`). Throws when no deal with that id exists.",
56
- z.object({
57
- id: z.string().min(1),
58
- }),
59
- async (p) => {
60
- const params = new URLSearchParams({ properties: DEAL_PROPERTIES });
61
- return jsonResult(
62
- await hubspotGet(`/crm/v3/objects/deals/${encodeURIComponent(p.id)}?${params.toString()}`),
63
- );
64
- },
65
- );
66
-
67
- reg(
68
- "hubspot_search",
69
- "**Substring search over the FIRST PAGE only** (up to 100 most-recently-listed deals) of the authenticated portal's HubSpot deals. This tool fetches GET /crm/v3/objects/deals?limit=100 once and matches the query locally (case-insensitive) against the deal name, deal stage, and pipeline. **Deals beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
70
- searchToolInputSchema(100),
71
- async (p) => {
72
- const params = new URLSearchParams({ limit: "100", properties: DEAL_PROPERTIES });
73
- const root = await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`);
74
- const matches = filterHubspotDeals(dealsFrom(root), {
75
- query: p.query,
76
- limit: p.limit,
77
- });
78
- return jsonResult({ matches });
79
- },
80
- );
5
+ registerHubspotTools(reg);
81
6
  });
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ import { createJsonGetter, envAuthHeaders } 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 { filterHubspotDeals } from "./search-filter.ts";
7
+
8
+ const BASE = "https://api.hubapi.com";
9
+ const DEAL_PROPERTIES =
10
+ "dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate";
11
+
12
+ const hubspotGet = createJsonGetter({
13
+ base: BASE,
14
+ label: "HubSpot",
15
+ headers: envAuthHeaders({ env: "HUBSPOT_TOKEN" }),
16
+ });
17
+
18
+ function dealsFrom(root: unknown): unknown[] {
19
+ const results = (root as { results?: unknown } | null)?.results;
20
+ return Array.isArray(results) ? results : [];
21
+ }
22
+
23
+ /** Tool names exposed by this connector — for contract/introspection tests. */
24
+ export const HUBSPOT_TOOL_NAMES = ["hubspot_get", "hubspot_list", "hubspot_search"] as const;
25
+
26
+ export function registerHubspotTools(reg: ZodToolRegistrar): void {
27
+ reg(
28
+ "hubspot_list",
29
+ "List the authenticated portal's HubSpot deals (GET /crm/v3/objects/deals?limit=N&properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the { results: [...], paging?: { next?: { after } } } envelope — `results` holds the deal objects, each shaped { id, properties: { dealname, amount, dealstage, pipeline, closedate, createdate, hs_lastmodifieddate }, createdAt, updatedAt }. `limit` (default 100, max 100) caps page_size of the single first-page request.",
30
+ z.object({
31
+ limit: z.number().int().min(1).max(100).optional(),
32
+ }),
33
+ async (p) => {
34
+ const params = new URLSearchParams({
35
+ limit: String(p.limit ?? 100),
36
+ properties: DEAL_PROPERTIES,
37
+ });
38
+ return jsonResult(await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`));
39
+ },
40
+ );
41
+
42
+ reg(
43
+ "hubspot_get",
44
+ "Fetch one HubSpot deal by its id (GET /crm/v3/objects/deals/{dealId}?properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the deal object directly (NOT wrapped in `results`). Throws when no deal with that id exists.",
45
+ z.object({
46
+ id: z.string().min(1),
47
+ }),
48
+ async (p) => {
49
+ const params = new URLSearchParams({ properties: DEAL_PROPERTIES });
50
+ return jsonResult(
51
+ await hubspotGet(`/crm/v3/objects/deals/${encodeURIComponent(p.id)}?${params.toString()}`),
52
+ );
53
+ },
54
+ );
55
+
56
+ reg(
57
+ "hubspot_search",
58
+ "**Substring search over the FIRST PAGE only** (up to 100 most-recently-listed deals) of the authenticated portal's HubSpot deals. This tool fetches GET /crm/v3/objects/deals?limit=100 once and matches the query locally (case-insensitive) against the deal name, deal stage, and pipeline. **Deals beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
59
+ searchToolInputSchema(100),
60
+ async (p) => {
61
+ const params = new URLSearchParams({ limit: "100", properties: DEAL_PROPERTIES });
62
+ const root = await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`);
63
+ const matches = filterHubspotDeals(dealsFrom(root), {
64
+ query: p.query,
65
+ limit: p.limit,
66
+ });
67
+ return jsonResult({ matches });
68
+ },
69
+ );
70
+ }
@@ -1,145 +1,10 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { z } from "zod";
4
- import {
5
- createRegisterSimpleTool,
6
- createZodToolRegistrar,
7
- mcpJsonResult as jsonResult,
8
- } from "../../../shared/mcp-tool-kit.ts";
9
- import { runCliOkThrowing } from "../../../shared/run-cli-json.ts";
3
+ import { registerIacTools } from "./tools.ts";
10
4
 
11
5
  const mcp = new McpServer({ name: "nimbus-iac", version: "0.1.0" });
12
6
 
13
- import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
14
-
15
- const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
16
-
17
- /**
18
- * Every MUTATING iac tool goes through here. Outside the gateway this adds the
19
- * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
20
- * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
21
- */
22
- const registerWriteTool = createWriteToolRegistrar(mcp, {
23
- connector: "iac",
24
- scopeEnv: "NIMBUS_MCP_IAC_WRITE_SCOPE",
25
- scopeKinds: ["dir", "stack"],
26
- });
27
-
28
- const processEnv = process.env as Record<string, string | undefined>;
29
-
30
- reg(
31
- "iac_terraform_plan",
32
- "Run terraform plan in a directory.",
33
- z.object({ workingDirectory: z.string().min(1) }),
34
- async (p) => {
35
- await runCliOkThrowing(["terraform", "-chdir", p.workingDirectory, "plan", "-input=false"], {
36
- ...processEnv,
37
- });
38
- return jsonResult({ ok: true });
39
- },
40
- );
41
-
42
- registerWriteTool(
43
- "iac_terraform_apply",
44
- {
45
- mutates: "iac.terraform.apply",
46
- recoverable: true,
47
- scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
48
- },
49
- "Run terraform apply.",
50
- z.object({ workingDirectory: z.string().min(1) }),
51
- async (p) => {
52
- await runCliOkThrowing(
53
- ["terraform", "-chdir", p.workingDirectory, "apply", "-auto-approve", "-input=false"],
54
- { ...processEnv },
55
- );
56
- return jsonResult({ ok: true });
57
- },
58
- );
59
-
60
- registerWriteTool(
61
- "iac_terraform_destroy",
62
- {
63
- mutates: "iac.terraform.destroy",
64
- // Destroying infrastructure cannot be undone from its own result, and there is nothing
65
- // queryable to snapshot, so the identifying parameters ARE the pre-state: a record naming
66
- // WHICH directory was destroyed still beats silence.
67
- recoverable: false,
68
- capturePreState: (p) => Promise.resolve({ workingDirectory: p.workingDirectory }),
69
- scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
70
- },
71
- "Run terraform destroy.",
72
- z.object({ workingDirectory: z.string().min(1) }),
73
- async (p) => {
74
- await runCliOkThrowing(
75
- ["terraform", "-chdir", p.workingDirectory, "destroy", "-auto-approve", "-input=false"],
76
- { ...processEnv },
77
- );
78
- return jsonResult({ ok: true });
79
- },
80
- );
81
-
82
- registerWriteTool(
83
- "iac_cloudformation_deploy",
84
- {
85
- mutates: "iac.cloudformation.deploy",
86
- recoverable: true,
87
- scopeTargetOf: (p) => ({ kind: "stack", value: p.stackName }),
88
- },
89
- "Deploy a CloudFormation stack via AWS CLI.",
90
- z.object({
91
- stackName: z.string().min(1),
92
- templateBody: z.string().min(1),
93
- }),
94
- async (p) => {
95
- await runCliOkThrowing(
96
- [
97
- "aws",
98
- "cloudformation",
99
- "deploy",
100
- "--stack-name",
101
- p.stackName,
102
- "--template-body",
103
- p.templateBody,
104
- "--capabilities",
105
- "CAPABILITY_IAM",
106
- ],
107
- { ...processEnv },
108
- );
109
- return jsonResult({ ok: true });
110
- },
111
- );
112
-
113
- reg(
114
- "iac_pulumi_preview",
115
- "Run pulumi preview in a stack directory.",
116
- z.object({ workingDirectory: z.string().min(1) }),
117
- async (p) => {
118
- await runCliOkThrowing(
119
- ["pulumi", "preview", "--cwd", p.workingDirectory, "--non-interactive"],
120
- { ...processEnv },
121
- );
122
- return jsonResult({ ok: true });
123
- },
124
- );
125
-
126
- registerWriteTool(
127
- "iac_pulumi_up",
128
- {
129
- mutates: "iac.pulumi.up",
130
- recoverable: true,
131
- scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
132
- },
133
- "Run pulumi up.",
134
- z.object({ workingDirectory: z.string().min(1) }),
135
- async (p) => {
136
- await runCliOkThrowing(
137
- ["pulumi", "up", "--yes", "--cwd", p.workingDirectory, "--non-interactive"],
138
- { ...processEnv },
139
- );
140
- return jsonResult({ ok: true });
141
- },
142
- );
7
+ registerIacTools(mcp);
143
8
 
144
9
  const transport = new StdioServerTransport();
145
10
  await mcp.connect(transport);
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import type { ConsentServer } from "../../../shared/consent-kit.ts";
3
+ import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
4
+ import {
5
+ createRegisterSimpleTool,
6
+ createZodToolRegistrar,
7
+ mcpJsonResult as jsonResult,
8
+ } from "../../../shared/mcp-tool-kit.ts";
9
+ import { runCliOkThrowing } from "../../../shared/run-cli-json.ts";
10
+
11
+ /** Tool names exposed by this connector — for contract/introspection tests. */
12
+ export const IAC_TOOL_NAMES = [
13
+ "iac_terraform_plan",
14
+ "iac_terraform_apply",
15
+ "iac_terraform_destroy",
16
+ "iac_cloudformation_deploy",
17
+ "iac_pulumi_preview",
18
+ "iac_pulumi_up",
19
+ ] as const;
20
+
21
+ export function registerIacTools(
22
+ server: ConsentServer & { tool: (...args: never) => unknown },
23
+ ): void {
24
+ const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
25
+
26
+ /**
27
+ * Every MUTATING iac tool goes through here. Outside the gateway this adds the
28
+ * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
29
+ * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
30
+ */
31
+ const registerWriteTool = createWriteToolRegistrar(server, {
32
+ connector: "iac",
33
+ scopeEnv: "NIMBUS_MCP_IAC_WRITE_SCOPE",
34
+ scopeKinds: ["dir", "stack"],
35
+ });
36
+
37
+ const processEnv = process.env as Record<string, string | undefined>;
38
+
39
+ reg(
40
+ "iac_terraform_plan",
41
+ "Run terraform plan in a directory.",
42
+ z.object({ workingDirectory: z.string().min(1) }),
43
+ async (p) => {
44
+ await runCliOkThrowing(["terraform", "-chdir", p.workingDirectory, "plan", "-input=false"], {
45
+ ...processEnv,
46
+ });
47
+ return jsonResult({ ok: true });
48
+ },
49
+ );
50
+
51
+ registerWriteTool(
52
+ "iac_terraform_apply",
53
+ {
54
+ mutates: "iac.terraform.apply",
55
+ recoverable: true,
56
+ scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
57
+ },
58
+ "Run terraform apply.",
59
+ z.object({ workingDirectory: z.string().min(1) }),
60
+ async (p) => {
61
+ await runCliOkThrowing(
62
+ ["terraform", "-chdir", p.workingDirectory, "apply", "-auto-approve", "-input=false"],
63
+ { ...processEnv },
64
+ );
65
+ return jsonResult({ ok: true });
66
+ },
67
+ );
68
+
69
+ registerWriteTool(
70
+ "iac_terraform_destroy",
71
+ {
72
+ mutates: "iac.terraform.destroy",
73
+ // Destroying infrastructure cannot be undone from its own result, and there is nothing
74
+ // queryable to snapshot, so the identifying parameters ARE the pre-state: a record naming
75
+ // WHICH directory was destroyed still beats silence.
76
+ recoverable: false,
77
+ capturePreState: (p) => Promise.resolve({ workingDirectory: p.workingDirectory }),
78
+ scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
79
+ },
80
+ "Run terraform destroy.",
81
+ z.object({ workingDirectory: z.string().min(1) }),
82
+ async (p) => {
83
+ await runCliOkThrowing(
84
+ ["terraform", "-chdir", p.workingDirectory, "destroy", "-auto-approve", "-input=false"],
85
+ { ...processEnv },
86
+ );
87
+ return jsonResult({ ok: true });
88
+ },
89
+ );
90
+
91
+ registerWriteTool(
92
+ "iac_cloudformation_deploy",
93
+ {
94
+ mutates: "iac.cloudformation.deploy",
95
+ recoverable: true,
96
+ scopeTargetOf: (p) => ({ kind: "stack", value: p.stackName }),
97
+ },
98
+ "Deploy a CloudFormation stack via AWS CLI.",
99
+ z.object({
100
+ stackName: z.string().min(1),
101
+ templateBody: z.string().min(1),
102
+ }),
103
+ async (p) => {
104
+ await runCliOkThrowing(
105
+ [
106
+ "aws",
107
+ "cloudformation",
108
+ "deploy",
109
+ "--stack-name",
110
+ p.stackName,
111
+ "--template-body",
112
+ p.templateBody,
113
+ "--capabilities",
114
+ "CAPABILITY_IAM",
115
+ ],
116
+ { ...processEnv },
117
+ );
118
+ return jsonResult({ ok: true });
119
+ },
120
+ );
121
+
122
+ reg(
123
+ "iac_pulumi_preview",
124
+ "Run pulumi preview in a stack directory.",
125
+ z.object({ workingDirectory: z.string().min(1) }),
126
+ async (p) => {
127
+ await runCliOkThrowing(
128
+ ["pulumi", "preview", "--cwd", p.workingDirectory, "--non-interactive"],
129
+ { ...processEnv },
130
+ );
131
+ return jsonResult({ ok: true });
132
+ },
133
+ );
134
+
135
+ registerWriteTool(
136
+ "iac_pulumi_up",
137
+ {
138
+ mutates: "iac.pulumi.up",
139
+ recoverable: true,
140
+ scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
141
+ },
142
+ "Run pulumi up.",
143
+ z.object({ workingDirectory: z.string().min(1) }),
144
+ async (p) => {
145
+ await runCliOkThrowing(
146
+ ["pulumi", "up", "--yes", "--cwd", p.workingDirectory, "--non-interactive"],
147
+ { ...processEnv },
148
+ );
149
+ return jsonResult({ ok: true });
150
+ },
151
+ );
152
+ }
@@ -11,10 +11,28 @@
11
11
  * preview — there is no surface to request `BODY[]` or an attachment part.
12
12
  *
13
13
  * The IMAP client and SMTP mailer are injected so tests never open real sockets.
14
+ *
15
+ * The shapes themselves now live in `shared/imap-mail-core.ts`: this connector
16
+ * and protonmail declared them separately and identically, which is the same
17
+ * duplication the tool layer had already been through. The `Imap*` aliases stay
18
+ * so this connector's own callers read as before.
14
19
  */
15
20
 
16
- import type { MailAddress } from "../../../shared/imap-mail-core.ts";
21
+ import type {
22
+ MailAddress,
23
+ MailAttachmentMeta,
24
+ MailClient,
25
+ MailEnvelope,
26
+ MailListOptions,
27
+ MailMessageMeta,
28
+ MailSearchOptions,
29
+ } from "../../../shared/imap-mail-core.ts";
17
30
 
31
+ export type {
32
+ SendMailInput,
33
+ SendMailResult,
34
+ SmtpMailer,
35
+ } from "../../../shared/imap-mail-core.ts";
18
36
  export {
19
37
  capPreview,
20
38
  clampLimit,
@@ -25,86 +43,15 @@ export {
25
43
 
26
44
  /** One parsed address (name optional). */
27
45
  export type ImapAddress = MailAddress;
28
-
29
46
  /** Message ENVELOPE — RFC 2822 header fields only. */
30
- export interface ImapEnvelope {
31
- readonly date?: Date | string | null;
32
- readonly subject?: string | null;
33
- readonly messageId?: string | null;
34
- readonly from?: readonly ImapAddress[];
35
- readonly to?: readonly ImapAddress[];
36
- readonly cc?: readonly ImapAddress[];
37
- }
38
-
39
- /**
40
- * Attachment METADATA only — filename, size, mimetype. NEVER the bytes.
41
- * Derived from BODYSTRUCTURE.
42
- */
43
- export interface ImapAttachmentMeta {
44
- readonly filename: string | null;
45
- readonly sizeBytes: number | null;
46
- readonly mimeType: string | null;
47
- }
48
-
49
- /**
50
- * A single mailbox message reduced to header + attachment-metadata + a capped
51
- * text preview. This is the ONLY shape the connector ever materializes; it has
52
- * no field that could carry attachment content or a full body.
53
- */
54
- export interface ImapMessageMeta {
55
- readonly uid: number;
56
- readonly mailbox: string;
57
- readonly uidValidity: string | null;
58
- readonly envelope: ImapEnvelope;
59
- readonly attachments: readonly ImapAttachmentMeta[];
60
- /** Plain-text body preview, already capped to {@link PREVIEW_MAX_CHARS}. */
61
- readonly preview: string;
62
- }
63
-
47
+ export type ImapEnvelope = MailEnvelope;
48
+ /** Attachment METADATA only — filename, size, mimetype. NEVER the bytes. */
49
+ export type ImapAttachmentMeta = MailAttachmentMeta;
50
+ /** Header + attachment-metadata + capped preview: the only shape materialized. */
51
+ export type ImapMessageMeta = MailMessageMeta;
64
52
  /** Listing options. */
65
- export interface ImapListOptions {
66
- readonly mailbox?: string;
67
- readonly limit?: number;
68
- }
69
-
70
- /** Search options — searches header/envelope fields, never document/body content scans. */
71
- export interface ImapSearchOptions {
72
- readonly query: string;
73
- readonly mailbox?: string;
74
- readonly limit?: number;
75
- }
76
-
77
- /**
78
- * Minimal IMAP client surface the connector depends on. Implemented for real by
79
- * the imapflow adapter in `server.ts` and by a fake in tests. Deliberately
80
- * exposes ONLY header/attachment-metadata + capped-preview reads.
81
- */
82
- export interface ImapClient {
83
- /** List the most recent messages (header + attachment metadata + preview). */
84
- list(options: ImapListOptions): Promise<ImapMessageMeta[]>;
85
- /** Fetch one message's header + attachment metadata + preview by uid. */
86
- get(uid: number, mailbox?: string): Promise<ImapMessageMeta | null>;
87
- /** Search by subject/from/to substring; returns matching message metas. */
88
- search(options: ImapSearchOptions): Promise<ImapMessageMeta[]>;
89
- }
90
-
91
- /** Outgoing message for the SMTP send tool. */
92
- export interface SendMailInput {
93
- readonly to: string;
94
- readonly subject: string;
95
- readonly body: string;
96
- readonly cc?: string;
97
- readonly bcc?: string;
98
- }
99
-
100
- /** Result of a send (message id assigned by the SMTP server, if any). */
101
- export interface SendMailResult {
102
- readonly messageId: string | null;
103
- readonly accepted: readonly string[];
104
- readonly rejected: readonly string[];
105
- }
106
-
107
- /** Minimal SMTP mailer surface; implemented for real over nodemailer. */
108
- export interface SmtpMailer {
109
- send(input: SendMailInput): Promise<SendMailResult>;
110
- }
53
+ export type ImapListOptions = MailListOptions;
54
+ /** Search options — header/envelope fields only, never body-content scans. */
55
+ export type ImapSearchOptions = MailSearchOptions;
56
+ /** Minimal IMAP client surface the connector depends on. */
57
+ export type ImapClient = MailClient;