@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
@@ -0,0 +1,283 @@
1
+ import { z } from "zod";
2
+ import type { ConsentServer } from "../../../shared/consent-kit.ts";
3
+ import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
4
+ import {
5
+ fetchBearerAuthorizedJson,
6
+ resolveUrlWithBase,
7
+ } from "../../../shared/fetch-bearer-json.ts";
8
+ import {
9
+ createRegisterSimpleTool,
10
+ createZodToolRegistrar,
11
+ type McpListResult,
12
+ mcpJsonResultIfOk,
13
+ requireProcessEnv,
14
+ } from "../../../shared/mcp-tool-kit.ts";
15
+ import { teamsBotSendActivity } from "./bot-send.ts";
16
+
17
+ const GRAPH = "https://graph.microsoft.com/v1.0";
18
+
19
+ async function graphRequest(
20
+ token: string,
21
+ pathOrUrl: string,
22
+ init?: RequestInit,
23
+ ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
24
+ const url = resolveUrlWithBase(GRAPH, pathOrUrl);
25
+ return fetchBearerAuthorizedJson(url, token, init);
26
+ }
27
+
28
+ async function teamsPagedGraph(
29
+ token: string,
30
+ nextLink: string | undefined,
31
+ initialPath: string,
32
+ ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
33
+ const path = nextLink !== undefined && nextLink !== "" ? nextLink : initialPath;
34
+ return graphRequest(token, path);
35
+ }
36
+
37
+ function graphListResult(r: {
38
+ ok: boolean;
39
+ status: number;
40
+ json: unknown;
41
+ text: string;
42
+ }): McpListResult {
43
+ return mcpJsonResultIfOk("Graph", r, 200);
44
+ }
45
+
46
+ /** POST one message to a Graph `…/messages` collection — a team channel's or a chat's. */
47
+ async function postGraphMessage(
48
+ token: string,
49
+ messagesPath: string,
50
+ content: string,
51
+ contentType: "text" | "html" = "text",
52
+ ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
53
+ return graphRequest(token, messagesPath, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ body: {
58
+ contentType: contentType === "html" ? "html" : "text",
59
+ content,
60
+ },
61
+ }),
62
+ });
63
+ }
64
+
65
+ /** Tool names exposed by this connector — for contract/introspection tests. */
66
+ export const TEAMS_TOOL_NAMES = [
67
+ "teams_team_list",
68
+ "teams_channel_list",
69
+ "teams_channel_messages",
70
+ "teams_chat_list",
71
+ "teams_chat_messages",
72
+ "teams_message_post",
73
+ "teams_message_post_chat",
74
+ "teams_user_info",
75
+ "teams_chat_post",
76
+ ] as const;
77
+
78
+ export function registerTeamsTools(
79
+ server: ConsentServer & { tool: (...args: never) => unknown },
80
+ ): void {
81
+ const registerSimpleTool = createRegisterSimpleTool(server);
82
+
83
+ const reg = createZodToolRegistrar(registerSimpleTool);
84
+
85
+ /**
86
+ * Every MUTATING teams tool goes through here. Outside the gateway this adds the
87
+ * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
88
+ * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
89
+ */
90
+ const registerWriteTool = createWriteToolRegistrar(server, {
91
+ connector: "teams",
92
+ scopeEnv: "NIMBUS_MCP_TEAMS_WRITE_SCOPE",
93
+ scopeKinds: ["channel", "chat"],
94
+ });
95
+
96
+ const teamsTeamListSchema = z.object({
97
+ top: z.number().int().min(1).max(100).optional(),
98
+ nextLink: z.url().optional(),
99
+ });
100
+
101
+ reg(
102
+ "teams_team_list",
103
+ "List Microsoft Teams the signed-in user has joined (pagination via nextLink).",
104
+ teamsTeamListSchema,
105
+ async (parsed) => {
106
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
107
+ const initial = `/me/joinedTeams?$top=${String(parsed.top ?? 50)}`;
108
+ const r = await teamsPagedGraph(token, parsed.nextLink, initial);
109
+ return graphListResult(r);
110
+ },
111
+ );
112
+
113
+ const teamsChannelListSchema = z.object({
114
+ teamId: z.string().min(1),
115
+ top: z.number().int().min(1).max(100).optional(),
116
+ nextLink: z.url().optional(),
117
+ });
118
+
119
+ reg(
120
+ "teams_channel_list",
121
+ "List channels in a team (standard + private the app can read).",
122
+ teamsChannelListSchema,
123
+ async (parsed) => {
124
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
125
+ const tid = encodeURIComponent(parsed.teamId);
126
+ const initial = `/teams/${tid}/channels?$top=${String(parsed.top ?? 50)}`;
127
+ const r = await teamsPagedGraph(token, parsed.nextLink, initial);
128
+ return graphListResult(r);
129
+ },
130
+ );
131
+
132
+ const teamsChannelMessagesSchema = z.object({
133
+ teamId: z.string().min(1),
134
+ channelId: z.string().min(1),
135
+ top: z.number().int().min(1).max(50).optional(),
136
+ nextLink: z.url().optional(),
137
+ });
138
+
139
+ reg(
140
+ "teams_channel_messages",
141
+ "List recent messages in a team channel (not delta; for interactive reads).",
142
+ teamsChannelMessagesSchema,
143
+ async (parsed) => {
144
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
145
+ const tid = encodeURIComponent(parsed.teamId);
146
+ const cid = encodeURIComponent(parsed.channelId);
147
+ const initial = `/teams/${tid}/channels/${cid}/messages?$top=${String(parsed.top ?? 25)}`;
148
+ const r = await teamsPagedGraph(token, parsed.nextLink, initial);
149
+ return graphListResult(r);
150
+ },
151
+ );
152
+
153
+ const teamsChatListSchema = z.object({
154
+ top: z.number().int().min(1).max(50).optional(),
155
+ nextLink: z.url().optional(),
156
+ });
157
+
158
+ reg(
159
+ "teams_chat_list",
160
+ "List 1:1 and group chats for the signed-in user.",
161
+ teamsChatListSchema,
162
+ async (parsed) => {
163
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
164
+ const initial = `/me/chats?$top=${String(parsed.top ?? 25)}`;
165
+ const r = await teamsPagedGraph(token, parsed.nextLink, initial);
166
+ return graphListResult(r);
167
+ },
168
+ );
169
+
170
+ const teamsChatMessagesSchema = z.object({
171
+ chatId: z.string().min(1),
172
+ top: z.number().int().min(1).max(50).optional(),
173
+ nextLink: z.url().optional(),
174
+ });
175
+
176
+ reg(
177
+ "teams_chat_messages",
178
+ "List messages in a chat.",
179
+ teamsChatMessagesSchema,
180
+ async (parsed) => {
181
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
182
+ const id = encodeURIComponent(parsed.chatId);
183
+ const initial = `/chats/${id}/messages?$top=${String(parsed.top ?? 25)}`;
184
+ const r = await teamsPagedGraph(token, parsed.nextLink, initial);
185
+ return graphListResult(r);
186
+ },
187
+ );
188
+
189
+ const teamsMessagePostSchema = z.object({
190
+ teamId: z.string().min(1),
191
+ channelId: z.string().min(1),
192
+ body: z.string().min(1),
193
+ contentType: z.enum(["text", "html"]).optional(),
194
+ });
195
+
196
+ registerWriteTool(
197
+ "teams_message_post",
198
+ {
199
+ mutates: "teams.message.post",
200
+ recoverable: true,
201
+ scopeTargetOf: (p) => ({ kind: "channel", value: `${p.teamId}/${p.channelId}` }),
202
+ },
203
+ "Post a message to a team channel.",
204
+ teamsMessagePostSchema,
205
+ async (parsed) => {
206
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
207
+ const tid = encodeURIComponent(parsed.teamId);
208
+ const cid = encodeURIComponent(parsed.channelId);
209
+ const path = `/teams/${tid}/channels/${cid}/messages`;
210
+ const r = await postGraphMessage(token, path, parsed.body, parsed.contentType);
211
+ return graphListResult(r);
212
+ },
213
+ );
214
+
215
+ const teamsMessagePostChatSchema = z.object({
216
+ chatId: z.string().min(1),
217
+ body: z.string().min(1),
218
+ contentType: z.enum(["text", "html"]).optional(),
219
+ });
220
+
221
+ // A WRITE: it posts a message. It was registered as a read, which in standalone mode offered it
222
+ // to every client with no consent prompt, scope check, budget or audit record.
223
+ registerWriteTool(
224
+ "teams_message_post_chat",
225
+ {
226
+ mutates: "teams.message.postChat",
227
+ recoverable: true,
228
+ scopeTargetOf: (p) => ({ kind: "chat", value: p.chatId }),
229
+ },
230
+ "Post a message to a chat.",
231
+ teamsMessagePostChatSchema,
232
+ async (parsed) => {
233
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
234
+ const id = encodeURIComponent(parsed.chatId);
235
+ const r = await postGraphMessage(
236
+ token,
237
+ `/chats/${id}/messages`,
238
+ parsed.body,
239
+ parsed.contentType,
240
+ );
241
+ return graphListResult(r);
242
+ },
243
+ );
244
+
245
+ // --- ChatOps operational tools (Slice 5) -------------------------------------------------------
246
+ // `teams_chat_post` uses bot app credentials (Bot Framework), distinct from the user Graph token.
247
+ // Only `chatops/reply-dispatcher.ts` + `chatops/transport/` reference `teams_chat_post` (static D17).
248
+
249
+ const teamsUserInfoSchema = z.object({ userId: z.string().min(1) });
250
+ reg(
251
+ "teams_user_info",
252
+ "Fetch a Teams/AAD user (incl. mail/userPrincipalName) by id (ChatOps identity mapping).",
253
+ teamsUserInfoSchema,
254
+ async (parsed) => {
255
+ const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
256
+ const r = await teamsPagedGraph(
257
+ token,
258
+ undefined,
259
+ `/users/${encodeURIComponent(parsed.userId)}`,
260
+ );
261
+ return graphListResult(r);
262
+ },
263
+ );
264
+
265
+ const teamsChatPostSchema = z.object({
266
+ conversationId: z.string().min(1),
267
+ text: z.string().min(1),
268
+ });
269
+ registerWriteTool(
270
+ "teams_chat_post",
271
+ {
272
+ mutates: "teams.chat.post",
273
+ recoverable: true,
274
+ scopeTargetOf: (p) => ({ kind: "chat", value: p.conversationId }),
275
+ },
276
+ "Post an operational bot message to a Teams conversation (ChatOps reply surface; bot app creds).",
277
+ teamsChatPostSchema,
278
+ async (parsed) => {
279
+ const r = await teamsBotSendActivity(parsed.conversationId, parsed.text);
280
+ return graphListResult(r);
281
+ },
282
+ );
283
+ }
@@ -1,74 +1,6 @@
1
- import { signAppStoreConnectJwt } 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
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
6
- import { jwtParamsFromEnv } from "./jwt.ts";
7
- import { filterTestflightBuilds } from "./search-filter.ts";
8
-
9
- const APPSTORECONNECT_API = "https://api.appstoreconnect.apple.com";
10
-
11
- function authHeader(): Record<string, string> {
12
- const token = signAppStoreConnectJwt(jwtParamsFromEnv());
13
- return { Authorization: `Bearer ${token}`, Accept: "application/json" };
14
- }
15
-
16
- async function appstoreGet(path: string): Promise<unknown> {
17
- const res = await fetch(`${APPSTORECONNECT_API}${path}`, { headers: authHeader() });
18
- const text = await res.text();
19
- if (!res.ok) {
20
- throw new Error(`App Store Connect ${String(res.status)}: ${text.slice(0, 400)}`);
21
- }
22
- return JSON.parse(text) as unknown;
23
- }
24
-
25
- function buildsPath(appId: string, limit: number): string {
26
- return `/v1/builds?filter[app]=${encodeURIComponent(appId)}&sort=-uploadedDate&limit=${String(limit)}`;
27
- }
2
+ import { registerTestflightTools } from "./tools.ts";
28
3
 
