@nimbus-dev/connectors 0.2.1 → 0.2.3

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 (220) hide show
  1. package/README.md +19 -7
  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 +26 -43
  12. package/connectors/aws/src/server.ts +2 -183
  13. package/connectors/aws/src/tools.ts +199 -0
  14. package/connectors/azure/src/server.ts +2 -131
  15. package/connectors/azure/src/tools.ts +134 -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 +19 -35
  29. package/connectors/cloudwatch/src/tools.ts +20 -38
  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 +153 -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 +160 -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 +21 -40
  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 +601 -34
  200. package/shared/cli-json-kit.ts +191 -0
  201. package/shared/collection-tool-kit.ts +143 -0
  202. package/shared/connector-mode.ts +3 -2
  203. package/shared/consent-kit.ts +81 -26
  204. package/shared/cursor-list-tool.ts +29 -0
  205. package/shared/env-json-api.ts +209 -0
  206. package/shared/fetch-json-text.ts +27 -0
  207. package/shared/github-rest.ts +62 -0
  208. package/shared/graphql-json.ts +60 -0
  209. package/shared/imap-mail-core.ts +91 -0
  210. package/shared/imap-tool-kit.ts +57 -23
  211. package/shared/imapflow-adapter.ts +417 -0
  212. package/shared/nimbus-spawn.ts +84 -27
  213. package/shared/rest-tool-kit.ts +77 -7
  214. package/shared/run-cli-json.ts +38 -16
  215. package/shared/safe-cli-arg.ts +139 -17
  216. package/shared/search-filter.ts +58 -18
  217. package/shared/walk-files.ts +118 -0
  218. package/shared/windows-batch-args.ts +273 -0
  219. package/shared/write-budget.ts +100 -0
  220. package/standalone/src/launcher.ts +19 -7
