@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,467 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ createRegisterSimpleTool,
7
+ createZodToolRegistrar,
8
+ mcpJsonResult as jsonResult,
9
+ requireProcessEnv,
10
+ } from "../../../shared/mcp-tool-kit.ts";
11
+
12
+ const LINEAR_GQL = "https://api.linear.app/graphql";
13
+
14
+ type LinearGqlResponse<T> = {
15
+ data?: T;
16
+ errors?: ReadonlyArray<{ message: string }>;
17
+ };
18
+
19
+ async function linearGraphql<T>(
20
+ apiKey: string,
21
+ query: string,
22
+ variables?: Record<string, unknown>,
23
+ ): Promise<{ ok: true; data: T; text: string } | { ok: false; status: number; text: string }> {
24
+ const res = await fetch(LINEAR_GQL, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ Authorization: apiKey,
29
+ },
30
+ body: JSON.stringify({ query, variables }),
31
+ });
32
+ const text = await res.text();
33
+ if (!res.ok) {
34
+ return { ok: false, status: res.status, text };
35
+ }
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = JSON.parse(text) as unknown;
39
+ } catch {
40
+ return { ok: false, status: res.status, text: "invalid JSON" };
41
+ }
42
+ const body = parsed as LinearGqlResponse<T>;
43
+ if (body.errors !== undefined && body.errors.length > 0) {
44
+ const msg = body.errors.map((e) => e.message).join("; ");
45
+ throw new Error(`Linear GraphQL: ${msg.slice(0, 400)}`);
46
+ }
47
+ if (body.data === undefined) {
48
+ throw new Error("Linear GraphQL: missing data");
49
+ }
50
+ return { ok: true, data: body.data, text };
51
+ }
52
+
53
+ function linearGqlData<T>(
54
+ res: { ok: true; data: T; text: string } | { ok: false; status: number; text: string },
55
+ ): T {
56
+ if (!res.ok) {
57
+ throw new Error(`Linear ${String(res.status)}: ${res.text.slice(0, 300)}`);
58
+ }
59
+ return res.data;
60
+ }
61
+
62
+ const server = new McpServer({ name: "nimbus-linear", version: "0.1.0" });
63
+
64
+ const registerSimpleTool = createRegisterSimpleTool(server);
65
+
66
+ import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
67
+
68
+ const reg = createZodToolRegistrar(registerSimpleTool);
69
+
70
+ /**
71
+ * Every MUTATING linear tool goes through here. Outside the gateway this adds the
72
+ * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
73
+ * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
74
+ */
75
+ const registerWriteTool = createWriteToolRegistrar(server, {
76
+ connector: "linear",
77
+ scopeEnv: "NIMBUS_MCP_LINEAR_WRITE_SCOPE",
78
+ scopeKinds: ["team", "issue"],
79
+ });
80
+
81
+ const linearIssueListSchema = z.object({
82
+ first: z.number().int().min(1).max(100).optional(),
83
+ teamId: z.string().min(1).optional(),
84
+ stateName: z.string().min(1).optional(),
85
+ assigneeId: z.string().min(1).optional(),
86
+ });
87
+
88
+ reg(
89
+ "linear_issue_list",
90
+ "List Linear issues with optional filters (team id, state name, assignee id).",
91
+ linearIssueListSchema,
92
+ async (parsed) => {
93
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
94
+ const filter: Record<string, unknown> = {};
95
+ if (parsed.teamId !== undefined) {
96
+ filter["team"] = { id: { eq: parsed.teamId } };
97
+ }
98
+ if (parsed.stateName !== undefined) {
99
+ filter["state"] = { name: { eq: parsed.stateName } };
100
+ }
101
+ if (parsed.assigneeId !== undefined) {
102
+ filter["assignee"] = { id: { eq: parsed.assigneeId } };
103
+ }
104
+ const hasFilter = Object.keys(filter).length > 0;
105
+ const q = hasFilter
106
+ ? `
107
+ query IssueList($first: Int!, $filter: IssueFilter!) {
108
+ issues(first: $first, filter: $filter, orderBy: updatedAt) {
109
+ nodes {
110
+ id
111
+ identifier
112
+ title
113
+ updatedAt
114
+ url
115
+ state { id name type }
116
+ team { id name key }
117
+ }
118
+ }
119
+ }
120
+ `
121
+ : `
122
+ query IssueList($first: Int!) {
123
+ issues(first: $first, orderBy: updatedAt) {
124
+ nodes {
125
+ id
126
+ identifier
127
+ title
128
+ updatedAt
129
+ url
130
+ state { id name type }
131
+ team { id name key }
132
+ }
133
+ }
134
+ }
135
+ `;
136
+ type Out = {
137
+ issues: {
138
+ nodes: ReadonlyArray<Record<string, unknown>>;
139
+ };
140
+ };
141
+ const vars: Record<string, unknown> = { first: parsed.first ?? 50 };
142
+ if (hasFilter) {
143
+ vars["filter"] = filter;
144
+ }
145
+ const res = await linearGraphql<Out>(apiKey, q, vars);
146
+ return jsonResult(linearGqlData(res));
147
+ },
148
+ );
149
+
150
+ const linearIssueIdSchema = z.object({ issueId: z.string().min(1) });
151
+
152
+ reg(
153
+ "linear_issue_get",
154
+ "Get a single Linear issue by UUID id.",
155
+ linearIssueIdSchema,
156
+ async (parsed) => {
157
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
158
+ const q = `
159
+ query IssueGet($id: String!) {
160
+ issue(id: $id) {
161
+ id
162
+ identifier
163
+ title
164
+ description
165
+ updatedAt
166
+ createdAt
167
+ url
168
+ priority
169
+ state { id name type }
170
+ team { id name key }
171
+ assignee { id name displayName }
172
+ }
173
+ }
174
+ `;
175
+ type Out = { issue: Record<string, unknown> | null };
176
+ const res = await linearGraphql<Out>(apiKey, q, { id: parsed.issueId });
177
+ return jsonResult(linearGqlData(res));
178
+ },
179
+ );
180
+
181
+ const linearIssueCreateSchema = z.object({
182
+ teamId: z.string().min(1),
183
+ title: z.string().min(1),
184
+ description: z.string().optional(),
185
+ priority: z.number().int().min(0).max(4).optional(),
186
+ stateId: z.string().min(1).optional(),
187
+ assigneeId: z.string().min(1).optional(),
188
+ });
189
+
190
+ registerWriteTool(
191
+ "linear_issue_create",
192
+ {
193
+ mutates: "linear.issue.create",
194
+ recoverable: true,
195
+ scopeTargetOf: (p) => ({ kind: "team", value: p.teamId }),
196
+ },
197
+ "Create a Linear issue (requires teamId and title).",
198
+ linearIssueCreateSchema,
199
+ async (parsed) => {
200
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
201
+ const input: Record<string, unknown> = {
202
+ teamId: parsed.teamId,
203
+ title: parsed.title,
204
+ };
205
+ if (parsed.description !== undefined) {
206
+ input["description"] = parsed.description;
207
+ }
208
+ if (parsed.priority !== undefined) {
209
+ input["priority"] = parsed.priority;
210
+ }
211
+ if (parsed.stateId !== undefined) {
212
+ input["stateId"] = parsed.stateId;
213
+ }
214
+ if (parsed.assigneeId !== undefined) {
215
+ input["assigneeId"] = parsed.assigneeId;
216
+ }
217
+ const q = `
218
+ mutation IssueCreate($input: IssueCreateInput!) {
219
+ issueCreate(input: $input) {
220
+ success
221
+ issue { id identifier url title }
222
+ }
223
+ }
224
+ `;
225
+ type Out = { issueCreate: { success: boolean; issue: Record<string, unknown> | null } };
226
+ const res = await linearGraphql<Out>(apiKey, q, { input });
227
+ return jsonResult(linearGqlData(res));
228
+ },
229
+ );
230
+
231
+ const linearIssueUpdateSchema = z.object({
232
+ issueId: z.string().min(1),
233
+ title: z.string().min(1).optional(),
234
+ description: z.string().optional(),
235
+ stateId: z.string().min(1).optional(),
236
+ priority: z.number().int().min(0).max(4).optional(),
237
+ assigneeId: z.string().min(1).optional(),
238
+ });
239
+
240
+ registerWriteTool(
241
+ "linear_issue_update",
242
+ {
243
+ mutates: "linear.issue.update",
244
+ recoverable: true,
245
+ scopeTargetOf: (p) => ({ kind: "issue", value: p.issueId }),
246
+ },
247
+ "Update a Linear issue by id.",
248
+ linearIssueUpdateSchema,
249
+ async (parsed) => {
250
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
251
+ const input: Record<string, unknown> = {};
252
+ if (parsed.title !== undefined) {
253
+ input["title"] = parsed.title;
254
+ }
255
+ if (parsed.description !== undefined) {
256
+ input["description"] = parsed.description;
257
+ }
258
+ if (parsed.stateId !== undefined) {
259
+ input["stateId"] = parsed.stateId;
260
+ }
261
+ if (parsed.priority !== undefined) {
262
+ input["priority"] = parsed.priority;
263
+ }
264
+ if (parsed.assigneeId !== undefined) {
265
+ input["assigneeId"] = parsed.assigneeId;
266
+ }
267
+ const q = `
268
+ mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
269
+ issueUpdate(id: $id, input: $input) {
270
+ success
271
+ issue { id identifier url title updatedAt }
272
+ }
273
+ }
274
+ `;
275
+ type Out = {
276
+ issueUpdate: { success: boolean; issue: Record<string, unknown> | null };
277
+ };
278
+ const res = await linearGraphql<Out>(apiKey, q, {
279
+ id: parsed.issueId,
280
+ input,
281
+ });
282
+ return jsonResult(linearGqlData(res));
283
+ },
284
+ );
285
+
286
+ const linearCommentCreateSchema = z.object({
287
+ issueId: z.string().min(1),
288
+ body: z.string().min(1),
289
+ });
290
+
291
+ registerWriteTool(
292
+ "linear_comment_create",
293
+ {
294
+ mutates: "linear.comment.create",
295
+ recoverable: true,
296
+ scopeTargetOf: (p) => ({ kind: "issue", value: p.issueId }),
297
+ },
298
+ "Add a comment to a Linear issue.",
299
+ linearCommentCreateSchema,
300
+ async (parsed) => {
301
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
302
+ const q = `
303
+ mutation CommentCreate($input: CommentCreateInput!) {
304
+ commentCreate(input: $input) {
305
+ success
306
+ comment { id body createdAt }
307
+ }
308
+ }
309
+ `;
310
+ type Out = {
311
+ commentCreate: { success: boolean; comment: Record<string, unknown> | null };
312
+ };
313
+ const res = await linearGraphql<Out>(apiKey, q, {
314
+ input: { issueId: parsed.issueId, body: parsed.body },
315
+ });
316
+ return jsonResult(linearGqlData(res));
317
+ },
318
+ );
319
+
320
+ const linearFirstOptionalSchema = z.object({
321
+ first: z.number().int().min(1).max(100).optional(),
322
+ });
323
+
324
+ reg("linear_project_list", "List Linear projects.", linearFirstOptionalSchema, async (parsed) => {
325
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
326
+ const q = `
327
+ query ProjectList($first: Int!) {
328
+ projects(first: $first) {
329
+ nodes {
330
+ id
331
+ name
332
+ description
333
+ slugId
334
+ url
335
+ updatedAt
336
+ state
337
+ }
338
+ }
339
+ }
340
+ `;
341
+ type Out = { projects: { nodes: ReadonlyArray<Record<string, unknown>> } };
342
+ const res = await linearGraphql<Out>(apiKey, q, { first: parsed.first ?? 50 });
343
+ return jsonResult(linearGqlData(res));
344
+ });
345
+
346
+ const linearProjectIdSchema = z.object({ projectId: z.string().min(1) });
347
+
348
+ reg("linear_project_get", "Get a Linear project by id.", linearProjectIdSchema, async (parsed) => {
349
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
350
+ const q = `
351
+ query ProjectGet($id: String!) {
352
+ project(id: $id) {
353
+ id
354
+ name
355
+ description
356
+ slugId
357
+ url
358
+ updatedAt
359
+ state
360
+ lead { id displayName }
361
+ }
362
+ }
363
+ `;
364
+ type Out = { project: Record<string, unknown> | null };
365
+ const res = await linearGraphql<Out>(apiKey, q, { id: parsed.projectId });
366
+ return jsonResult(linearGqlData(res));
367
+ });
368
+
369
+ const linearCycleListSchema = z.object({
370
+ teamId: z.string().min(1),
371
+ first: z.number().int().min(1).max(50).optional(),
372
+ });
373
+
374
+ reg(
375
+ "linear_cycle_list",
376
+ "List Linear cycles for a team.",
377
+ linearCycleListSchema,
378
+ async (parsed) => {
379
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
380
+ const q = `
381
+ query CycleList($teamId: ID!, $first: Int!) {
382
+ team(id: $teamId) {
383
+ id
384
+ cycles(first: $first) {
385
+ nodes {
386
+ id
387
+ name
388
+ number
389
+ startsAt
390
+ endsAt
391
+ completedAt
392
+ }
393
+ }
394
+ }
395
+ }
396
+ `;
397
+ type Out = {
398
+ team: {
399
+ id: string;
400
+ cycles: { nodes: ReadonlyArray<Record<string, unknown>> };
401
+ } | null;
402
+ };
403
+ const res = await linearGraphql<Out>(apiKey, q, {
404
+ teamId: parsed.teamId,
405
+ first: parsed.first ?? 20,
406
+ });
407
+ return jsonResult(linearGqlData(res));
408
+ },
409
+ );
410
+
411
+ const linearRoadmapFirstSchema = z.object({
412
+ first: z.number().int().min(1).max(50).optional(),
413
+ });
414
+
415
+ reg(
416
+ "linear_roadmap_list",
417
+ "List Linear initiatives (roadmap).",
418
+ linearRoadmapFirstSchema,
419
+ async (parsed) => {
420
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
421
+ const q = `
422
+ query InitiativeList($first: Int!) {
423
+ initiatives(first: $first) {
424
+ nodes {
425
+ id
426
+ name
427
+ description
428
+ targetDate
429
+ updatedAt
430
+ url
431
+ }
432
+ }
433
+ }
434
+ `;
435
+ type Out = { initiatives: { nodes: ReadonlyArray<Record<string, unknown>> } };
436
+ const res = await linearGraphql<Out>(apiKey, q, { first: parsed.first ?? 30 });
437
+ return jsonResult(linearGqlData(res));
438
+ },
439
+ );
440
+
441
+ reg(
442
+ "linear_member_list",
443
+ "List users in the workspace.",
444
+ linearFirstOptionalSchema,
445
+ async (parsed) => {
446
+ const apiKey = requireProcessEnv("LINEAR_API_KEY");
447
+ const q = `
448
+ query Users($first: Int!) {
449
+ users(first: $first) {
450
+ nodes {
451
+ id
452
+ name
453
+ displayName
454
+ email
455
+ active
456
+ }
457
+ }
458
+ }
459
+ `;
460
+ type Out = { users: { nodes: ReadonlyArray<Record<string, unknown>> } };
461
+ const res = await linearGraphql<Out>(apiKey, q, { first: parsed.first ?? 50 });
462
+ return jsonResult(linearGqlData(res));
463
+ },
464
+ );
465
+
466
+ const transport = new StdioServerTransport();
467
+ await server.connect(transport);
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "com.nimbus.localdb",
3
+ "displayName": "Local DB Schema / Saved Queries",
4
+ "version": "0.1.0",
5
+ "description": "Local DB Schema Indexing connector (Tier-5 local class). Indexes saved SQL queries and schema scripts that local database tools (DBeaver, DataGrip, pgAdmin) keep on disk as `.sql` files, as `localdb:saved_query` items — the SQL TEXT (capped) plus lightweight metadata (relative path, referenced table names, statement count, size). This enables semantic recall of 'that one SQL query I wrote last month' without any live database connection. It is a pure, bounded, path-traversal-guarded filesystem read: NO database is ever connected to, NO query is ever executed, and NO binary is spawned. The user points the connector at the directory their DB tool stores scripts/consoles in via the non-secret path `localdb.scripts_dir`; read tools are `localdb_list`, `localdb_get`, `localdb_search`. The configured scripts directory arrives as the scoped environment variable LOCALDB_SCRIPTS_DIR and is added to the sandbox filesystem.read allow-list at spawn time. No network access.",
6
+ "author": "Nimbus",
7
+ "entrypoint": "dist/server.js",
8
+ "runtime": "bun",
9
+ "permissions": {
10
+ "network": [],
11
+ "filesystem": { "read": [], "write": [] }
12
+ },
13
+ "hitlRequired": [],
14
+ "syncInterval": 600,
15
+ "minNimbusVersion": "0.2.0"
16
+ }
@@ -0,0 +1,6 @@
1
+ import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
2
+ import { registerLocaldbTools } from "./tools.ts";
3
+
4
+ await runReadOnlyMcpConnector("nimbus-localdb", (reg) => {
5
+ registerLocaldbTools(reg);
6
+ });
@@ -0,0 +1,158 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+
4
+ /**
5
+ * Local-only reader for saved SQL queries. This module is the MCP-server-side
6
+ * counterpart of the gateway's `mapLocalDbQueryToItem` mapper: it reads `.sql`
7
+ * files from the configured local DB-tool scripts dir (DBeaver/DataGrip/pgAdmin
8
+ * exports) and returns their TEXT + lightweight metadata for semantic recall.
9
+ *
10
+ * No database is ever connected to and no query is ever executed — this is a
11
+ * pure filesystem read, bounded and path-traversal-guarded.
12
+ */
13
+
14
+ const MAX_FILES = 2000;
15
+ const MAX_FILE_BYTES = 2 * 1024 * 1024;
16
+ const MAX_WALK_DEPTH = 12;
17
+ const PREVIEW_MAX_CHARS = 4000;
18
+
19
+ export interface SavedQuery {
20
+ /** Path relative to the configured scripts dir — the stable id. */
21
+ readonly relativePath: string;
22
+ readonly title: string;
23
+ readonly sizeBytes: number;
24
+ readonly lineCount: number;
25
+ /** SQL text, capped. */
26
+ readonly preview: string;
27
+ }
28
+
29
+ function clamp(s: string, max: number): string {
30
+ return s.length > max ? `${s.slice(0, max)}…` : s;
31
+ }
32
+
33
+ export function baseTitle(relativePath: string): string {
34
+ const segments = relativePath.split(/[/\\]/);
35
+ const last = segments.at(-1) ?? relativePath;
36
+ return last.replace(/\.sql$/i, "");
37
+ }
38
+
39
+ /**
40
+ * Resolve the configured scripts dir from the environment. Throws when unset so
41
+ * the MCP tools surface a clear error rather than reading an empty path.
42
+ */
43
+ export function scriptsDir(): string {
44
+ const dir = process.env["LOCALDB_SCRIPTS_DIR"]?.trim();
45
+ if (dir === undefined || dir === "") {
46
+ throw new Error("LOCALDB_SCRIPTS_DIR is not set");
47
+ }
48
+ return resolve(dir);
49
+ }
50
+
51
+ /**
52
+ * Path-traversal guard: confirm `candidate` (an absolute resolved path) is the
53
+ * scripts dir itself or a descendant. Rejects `..`-escaping inputs.
54
+ */
55
+ export function assertWithinScriptsDir(candidate: string, root: string): void {
56
+ const rel = relative(root, candidate);
57
+ if (rel === "") {
58
+ return;
59
+ }
60
+ if (rel.startsWith("..") || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
61
+ throw new Error("path escapes the configured local DB scripts dir");
62
+ }
63
+ }
64
+
65
+ async function collectSqlFiles(root: string): Promise<string[]> {
66
+ const found: string[] = [];
67
+ async function walk(dir: string, depth: number): Promise<void> {
68
+ if (depth > MAX_WALK_DEPTH || found.length >= MAX_FILES) {
69
+ return;
70
+ }
71
+ let entries: import("node:fs").Dirent[];
72
+ try {
73
+ entries = await readdir(dir, { withFileTypes: true });
74
+ } catch {
75
+ return;
76
+ }
77
+ for (const entry of entries) {
78
+ if (found.length >= MAX_FILES) {
79
+ return;
80
+ }
81
+ const full = join(dir, entry.name);
82
+ if (entry.isDirectory()) {
83
+ await walk(full, depth + 1);
84
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".sql")) {
85
+ found.push(full);
86
+ }
87
+ }
88
+ }
89
+ await walk(root, 0);
90
+ return found;
91
+ }
92
+
93
+ async function readSavedQuery(path: string, root: string): Promise<SavedQuery | null> {
94
+ try {
95
+ const buf = await readFile(path);
96
+ if (buf.byteLength > MAX_FILE_BYTES) {
97
+ return null;
98
+ }
99
+ const sql = buf.toString("utf8");
100
+ if (sql.trim() === "") {
101
+ return null;
102
+ }
103
+ let sizeBytes = buf.byteLength;
104
+ try {
105
+ const info = await stat(path);
106
+ sizeBytes = Number.isFinite(info.size) ? info.size : buf.byteLength;
107
+ } catch {
108
+ sizeBytes = buf.byteLength;
109
+ }
110
+ const relativePath = relative(root, path);
111
+ return {
112
+ relativePath,
113
+ title: baseTitle(relativePath) || relativePath,
114
+ sizeBytes,
115
+ lineCount: sql.split("\n").length,
116
+ preview: clamp(sql, PREVIEW_MAX_CHARS),
117
+ };
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ /** Read all saved `.sql` queries under the configured scripts dir (bounded). */
124
+ export async function scanSavedQueries(): Promise<SavedQuery[]> {
125
+ const root = scriptsDir();
126
+ const files = await collectSqlFiles(root);
127
+ const out: SavedQuery[] = [];
128
+ for (const file of files) {
129
+ assertWithinScriptsDir(file, root);
130
+ const q = await readSavedQuery(file, root);
131
+ if (q !== null) {
132
+ out.push(q);
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+
138
+ /** Read one saved query by its relative path (path-guarded). */
139
+ export async function getSavedQuery(relativePath: string): Promise<SavedQuery | null> {
140
+ const root = scriptsDir();
141
+ const candidate = resolve(root, relativePath);
142
+ assertWithinScriptsDir(candidate, root);
143
+ return readSavedQuery(candidate, root);
144
+ }
145
+
146
+ /** Substring search over saved-query title / relative path / SQL text. */
147
+ export function filterSavedQueries(queries: readonly SavedQuery[], query: string): SavedQuery[] {
148
+ const q = query.trim().toLowerCase();
149
+ if (q === "") {
150
+ return [...queries];
151
+ }
152
+ return queries.filter(
153
+ (sq) =>
154
+ sq.title.toLowerCase().includes(q) ||
155
+ sq.relativePath.toLowerCase().includes(q) ||
156
+ sq.preview.toLowerCase().includes(q),
157
+ );
158
+ }