@nimbus-dev/connectors 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +14 -4
  2. package/connectors/airflow/src/search-filter.ts +2 -15
  3. package/connectors/airflow/src/server.ts +2 -87
  4. package/connectors/airflow/src/tools.ts +74 -0
  5. package/connectors/apple/src/adapters.ts +326 -0
  6. package/connectors/apple/src/apple-mail-core.ts +4 -8
  7. package/connectors/apple/src/calendar-tools.ts +28 -8
  8. package/connectors/apple/src/server.ts +23 -512
  9. package/connectors/apple/src/tools.ts +18 -44
  10. package/connectors/argocd/src/server.ts +25 -38
  11. package/connectors/athena/src/tools.ts +15 -34
  12. package/connectors/aws/src/server.ts +2 -183
  13. package/connectors/aws/src/tools.ts +197 -0
  14. package/connectors/azure/src/server.ts +2 -131
  15. package/connectors/azure/src/tools.ts +133 -0
  16. package/connectors/bigeye/src/search-filter.ts +4 -15
  17. package/connectors/bigeye/src/server.ts +38 -38
  18. package/connectors/bigquery/src/tools.ts +1 -26
  19. package/connectors/bitbucket/src/server.ts +2 -244
  20. package/connectors/bitbucket/src/tools.ts +241 -0
  21. package/connectors/bitrise/src/server.ts +2 -79
  22. package/connectors/bitrise/src/tools.ts +76 -0
  23. package/connectors/canva/src/search-filter.ts +2 -11
  24. package/connectors/canva/src/server.ts +2 -73
  25. package/connectors/canva/src/tools.ts +66 -0
  26. package/connectors/circleci/src/server.ts +2 -176
  27. package/connectors/circleci/src/tools.ts +193 -0
  28. package/connectors/cloud-logging/src/tools.ts +14 -27
  29. package/connectors/cloudwatch/src/tools.ts +15 -34
  30. package/connectors/codemagic/src/server.ts +2 -70
  31. package/connectors/codemagic/src/tools.ts +71 -0
  32. package/connectors/confluence/src/server.ts +2 -301
  33. package/connectors/confluence/src/tools.ts +320 -0
  34. package/connectors/dagster/src/server.ts +2 -214
  35. package/connectors/dagster/src/tools.ts +187 -0
  36. package/connectors/databricks/src/search-filter.ts +7 -7
  37. package/connectors/databricks/src/server.ts +2 -77
  38. package/connectors/databricks/src/tools.ts +66 -0
  39. package/connectors/datadog/src/server.ts +2 -48
  40. package/connectors/datadog/src/tools.ts +48 -0
  41. package/connectors/dataprofile/src/profile.ts +16 -46
  42. package/connectors/dbt/src/server.ts +2 -92
  43. package/connectors/dbt/src/tools.ts +80 -0
  44. package/connectors/dependencytrack/src/server.ts +2 -76
  45. package/connectors/dependencytrack/src/tools.ts +65 -0
  46. package/connectors/discord/src/server.ts +2 -85
  47. package/connectors/discord/src/tools.ts +82 -0
  48. package/connectors/elasticsearch/src/tools.ts +7 -37
  49. package/connectors/fastmail/src/jmap-client.ts +202 -0
  50. package/connectors/fastmail/src/jmap-core.ts +8 -21
  51. package/connectors/fastmail/src/server.ts +2 -187
  52. package/connectors/fastmail/src/tools.ts +4 -23
  53. package/connectors/figma/src/search-filter.ts +2 -11
  54. package/connectors/figma/src/server.ts +2 -113
  55. package/connectors/figma/src/tools.ts +99 -0
  56. package/connectors/firebase/src/server.ts +2 -90
  57. package/connectors/firebase/src/token.ts +2 -5
  58. package/connectors/firebase/src/tools.ts +106 -0
  59. package/connectors/flagsmith/src/server.ts +2 -98
  60. package/connectors/flagsmith/src/tools.ts +95 -0
  61. package/connectors/flux/src/server.ts +52 -53
  62. package/connectors/gcp/src/server.ts +2 -132
  63. package/connectors/gcp/src/tools.ts +123 -0
  64. package/connectors/github/src/server.ts +2 -328
  65. package/connectors/github/src/tools.ts +311 -0
  66. package/connectors/github-actions/src/server.ts +2 -205
  67. package/connectors/github-actions/src/tools.ts +209 -0
  68. package/connectors/gitlab/src/server.ts +2 -384
  69. package/connectors/gitlab/src/tools.ts +392 -0
  70. package/connectors/gmail/src/server.ts +2 -270
  71. package/connectors/gmail/src/tools.ts +209 -0
  72. package/connectors/google-drive/src/server.ts +2 -524
  73. package/connectors/google-drive/src/tools.ts +529 -0
  74. package/connectors/google-meet/src/server.ts +2 -82
  75. package/connectors/google-meet/src/tools.ts +87 -0
  76. package/connectors/google-photos/src/server.ts +2 -132
  77. package/connectors/google-photos/src/tools.ts +138 -0
  78. package/connectors/grafana/src/server.ts +2 -53
  79. package/connectors/grafana/src/tools.ts +53 -0
  80. package/connectors/great-expectations/src/gx-parse.ts +14 -45
  81. package/connectors/greenhouse/src/server.ts +2 -63
  82. package/connectors/greenhouse/src/tools.ts +68 -0
  83. package/connectors/hubspot/src/server.ts +2 -77
  84. package/connectors/hubspot/src/tools.ts +70 -0
  85. package/connectors/iac/src/server.ts +2 -137
  86. package/connectors/iac/src/tools.ts +152 -0
  87. package/connectors/imap/src/imap-core.ts +29 -82
  88. package/connectors/imap/src/server.ts +33 -220
  89. package/connectors/intercom/src/server.ts +2 -61
  90. package/connectors/intercom/src/tools.ts +58 -0
  91. package/connectors/jenkins/src/jenkins-api.ts +2 -6
  92. package/connectors/jenkins/src/server.ts +2 -215
  93. package/connectors/jenkins/src/tools.ts +240 -0
  94. package/connectors/jira/src/server.ts +2 -301
  95. package/connectors/jira/src/tools.ts +325 -0
  96. package/connectors/kubernetes/src/server.ts +2 -155
  97. package/connectors/kubernetes/src/tools.ts +159 -0
  98. package/connectors/launchdarkly/src/server.ts +2 -75
  99. package/connectors/launchdarkly/src/tools.ts +72 -0
  100. package/connectors/lever/src/search-filter.ts +4 -12
  101. package/connectors/lever/src/server.ts +2 -60
  102. package/connectors/lever/src/tools.ts +61 -0
  103. package/connectors/linear/src/server.ts +2 -459
  104. package/connectors/linear/src/tools.ts +482 -0
  105. package/connectors/localdb/src/sql-scan.ts +13 -41
  106. package/connectors/localdb/src/tools.ts +1 -1
  107. package/connectors/looker/src/search-filter.ts +2 -15
  108. package/connectors/looker/src/server.ts +35 -70
  109. package/connectors/mendeley/src/server.ts +2 -55
  110. package/connectors/mendeley/src/tools.ts +54 -0
  111. package/connectors/mercury/src/server.ts +2 -57
  112. package/connectors/mercury/src/tools.ts +50 -0
  113. package/connectors/metabase/src/search-filter.ts +4 -15
  114. package/connectors/metabase/src/server.ts +2 -79
  115. package/connectors/metabase/src/tools.ts +64 -0
  116. package/connectors/miro/src/server.ts +2 -72
  117. package/connectors/miro/src/tools.ts +65 -0
  118. package/connectors/mlflow/src/search-filter.ts +3 -7
  119. package/connectors/mlflow/src/server.ts +20 -40
  120. package/connectors/monte-carlo/src/server.ts +50 -57
  121. package/connectors/netlify/src/search-filter.ts +6 -14
  122. package/connectors/netlify/src/server.ts +2 -60
  123. package/connectors/netlify/src/tools.ts +53 -0
  124. package/connectors/newrelic/src/server.ts +2 -40
  125. package/connectors/newrelic/src/tools.ts +37 -0
  126. package/connectors/notion/src/server.ts +2 -335
  127. package/connectors/notion/src/tools.ts +344 -0
  128. package/connectors/obsidian/src/server.ts +2 -425
  129. package/connectors/obsidian/src/tools.ts +466 -0
  130. package/connectors/onedrive/src/server.ts +2 -249
  131. package/connectors/onedrive/src/tools.ts +253 -0
  132. package/connectors/outlook/src/server.ts +2 -351
  133. package/connectors/outlook/src/tools.ts +357 -0
  134. package/connectors/pagerduty/src/server.ts +2 -132
  135. package/connectors/pagerduty/src/tools.ts +126 -0
  136. package/connectors/pipedrive/src/server.ts +2 -58
  137. package/connectors/pipedrive/src/tools.ts +63 -0
  138. package/connectors/powerbi/src/search-filter.ts +2 -15
  139. package/connectors/powerbi/src/server.ts +43 -68
  140. package/connectors/prefect/src/search-filter.ts +3 -16
  141. package/connectors/prefect/src/server.ts +2 -90
  142. package/connectors/prefect/src/tools.ts +83 -0
  143. package/connectors/protonmail/src/mail-core.ts +16 -75
  144. package/connectors/protonmail/src/server.ts +34 -214
  145. package/connectors/raindrop/src/server.ts +2 -103
  146. package/connectors/raindrop/src/tools.ts +103 -0
  147. package/connectors/ramp/src/server.ts +2 -105
  148. package/connectors/ramp/src/tools.ts +98 -0
  149. package/connectors/readwise/src/server.ts +2 -88
  150. package/connectors/readwise/src/tools.ts +81 -0
  151. package/connectors/sagemaker/src/tools.ts +19 -38
  152. package/connectors/salesforce/src/search-filter.ts +4 -11
  153. package/connectors/salesforce/src/server.ts +2 -90
  154. package/connectors/salesforce/src/tools.ts +75 -0
  155. package/connectors/semgrep/src/search-filter.ts +3 -10
  156. package/connectors/semgrep/src/server.ts +2 -104
  157. package/connectors/semgrep/src/tools.ts +92 -0
  158. package/connectors/sentry/src/server.ts +2 -58
  159. package/connectors/sentry/src/tools.ts +53 -0
  160. package/connectors/slack/src/server.ts +2 -352
  161. package/connectors/slack/src/tools.ts +376 -0
  162. package/connectors/snowflake/src/search-filter.ts +4 -19
  163. package/connectors/snowflake/src/server.ts +6 -25
  164. package/connectors/snyk/src/server.ts +2 -140
  165. package/connectors/snyk/src/tools.ts +136 -0
  166. package/connectors/sonarqube/src/server.ts +2 -111
  167. package/connectors/sonarqube/src/tools.ts +98 -0
  168. package/connectors/stackoverflow/src/server.ts +2 -77
  169. package/connectors/stackoverflow/src/tools.ts +66 -0
  170. package/connectors/storybook/src/storybook-parse.ts +3 -6
  171. package/connectors/stripe/src/server.ts +2 -61
  172. package/connectors/stripe/src/tools.ts +54 -0
  173. package/connectors/superset/src/search-filter.ts +4 -15
  174. package/connectors/superset/src/server.ts +2 -115
  175. package/connectors/superset/src/tools.ts +98 -0
  176. package/connectors/tableau/src/search-filter.ts +2 -15
  177. package/connectors/tableau/src/server.ts +13 -65
  178. package/connectors/teams/src/server.ts +2 -249
  179. package/connectors/teams/src/tools.ts +283 -0
  180. package/connectors/testflight/src/server.ts +2 -70
  181. package/connectors/testflight/src/tools.ts +79 -0
  182. package/connectors/vercel/src/search-filter.ts +2 -10
  183. package/connectors/vercel/src/server.ts +2 -75
  184. package/connectors/vercel/src/tools.ts +73 -0
  185. package/connectors/vertex-ai/src/tools.ts +23 -33
  186. package/connectors/wiz/src/search-filter.ts +6 -30
  187. package/connectors/wiz/src/server.ts +2 -188
  188. package/connectors/wiz/src/tools.ts +168 -0
  189. package/connectors/zendesk/src/server.ts +2 -73
  190. package/connectors/zendesk/src/tools.ts +60 -0
  191. package/connectors/zoom/src/search-filter.ts +1 -7
  192. package/connectors/zoom/src/server.ts +2 -94
  193. package/connectors/zoom/src/tools.ts +93 -0
  194. package/connectors/zotero/src/server.ts +2 -78
  195. package/connectors/zotero/src/tools.ts +67 -0
  196. package/package.json +14 -12
  197. package/shared/access-token-cache.ts +123 -0
  198. package/shared/atlassian-json-fetch.ts +6 -16
  199. package/shared/audit-chain.ts +13 -6
  200. package/shared/cli-json-kit.ts +150 -0
  201. package/shared/collection-tool-kit.ts +143 -0
  202. package/shared/connector-mode.ts +3 -2
  203. package/shared/cursor-list-tool.ts +29 -0
  204. package/shared/env-json-api.ts +209 -0
  205. package/shared/fetch-json-text.ts +27 -0
  206. package/shared/github-rest.ts +62 -0
  207. package/shared/graphql-json.ts +60 -0
  208. package/shared/imap-mail-core.ts +91 -0
  209. package/shared/imap-tool-kit.ts +57 -23
  210. package/shared/imapflow-adapter.ts +417 -0
  211. package/shared/rest-tool-kit.ts +77 -7
  212. package/shared/run-cli-json.ts +38 -16
  213. package/shared/search-filter.ts +58 -18
  214. package/shared/walk-files.ts +118 -0
  215. package/standalone/src/launcher.ts +19 -7