@@ -1,139 +1,9 @@
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
-
5
- import {
6
- fetchBearerAuthorizedJson,
7
- resolveUrlWithBase,
8
- } from "../../../shared/fetch-bearer-json.ts";
9
- import { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
10
- import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
11
-
12
- const PHOTOS_BASE = "https://photoslibrary.googleapis.com/v1";
13
-
14
- async function photosFetch(
15
- token: string,
16
- path: string,
17
- init?: RequestInit,
18
- ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
19
- const url = resolveUrlWithBase(PHOTOS_BASE, path);
20
- return fetchBearerAuthorizedJson(url, token, init, { "Content-Type": "application/json" });
21
- }
3
+ import { registerGooglePhotosTools } from "./tools.ts";
22
4
 
23
5
  const server = new McpServer({ name: "nimbus-google-photos", version: "0.1.0" });
24
6
 
25
- const registerSimpleTool = createRegisterSimpleTool(server);
26
- const reg = createZodToolRegistrar(registerSimpleTool);
27
-
28
- /** Standard Photos tool: token → photosFetch(buildPath[, buildInit]) → mcpJsonResultIfOk("Google Photos API"). */
29
- const registerPhotosTool = makeRestToolRegistrar({
30
- registrar: reg,
31
- tokenEnv: "GOOGLE_OAUTH_ACCESS_TOKEN",
32
- serviceLabel: "Google Photos API",
33
- fetch: photosFetch,
34
- });
35
-
36
- const gphotosAlbumListArgs = z.object({
37
- pageSize: z.number().int().min(1).max(50).optional(),
38
- pageToken: z.string().optional(),
39
- });
40
-
41
- registerPhotosTool(
42
- "gphotos_album_list",
43
- "List Google Photos albums (metadata). Pagination via pageToken.",
44
- gphotosAlbumListArgs,
45
- (parsed) => {
46
- const u = new URL(`${PHOTOS_BASE}/albums`);
47
- u.searchParams.set("pageSize", String(parsed.pageSize ?? 25));
48
- if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
49
- u.searchParams.set("pageToken", parsed.pageToken);
50
- }
51
- return `${u.pathname}${u.search}`;
52
- },
53
- );
54
-
55
- const gphotosAlbumGetArgs = z.object({
56
- albumId: z.string().min(1),
57
- });
58
-
59
- registerPhotosTool(
60
- "gphotos_album_get",
61
- "Get a single album by id (title, mediaItemsCount, coverPhotoBaseUrl).",
62
- gphotosAlbumGetArgs,
63
- (parsed) => `/albums/${encodeURIComponent(parsed.albumId)}`,
64
- );
65
-
66
- const gphotosMediaListArgs = z.object({
67
- albumId: z.string().min(1).optional(),
68
- pageSize: z.number().int().min(1).max(100).optional(),
69
- pageToken: z.string().optional(),
70
- });
71
-
72
- registerPhotosTool(
73
- "gphotos_media_list",
74
- "List media items (metadata + baseUrl/productUrl only). Optional albumId scopes to one album.",
75
- gphotosMediaListArgs,
76
- () => "/mediaItems:search",
77
- (parsed) => {
78
- const body: Record<string, unknown> = {
79
- pageSize: parsed.pageSize ?? 50,
80
- };
81
- if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
82
- body["pageToken"] = parsed.pageToken;
83
- }
84
- if (parsed.albumId !== undefined && parsed.albumId !== "") {
85
- body["albumId"] = parsed.albumId;
86
- }
87
- return { method: "POST", body: JSON.stringify(body) };
88
- },
89
- );
90
-
91
- const gphotosMediaGetArgs = z.object({
92
- mediaItemId: z.string().min(1),
93
- });
94
-
95
- registerPhotosTool(
96
- "gphotos_media_get",
97
- "Get a single media item metadata by id.",
98
- gphotosMediaGetArgs,
99
- (parsed) => `/mediaItems/${encodeURIComponent(parsed.mediaItemId)}`,
100
- );
101
-
102
- const gphotosMediaSearchArgs = z.object({
103
- albumId: z.string().min(1).optional(),
104
- pageSize: z.number().int().min(1).max(100).optional(),
105
- pageToken: z.string().optional(),
106
- includeArchivedMedia: z.boolean().optional(),
107
- excludeNonAppCreatedData: z.boolean().optional(),
108
- });
109
-
110
- registerPhotosTool(
111
- "gphotos_media_search",
112
- "Search media items (metadata only). Optional album filter; supports pagination.",
113
- gphotosMediaSearchArgs,
114
- () => "/mediaItems:search",
115
- (parsed) => {
116
- const body: Record<string, unknown> = {
117
- pageSize: parsed.pageSize ?? 50,
118
- };
119
- if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
120
- body["pageToken"] = parsed.pageToken;
121
- }
122
- if (parsed.albumId !== undefined && parsed.albumId !== "") {
123
- body["albumId"] = parsed.albumId;
124
- }
125
- const filters: Record<string, unknown> = {};
126
- if (parsed.includeArchivedMedia === true) {
127
- filters["includeArchivedMedia"] = true;
128
- }
129
- if (parsed.excludeNonAppCreatedData === true) {
130
- filters["excludeNonAppCreatedData"] = true;
131
- }
132
- if (Object.keys(filters).length > 0) {
133
- body["filters"] = filters;
134
- }
135
- return { method: "POST", body: JSON.stringify(body) };
136
- },
137
- );
7
+ registerGooglePhotosTools(server);
138
8
 