29
4
  await runReadOnlyMcpConnector("nimbus-testflight", (reg) => {
30
- reg(
31
- "testflight_list",
32
- "List TestFlight resources via the App Store Connect API. When `appId` is omitted, returns the user's accessible apps via `GET /v1/apps`. When `appId` is provided, returns that app's most-recent builds via `GET /v1/builds?filter[app]=<id>&sort=-uploadedDate` (limit optional).",
33
- z.object({
34
- appId: z.string().min(1).optional(),
35
- limit: z.number().int().min(1).max(200).optional(),
36
- }),
37
- async (p) => {
38
- if (p.appId === undefined) {
39
- return jsonResult(await appstoreGet("/v1/apps"));
40
- }
41
- return jsonResult(await appstoreGet(buildsPath(p.appId, p.limit ?? 50)));
42
- },
43
- );
44
-
45
- reg(
46
- "testflight_get",
47
- "Fetch a single TestFlight resource. When `buildId` is provided, returns the build via `GET /v1/builds/<buildId>`. When omitted, returns the app via `GET /v1/apps/<appId>`.",
48
- z.object({
49
- appId: z.string().min(1),
50
- buildId: z.string().min(1).optional(),
51
- }),
52
- async (p) => {
53
- if (p.buildId === undefined) {
54
- return jsonResult(await appstoreGet(`/v1/apps/${encodeURIComponent(p.appId)}`));
55
- }
56
- return jsonResult(await appstoreGet(`/v1/builds/${encodeURIComponent(p.buildId)}`));
57
- },
58
- );
59
-
60
- reg(
61
- "testflight_search",
62
- "Substring search across a TestFlight app's recent builds. Matches the query against each build's `version`, `processingState`, `minOsVersion`, and `id` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
63
- z.object({
64
- appId: z.string().min(1),
65
- query: z.string().min(1),
66
- limit: z.number().int().min(1).max(200).optional(),
67
- }),
68
- async (p) => {
69
- const root = await appstoreGet(buildsPath(p.appId, 200));
70
- const data = (root as { data?: unknown[] } | null)?.data;
71
- return matchesResult(data, filterTestflightBuilds, p);
72
- },
73
- );
5
+ registerTestflightTools(reg);
74
6
  });