@@ -0,0 +1,209 @@
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
+ GH_ACCEPT,
6
+ GH_API,
7
+ GH_API_VERSION,
8
+ ghFetch,
9
+ ghQueryPath,
10
+ ghRepoPath,
11
+ setGhPaging,
12
+ } from "../../../shared/github-rest.ts";
13
+ import {
14
+ createRegisterSimpleTool,
15
+ createZodToolRegistrar,
16
+ mcpJsonResult as jsonResult,
17
+ requireProcessEnv,
18
+ } from "../../../shared/mcp-tool-kit.ts";
19
+ import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
20
+
21
+ /** Tool names exposed by this connector — for contract/introspection tests. */
22
+ export const GITHUB_ACTIONS_TOOL_NAMES = [
23
+ "gha_workflow_list",
24
+ "gha_run_list",
25
+ "gha_run_get",
26
+ "gha_run_jobs",
27
+ "gha_run_log",
28
+ "gha_run_trigger",
29
+ "gha_run_cancel",
30
+ ] as const;
31
+
32
+ export function registerGithubActionsTools(
33
+ server: ConsentServer & { tool: (...args: never) => unknown },
34
+ ): void {
35
+ const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
36
+
37
+ /**
38
+ * Every MUTATING github-actions tool goes through here. Outside the gateway this adds the
39
+ * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
40
+ * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
41
+ */
42
+ const registerWriteTool = createWriteToolRegistrar(server, {
43
+ connector: "github-actions",
44
+ scopeEnv: "NIMBUS_MCP_GITHUB_ACTIONS_WRITE_SCOPE",
45
+ scopeKinds: ["repo"],
46
+ });
47
+
48
+ /** Standard GitHub Actions tool: token → ghFetch(buildPath) → mcpJsonResultIfOk("GitHub Actions"). */
49
+ const registerGhaTool = makeRestToolRegistrar({
50
+ registrar: reg,
51
+ tokenEnv: "GITHUB_PAT",
52
+ serviceLabel: "GitHub Actions",
53
+ fetch: ghFetch,
54
+ });
55
+
56
+ const repoSlugArgs = z.object({
57
+ owner: z.string().min(1),
58
+ repo: z.string().min(1),
59
+ });
60
+
61
+ const runListSchema = repoSlugArgs.extend({
62
+ perPage: z.number().int().min(1).max(100).optional(),
63
+ page: z.number().int().min(1).optional(),
64
+ branch: z.string().optional(),
65
+ event: z.string().optional(),
66
+ status: z.string().optional(),
67
+ });
68
+
69
+ registerGhaTool(
70
+ "gha_workflow_list",
71
+ "List GitHub Actions workflows for a repository.",
72
+ repoSlugArgs.extend({
73
+ perPage: z.number().int().min(1).max(100).optional(),
74
+ page: z.number().int().min(1).optional(),
75
+ }),
76
+ (parsed) =>
77
+ ghQueryPath(`${ghRepoPath(parsed.owner, parsed.repo)}/actions/workflows`, (q) => {
78
+ setGhPaging(q, parsed);
79
+ }),
80
+ );
81
+
82
+ registerGhaTool("gha_run_list", "List workflow runs for a repository.", runListSchema, (parsed) =>
83
+ ghQueryPath(`${ghRepoPath(parsed.owner, parsed.repo)}/actions/runs`, (q) => {
84
+ setGhPaging(q, parsed);
85
+ if (parsed.branch !== undefined) {
86
+ q.set("branch", parsed.branch);
87
+ }
88
+ if (parsed.event !== undefined) {
89
+ q.set("event", parsed.event);
90
+ }
91
+ if (parsed.status !== undefined) {
92
+ q.set("status", parsed.status);
93
+ }
94
+ }),
95
+ );
96
+
97
+ const runIdSchema = repoSlugArgs.extend({
98
+ runId: z.number().int().min(1),
99
+ });
100
+
101
+ registerGhaTool(
102
+ "gha_run_get",
103
+ "Get a single workflow run by id.",
104
+ runIdSchema,
105
+ (parsed) => `${ghRepoPath(parsed.owner, parsed.repo)}/actions/runs/${String(parsed.runId)}`,
106
+ );
107
+
108
+ registerGhaTool(
109
+ "gha_run_jobs",
110
+ "List jobs for a workflow run.",
111
+ runIdSchema,
112
+ (parsed) =>
113
+ `${ghRepoPath(parsed.owner, parsed.repo)}/actions/runs/${String(parsed.runId)}/jobs`,
114
+ );
115
+
116
+ reg(
117
+ "gha_run_log",
118
+ "Download console log text for a job (truncated).",
119
+ repoSlugArgs.extend({
120
+ jobId: z.number().int().min(1),
121
+ maxChars: z.number().int().min(1000).max(500_000).optional(),
122
+ }),
123
+ async (parsed) => {
124
+ const token = requireProcessEnv("GITHUB_PAT");
125
+ const url = `${GH_API}${ghRepoPath(parsed.owner, parsed.repo)}/actions/jobs/${String(parsed.jobId)}/logs`;
126
+ const res = await fetch(url, {
127
+ headers: {
128
+ Accept: GH_ACCEPT,
129
+ Authorization: `Bearer ${token}`,
130
+ "X-GitHub-Api-Version": GH_API_VERSION,
131
+ },
132
+ redirect: "follow",
133
+ });
134
+ const text = await res.text();
135
+ if (!res.ok) {
136
+ throw new Error(`GitHub Actions logs ${String(res.status)}: ${text.slice(0, 400)}`);
137
+ }
138
+ const max = parsed.maxChars ?? 64_000;
139
+ const tail = text.length > max ? text.slice(-max) : text;
140
+ return jsonResult({
141
+ jobId: parsed.jobId,
142
+ truncated: text.length > max,
143
+ totalChars: text.length,
144
+ text: tail,
145
+ });
146
+ },
147
+ );
148
+
149
+ registerWriteTool(
150
+ "gha_run_trigger",
151
+ {
152
+ mutates: "github_actions.run.trigger",
153
+ recoverable: true,
154
+ scopeTargetOf: (p) => ({ kind: "repo", value: `${p.owner}/${p.repo}` }),
155
+ },
156
+ "Dispatch a workflow (workflow_dispatch).",
157
+ repoSlugArgs.extend({
158
+ workflowId: z
159
+ .string()
160
+ .min(1)
161
+ .describe("Numeric workflow id or workflow file name (e.g. ci.yml)"),
162
+ ref: z.string().min(1).optional(),
163
+ inputs: z.record(z.string(), z.string()).optional(),
164
+ }),
165
+ async (parsed) => {
166
+ const token = requireProcessEnv("GITHUB_PAT");
167
+ const encWf = encodeURIComponent(parsed.workflowId);
168
+ const path = `${ghRepoPath(parsed.owner, parsed.repo)}/actions/workflows/${encWf}/dispatches`;
169
+ const ref = parsed.ref ?? "main";
170
+ const res = await ghFetch(token, path, {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/json" },
173
+ body: JSON.stringify({
174
+ ref,
175
+ inputs: parsed.inputs ?? {},
176
+ }),
177
+ });
178
+ if (!res.ok) {
179
+ throw new Error(`GitHub Actions dispatch ${String(res.status)}: ${res.text.slice(0, 400)}`);
180
+ }
181
+ return jsonResult({
182
+ ok: true,
183
+ owner: parsed.owner,
184
+ repo: parsed.repo,
185
+ workflowId: parsed.workflowId,
186
+ });
187
+ },
188
+ );
189
+
190
+ registerWriteTool(
191
+ "gha_run_cancel",
192
+ {
193
+ mutates: "github_actions.run.cancel",
194
+ recoverable: true,
195
+ scopeTargetOf: (p) => ({ kind: "repo", value: `${p.owner}/${p.repo}` }),
196
+ },
197
+ "Cancel a workflow run.",
198
+ runIdSchema,
199
+ async (parsed) => {
200
+ const token = requireProcessEnv("GITHUB_PAT");
201
+ const path = `${ghRepoPath(parsed.owner, parsed.repo)}/actions/runs/${String(parsed.runId)}/cancel`;
202
+ const res = await ghFetch(token, path, { method: "POST" });
203
+ if (!res.ok) {
204
+ throw new Error(`GitHub Actions cancel ${String(res.status)}: ${res.text.slice(0, 400)}`);
205
+ }
206
+ return jsonResult({ ok: true, runId: parsed.runId });
207
+ },
208
+ );
209
+ }
@@ -1,392 +1,10 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { z } from "zod";
4
- import { createWriteToolRegistrar, type WriteToolConfig } from "../../../shared/consent-kit.ts";
5
- import {
6
- createRegisterSimpleTool,
7
- createZodToolRegistrar,
8
- mcpJsonResult as jsonResult,
9
- mcpJsonResultIfOk,
10
- requireProcessEnv,
11
- type ZodObjectSchema,
12
- } from "../../../shared/mcp-tool-kit.ts";
13
- import { stripTrailingSlashes } from "../../../shared/strip-trailing-slashes.ts";
14
-
15
- function apiBase(): string {
16
- const b = process.env["GITLAB_API_BASE_URL"];
17
- if (b !== undefined && b.trim() !== "") {
18
- return stripTrailingSlashes(b);
19
- }
20
- return "https://gitlab.com/api/v4";
21
- }
22
-
23
- async function glFetch(
24
- token: string,
25
- path: string,
26
- init?: RequestInit,
27
- ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
28
- const base = apiBase();
29
- const relativePath = path.startsWith("/") ? path : `/${path}`;
30
- const url = path.startsWith("http") ? path : `${base}${relativePath}`;
31
- const baseHeaders: Record<string, string> = { "PRIVATE-TOKEN": token };
32
- const mergedHeaders =
33
- init?.headers === undefined
34
- ? baseHeaders
35
- : { ...baseHeaders, ...(init.headers as Record<string, string>) };
36
- const res = await fetch(url, {
37
- ...init,
38
- headers: mergedHeaders,
39
- });
40
- const text = await res.text();
41
- let json: unknown;
42
- try {
43
- json = JSON.parse(text) as unknown;
44
- } catch {
45
- json = null;
46
- }
47
- return { ok: res.ok, status: res.status, json, text };
48
- }
3
+ import { registerGitlabTools } from "./tools.ts";
49
4
 