139
9
  await server.connect(new StdioServerTransport());
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ import {
3
+ fetchBearerAuthorizedJson,
4
+ resolveUrlWithBase,
5
+ } from "../../../shared/fetch-bearer-json.ts";
6
+ import { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
7
+ import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
8
+
9
+ const PHOTOS_BASE = "https://photoslibrary.googleapis.com/v1";
10
+
11
+ async function photosFetch(
12
+ token: string,
13
+ path: string,
14
+ init?: RequestInit,
15
+ ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
16
+ const url = resolveUrlWithBase(PHOTOS_BASE, path);
17
+ return fetchBearerAuthorizedJson(url, token, init, { "Content-Type": "application/json" });
18
+ }
19
+
20
+ /**
21
+ * The `mediaItems:search` body both media tools start from: the page size (default 50), then the
22
+ * page token and the album scope, each only when non-empty.
23
+ */
24
+ function mediaSearchBody(parsed: {
25
+ readonly pageSize?: number | undefined;
26
+ readonly pageToken?: string | undefined;
27
+ readonly albumId?: string | undefined;
28
+ }): Record<string, unknown> {
29
+ const body: Record<string, unknown> = {
30
+ pageSize: parsed.pageSize ?? 50,
31
+ };
32
+ if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
33
+ body["pageToken"] = parsed.pageToken;
34
+ }
35
+ if (parsed.albumId !== undefined && parsed.albumId !== "") {
36
+ body["albumId"] = parsed.albumId;
37
+ }
38
+ return body;
39
+ }
40
+
41
+ export function registerGooglePhotosTools(server: { tool: (...args: never) => unknown }): void {
42
+ const registerSimpleTool = createRegisterSimpleTool(server);
43
+ const reg = createZodToolRegistrar(registerSimpleTool);
44
+
45
+ /** Standard Photos tool: token → photosFetch(buildPath[, buildInit]) → mcpJsonResultIfOk("Google Photos API"). */
46
+ const registerPhotosTool = makeRestToolRegistrar({
47
+ registrar: reg,
48
+ tokenEnv: "GOOGLE_OAUTH_ACCESS_TOKEN",
49
+ serviceLabel: "Google Photos API",
50
+ fetch: photosFetch,
51
+ });
52
+
53
+ const gphotosAlbumListArgs = z.object({
54
+ pageSize: z.number().int().min(1).max(50).optional(),
55
+ pageToken: z.string().optional(),
56
+ });
57
+
58
+ registerPhotosTool(
59
+ "gphotos_album_list",
60
+ "List Google Photos albums (metadata). Pagination via pageToken.",
61
+ gphotosAlbumListArgs,
62
+ (parsed) => {
63
+ const u = new URL(`${PHOTOS_BASE}/albums`);
64
+ u.searchParams.set("pageSize", String(parsed.pageSize ?? 25));
65
+ if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
66
+ u.searchParams.set("pageToken", parsed.pageToken);
67
+ }
68
+ // Relative to PHOTOS_BASE, like every other tool here: the fetcher prefixes the base, which
69
+ // already ends in /v1, so returning `u.pathname` requested /v1/v1/albums.
70
+ return `/albums${u.search}`;
71
+ },
72
+ );
73
+
74
+ const gphotosAlbumGetArgs = z.object({
75
+ albumId: z.string().min(1),
76
+ });
77
+
78
+ registerPhotosTool(
79
+ "gphotos_album_get",
80
+ "Get a single album by id (title, mediaItemsCount, coverPhotoBaseUrl).",
81
+ gphotosAlbumGetArgs,
82
+ (parsed) => `/albums/${encodeURIComponent(parsed.albumId)}`,
83
+ );
84
+
85
+ const gphotosMediaListArgs = z.object({
86
+ albumId: z.string().min(1).optional(),
87
+ pageSize: z.number().int().min(1).max(100).optional(),
88
+ pageToken: z.string().optional(),
89
+ });
90
+
91
+ registerPhotosTool(
92
+ "gphotos_media_list",
93
+ "List media items (metadata + baseUrl/productUrl only). Optional albumId scopes to one album.",
94
+ gphotosMediaListArgs,
95
+ () => "/mediaItems:search",
96
+ (parsed) => ({ method: "POST", body: JSON.stringify(mediaSearchBody(parsed)) }),
97
+ );
98
+
99
+ const gphotosMediaGetArgs = z.object({
100
+ mediaItemId: z.string().min(1),
101
+ });
102
+
103
+ registerPhotosTool(
104
+ "gphotos_media_get",
105
+ "Get a single media item metadata by id.",
106
+ gphotosMediaGetArgs,
107
+ (parsed) => `/mediaItems/${encodeURIComponent(parsed.mediaItemId)}`,
108
+ );
109
+
110
+ const gphotosMediaSearchArgs = z.object({
111
+ albumId: z.string().min(1).optional(),
112
+ pageSize: z.number().int().min(1).max(100).optional(),
113
+ pageToken: z.string().optional(),
114
+ includeArchivedMedia: z.boolean().optional(),
115
+ excludeNonAppCreatedData: z.boolean().optional(),
116
+ });
117
+
118
+ registerPhotosTool(
119
+ "gphotos_media_search",
120
+ "Search media items (metadata only). Optional album filter; supports pagination.",
121
+ gphotosMediaSearchArgs,
122
+ () => "/mediaItems:search",
123
+ (parsed) => {
124
+ const body = mediaSearchBody(parsed);
125
+ const filters: Record<string, unknown> = {};
126
+ if (parsed.includeArchivedMedia === true) {
127
+ filters["includeArchivedMedia"] = true;
128
+ }
129
+ if (parsed.excludeNonAppCreatedData === true) {
130
+ filters["excludeNonAppCreatedData"] = true;
131
+ }
132
+ if (Object.keys(filters).length > 0) {
133
+ body["filters"] = filters;
134
+ }
135
+ return { method: "POST", body: JSON.stringify(body) };
136
+ },
137
+ );
138
+ }
@@ -1,61 +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
-
5
- import {
6
- createRegisterSimpleTool,
7
- createZodToolRegistrar,
8
- mcpJsonResult as jsonResult,
9
- } from "../../../shared/mcp-tool-kit.ts";
10
-
11
- function baseUrl(): string {
12
- const u = process.env["GRAFANA_URL"]?.trim();
13
- if (u === undefined || u === "") {
14
- throw new Error("GRAFANA_URL is not set");
15
- }
16
- return u.replace(/\/$/, "");
17
- }
18
-
19
- function authHeaders(): Record<string, string> {
20
- const tok = process.env["GRAFANA_API_TOKEN"]?.trim();
21
- if (tok === undefined || tok === "") {
22
- throw new Error("GRAFANA_API_TOKEN is not set");
23
- }
24
- return { Authorization: `Bearer ${tok}`, Accept: "application/json" };
25
- }
26
-
27
- async function grafanaGet(path: string): Promise<unknown> {
28
- const pathPart = path.startsWith("/") ? path : `/${path}`;
29
- const res = await fetch(`${baseUrl()}${pathPart}`, {
30
- headers: authHeaders(),
31
- });
32
- const text = await res.text();
33
- if (!res.ok) {
34
- throw new Error(`Grafana ${String(res.status)}: ${text.slice(0, 400)}`);
35
- }
36
- try {
37
- return JSON.parse(text) as unknown;
38
- } catch {
39
- return { raw: text };
40
- }
41
- }
3
+ import { registerGrafanaTools } from "./tools.ts";
42
4
 