@@ -0,0 +1,79 @@
1
+ import { signAppStoreConnectJwt } from "@nimbus-dev/sdk";
2
+ import { z } from "zod";
3
+ import { createJsonGetter } from "../../../shared/env-json-api.ts";
4
+ import { matchesResult } from "../../../shared/mcp-search-tool.ts";
5
+ import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
6
+ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
7
+ import { jwtParamsFromEnv } from "./jwt.ts";
8
+ import { filterTestflightBuilds } from "./search-filter.ts";
9
+
10
+ const APPSTORECONNECT_API = "https://api.appstoreconnect.apple.com";
11
+
12
+ function authHeader(): Record<string, string> {
13
+ const token = signAppStoreConnectJwt(jwtParamsFromEnv());
14
+ return { Authorization: `Bearer ${token}`, Accept: "application/json" };
15
+ }
16
+
17
+ const appstoreGet = createJsonGetter({
18
+ base: APPSTORECONNECT_API,
19
+ label: "App Store Connect",
20
+ headers: authHeader,
21
+ });
22
+
23
+ function buildsPath(appId: string, limit: number): string {
24
+ return `/v1/builds?filter[app]=${encodeURIComponent(appId)}&sort=-uploadedDate&limit=${String(limit)}`;
25
+ }
26
+
27
+ /** Tool names exposed by this connector — for contract/introspection tests. */
28
+ export const TESTFLIGHT_TOOL_NAMES = [
29
+ "testflight_list",
30
+ "testflight_get",
31
+ "testflight_search",
32
+ ] as const;
33
+
34
+ export function registerTestflightTools(reg: ZodToolRegistrar): void {
35
+ reg(
36
+ "testflight_list",
37
+ "List TestFlight resources via the App Store Connect API. When `appId` is omitted, returns the user's accessible apps via `GET /v1/apps`. When `appId` is provided, returns that app's most-recent builds via `GET /v1/builds?filter[app]=<id>&sort=-uploadedDate` (limit optional).",
38
+ z.object({
39
+ appId: z.string().min(1).optional(),
40
+ limit: z.number().int().min(1).max(200).optional(),
41
+ }),
42
+ async (p) => {
43
+ if (p.appId === undefined) {
44
+ return jsonResult(await appstoreGet("/v1/apps"));
45
+ }
46
+ return jsonResult(await appstoreGet(buildsPath(p.appId, p.limit ?? 50)));
47
+ },
48
+ );
49
+
50
+ reg(
51
+ "testflight_get",
52
+ "Fetch a single TestFlight resource. When `buildId` is provided, returns the build via `GET /v1/builds/<buildId>`. When omitted, returns the app via `GET /v1/apps/<appId>`.",
53
+ z.object({
54
+ appId: z.string().min(1),
55
+ buildId: z.string().min(1).optional(),
56
+ }),
57
+ async (p) => {
58
+ if (p.buildId === undefined) {
59
+ return jsonResult(await appstoreGet(`/v1/apps/${encodeURIComponent(p.appId)}`));
60
+ }
61
+ return jsonResult(await appstoreGet(`/v1/builds/${encodeURIComponent(p.buildId)}`));
62
+ },
63
+ );
64
+
65
+ reg(
66
+ "testflight_search",
67
+ "Substring search across a TestFlight app's recent builds. Matches the query against each build's `version`, `processingState`, `minOsVersion`, and `id` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
68
+ z.object({
69
+ appId: z.string().min(1),
70
+ query: z.string().min(1),
71
+ limit: z.number().int().min(1).max(200).optional(),
72
+ }),
73
+ async (p) => {
74
+ const root = await appstoreGet(buildsPath(p.appId, 200));
75
+ const data = (root as { data?: unknown[] } | null)?.data;
76
+ return matchesResult(data, filterTestflightBuilds, p);
77
+ },
78
+ );
79
+ }
@@ -1,21 +1,13 @@
1
1
  import {
2
2
  asObjectish,
3
3
  makeQueryFilter,
4
+ nestedString,
4
5
  type SearchMatchOptions,
5
6
  stringField,
6
7
  } from "../../../shared/search-filter.ts";