50
5
  const server = new McpServer({ name: "nimbus-gitlab", version: "0.1.0" });
51
6
 
52
- const registerSimpleTool = createRegisterSimpleTool(server);
53
- const reg = createZodToolRegistrar(registerSimpleTool);
54
-
55
- /**
56
- * Register a standard GitLab read/write tool whose body is the repeated shape
57
- * `token → glFetch(buildUrl[, buildInit]) → mcpJsonResultIfOk("GitLab", res)`.
58
- * `buildUrl` returns the relative path (or absolute URL) and `buildInit` the
59
- * optional fetch init (method/body). Tools with a non-standard tail (raw text
60
- * trace, custom error text) stay hand-written below.
61
- */
62
- /**
63
- * Every MUTATING gitlab tool goes through here. Outside the gateway this adds the
64
- * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
65
- * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
66
- */
67
- const registerWriteTool = createWriteToolRegistrar(server, {
68
- connector: "gitlab",
69
- scopeEnv: "NIMBUS_MCP_GITLAB_WRITE_SCOPE",
70
- scopeKinds: ["repo"],
71
- });
72
-
73
- /**
74
- * The write-tool equivalent of `registerGitlabTool`: identical fetch and result handling, routed
75
- * through the write registrar. `scopeTargetOf` is supplied here rather than per tool — every
76
- * GitLab mutation is scoped to one project — so a new write tool cannot forget it.
77
- */
78
- function registerGitlabWriteTool<T extends { projectPath: string }>(
79
- name: string,
80
- cfg: Omit<WriteToolConfig<T>, "scopeTargetOf">,
81
- description: string,
82
- schema: ZodObjectSchema<T>,
83
- buildUrl: (p: T) => string,
84
- buildInit?: (p: T) => RequestInit,
85
- ): void {
86
- registerWriteTool(
87
- name,
88
- { ...cfg, scopeTargetOf: (p) => ({ kind: "repo", value: p.projectPath }) },
89
- description,
90
- schema,
91
- async (parsed) => {
92
- const token = requireProcessEnv("GITLAB_PAT");
93
- const res = await glFetch(token, buildUrl(parsed), buildInit?.(parsed));
94
- return mcpJsonResultIfOk("GitLab", res);
95
- },
96
- );
97
- }
98
-
99
- function registerGitlabTool<T>(
100
- name: string,
101
- description: string,
102
- schema: ZodObjectSchema<T>,
103
- buildUrl: (p: T) => string,
104
- buildInit?: (p: T) => RequestInit,
105
- ): void {
106
- reg(name, description, schema, async (parsed) => {
107
- const token = requireProcessEnv("GITLAB_PAT");
108
- const res = await glFetch(token, buildUrl(parsed), buildInit?.(parsed));
109
- return mcpJsonResultIfOk("GitLab", res);
110
- });
111
- }
112
-
113
- const projectPathArg = z.object({
114
- projectPath: z
115
- .string()
116
- .min(1)
117
- .describe("URL-encoded path or numeric project id, e.g. group/repo"),
118
- });
119
-
120
- const gitlabProjectListSchema = z.object({
121
- perPage: z.number().int().min(1).max(100).optional(),
122
- page: z.number().int().min(1).optional(),
123
- });
124
-
125
- registerGitlabTool(
126
- "gitlab_project_list",
127
- "List projects visible to the authenticated user (membership).",
128
- gitlabProjectListSchema,
129
- (parsed) => {
130
- const u = new URL(`${apiBase()}/projects`);
131
- u.searchParams.set("membership", "true");
132
- u.searchParams.set("order_by", "last_activity_at");
133
- u.searchParams.set("sort", "desc");
134
- u.searchParams.set("per_page", String(parsed.perPage ?? 30));
135
- if (parsed.page !== undefined) {
136
- u.searchParams.set("page", String(parsed.page));
137
- }
138
- // Absolute URL: u already includes apiBase()'s `/api/v4`; returning a
139
- // relative path would let glFetch re-prefix apiBase() → `/api/v4/api/v4/…`.
140
- return u.toString();
141
- },
142
- );
143
-
144
- const gitlabMrListSchema = projectPathArg.extend({
145
- state: z.enum(["opened", "closed", "locked", "merged", "all"]).optional(),
146
- perPage: z.number().int().min(1).max(100).optional(),
147
- page: z.number().int().min(1).optional(),
148
- });
149
-
150
- registerGitlabTool(
151
- "gitlab_mr_list",
152
- "List merge requests for a project.",
153
- gitlabMrListSchema,
154
- (parsed) => {
155
- const enc = encodeURIComponent(parsed.projectPath);
156
- const u = new URL(`${apiBase()}/projects/${enc}/merge_requests`);
157
- u.searchParams.set("state", parsed.state ?? "opened");
158
- u.searchParams.set("per_page", String(parsed.perPage ?? 30));
159
- if (parsed.page !== undefined) {
160
- u.searchParams.set("page", String(parsed.page));
161
- }
162
- // Absolute URL: u already includes apiBase()'s `/api/v4`; returning a
163
- // relative path would let glFetch re-prefix apiBase() → `/api/v4/api/v4/…`.
164
- return u.toString();
165
- },
166
- );
167
-
168
- const gitlabMrGetSchema = projectPathArg.extend({
169
- mergeRequestIid: z.number().int().min(1),
170
- });
171
-
172
- registerGitlabTool(
173
- "gitlab_mr_get",
174
- "Get a single merge request by IID.",
175
- gitlabMrGetSchema,
176
- (parsed) =>
177
- `/projects/${encodeURIComponent(parsed.projectPath)}/merge_requests/${String(parsed.mergeRequestIid)}`,
178
- );
179
-
180
- const gitlabMrMergeSchema = projectPathArg.extend({
181
- mergeRequestIid: z.number().int().min(1),
182
- mergeCommitMessage: z.string().max(10_000).optional(),
183
- squash: z.boolean().optional(),
184
- shouldRemoveSourceBranch: z.boolean().optional(),
185
- });
186
-
187
- registerGitlabWriteTool(
188
- "gitlab_mr_merge",
189
- {
190
- mutates: "gitlab.mr.merge",
191
- recoverable: true,
192
- },
193
- "Merge a merge request.",
194
- gitlabMrMergeSchema,
195
- (parsed) =>
196
- `/projects/${encodeURIComponent(parsed.projectPath)}/merge_requests/${String(parsed.mergeRequestIid)}/merge`,
197
- (parsed) => {
198
- const body: Record<string, unknown> = {};
199
- if (parsed.mergeCommitMessage !== undefined) {
200
- body["merge_commit_message"] = parsed.mergeCommitMessage;
201
- }
202
- if (parsed.squash !== undefined) {
203
- body["squash"] = parsed.squash;
204
- }
205
- if (parsed.shouldRemoveSourceBranch !== undefined) {
206
- body["should_remove_source_branch"] = parsed.shouldRemoveSourceBranch;
207
- }
208
- return {
209
- method: "PUT",
210
- headers: { "Content-Type": "application/json" },
211
- body: JSON.stringify(body),
212
- };
213
- },
214
- );
215
-
216
- const gitlabIssueListSchema = projectPathArg.extend({
217
- state: z.enum(["opened", "closed", "all"]).optional(),
218
- perPage: z.number().int().min(1).max(100).optional(),
219
- page: z.number().int().min(1).optional(),
220
- });
221
-
222
- registerGitlabTool(
223
- "gitlab_issue_list",
224
- "List issues for a project.",
225
- gitlabIssueListSchema,
226
- (parsed) => {
227
- const enc = encodeURIComponent(parsed.projectPath);
228
- const u = new URL(`${apiBase()}/projects/${enc}/issues`);
229
- u.searchParams.set("state", parsed.state ?? "opened");
230
- u.searchParams.set("per_page", String(parsed.perPage ?? 30));
231
- if (parsed.page !== undefined) {
232
- u.searchParams.set("page", String(parsed.page));
233
- }
234
- // Absolute URL: u already includes apiBase()'s `/api/v4`; returning a
235
- // relative path would let glFetch re-prefix apiBase() → `/api/v4/api/v4/…`.
236
- return u.toString();
237
- },
238
- );
239
-
240
- const gitlabIssueGetSchema = projectPathArg.extend({
241
- issueIid: z.number().int().min(1),
242
- });
243
-
244
- registerGitlabTool(
245
- "gitlab_issue_get",
246
- "Get a single issue by IID.",
247
- gitlabIssueGetSchema,
248
- (parsed) =>
249
- `/projects/${encodeURIComponent(parsed.projectPath)}/issues/${String(parsed.issueIid)}`,
250
- );
251
-
252
- const gitlabPipelineListSchema = projectPathArg.extend({
253
- ref: z.string().max(500).optional(),
254
- status: z.string().max(64).optional(),
255
- perPage: z.number().int().min(1).max(100).optional(),
256
- page: z.number().int().min(1).optional(),
257
- });
258
-
259
- registerGitlabTool(
260
- "gitlab_pipeline_list",
261
- "List CI pipelines for a project.",
262
- gitlabPipelineListSchema,
263
- (parsed) => {
264
- const enc = encodeURIComponent(parsed.projectPath);
265
- const u = new URL(`${apiBase()}/projects/${enc}/pipelines`);
266
- u.searchParams.set("per_page", String(parsed.perPage ?? 30));
267
- if (parsed.page !== undefined) {
268
- u.searchParams.set("page", String(parsed.page));
269
- }
270
- if (parsed.ref !== undefined) {
271
- u.searchParams.set("ref", parsed.ref);
272
- }
273
- if (parsed.status !== undefined) {
274
- u.searchParams.set("status", parsed.status);
275
- }
276
- // Absolute URL: u already includes apiBase()'s `/api/v4`; returning a
277
- // relative path would let glFetch re-prefix apiBase() → `/api/v4/api/v4/…`.
278
- return u.toString();
279
- },
280
- );
281
-
282
- const gitlabPipelineGetSchema = projectPathArg.extend({
283
- pipelineId: z.number().int().min(1),
284
- });
285
-
286
- registerGitlabTool(
287
- "gitlab_pipeline_get",
288
- "Get a single pipeline by id.",
289
- gitlabPipelineGetSchema,
290
- (parsed) =>
291
- `/projects/${encodeURIComponent(parsed.projectPath)}/pipelines/${String(parsed.pipelineId)}`,
292
- );
293
-
294
- registerGitlabTool(
295
- "gitlab_pipeline_jobs_get",
296
- "List jobs for a CI pipeline (by pipeline id).",
297
- gitlabPipelineGetSchema,
298
- (parsed) =>
299
- `/projects/${encodeURIComponent(parsed.projectPath)}/pipelines/${String(parsed.pipelineId)}/jobs`,
300
- );
301
-
302
- const gitlabJobTraceSchema = projectPathArg.extend({
303
- jobId: z.number().int().min(1),
304
- });
305
-
306
- reg(
307
- "gitlab_job_trace",
308
- "Download plain-text trace for a CI job (by job id).",
309
- gitlabJobTraceSchema,
310
- async (parsed) => {
311
- const token = requireProcessEnv("GITLAB_PAT");
312
- const enc = encodeURIComponent(parsed.projectPath);
313
- const url = `${apiBase()}/projects/${enc}/jobs/${String(parsed.jobId)}/trace`;
314
- const res = await fetch(url, { headers: { "PRIVATE-TOKEN": token } });
315
- const text = await res.text();
316
- if (!res.ok) {
317
- throw new Error(`GitLab ${String(res.status)}: ${text.slice(0, 300)}`);
318
- }
319
- return jsonResult({ trace: text });
320
- },
321
- );
322
-
323
- reg(
324
- "gitlab_job_log_tail",
325
- "Download job trace text, optionally keeping only the last N characters (tail).",
326
- gitlabJobTraceSchema.extend({
327
- maxChars: z.number().int().min(1000).max(500_000).optional(),
328
- }),
329
- async (parsed) => {
330
- const token = requireProcessEnv("GITLAB_PAT");
331
- const enc = encodeURIComponent(parsed.projectPath);
332
- const url = `${apiBase()}/projects/${enc}/jobs/${String(parsed.jobId)}/trace`;
333
- const res = await fetch(url, { headers: { "PRIVATE-TOKEN": token } });
334
- const text = await res.text();
335
- if (!res.ok) {
336
- throw new Error(`GitLab ${String(res.status)}: ${text.slice(0, 300)}`);
337
- }
338
- const max = parsed.maxChars ?? 64_000;
339
- const tail = text.length > max ? text.slice(-max) : text;
340
- return jsonResult({
341
- jobId: parsed.jobId,
342
- truncated: text.length > max,
343
- totalChars: text.length,
344
- trace: tail,
345
- });
346
- },
347
- );
348
-
349
- registerWriteTool(
350
- "gitlab_pipeline_retry",
351
- {
352
- mutates: "gitlab.pipeline.retry",
353
- recoverable: true,
354
- scopeTargetOf: (p) => ({ kind: "repo", value: p.projectPath }),
355
- },
356
- "Retry failed jobs in a pipeline.",
357
- gitlabPipelineGetSchema,
358
- async (parsed) => {
359
- const token = requireProcessEnv("GITLAB_PAT");
360
- const enc = encodeURIComponent(parsed.projectPath);
361
- const path = `/projects/${enc}/pipelines/${String(parsed.pipelineId)}/retry`;
362
- const res = await glFetch(token, path, { method: "POST" });
363
- if (!res.ok) {
364
- throw new Error(`GitLab pipeline retry ${String(res.status)}: ${res.text.slice(0, 400)}`);
365
- }
366
- return mcpJsonResultIfOk("GitLab", res);
367
- },
368
- );
369
-
370
- registerWriteTool(
371
- "gitlab_pipeline_cancel",
372
- {
373
- mutates: "gitlab.pipeline.cancel",
374
- recoverable: true,
375
- scopeTargetOf: (p) => ({ kind: "repo", value: p.projectPath }),
376
- },
377
- "Cancel a pipeline.",
378
- gitlabPipelineGetSchema,
379
- async (parsed) => {
380
- const token = requireProcessEnv("GITLAB_PAT");
381
- const enc = encodeURIComponent(parsed.projectPath);
382
- const path = `/projects/${enc}/pipelines/${String(parsed.pipelineId)}/cancel`;
383
- const res = await glFetch(token, path, { method: "POST" });
384
- if (!res.ok) {
385
- throw new Error(`GitLab pipeline cancel ${String(res.status)}: ${res.text.slice(0, 400)}`);
386
- }
387
- return mcpJsonResultIfOk("GitLab", res);
388
- },
389
- );
7
+ registerGitlabTools(server);
390
8
 
391
9
  const transport = new StdioServerTransport();
392
10
  await server.connect(transport);