@nimbus-dev/connectors 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/LICENSE +662 -0
  2. package/NOTICE +40 -0
  3. package/README.md +111 -0
  4. package/connectors/airflow/nimbus.extension.json +16 -0
  5. package/connectors/airflow/src/search-filter.ts +38 -0
  6. package/connectors/airflow/src/server.ts +91 -0
  7. package/connectors/apple/nimbus.extension.json +40 -0
  8. package/connectors/apple/src/apple-mail-core.ts +43 -0
  9. package/connectors/apple/src/caldav-core.ts +116 -0
  10. package/connectors/apple/src/calendar-tools.ts +278 -0
  11. package/connectors/apple/src/server.ts +541 -0
  12. package/connectors/apple/src/tools.ts +138 -0
  13. package/connectors/argocd/nimbus.extension.json +16 -0
  14. package/connectors/argocd/src/search-filter.ts +24 -0
  15. package/connectors/argocd/src/server.ts +158 -0
  16. package/connectors/athena/nimbus.extension.json +15 -0
  17. package/connectors/athena/src/server.ts +6 -0
  18. package/connectors/athena/src/tools.ts +181 -0
  19. package/connectors/aws/nimbus.extension.json +21 -0
  20. package/connectors/aws/src/server.ts +191 -0
  21. package/connectors/azure/nimbus.extension.json +15 -0
  22. package/connectors/azure/src/server.ts +139 -0
  23. package/connectors/bigeye/nimbus.extension.json +16 -0
  24. package/connectors/bigeye/src/search-filter.ts +22 -0
  25. package/connectors/bigeye/src/server.ts +161 -0
  26. package/connectors/bigquery/nimbus.extension.json +15 -0
  27. package/connectors/bigquery/src/server.ts +6 -0
  28. package/connectors/bigquery/src/tools.ts +175 -0
  29. package/connectors/bitbucket/nimbus.extension.json +15 -0
  30. package/connectors/bitbucket/src/server.ts +252 -0
  31. package/connectors/bitrise/nimbus.extension.json +15 -0
  32. package/connectors/bitrise/src/search-filter.ts +11 -0
  33. package/connectors/bitrise/src/server.ts +83 -0
  34. package/connectors/canva/nimbus.extension.json +15 -0
  35. package/connectors/canva/src/search-filter.ts +23 -0
  36. package/connectors/canva/src/server.ts +77 -0
  37. package/connectors/circleci/nimbus.extension.json +15 -0
  38. package/connectors/circleci/src/server.ts +184 -0
  39. package/connectors/cloud-logging/nimbus.extension.json +15 -0
  40. package/connectors/cloud-logging/src/server.ts +6 -0
  41. package/connectors/cloud-logging/src/tools.ts +115 -0
  42. package/connectors/cloudwatch/nimbus.extension.json +15 -0
  43. package/connectors/cloudwatch/src/server.ts +6 -0
  44. package/connectors/cloudwatch/src/tools.ts +144 -0
  45. package/connectors/codemagic/nimbus.extension.json +15 -0
  46. package/connectors/codemagic/src/search-filter.ts +11 -0
  47. package/connectors/codemagic/src/server.ts +74 -0
  48. package/connectors/confluence/nimbus.extension.json +15 -0
  49. package/connectors/confluence/src/kb-append.ts +25 -0
  50. package/connectors/confluence/src/server.ts +309 -0
  51. package/connectors/dagster/nimbus.extension.json +16 -0
  52. package/connectors/dagster/src/search-filter.ts +53 -0
  53. package/connectors/dagster/src/server.ts +218 -0
  54. package/connectors/databricks/nimbus.extension.json +16 -0
  55. package/connectors/databricks/src/search-filter.ts +28 -0
  56. package/connectors/databricks/src/server.ts +81 -0
  57. package/connectors/datadog/nimbus.extension.json +15 -0
  58. package/connectors/datadog/src/server.ts +56 -0
  59. package/connectors/dataprofile/nimbus.extension.json +16 -0
  60. package/connectors/dataprofile/src/profile.ts +298 -0
  61. package/connectors/dataprofile/src/server.ts +6 -0
  62. package/connectors/dataprofile/src/tools.ts +69 -0
  63. package/connectors/dbt/nimbus.extension.json +16 -0
  64. package/connectors/dbt/src/search-filter.ts +28 -0
  65. package/connectors/dbt/src/server.ts +96 -0
  66. package/connectors/dependencytrack/nimbus.extension.json +16 -0
  67. package/connectors/dependencytrack/src/search-filter.ts +24 -0
  68. package/connectors/dependencytrack/src/server.ts +80 -0
  69. package/connectors/discord/nimbus.extension.json +15 -0
  70. package/connectors/discord/src/server.ts +93 -0
  71. package/connectors/elasticsearch/nimbus.extension.json +16 -0
  72. package/connectors/elasticsearch/src/server.ts +6 -0
  73. package/connectors/elasticsearch/src/tools.ts +110 -0
  74. package/connectors/fastmail/nimbus.extension.json +16 -0
  75. package/connectors/fastmail/src/jmap-core.ts +86 -0
  76. package/connectors/fastmail/src/server.ts +194 -0
  77. package/connectors/fastmail/src/tools.ts +143 -0
  78. package/connectors/figma/nimbus.extension.json +15 -0
  79. package/connectors/figma/src/search-filter.ts +24 -0
  80. package/connectors/figma/src/server.ts +117 -0
  81. package/connectors/firebase/nimbus.extension.json +15 -0
  82. package/connectors/firebase/src/search-filter.ts +30 -0
  83. package/connectors/firebase/src/server.ts +94 -0
  84. package/connectors/firebase/src/token.ts +41 -0
  85. package/connectors/flagsmith/nimbus.extension.json +15 -0
  86. package/connectors/flagsmith/src/search-filter.ts +29 -0
  87. package/connectors/flagsmith/src/server.ts +102 -0
  88. package/connectors/flux/nimbus.extension.json +16 -0
  89. package/connectors/flux/src/search-filter.ts +42 -0
  90. package/connectors/flux/src/server.ts +182 -0
  91. package/connectors/gcp/nimbus.extension.json +15 -0
  92. package/connectors/gcp/src/server.ts +140 -0
  93. package/connectors/github/nimbus.extension.json +15 -0
  94. package/connectors/github/src/server.ts +347 -0
  95. package/connectors/github-actions/nimbus.extension.json +15 -0
  96. package/connectors/github-actions/src/server.ts +213 -0
  97. package/connectors/gitlab/nimbus.extension.json +15 -0
  98. package/connectors/gitlab/src/server.ts +392 -0
  99. package/connectors/gmail/nimbus.extension.json +15 -0
  100. package/connectors/gmail/src/server.ts +277 -0
  101. package/connectors/google-drive/nimbus.extension.json +15 -0
  102. package/connectors/google-drive/src/drive-query.ts +3 -0
  103. package/connectors/google-drive/src/server.ts +531 -0
  104. package/connectors/google-meet/nimbus.extension.json +15 -0
  105. package/connectors/google-meet/src/server.ts +89 -0
  106. package/connectors/google-photos/nimbus.extension.json +15 -0
  107. package/connectors/google-photos/src/server.ts +139 -0
  108. package/connectors/grafana/nimbus.extension.json +15 -0
  109. package/connectors/grafana/src/server.ts +61 -0
  110. package/connectors/great-expectations/nimbus.extension.json +16 -0
  111. package/connectors/great-expectations/src/gx-parse.ts +292 -0
  112. package/connectors/great-expectations/src/server.ts +6 -0
  113. package/connectors/great-expectations/src/tools.ts +91 -0
  114. package/connectors/greenhouse/nimbus.extension.json +15 -0
  115. package/connectors/greenhouse/src/search-filter.ts +65 -0
  116. package/connectors/greenhouse/src/server.ts +67 -0
  117. package/connectors/hubspot/nimbus.extension.json +15 -0
  118. package/connectors/hubspot/src/search-filter.ts +28 -0
  119. package/connectors/hubspot/src/server.ts +81 -0
  120. package/connectors/iac/nimbus.extension.json +14 -0
  121. package/connectors/iac/src/server.ts +145 -0
  122. package/connectors/imap/nimbus.extension.json +16 -0
  123. package/connectors/imap/src/imap-core.ts +110 -0
  124. package/connectors/imap/src/server.ts +232 -0
  125. package/connectors/imap/src/tools.ts +40 -0
  126. package/connectors/intercom/nimbus.extension.json +15 -0
  127. package/connectors/intercom/src/search-filter.ts +46 -0
  128. package/connectors/intercom/src/server.ts +65 -0
  129. package/connectors/jenkins/nimbus.extension.json +15 -0
  130. package/connectors/jenkins/src/jenkins-api.ts +156 -0
  131. package/connectors/jenkins/src/server.ts +223 -0
  132. package/connectors/jira/nimbus.extension.json +15 -0
  133. package/connectors/jira/src/server.ts +309 -0
  134. package/connectors/kubernetes/nimbus.extension.json +15 -0
  135. package/connectors/kubernetes/src/server.ts +163 -0
  136. package/connectors/launchdarkly/nimbus.extension.json +15 -0
  137. package/connectors/launchdarkly/src/search-filter.ts +31 -0
  138. package/connectors/launchdarkly/src/server.ts +79 -0
  139. package/connectors/lever/nimbus.extension.json +15 -0
  140. package/connectors/lever/src/search-filter.ts +35 -0
  141. package/connectors/lever/src/server.ts +64 -0
  142. package/connectors/linear/nimbus.extension.json +15 -0
  143. package/connectors/linear/src/server.ts +467 -0
  144. package/connectors/localdb/nimbus.extension.json +16 -0
  145. package/connectors/localdb/src/server.ts +6 -0
  146. package/connectors/localdb/src/sql-scan.ts +158 -0
  147. package/connectors/localdb/src/tools.ts +67 -0
  148. package/connectors/looker/nimbus.extension.json +16 -0
  149. package/connectors/looker/src/search-filter.ts +22 -0
  150. package/connectors/looker/src/server.ts +254 -0
  151. package/connectors/mendeley/nimbus.extension.json +15 -0
  152. package/connectors/mendeley/src/search-filter.ts +68 -0
  153. package/connectors/mendeley/src/server.ts +59 -0
  154. package/connectors/mercury/nimbus.extension.json +15 -0
  155. package/connectors/mercury/src/search-filter.ts +11 -0
  156. package/connectors/mercury/src/server.ts +61 -0
  157. package/connectors/metabase/nimbus.extension.json +16 -0
  158. package/connectors/metabase/src/search-filter.ts +22 -0
  159. package/connectors/metabase/src/server.ts +83 -0
  160. package/connectors/miro/nimbus.extension.json +15 -0
  161. package/connectors/miro/src/search-filter.ts +24 -0
  162. package/connectors/miro/src/server.ts +76 -0
  163. package/connectors/mlflow/nimbus.extension.json +16 -0
  164. package/connectors/mlflow/src/search-filter.ts +38 -0
  165. package/connectors/mlflow/src/server.ts +167 -0
  166. package/connectors/monte-carlo/nimbus.extension.json +16 -0
  167. package/connectors/monte-carlo/src/search-filter.ts +11 -0
  168. package/connectors/monte-carlo/src/server.ts +241 -0
  169. package/connectors/netlify/nimbus.extension.json +15 -0
  170. package/connectors/netlify/src/search-filter.ts +37 -0
  171. package/connectors/netlify/src/server.ts +64 -0
  172. package/connectors/newrelic/nimbus.extension.json +15 -0
  173. package/connectors/newrelic/src/server.ts +48 -0
  174. package/connectors/notion/nimbus.extension.json +15 -0
  175. package/connectors/notion/src/kb-append.ts +23 -0
  176. package/connectors/notion/src/server.ts +343 -0
  177. package/connectors/obsidian/nimbus.extension.json +19 -0
  178. package/connectors/obsidian/src/server.ts +433 -0
  179. package/connectors/onedrive/nimbus.extension.json +15 -0
  180. package/connectors/onedrive/src/server.ts +256 -0
  181. package/connectors/outlook/nimbus.extension.json +15 -0
  182. package/connectors/outlook/src/server.ts +358 -0
  183. package/connectors/outlook/src/tool-scope-policy.ts +53 -0
  184. package/connectors/pagerduty/nimbus.extension.json +15 -0
  185. package/connectors/pagerduty/src/server.ts +140 -0
  186. package/connectors/pipedrive/nimbus.extension.json +15 -0
  187. package/connectors/pipedrive/src/search-filter.ts +11 -0
  188. package/connectors/pipedrive/src/server.ts +62 -0
  189. package/connectors/powerbi/nimbus.extension.json +16 -0
  190. package/connectors/powerbi/src/search-filter.ts +22 -0
  191. package/connectors/powerbi/src/server.ts +249 -0
  192. package/connectors/prefect/nimbus.extension.json +16 -0
  193. package/connectors/prefect/src/search-filter.ts +40 -0
  194. package/connectors/prefect/src/server.ts +94 -0
  195. package/connectors/protonmail/nimbus.extension.json +16 -0
  196. package/connectors/protonmail/src/mail-core.ts +96 -0
  197. package/connectors/protonmail/src/server.ts +231 -0
  198. package/connectors/protonmail/src/tools.ts +45 -0
  199. package/connectors/raindrop/nimbus.extension.json +15 -0
  200. package/connectors/raindrop/src/search-filter.ts +20 -0
  201. package/connectors/raindrop/src/server.ts +107 -0
  202. package/connectors/ramp/nimbus.extension.json +15 -0
  203. package/connectors/ramp/src/search-filter.ts +37 -0
  204. package/connectors/ramp/src/server.ts +109 -0
  205. package/connectors/readwise/nimbus.extension.json +15 -0
  206. package/connectors/readwise/src/search-filter.ts +64 -0
  207. package/connectors/readwise/src/server.ts +92 -0
  208. package/connectors/sagemaker/nimbus.extension.json +15 -0
  209. package/connectors/sagemaker/src/server.ts +6 -0
  210. package/connectors/sagemaker/src/tools.ts +121 -0
  211. package/connectors/salesforce/nimbus.extension.json +15 -0
  212. package/connectors/salesforce/src/search-filter.ts +23 -0
  213. package/connectors/salesforce/src/server.ts +94 -0
  214. package/connectors/semgrep/nimbus.extension.json +15 -0
  215. package/connectors/semgrep/src/search-filter.ts +31 -0
  216. package/connectors/semgrep/src/server.ts +108 -0
  217. package/connectors/sentry/nimbus.extension.json +15 -0
  218. package/connectors/sentry/src/server.ts +66 -0
  219. package/connectors/slack/nimbus.extension.json +15 -0
  220. package/connectors/slack/src/server.ts +360 -0
  221. package/connectors/snowflake/nimbus.extension.json +16 -0
  222. package/connectors/snowflake/src/search-filter.ts +26 -0
  223. package/connectors/snowflake/src/server.ts +236 -0
  224. package/connectors/snyk/nimbus.extension.json +15 -0
  225. package/connectors/snyk/src/search-filter.ts +28 -0
  226. package/connectors/snyk/src/server.ts +144 -0
  227. package/connectors/sonarqube/nimbus.extension.json +15 -0
  228. package/connectors/sonarqube/src/search-filter.ts +28 -0
  229. package/connectors/sonarqube/src/server.ts +115 -0
  230. package/connectors/stackoverflow/nimbus.extension.json +15 -0
  231. package/connectors/stackoverflow/src/search-filter.ts +52 -0
  232. package/connectors/stackoverflow/src/server.ts +81 -0
  233. package/connectors/storybook/nimbus.extension.json +16 -0
  234. package/connectors/storybook/src/server.ts +6 -0
  235. package/connectors/storybook/src/storybook-parse.ts +67 -0
  236. package/connectors/storybook/src/tools.ts +68 -0
  237. package/connectors/stripe/nimbus.extension.json +15 -0
  238. package/connectors/stripe/src/search-filter.ts +19 -0
  239. package/connectors/stripe/src/server.ts +65 -0
  240. package/connectors/superset/nimbus.extension.json +16 -0
  241. package/connectors/superset/src/search-filter.ts +22 -0
  242. package/connectors/superset/src/server.ts +119 -0
  243. package/connectors/tableau/nimbus.extension.json +16 -0
  244. package/connectors/tableau/src/search-filter.ts +22 -0
  245. package/connectors/tableau/src/server.ts +248 -0
  246. package/connectors/teams/nimbus.extension.json +20 -0
  247. package/connectors/teams/src/bot-send.ts +72 -0
  248. package/connectors/teams/src/server.ts +257 -0
  249. package/connectors/testflight/nimbus.extension.json +15 -0
  250. package/connectors/testflight/src/jwt.ts +27 -0
  251. package/connectors/testflight/src/search-filter.ts +30 -0
  252. package/connectors/testflight/src/server.ts +74 -0
  253. package/connectors/vercel/nimbus.extension.json +15 -0
  254. package/connectors/vercel/src/search-filter.ts +34 -0
  255. package/connectors/vercel/src/server.ts +79 -0
  256. package/connectors/vertex-ai/nimbus.extension.json +15 -0
  257. package/connectors/vertex-ai/src/server.ts +6 -0
  258. package/connectors/vertex-ai/src/tools.ts +145 -0
  259. package/connectors/wiz/nimbus.extension.json +15 -0
  260. package/connectors/wiz/src/search-filter.ts +50 -0
  261. package/connectors/wiz/src/server.ts +192 -0
  262. package/connectors/workday/nimbus.extension.json +19 -0
  263. package/connectors/workday/src/search-filter.ts +5 -0
  264. package/connectors/workday/src/server.ts +78 -0
  265. package/connectors/workday/src/tools.ts +1 -0
  266. package/connectors/zendesk/nimbus.extension.json +16 -0
  267. package/connectors/zendesk/src/search-filter.ts +11 -0
  268. package/connectors/zendesk/src/server.ts +77 -0
  269. package/connectors/zoom/nimbus.extension.json +15 -0
  270. package/connectors/zoom/src/encode-meeting-path.ts +5 -0
  271. package/connectors/zoom/src/recordings-window.ts +34 -0
  272. package/connectors/zoom/src/search-filter.ts +42 -0
  273. package/connectors/zoom/src/server.ts +98 -0
  274. package/connectors/zotero/nimbus.extension.json +15 -0
  275. package/connectors/zotero/src/search-filter.ts +81 -0
  276. package/connectors/zotero/src/server.ts +82 -0
  277. package/package.json +152 -0
  278. package/shared/atlassian-json-fetch.ts +42 -0
  279. package/shared/audit-chain.ts +133 -0
  280. package/shared/connector-mode.ts +60 -0
  281. package/shared/consent-kit.ts +323 -0
  282. package/shared/fetch-bearer-json.ts +18 -0
  283. package/shared/header-safe.ts +28 -0
  284. package/shared/imap-bodystructure.ts +113 -0
  285. package/shared/imap-mail-core.ts +57 -0
  286. package/shared/imap-tool-kit.ts +293 -0
  287. package/shared/join-api-path.ts +18 -0
  288. package/shared/kb-markdown.ts +142 -0
  289. package/shared/mcp-search-tool.ts +37 -0
  290. package/shared/mcp-tool-kit.ts +35 -0
  291. package/shared/nimbus-spawn.ts +128 -0
  292. package/shared/rest-tool-kit.ts +18 -0
  293. package/shared/run-cli-json.ts +55 -0
  294. package/shared/run-read-only-mcp-connector.ts +59 -0
  295. package/shared/safe-cli-arg.ts +55 -0
  296. package/shared/search-filter.ts +140 -0
  297. package/shared/strip-trailing-slashes.ts +7 -0
  298. package/shared/write-scope.ts +58 -0
  299. package/standalone/src/bin.ts +19 -0
  300. package/standalone/src/launcher.ts +186 -0
  301. package/standalone/tsconfig.json +8 -0
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
3
+
4
+ import { emailToolSchemas } from "../../../shared/imap-tool-kit.ts";
5
+ import {
6
+ createRegisterSimpleTool,
7
+ type McpListResult,
8
+ mcpJsonResult,
9
+ } from "../../../shared/mcp-tool-kit.ts";
10
+ import { clampLimit, type JmapClient, type JmapEmailView } from "./jmap-core.ts";
11
+
12
+ /** A JMAP email view is already the JSON-safe header/metadata/preview shape. */
13
+ function viewToJson(v: JmapEmailView): Record<string, unknown> {
14
+ return {
15
+ id: v.id,
16
+ messageId: v.messageId,
17
+ subject: v.subject,
18
+ from: [...v.from],
19
+ to: [...v.to],
20
+ cc: [...v.cc],
21
+ receivedAt: v.receivedAt,
22
+ attachments: v.attachments.map((a) => ({
23
+ filename: a.name,
24
+ sizeBytes: a.sizeBytes,
25
+ mimeType: a.mimeType,
26
+ })),
27
+ preview: v.preview,
28
+ };
29
+ }
30
+
31
+ const listArgs = z.object({
32
+ limit: z.number().int().min(1).max(200).optional(),
33
+ });
34
+
35
+ const getArgs = z.object({
36
+ id: z.string().min(1),
37
+ });
38
+
39
+ const searchArgs = z.object({
40
+ query: z.string().min(1).max(500),
41
+ limit: z.number().int().min(1).max(200).optional(),
42
+ });
43
+
44
+ /**
45
+ * Register the Fastmail JMAP read tools + the HITL-gated send tool onto an MCP
46
+ * server. The JMAP client is injected so tests exercise the tool surface without
47
+ * opening a real socket.
48
+ */
49
+ export function registerFastmailTools(
50
+ // Widened from `{ tool: ... }`: the consent kit needs the real server surface — capabilities,
51
+ // registerTool, list-changed and logging — not just the deprecated `.tool` shim.
52
+ server: ConsentServer & { tool: (...args: never) => unknown },
53
+ client: JmapClient,
54
+ ): void {
55
+ const registerSimpleTool = createRegisterSimpleTool(server);
56
+ const { sendArgs } = emailToolSchemas;
57
+
58
+ registerSimpleTool(
59
+ "fastmail_list",
60
+ "List recent mailbox emails — HEADERS + attachment METADATA + a short capped text preview ONLY. Returns subject, from, to/cc, received date, message-id, attachment {filename,size,mimetype}, and a <=2000-char plain-text body preview. NEVER returns attachment bytes or the full message body.",
61
+ listArgs.shape,
62
+ async (args: unknown): Promise<McpListResult> => {
63
+ const parsed = listArgs.safeParse(args);
64
+ if (!parsed.success) {
65
+ throw new Error(parsed.error.message);
66
+ }
67
+ const items = await client.list(clampLimit(parsed.data.limit));
68
+ return mcpJsonResult({ items: items.map(viewToJson) });
69
+ },
70
+ );
71
+
72
+ registerSimpleTool(
73
+ "fastmail_get",
74
+ "Fetch one email by JMAP id — HEADERS + attachment METADATA + a short capped text preview ONLY. NEVER returns attachment bytes or the full message body.",
75
+ getArgs.shape,
76
+ async (args: unknown): Promise<McpListResult> => {
77
+ const parsed = getArgs.safeParse(args);
78
+ if (!parsed.success) {
79
+ throw new Error(parsed.error.message);
80
+ }
81
+ const v = await client.get(parsed.data.id);
82
+ return mcpJsonResult(v === null ? { item: null } : { item: viewToJson(v) });
83
+ },
84
+ );
85
+
86
+ registerSimpleTool(
87
+ "fastmail_search",
88
+ "Full-text search over emails (JMAP Email/query text filter) — returns the same header + attachment-metadata + preview view as fastmail_list. NEVER returns attachment bytes or the full message body beyond the capped preview.",
89
+ searchArgs.shape,
90
+ async (args: unknown): Promise<McpListResult> => {
91
+ const parsed = searchArgs.safeParse(args);
92
+ if (!parsed.success) {
93
+ throw new Error(parsed.error.message);
94
+ }
95
+ const items = await client.search(parsed.data.query, clampLimit(parsed.data.limit));
96
+ return mcpJsonResult({ matches: items.map(viewToJson) });
97
+ },
98
+ );
99
+
100
+ const registerWriteTool = createWriteToolRegistrar(server, {
101
+ connector: "fastmail",
102
+ scopeEnv: "NIMBUS_MCP_FASTMAIL_WRITE_SCOPE",
103
+ scopeKinds: ["recipient"],
104
+ });
105
+
106
+ registerWriteTool(
107
+ "fastmail_mail_send",
108
+ {
109
+ mutates: "fastmail.mail.send",
110
+ // A sent mail cannot be recalled and nothing remains to query, so the recipient and
111
+ // subject ARE the pre-state.
112
+ recoverable: false,
113
+ capturePreState: (p) => Promise.resolve({ to: p.to, subject: p.subject }),
114
+ scopeTargetOf: (p) => ({ kind: "recipient", value: p.to }),
115
+ },
116
+ "Send a new email via JMAP EmailSubmission.",
117
+ sendArgs,
118
+ async (parsedData): Promise<McpListResult> => {
119
+ const parsed = { success: true as const, data: parsedData };
120
+ const input: { to: string; subject: string; body: string; cc?: string; bcc?: string } = {
121
+ to: parsed.data.to,
122
+ subject: parsed.data.subject,
123
+ body: parsed.data.body,
124
+ };
125
+ if (parsed.data.cc !== undefined && parsed.data.cc !== "") {
126
+ input.cc = parsed.data.cc;
127
+ }
128
+ if (parsed.data.bcc !== undefined && parsed.data.bcc !== "") {
129
+ input.bcc = parsed.data.bcc;
130
+ }
131
+ const res = await client.send(input);
132
+ return mcpJsonResult({ emailId: res.emailId, submissionId: res.submissionId });
133
+ },
134
+ );
135
+ }
136
+
137
+ /** Tool names exposed by this connector — for contract/introspection tests. */
138
+ export const FASTMAIL_TOOL_NAMES = [
139
+ "fastmail_list",
140
+ "fastmail_get",
141
+ "fastmail_search",
142
+ "fastmail_mail_send",
143
+ ] as const;
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "com.nimbus.figma",
3
+ "displayName": "Figma",
4
+ "version": "0.1.0",
5
+ "description": "Figma connector (read-only). Surfaces the files of a single configured team as figma:file items in the local index via a two-level fetch — GET /v1/teams/<team_id>/projects then GET /v1/projects/<project_id>/files for each project — capturing each file's name, project name, thumbnail url, and last-modified timestamp. Bearer OAuth auth (Authorization: Bearer <token>; short-lived access token refreshed by the Gateway via the 3-legged OAuth authorization-code flow; never logged); fixed SaaS host api.figma.com. Neither endpoint paginates with a cursor (each returns the full list), so the gateway-side syncable walks a single forward pass per cycle, project- and file-capped. Read-only — hitlRequired is intentionally empty. Sparse structured file metadata; the canonical url is constructed from the file key.",
6
+ "author": "Nimbus",
7
+ "entrypoint": "dist/server.js",
8
+ "runtime": "bun",
9
+ "permissions": {
10
+ "network": ["api.figma.com"]
11
+ },
12
+ "hitlRequired": [],
13
+ "syncInterval": 600,
14
+ "minNimbusVersion": "0.2.0"
15
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ asObjectish,
3
+ makeQueryFilter,
4
+ type SearchMatchOptions,
5
+ stringField,
6
+ } from "../../../shared/search-filter.ts";
7
+
8
+ export type FigmaSearchMatchOptions = SearchMatchOptions;
9
+
10
+ /**
11
+ * Figma file objects are flat — `{ key, name, thumbnail_url, last_modified }`.
12
+ * When flattened by the connector for search, each file additionally carries
13
+ * a `project_name` label. Match against the file name and the project name
14
+ * (case-insensitive substring).
15
+ */
16
+ function fieldsOf(item: unknown): readonly string[] | null {
17
+ const row = asObjectish(item);
18
+ if (row === undefined) {
19
+ return null;
20
+ }
21
+ return [stringField(row, "name"), stringField(row, "project_name")];
22
+ }
23
+
24
+ export const filterFigmaFiles = makeQueryFilter(fieldsOf);
@@ -0,0 +1,117 @@
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
+ import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
5
+ import { filterFigmaFiles } from "./search-filter.ts";
6
+
7
+ const BASE = "https://api.figma.com";
8
+
9
+ function apiToken(): string {
10
+ const t = process.env["FIGMA_TOKEN"]?.trim();
11
+ if (t === undefined || t === "") {
12
+ throw new Error("FIGMA_TOKEN is not set");
13
+ }
14
+ return t;
15
+ }
16
+
17
+ function teamId(): string {
18
+ const t = process.env["FIGMA_TEAM_ID"]?.trim();
19
+ if (t === undefined || t === "") {
20
+ throw new Error("FIGMA_TEAM_ID is not set");
21
+ }
22
+ return t;
23
+ }
24
+
25
+ function authHeader(): Record<string, string> {
26
+ return { Authorization: `Bearer ${apiToken()}`, Accept: "application/json" };
27
+ }
28
+
29
+ async function figmaGet(path: string): Promise<unknown> {
30
+ const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
31
+ const text = await res.text();
32
+ if (!res.ok) {
33
+ throw new Error(`Figma ${String(res.status)}: ${text.slice(0, 400)}`);
34
+ }
35
+ return JSON.parse(text) as unknown;
36
+ }
37
+
38
+ function projectsFrom(root: unknown): Array<{ id: string; name: string }> {
39
+ const projects = (root as { projects?: unknown } | null)?.projects;
40
+ if (!Array.isArray(projects)) {
41
+ return [];
42
+ }
43
+ const out: Array<{ id: string; name: string }> = [];
44
+ for (const p of projects) {
45
+ const rec = p as { id?: unknown; name?: unknown } | null;
46
+ const id = rec?.id;
47
+ if (typeof id === "string" || typeof id === "number") {
48
+ out.push({
49
+ id: String(id),
50
+ name: typeof rec?.name === "string" ? rec.name : "",
51
+ });
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+
57
+ function filesFrom(root: unknown): unknown[] {
58
+ const files = (root as { files?: unknown } | null)?.files;
59
+ return Array.isArray(files) ? files : [];
60
+ }
61
+
62
+ /**
63
+ * Flatten the configured team's projects into a single list of files, tagging
64
+ * each file with its `project_name` for searchability. This is the two-level
65
+ * fetch: GET /v1/teams/<team_id>/projects, then GET /v1/projects/<id>/files for
66
+ * each project.
67
+ */
68
+ async function listTeamFiles(): Promise<unknown[]> {
69
+ const projectsRoot = await figmaGet(`/v1/teams/${encodeURIComponent(teamId())}/projects`);
70
+ const projects = projectsFrom(projectsRoot);
71
+ const out: unknown[] = [];
72
+ for (const project of projects) {
73
+ const filesRoot = await figmaGet(`/v1/projects/${encodeURIComponent(project.id)}/files`);
74
+ for (const f of filesFrom(filesRoot)) {
75
+ const rec = f as Record<string, unknown> | null;
76
+ out.push({
77
+ ...rec,
78
+ project_id: project.id,
79
+ project_name: project.name,
80
+ });
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+
86
+ await runReadOnlyMcpConnector("nimbus-figma", (reg) => {
87
+ reg(
88
+ "figma_list",
89
+ "List the configured team's Figma files. Two-level fetch: GET /v1/teams/<team_id>/projects (the team id is injected at spawn from the figma.team_id vault key) returns { name, projects: [{ id, name }] }; then for each project GET /v1/projects/<project_id>/files returns { name, files: [{ key, name, thumbnail_url, last_modified }] }. Returns a flattened { files: [...] } envelope where each file is shaped { key, name, thumbnail_url, last_modified, project_id, project_name }. Neither endpoint paginates — each returns the full list.",
90
+ z.object({}),
91
+ async () => {
92
+ return jsonResult({ files: await listTeamFiles() });
93
+ },
94
+ );
95
+
96
+ reg(
97
+ "figma_get",
98
+ "List the files in one Figma project by its id (GET /v1/projects/{projectId}/files). Returns the { name, files: [{ key, name, thumbnail_url, last_modified }] } envelope directly. Throws when no project with that id is accessible.",
99
+ z.object({
100
+ projectId: z.string().min(1),
101
+ }),
102
+ async (p) => {
103
+ return jsonResult(await figmaGet(`/v1/projects/${encodeURIComponent(p.projectId)}/files`));
104
+ },
105
+ );
106
+
107
+ reg(
108
+ "figma_search",
109
+ "Substring search across the configured team's Figma files. Runs the same two-level fetch as figma_list (team projects, then files per project), flattens the result, and matches the query locally (case-insensitive) against each file name and its project name. Returns a { matches: [...] } envelope.",
110
+ searchToolInputSchema(200),
111
+ async (p) => {
112
+ const files = await listTeamFiles();
113
+ const matches = filterFigmaFiles(files, { query: p.query, limit: p.limit });
114
+ return jsonResult({ matches });
115
+ },
116
+ );
117
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "com.nimbus.firebase",
3
+ "displayName": "Firebase App Distribution",
4
+ "version": "0.1.0",
5
+ "description": "Firebase App Distribution connector (read-only). Surfaces each configured app's recent releases (display/build version, release notes, create time, console + testing URIs) so the local index can correlate mobile beta releases back to builds and incidents. Binary download links are stored as metadata but never fetched.",
6
+ "author": "Nimbus",
7
+ "entrypoint": "dist/server.js",
8
+ "runtime": "bun",
9
+ "permissions": {
10
+ "network": ["firebaseappdistribution.googleapis.com", "oauth2.googleapis.com"]
11
+ },
12
+ "hitlRequired": [],
13
+ "syncInterval": 600,
14
+ "minNimbusVersion": "0.2.0"
15
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ asObjectish,
3
+ type FieldExtractor,
4
+ makeQueryFilter,
5
+ type SearchMatchOptions,
6
+ stringField,
7
+ } from "../../../shared/search-filter.ts";
8
+
9
+ export type FirebaseSearchMatchOptions = SearchMatchOptions;
10
+
11
+ /**
12
+ * App Distribution `releases[]` resources carry their searchable fields at the
13
+ * top level, except release notes which nest under `releaseNotes.text`. Project
14
+ * those up so the shared substring filter can match them.
15
+ */
16
+ const releaseFields: FieldExtractor = (item: unknown) => {
17
+ const row = asObjectish(item);
18
+ if (row === undefined) {
19
+ return null;
20
+ }
21
+ const releaseNotes = asObjectish(row["releaseNotes"]) ?? {};
22
+ return [
23
+ stringField(row, "displayVersion"),
24
+ stringField(row, "buildVersion"),
25
+ stringField(releaseNotes, "text"),
26
+ stringField(row, "name"),
27
+ ];
28
+ };
29
+
30
+ export const filterFirebaseReleases = makeQueryFilter(releaseFields);
@@ -0,0 +1,94 @@
1
+ import { mintGoogleAccessToken } from "@nimbus-dev/sdk";
2
+ import { z } from "zod";
3
+ import { matchesResult } from "../../../shared/mcp-search-tool.ts";
4
+ import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
5
+ import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
6
+ import { filterFirebaseReleases } from "./search-filter.ts";
7
+ import { configuredAppIds, projectNumberFromAppId, serviceAccountFromEnv } from "./token.ts";
8
+
9
+ const APP_DISTRIBUTION_API = "https://firebaseappdistribution.googleapis.com";
10
+
11
+ // App Distribution OAuth2 tokens last ~1h; cache and refresh conservatively.
12
+ let cachedToken: { token: string; expiresAt: number } | null = null;
13
+
14
+ async function accessToken(): Promise<string> {
15
+ const now = Date.now();
16
+ if (cachedToken !== null && cachedToken.expiresAt > now + 60_000) {
17
+ return cachedToken.token;
18
+ }
19
+ const token = await mintGoogleAccessToken(serviceAccountFromEnv());
20
+ if (token === null) {
21
+ throw new Error("failed to mint a Firebase access token");
22
+ }
23
+ cachedToken = { token, expiresAt: now + 30 * 60 * 1000 };
24
+ return token;
25
+ }
26
+
27
+ async function appDistGet(path: string): Promise<unknown> {
28
+ const res = await fetch(`${APP_DISTRIBUTION_API}${path}`, {
29
+ headers: { Authorization: `Bearer ${await accessToken()}`, Accept: "application/json" },
30
+ });
31
+ const text = await res.text();
32
+ if (!res.ok) {
33
+ throw new Error(`Firebase App Distribution ${String(res.status)}: ${text.slice(0, 400)}`);
34
+ }
35
+ return JSON.parse(text) as unknown;
36
+ }
37
+
38
+ function appBasePath(appId: string): string {
39
+ const projectNumber = projectNumberFromAppId(appId);
40
+ if (projectNumber === null) {
41
+ throw new Error(`cannot derive a project number from app id: ${appId}`);
42
+ }
43
+ return `/v1/projects/${encodeURIComponent(projectNumber)}/apps/${encodeURIComponent(appId)}`;
44
+ }
45
+
46
+ function releasesPath(appId: string, pageSize: number): string {
47
+ return `${appBasePath(appId)}/releases?pageSize=${String(pageSize)}`;
48
+ }
49
+
50
+ function releasePath(appId: string, releaseId: string): string {
51
+ return `${appBasePath(appId)}/releases/${encodeURIComponent(releaseId)}`;
52
+ }
53
+
54
+ await runReadOnlyMcpConnector("nimbus-firebase", (reg) => {
55
+ reg(
56
+ "firebase_list",
57
+ "List Firebase App Distribution resources. When `appId` is omitted, returns the connector's configured app ids. When `appId` is provided, returns that app's most-recent releases via GET /v1/projects/<n>/apps/<appId>/releases (pageSize optional, default 50).",
58
+ z.object({
59
+ appId: z.string().min(1).optional(),
60
+ pageSize: z.number().int().min(1).max(100).optional(),
61
+ }),
62
+ async (p) => {
63
+ if (p.appId === undefined) {
64
+ return jsonResult({ appIds: configuredAppIds() });
65
+ }
66
+ return jsonResult(await appDistGet(releasesPath(p.appId, p.pageSize ?? 50)));
67
+ },
68
+ );
69
+
70
+ reg(
71
+ "firebase_get",
72
+ "Fetch a single Firebase App Distribution release via GET /v1/projects/<n>/apps/<appId>/releases/<releaseId>.",
73
+ z.object({
74
+ appId: z.string().min(1),
75
+ releaseId: z.string().min(1),
76
+ }),
77
+ async (p) => jsonResult(await appDistGet(releasePath(p.appId, p.releaseId))),
78
+ );
79
+
80
+ reg(
81
+ "firebase_search",
82
+ "Substring search across an app's recent releases. Matches the query against each release's display version, build version, release notes, and resource name (case-insensitive). Returns a `{ matches: [...] }` envelope.",
83
+ z.object({
84
+ appId: z.string().min(1),
85
+ query: z.string().min(1),
86
+ limit: z.number().int().min(1).max(100).optional(),
87
+ }),
88
+ async (p) => {
89
+ const root = await appDistGet(releasesPath(p.appId, 100));
90
+ const releases = (root as { releases?: unknown[] } | null)?.releases;
91
+ return matchesResult(releases, filterFirebaseReleases, p);
92
+ },
93
+ );
94
+ });
@@ -0,0 +1,41 @@
1
+ import { type GoogleServiceAccount, parseServiceAccountJson } from "@nimbus-dev/sdk";
2
+
3
+ /**
4
+ * Firebase App Distribution credential plumbing.
5
+ *
6
+ * The OAuth2 token-mint flow (RS256 JWT-bearer) lives in `@nimbus-dev/sdk`
7
+ * (`mintGoogleAccessToken`) so the gateway sync and this MCP server share one
8
+ * signer across the package boundary. This module holds only the connector's
9
+ * env-specific glue: reading the spawn-injected credentials and deriving the
10
+ * project number from a Firebase app id.
11
+ */
12
+
13
+ /** The project number is the 2nd colon-segment of a Firebase app id. */
14
+ export function projectNumberFromAppId(appId: string): string | null {
15
+ const segment = appId.split(":")[1];
16
+ return segment !== undefined && segment !== "" ? segment : null;
17
+ }
18
+
19
+ /**
20
+ * Parse the injected `FIREBASE_SERVICE_ACCOUNT_JSON` env into a service account;
21
+ * throws if unset or malformed. The Gateway sets this at spawn time.
22
+ */
23
+ export function serviceAccountFromEnv(): GoogleServiceAccount {
24
+ const json = process.env["FIREBASE_SERVICE_ACCOUNT_JSON"]?.trim();
25
+ if (json === undefined || json === "") {
26
+ throw new Error("FIREBASE_SERVICE_ACCOUNT_JSON is not set");
27
+ }
28
+ const sa = parseServiceAccountJson(json);
29
+ if (sa === null) {
30
+ throw new Error("FIREBASE_SERVICE_ACCOUNT_JSON is not a valid service-account key");
31
+ }
32
+ return sa;
33
+ }
34
+
35
+ /** Parse the injected comma-separated `FIREBASE_APP_IDS` env into a list. */
36
+ export function configuredAppIds(): string[] {
37
+ return (process.env["FIREBASE_APP_IDS"] ?? "")
38
+ .split(",")
39
+ .map((s) => s.trim())
40
+ .filter((s) => s !== "");
41
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "com.nimbus.flagsmith",
3
+ "displayName": "Flagsmith",
4
+ "version": "0.1.0",
5
+ "description": "Flagsmith feature-flag connector (read-only). Surfaces feature-flag definitions across all projects — name, type, default-enabled, initial value, description, tags, archived state, owner count, and timestamps — into the local index as `flagsmith:feature_flag` items, useful for incident correlation (\"did this flag exist when the alert fired?\"). Admin-API-token auth (`Authorization: Token <token>`). Regional / self-hosted instances override `flagsmith.api_base` (default https://api.flagsmith.com); the SaaS API host (api.flagsmith.com) is the only one in the static sandbox manifest today. v1 indexes flag definitions only — per-environment on/off state + segments are a deferred follow-up. The `flagsmith.flag.toggle` write tool is a deferred Phase 8 follow-up.",
6
+ "author": "Nimbus",
7
+ "entrypoint": "dist/server.js",
8
+ "runtime": "bun",
9
+ "permissions": {
10
+ "network": ["api.flagsmith.com"]
11
+ },
12
+ "hitlRequired": [],
13
+ "syncInterval": 600,
14
+ "minNimbusVersion": "0.2.0"
15
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ asObjectish,
3
+ makeQueryFilter,
4
+ type SearchMatchOptions,
5
+ stringField,
6
+ } from "../../../shared/search-filter.ts";
7
+
8
+ export type FlagsmithSearchMatchOptions = SearchMatchOptions;
9
+
10
+ function tagsString(row: Record<string, unknown>): string {
11
+ const tags = row["tags"];
12
+ if (!Array.isArray(tags)) {
13
+ return "";
14
+ }
15
+ return tags
16
+ .filter((t): t is string | number => typeof t === "string" || typeof t === "number")
17
+ .map(String)
18
+ .join(" ");
19
+ }
20
+
21
+ function fieldsOf(item: unknown): readonly string[] | null {
22
+ const row = asObjectish(item);
23
+ if (row === undefined) {
24
+ return null;
25
+ }
26
+ return [stringField(row, "name"), stringField(row, "description"), tagsString(row)];
27
+ }
28
+
29
+ export const filterFlagsmithFeatures = makeQueryFilter(fieldsOf);
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+ import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
3
+ import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
4
+ import { filterFlagsmithFeatures } from "./search-filter.ts";
5
+
6
+ const DEFAULT_API_BASE = "https://api.flagsmith.com";
7
+
8
+ function apiBase(): string {
9
+ const v = process.env["FLAGSMITH_API_BASE"]?.trim();
10
+ return v === undefined || v === "" ? DEFAULT_API_BASE : v;
11
+ }
12
+
13
+ function authHeader(): Record<string, string> {
14
+ const t = process.env["FLAGSMITH_TOKEN"]?.trim();
15
+ if (t === undefined || t === "") {
16
+ throw new Error("FLAGSMITH_TOKEN is not set");
17
+ }
18
+ return { Authorization: `Token ${t}`, Accept: "application/json" };
19
+ }
20
+
21
+ async function fsGet(path: string): Promise<unknown> {
22
+ const res = await fetch(`${apiBase()}/api/v1${path}`, { headers: authHeader() });
23
+ const text = await res.text();
24
+ if (!res.ok) {
25
+ throw new Error(`Flagsmith ${String(res.status)}: ${text.slice(0, 400)}`);
26
+ }
27
+ return JSON.parse(text) as unknown;
28
+ }
29
+
30
+ function featuresFromPage(root: unknown): unknown[] {
31
+ if (Array.isArray(root)) {
32
+ return root;
33
+ }
34
+ const results = (root as { results?: unknown } | null)?.results;
35
+ return Array.isArray(results) ? results : [];
36
+ }
37
+
38
+ await runReadOnlyMcpConnector("nimbus-flagsmith", (reg) => {
39
+ reg(
40
+ "flagsmith_list",
41
+ "List Flagsmith projects, or feature flags for a project. Without `projectId`, returns the account's projects (`/projects/`). With `projectId`, returns that project's feature flags (`/projects/{projectId}/features/`), capped at `limit` (default 100).",
42
+ z.object({
43
+ projectId: z.number().int().optional(),
44
+ limit: z.number().int().min(1).max(500).optional(),
45
+ }),
46
+ async (p) => {
47
+ if (p.projectId === undefined) {
48
+ return jsonResult(await fsGet("/projects/"));
49
+ }
50
+ const search = new URLSearchParams({ page_size: String(p.limit ?? 100) });
51
+ return jsonResult(
52
+ await fsGet(
53
+ `/projects/${encodeURIComponent(String(p.projectId))}/features/?${search.toString()}`,
54
+ ),
55
+ );
56
+ },
57
+ );
58
+
59
+ reg(
60
+ "flagsmith_get",
61
+ "Fetch one Flagsmith feature flag by project id + feature name. Flagsmith has no GET-by-name endpoint, so this searches the project's features and narrows to the exact-name match. Throws when no match is found.",
62
+ z.object({
63
+ projectId: z.number().int(),
64
+ featureName: z.string().min(1),
65
+ }),
66
+ async (p) => {
67
+ const search = new URLSearchParams({ page_size: "100", search: p.featureName });
68
+ const root = await fsGet(
69
+ `/projects/${encodeURIComponent(String(p.projectId))}/features/?${search.toString()}`,
70
+ );
71
+ const match = featuresFromPage(root).find(
72
+ (f) =>
73
+ f !== null && typeof f === "object" && (f as { name?: unknown }).name === p.featureName,
74
+ );
75
+ if (match === undefined) {
76
+ throw new Error(`Flagsmith feature not found: ${p.featureName}`);
77
+ }
78
+ return jsonResult(match);
79
+ },
80
+ );
81
+
82
+ reg(
83
+ "flagsmith_search",
84
+ "Substring search across a project's feature flags. Matches the query against flag name, description, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
85
+ z.object({
86
+ projectId: z.number().int(),
87
+ query: z.string().min(1),
88
+ limit: z.number().int().min(1).max(200).optional(),
89
+ }),
90
+ async (p) => {
91
+ const search = new URLSearchParams({ page_size: "500" });
92
+ const root = await fsGet(
93
+ `/projects/${encodeURIComponent(String(p.projectId))}/features/?${search.toString()}`,
94
+ );
95
+ const matches = filterFlagsmithFeatures(featuresFromPage(root), {
96
+ query: p.query,
97
+ limit: p.limit,
98
+ });
99
+ return jsonResult({ matches });
100
+ },
101
+ );
102
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "com.nimbus.flux",
3
+ "displayName": "Flux",
4
+ "version": "0.1.0",
5
+ "description": "Flux (GitOps Toolkit) connector. Reads Flux Custom Resources directly from the Kubernetes API server — Kustomizations, HelmReleases, sources (GitRepository / OCIRepository / HelmRepository / Bucket), and image-automation objects (ImageRepository / ImagePolicy / ImageUpdateAutomation) — into the local index as a single `flux:resource` item type with a `kind` discriminator in metadata (external_id `<kind>/<namespace>/<name>`). Surfaces the `status.conditions` Ready health (ready_status True/False/Unknown + reason + message), `spec.suspend`, source `url`, kustomization `path`, and `last_applied_revision` / `last_attempted_revision`. Authenticates with a read-only Kubernetes ServiceAccount JWT sent as `Authorization: Bearer <token>`; the API server base is supplied via the `flux.api_url` vault key. Flux has no central API and no SaaS host: the connector is always self-hosted, so the static manifest network list is intentionally empty — `phase3AddFluxMcp` parses the hostname from `flux.api_url` and extends the sandbox network list at spawn time (Grafana pattern). TLS note: Kubernetes API servers commonly present self-signed certificates that Bun's fetch rejects; v1 relies on `flux.api_url` pointing at a CA-trusted endpoint (a TLS-terminating ingress or `kubectl proxy`). Custom-CA / insecure-TLS handling is a deferred follow-up. Two HITL-gated write tools request a reconcile by annotating `reconcile.fluxcd.io/requestedAt` on the CR (PATCH): `flux_kustomization_reconcile` (HITL `flux.kustomization.reconcile`) and `flux_helmrelease_reconcile` (HITL `flux.helmrelease.reconcile`); both are async (verify via the next metadata sync) and require the ServiceAccount's `patch` RBAC verb on the target CR. `flux suspend`/`resume` remain deferred.",
6
+ "author": "Nimbus",
7
+ "entrypoint": "dist/server.js",
8
+ "runtime": "bun",
9
+ "permissions": {
10
+ "network": [],
11
+ "filesystem": { "read": [], "write": [] }
12
+ },
13
+ "hitlRequired": ["write"],
14
+ "syncInterval": 600,
15
+ "minNimbusVersion": "0.2.0"
16
+ }