43
5
  const mcp = new McpServer({ name: "nimbus-grafana", version: "0.1.0" });
44
- const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
45
-
46
- reg("grafana_alert_list", "List alert rules (Ruler API).", z.object({}), async () =>
47
- jsonResult(await grafanaGet("/api/ruler/grafana/api/v1/rules")),
48
- );
49
6
 
50
- reg(
51
- "grafana_dashboard_list",
52
- "Search dashboards.",
53
- z.object({ query: z.string().optional() }),
54
- async (p) => {
55
- const q = p.query ?? "";
56
- return jsonResult(await grafanaGet(`/api/search?type=dash-db&query=${encodeURIComponent(q)}`));
57
- },
58
- );
7
+ registerGrafanaTools(mcp);
59
8
 
60
9
  const transport = new StdioServerTransport();
61
10
  await mcp.connect(transport);
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ import { envAuthHeaders, requiredEnv } from "../../../shared/env-json-api.ts";
3
+ import {
4
+ createRegisterSimpleTool,
5
+ createZodToolRegistrar,
6
+ mcpJsonResult as jsonResult,
7
+ } from "../../../shared/mcp-tool-kit.ts";
8
+
9
+ /** Only ONE trailing slash is dropped here — not `requiredBaseUrl`'s strip-them-all. */
10
+ function baseUrl(): string {
11
+ return requiredEnv("GRAFANA_URL").replace(/\/$/, "");
12
+ }
13
+
14
+ const authHeaders = envAuthHeaders({ env: "GRAFANA_API_TOKEN" });
15
+
16
+ async function grafanaGet(path: string): Promise<unknown> {
17
+ const pathPart = path.startsWith("/") ? path : `/${path}`;
18
+ const res = await fetch(`${baseUrl()}${pathPart}`, {
19
+ headers: authHeaders(),
20
+ });
21
+ const text = await res.text();
22
+ if (!res.ok) {
23
+ throw new Error(`Grafana ${String(res.status)}: ${text.slice(0, 400)}`);
24
+ }
25
+ try {
26
+ return JSON.parse(text) as unknown;
27
+ } catch {
28
+ return { raw: text };
29
+ }
30
+ }
31
+
32
+ /** Tool names exposed by this connector — for contract/introspection tests. */
33
+ export const GRAFANA_TOOL_NAMES = ["grafana_alert_list", "grafana_dashboard_list"] as const;
34
+
35
+ export function registerGrafanaTools(server: { tool: (...args: never) => unknown }): void {
36
+ const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
37
+
38
+ reg("grafana_alert_list", "List alert rules (Ruler API).", z.object({}), async () =>
39
+ jsonResult(await grafanaGet("/api/ruler/grafana/api/v1/rules")),
40
+ );
41
+
42
+ reg(
43
+ "grafana_dashboard_list",
44
+ "Search dashboards.",
45
+ z.object({ query: z.string().optional() }),
46
+ async (p) => {
47
+ const q = p.query ?? "";
48
+ return jsonResult(
49
+ await grafanaGet(`/api/search?type=dash-db&query=${encodeURIComponent(q)}`),
50
+ );
51
+ },
52
+ );
53
+ }
@@ -1,5 +1,8 @@
1
- import { readdir, readFile } from "node:fs/promises";
2
- import { isAbsolute, join, relative, resolve, sep } from "node:path";
1
+ import { readFile } from "node:fs/promises";
2
+ import { relative, resolve } from "node:path";
3
+ import { isRecord } from "../../../shared/cli-json-kit.ts";
4
+ import { requiredEnv } from "../../../shared/env-json-api.ts";
5
+ import { assertWithinDir, byExtension, walkFiles } from "../../../shared/walk-files.ts";
3
6
 