7
8
 
8
9
  export type VercelSearchMatchOptions = SearchMatchOptions;
9
10
 
10
- function commitMessage(row: Record<string, unknown>): string {
11
- const meta = row["meta"];
12
- if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
13
- return "";
14
- }
15
- const v = (meta as Record<string, unknown>)["githubCommitMessage"];
16
- return typeof v === "string" ? v : "";
17
- }
18
-
19
11
  function fieldsOf(item: unknown): readonly string[] | null {
20
12
  const row = asObjectish(item);
21
13
  if (row === undefined) {
@@ -27,7 +19,7 @@ function fieldsOf(item: unknown): readonly string[] | null {
27
19
  stringField(row, "state"),
28
20
  stringField(row, "target"),
29
21
  stringField(row, "url"),
30
- commitMessage(row),
22
+ nestedString(row, ["meta", "githubCommitMessage"]),
31
23
  ];
32
24
  }
33
25
 
@@ -1,79 +1,6 @@
1
- import { z } from "zod";
2
- import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
3
- import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
4
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
5
- import { filterVercelDeployments } from "./search-filter.ts";
6
-
7
- const BASE = "https://api.vercel.com";
8
-
9
- function token(): string {
10
- const t = process.env["VERCEL_TOKEN"]?.trim();
11
- if (t === undefined || t === "") {
12
- throw new Error("VERCEL_TOKEN is not set");
13
- }
14
- return t;
15
- }
16
-
17
- function teamId(): string | undefined {
18
- const v = process.env["VERCEL_TEAM_ID"]?.trim();
19
- return v === undefined || v === "" ? undefined : v;
20
- }
21
-
22
- function authHeader(): Record<string, string> {
23
- return { Authorization: `Bearer ${token()}`, Accept: "application/json" };
24
- }
25
-
26
- function withTeam(path: string): string {
27
- const team = teamId();
28
- if (team === undefined) {
29
- return path;
30
- }
31
- const sep = path.includes("?") ? "&" : "?";
32
- return `${path}${sep}teamId=${encodeURIComponent(team)}`;
33
- }
34
-
35
- async function vercelGet(path: string): Promise<unknown> {
36
- const res = await fetch(`${BASE}${withTeam(path)}`, { headers: authHeader() });
37
- const text = await res.text();
38
- if (!res.ok) {
39
- throw new Error(`Vercel ${String(res.status)}: ${text.slice(0, 400)}`);
40
- }
41
- return JSON.parse(text) as unknown;
42
- }
2
+ import { registerVercelTools } from "./tools.ts";
43
3
 
44
4
  await runReadOnlyMcpConnector("nimbus-vercel", (reg) => {
45
- reg(
46
- "vercel_list",
47
- "List recent Vercel deployments (`GET /v6/deployments`), most-recent-first, capped at `limit` (default 100). Returns the `{ deployments, pagination }` envelope.",
48
- z.object({
49
- limit: z.number().int().min(1).max(100).optional(),
50
- }),
51
- async (p) => {
52
- const search = new URLSearchParams({ limit: String(p.limit ?? 100) });
53
- return jsonResult(await vercelGet(`/v6/deployments?${search.toString()}`));
54
- },
55
- );
56
-
57
- reg(
58
- "vercel_get",
59
- "Fetch one Vercel deployment by its id or *.vercel.app URL (`GET /v13/deployments/{idOrUrl}`). Returns the deployment object directly. Throws when no match is found.",
60
- z.object({
61
- idOrUrl: z.string().min(1),
62
- }),
63
- async (p) => {
64
- return jsonResult(await vercelGet(`/v13/deployments/${encodeURIComponent(p.idOrUrl)}`));
65
- },
66
- );
67
-
68
- reg(
69
- "vercel_search",
70
- "Substring search across recent Vercel deployments. Matches the query against uid, project name, state, target, the *.vercel.app host, and the git commit message (case-insensitive). Returns a `{ matches: [...] }` envelope.",
71
- searchToolInputSchema(100),
72
- async (p) => {
73
- const search = new URLSearchParams({ limit: "100" });
74
- const root = await vercelGet(`/v6/deployments?${search.toString()}`);
75
- const deployments = (root as { deployments?: unknown[] } | null)?.deployments;
76
- return matchesResult(deployments, filterVercelDeployments, p);
77
- },
78
- );
5
+ registerVercelTools(reg);
79
6
  });
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ import { createJsonGetter, envAuthHeaders } from "../../../shared/env-json-api.ts";
3
+ import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
4
+ import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
5
+ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
6
+ import { filterVercelDeployments } from "./search-filter.ts";
7
+
8
+ const BASE = "https://api.vercel.com";
9
+
10
+ function teamId(): string | undefined {
11
+ const v = process.env["VERCEL_TEAM_ID"]?.trim();
12
+ return v === undefined || v === "" ? undefined : v;
13
+ }
14
+
15
+ function withTeam(path: string): string {
16
+ const team = teamId();
17
+ if (team === undefined) {
18
+ return path;
19
+ }
20
+ const sep = path.includes("?") ? "&" : "?";
21
+ return `${path}${sep}teamId=${encodeURIComponent(team)}`;
22
+ }
23
+
24
+ const getJson = createJsonGetter({
25
+ base: BASE,
26
+ label: "Vercel",
27
+ headers: envAuthHeaders({ env: "VERCEL_TOKEN" }),
28
+ });
29
+
30
+ /** Every request is scoped to `VERCEL_TEAM_ID` when one is configured. */
31
+ async function vercelGet(path: string): Promise<unknown> {
32
+ return getJson(withTeam(path));
33
+ }
34
+
35
+ /** Tool names exposed by this connector — for contract/introspection tests. */
36
+ export const VERCEL_TOOL_NAMES = ["vercel_get", "vercel_list", "vercel_search"] as const;
37
+
38
+ export function registerVercelTools(reg: ZodToolRegistrar): void {
39
+ reg(
40
+ "vercel_list",
41
+ "List recent Vercel deployments (`GET /v6/deployments`), most-recent-first, capped at `limit` (default 100). Returns the `{ deployments, pagination }` envelope.",
42
+ z.object({
43
+ limit: z.number().int().min(1).max(100).optional(),
44
+ }),
45
+ async (p) => {
46
+ const search = new URLSearchParams({ limit: String(p.limit ?? 100) });
47
+ return jsonResult(await vercelGet(`/v6/deployments?${search.toString()}`));
48
+ },
49
+ );
50
+
51
+ reg(
52
+ "vercel_get",
53
+ "Fetch one Vercel deployment by its id or *.vercel.app URL (`GET /v13/deployments/{idOrUrl}`). Returns the deployment object directly. Throws when no match is found.",
54
+ z.object({
55
+ idOrUrl: z.string().min(1),
56
+ }),
57
+ async (p) => {
58
+ return jsonResult(await vercelGet(`/v13/deployments/${encodeURIComponent(p.idOrUrl)}`));
59
+ },
60
+ );
61
+
62
+ reg(
63
+ "vercel_search",
64
+ "Substring search across recent Vercel deployments. Matches the query against uid, project name, state, target, the *.vercel.app host, and the git commit message (case-insensitive). Returns a `{ matches: [...] }` envelope.",
65
+ searchToolInputSchema(100),
66
+ async (p) => {
67
+ const search = new URLSearchParams({ limit: "100" });
68
+ const root = await vercelGet(`/v6/deployments?${search.toString()}`);
69
+ const deployments = (root as { deployments?: unknown[] } | null)?.deployments;
70
+ return matchesResult(deployments, filterVercelDeployments, p);
71
+ },
72
+ );
73
+ }