4
7
  /**
5
8
  * Tier-3 no-row-data reader for Great Expectations validation-result JSON
@@ -53,10 +56,6 @@ export interface GxExpectationMeta {
53
56
  readonly sourceFile: string;
54
57
  }
55
58
 
56
- function isRecord(v: unknown): v is Record<string, unknown> {
57
- return v !== null && typeof v === "object" && !Array.isArray(v);
58
- }
59
-
60
59
  function strField(r: Record<string, unknown>, key: string): string | null {
61
60
  const v = r[key];
62
61
  return typeof v === "string" && v !== "" ? v : null;
@@ -206,11 +205,7 @@ export function parseValidationResult(parsed: unknown, sourceFile: string): GxEx
206
205
  * so the MCP tools surface a clear error rather than reading an empty path.
207
206
  */
208
207
  export function resultsDir(): string {
209
- const dir = process.env["GREAT_EXPECTATIONS_RESULTS_DIR"]?.trim();
210
- if (dir === undefined || dir === "") {
211
- throw new Error("GREAT_EXPECTATIONS_RESULTS_DIR is not set");
212
- }
213
- return resolve(dir);
208
+ return resolve(requiredEnv("GREAT_EXPECTATIONS_RESULTS_DIR"));
214
209
  }
215
210
 
216
211
  /**
@@ -219,42 +214,16 @@ export function resultsDir(): string {
219
214
  * is the filesystem analog of the argv flag-smuggling guard.
220
215
  */
221
216
  export function assertWithinResultsDir(candidate: string, root: string): void {
222
- const rel = relative(root, candidate);
223
- if (rel === "") {
224
- return; // the dir itself
225
- }
226
- if (rel.startsWith("..") || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
227
- throw new Error("path escapes the configured Great Expectations results dir");
228
- }
217
+ assertWithinDir(candidate, root, "the configured Great Expectations results dir");
229
218
  }
230
219
 
231
220
  /** Recursively collect `*.json` artefact paths under `root` (bounded). */
232
- async function collectJsonFiles(root: string): Promise<string[]> {
233
- const found: string[] = [];
234
- async function walk(dir: string, depth: number): Promise<void> {
235
- if (depth > MAX_WALK_DEPTH || found.length >= MAX_FILES) {
236
- return;
237
- }
238
- let entries: import("node:fs").Dirent[];
239
- try {
240
- entries = await readdir(dir, { withFileTypes: true });
241
- } catch {
242
- return;
243
- }
244
- for (const entry of entries) {
245
- if (found.length >= MAX_FILES) {
246
- return;
247
- }
248
- const full = join(dir, entry.name);
249
- if (entry.isDirectory()) {
250
- await walk(full, depth + 1);
251
- } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) {
252
- found.push(full);
253
- }
254
- }
255
- }
256
- await walk(root, 0);
257
- return found;
221
+ function collectJsonFiles(root: string): Promise<string[]> {
222
+ return walkFiles(root, {
223
+ maxFiles: MAX_FILES,
224
+ maxDepth: MAX_WALK_DEPTH,
225
+ select: byExtension(".json"),
226
+ });
258
227
  }
259
228
 
260
229
  async function readArtefact(path: string): Promise<unknown> {
@@ -282,7 +251,7 @@ export async function listAllExpectations(): Promise<GxExpectationMeta[]> {
282
251
  const out: GxExpectationMeta[] = [];
283
252
  for (const file of files) {
284
253
  assertWithinResultsDir(file, root);
285
- const parsed = await readArtefact(file);
254
+ const parsed = await readArtefact(file); // NOSONAR S9382: one artefact at a time on purpose — up to MAX_FILES (1000) files, each read whole before the 4 MiB cap is checked, so a fan-out would open a thousand descriptors and buffer every artefact at once.
286
255
  if (parsed === null) {
287
256
  continue;
288
257
  }
@@ -1,67 +1,6 @@
1
- import { z } from "zod";
2
- import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
3
- import {
4
- encodeBasicAuthHeader,
5
- mcpJsonResult as jsonResult,
6
- } from "../../../shared/mcp-tool-kit.ts";
7
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
8
- import { filterGreenhouseJobs } from "./search-filter.ts";
9
-
10
- const BASE = "https://harvest.greenhouse.io";
11
-
12
- function apiKey(): string {
13
- const t = process.env["GREENHOUSE_API_KEY"]?.trim();
14
- if (t === undefined || t === "") {
15
- throw new Error("GREENHOUSE_API_KEY is not set");
16
- }
17
- return t;
18
- }
19
-
20
- function authHeader(): Record<string, string> {
21
- return { Authorization: encodeBasicAuthHeader(apiKey(), ""), Accept: "application/json" };
22
- }
23
-
24
- async function greenhouseGet(path: string): Promise<unknown> {
25
- const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
26
- const text = await res.text();
27
- if (!res.ok) {
28
- throw new Error(`Greenhouse ${String(res.status)}: ${text.slice(0, 400)}`);
29
- }
30
- return JSON.parse(text) as unknown;
31
- }
2
+ import { registerGreenhouseTools } from "./tools.ts";
32
3
 
33
4
  await runReadOnlyMcpConnector("nimbus-greenhouse", (reg) => {
34
- reg(
35
- "greenhouse_list",
36
- "List the company's Greenhouse Harvest job openings (`GET /v1/jobs?per_page=100`). Returns a bare JSON array of job objects (NOT an envelope), each carrying the name, status, requisition id, departments, and offices.",
37
- z.object({
38
- per_page: z.number().int().min(1).max(100).optional(),
39
- }),
40
- async (p) => {
41
- const search = new URLSearchParams({ per_page: String(p.per_page ?? 100), page: "1" });
42
- return jsonResult(await greenhouseGet(`/v1/jobs?${search.toString()}`));
43
- },
44
- );
45
-
46
- reg(
47
- "greenhouse_get",
48
- "Fetch one Greenhouse job by its id (`GET /v1/jobs/{id}`). Returns the job object directly. Throws when no match is found.",
49
- z.object({
50
- id: z.string().min(1),
51
- }),
52
- async (p) => {
53
- return jsonResult(await greenhouseGet(`/v1/jobs/${encodeURIComponent(p.id)}`));
54
- },
55
- );
56
-
57
- reg(
58
- "greenhouse_search",
59
- "Substring search across the company's Greenhouse job openings (first page only). Matches the query against the job name, status, requisition id, the department names, and the office names + locations (case-insensitive). Returns a `{ matches: [...] }` envelope.",
60
- searchToolInputSchema(100),
61
- async (p) => {
62
- const search = new URLSearchParams({ per_page: "100", page: "1" });
63
- const root = await greenhouseGet(`/v1/jobs?${search.toString()}`);
64
- return matchesResult(root, filterGreenhouseJobs, p);
65
- },
66
- );
5
+ registerGreenhouseTools(reg);
67
6
  });
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { createJsonGetter, requiredEnv } from "../../../shared/env-json-api.ts";
3
+ import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
4
+ import {
5
+ encodeBasicAuthHeader,
6
+ mcpJsonResult as jsonResult,
7
+ } from "../../../shared/mcp-tool-kit.ts";
8
+ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
9
+ import { filterGreenhouseJobs } from "./search-filter.ts";
10
+
11
+ const BASE = "https://harvest.greenhouse.io";
12
+
13
+ /** HTTP Basic with the API key as the username and an empty password. */
14
+ function authHeader(): Record<string, string> {
15
+ return {
16
+ Authorization: encodeBasicAuthHeader(requiredEnv("GREENHOUSE_API_KEY"), ""),
17
+ Accept: "application/json",
18
+ };
19
+ }
20
+
21
+ const greenhouseGet = createJsonGetter({
22
+ base: BASE,
23
+ label: "Greenhouse",
24
+ headers: authHeader,
25
+ });
26
+
27
+ /** Tool names exposed by this connector — for contract/introspection tests. */
28
+ export const GREENHOUSE_TOOL_NAMES = [
29
+ "greenhouse_list",
30
+ "greenhouse_get",
31
+ "greenhouse_search",
32
+ ] as const;
33
+
34
+ export function registerGreenhouseTools(reg: ZodToolRegistrar): void {
35
+ reg(
36
+ "greenhouse_list",
37
+ "List the company's Greenhouse Harvest job openings (`GET /v1/jobs?per_page=100`). Returns a bare JSON array of job objects (NOT an envelope), each carrying the name, status, requisition id, departments, and offices.",
38
+ z.object({
39
+ per_page: z.number().int().min(1).max(100).optional(),
40
+ }),
41
+ async (p) => {
42
+ const search = new URLSearchParams({ per_page: String(p.per_page ?? 100), page: "1" });
43
+ return jsonResult(await greenhouseGet(`/v1/jobs?${search.toString()}`));
44
+ },
45
+ );
46
+
47
+ reg(
48
+ "greenhouse_get",
49
+ "Fetch one Greenhouse job by its id (`GET /v1/jobs/{id}`). Returns the job object directly. Throws when no match is found.",
50
+ z.object({
51
+ id: z.string().min(1),
52
+ }),
53
+ async (p) => {
54
+ return jsonResult(await greenhouseGet(`/v1/jobs/${encodeURIComponent(p.id)}`));
55
+ },
56
+ );
57
+
58
+ reg(
59
+ "greenhouse_search",
60
+ "Substring search across the company's Greenhouse job openings (first page only). Matches the query against the job name, status, requisition id, the department names, and the office names + locations (case-insensitive). Returns a `{ matches: [...] }` envelope.",
61
+ searchToolInputSchema(100),
62
+ async (p) => {
63
+ const search = new URLSearchParams({ per_page: "100", page: "1" });
64
+ const root = await greenhouseGet(`/v1/jobs?${search.toString()}`);
65
+ return matchesResult(root, filterGreenhouseJobs, p);
66
+ },
67
+ );
68
+ }