@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,119 +1,6 @@
1
- import { z } from "zod";
2
- import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
3
- import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
4
1
  import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
5
- import { filterSupersetDashboards } from "./search-filter.ts";
6
-
7
- function trimTrailingSlash(s: string): string {
8
- return s.endsWith("/") ? s.slice(0, -1) : s;
9
- }
10
-
11
- function apiBase(): string {
12
- const v = process.env["SUPERSET_URL"]?.trim();
13
- if (v === undefined || v === "") {
14
- throw new Error("SUPERSET_URL is not set");
15
- }
16
- return trimTrailingSlash(v);
17
- }
18
-
19
- function requiredEnv(name: string): string {
20
- const v = process.env[name]?.trim();
21
- if (v === undefined || v === "") {
22
- throw new Error(`${name} is not set`);
23
- }
24
- return v;
25
- }
26
-
27
- let cachedToken: string | null = null;
28
-
29
- async function login(): Promise<string> {
30
- if (cachedToken !== null) {
31
- return cachedToken;
32
- }
33
- const username = requiredEnv("SUPERSET_USERNAME");
34
- const password = requiredEnv("SUPERSET_PASSWORD");
35
- const res = await fetch(`${apiBase()}/api/v1/security/login`, {
36
- method: "POST",
37
- headers: { "Content-Type": "application/json", Accept: "application/json" },
38
- body: JSON.stringify({ username, password, provider: "db", refresh: true }),
39
- });
40
- const text = await res.text();
41
- if (!res.ok) {
42
- throw new Error(`Superset login ${String(res.status)}: ${text.slice(0, 400)}`);
43
- }
44
- let parsed: { access_token?: unknown };
45
- try {
46
- parsed = JSON.parse(text) as { access_token?: unknown };
47
- } catch {
48
- throw new Error("Superset login: invalid JSON response");
49
- }
50
- if (typeof parsed.access_token !== "string" || parsed.access_token === "") {
51
- throw new Error("Superset login: no access_token in response");
52
- }
53
- cachedToken = parsed.access_token;
54
- return cachedToken;
55
- }
56
-
57
- async function supersetGet(path: string): Promise<unknown> {
58
- const token = await login();
59
- const res = await fetch(`${apiBase()}${path}`, {
60
- headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
61
- });
62
- const text = await res.text();
63
- if (!res.ok) {
64
- throw new Error(`Superset ${String(res.status)}: ${text.slice(0, 400)}`);
65
- }
66
- return JSON.parse(text) as unknown;
67
- }
68
-
69
- function dashboardsFrom(root: unknown): unknown[] {
70
- const result = (root as { result?: unknown } | null)?.result;
71
- if (Array.isArray(result)) {
72
- return result;
73
- }
74
- return Array.isArray(root) ? root : [];
75
- }
76
-
77
- function dashboardListPath(page: number, pageSize: number): string {
78
- const q = encodeURIComponent(`(page:${String(page)},page_size:${String(pageSize)})`);
79
- return `/api/v1/dashboard/?q=${q}`;
80
- }
2
+ import { registerSupersetTools } from "./tools.ts";
81
3
 
82
4
  await runReadOnlyMcpConnector("nimbus-superset", (reg) => {
83
- reg(
84
- "superset_list",
85
- "List Apache Superset dashboards (`GET /api/v1/dashboard/`). `limit` (default 100) caps the page_size of the single first-page request. Returns the `.result` array from the Superset envelope.",
86
- z.object({
87
- limit: z.number().int().min(1).max(500).optional(),
88
- }),
89
- async (p) => {
90
- const root = await supersetGet(dashboardListPath(0, p.limit ?? 100));
91
- return jsonResult(dashboardsFrom(root));
92
- },
93
- );
94
-
95
- reg(
96
- "superset_get",
97
- "Fetch one Apache Superset dashboard by numeric id (`/api/v1/dashboard/{id}`). Throws when no dashboard with that id exists.",
98
- z.object({
99
- id: z.number().int(),
100
- }),
101
- async (p) => {
102
- return jsonResult(await supersetGet(`/api/v1/dashboard/${encodeURIComponent(String(p.id))}`));
103
- },
104
- );
105
-
106
- reg(
107
- "superset_search",
108
- "Substring search across Apache Superset dashboards. Matches the query (case-insensitive) against dashboard title and slug. Returns a `{ matches: [...] }` envelope.",
109
- searchToolInputSchema(200),
110
- async (p) => {
111
- const root = await supersetGet(dashboardListPath(0, 500));
112
- const matches = filterSupersetDashboards(dashboardsFrom(root), {
113
- query: p.query,
114
- limit: p.limit,
115
- });
116
- return jsonResult({ matches });
117
- },
118
- );
5
+ registerSupersetTools(reg);
119
6
  });
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { createAccessTokenCache } from "../../../shared/access-token-cache.ts";
3
+ import { requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
4
+ import { searchToolInputSchema } 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 { filterSupersetDashboards } from "./search-filter.ts";
8
+
9
+ function apiBase(): string {
10
+ return requiredBaseUrl("SUPERSET_URL");
11
+ }
12
+
13
+ /** Superset issues its own JWT from a username/password POST, not OAuth. */
14
+ const login = createAccessTokenCache({
15
+ label: "Superset login",
16
+ exchange: async () => {
17
+ const username = requiredEnv("SUPERSET_USERNAME");
18
+ const password = requiredEnv("SUPERSET_PASSWORD");
19
+ const res = await fetch(`${apiBase()}/api/v1/security/login`, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
22
+ body: JSON.stringify({ username, password, provider: "db", refresh: true }),
23
+ });
24
+ return { ok: res.ok, status: res.status, text: await res.text() };
25
+ },
26
+ });
27
+
28
+ /** Forget the login token. Tests only: the cache otherwise outlives the test that filled it. */
29
+ export function __resetSupersetLoginForTests(): void {
30
+ login.clear();
31
+ }
32
+
33
+ async function supersetGet(path: string): Promise<unknown> {
34
+ const token = await login();
35
+ const res = await fetch(`${apiBase()}${path}`, {
36
+ headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
37
+ });
38
+ const text = await res.text();
39
+ if (!res.ok) {
40
+ throw new Error(`Superset ${String(res.status)}: ${text.slice(0, 400)}`);
41
+ }
42
+ return JSON.parse(text) as unknown;
43
+ }
44
+
45
+ function dashboardsFrom(root: unknown): unknown[] {
46
+ const result = (root as { result?: unknown } | null)?.result;
47
+ if (Array.isArray(result)) {
48
+ return result;
49
+ }
50
+ return Array.isArray(root) ? root : [];
51
+ }
52
+
53
+ function dashboardListPath(page: number, pageSize: number): string {
54
+ const q = encodeURIComponent(`(page:${String(page)},page_size:${String(pageSize)})`);
55
+ return `/api/v1/dashboard/?q=${q}`;
56
+ }
57
+
58
+ /** Tool names exposed by this connector — for contract/introspection tests. */
59
+ export const SUPERSET_TOOL_NAMES = ["superset_get", "superset_list", "superset_search"] as const;
60
+
61
+ export function registerSupersetTools(reg: ZodToolRegistrar): void {
62
+ reg(
63
+ "superset_list",
64
+ "List Apache Superset dashboards (`GET /api/v1/dashboard/`). `limit` (default 100) caps the page_size of the single first-page request. Returns the `.result` array from the Superset envelope.",
65
+ z.object({
66
+ limit: z.number().int().min(1).max(500).optional(),
67
+ }),
68
+ async (p) => {
69
+ const root = await supersetGet(dashboardListPath(0, p.limit ?? 100));
70
+ return jsonResult(dashboardsFrom(root));
71
+ },
72
+ );
73
+
74
+ reg(
75
+ "superset_get",
76
+ "Fetch one Apache Superset dashboard by numeric id (`/api/v1/dashboard/{id}`). Throws when no dashboard with that id exists.",
77
+ z.object({
78
+ id: z.number().int(),
79
+ }),
80
+ async (p) => {
81
+ return jsonResult(await supersetGet(`/api/v1/dashboard/${encodeURIComponent(String(p.id))}`));
82
+ },
83
+ );
84
+
85
+ reg(
86
+ "superset_search",
87
+ "Substring search across Apache Superset dashboards. Matches the query (case-insensitive) against dashboard title and slug. Returns a `{ matches: [...] }` envelope.",
88
+ searchToolInputSchema(200),
89
+ async (p) => {
90
+ const root = await supersetGet(dashboardListPath(0, 500));
91
+ const matches = filterSupersetDashboards(dashboardsFrom(root), {
92
+ query: p.query,
93
+ limit: p.limit,
94
+ });
95
+ return jsonResult({ matches });
96
+ },
97
+ );
98
+ }
@@ -1,22 +1,9 @@
1
1
  import {
2
- asRecord,
3
2
  makeQueryFilter,
3
+ recordFieldsFromKeys,
4
4
  type SearchMatchOptions,
5
5
  } from "../../../shared/search-filter.ts";
6
6
 
7
7
  export type TableauSearchMatchOptions = SearchMatchOptions;
8
8
 
9
- function stringAt(row: Record<string, unknown>, key: string): string {
10
- const v = row[key];
11
- return typeof v === "string" ? v : "";
12
- }
13
-
14
- function fieldsOf(item: unknown): readonly string[] | null {
15
- const row = asRecord(item);
16
- if (row === undefined) {
17
- return null;
18
- }
19
- return [stringAt(row, "name"), stringAt(row, "luid")];
20
- }
21
-
22
- export const filterTableauViews = makeQueryFilter(fieldsOf);
9
+ export const filterTableauViews = makeQueryFilter(recordFieldsFromKeys(["name", "luid"]));
@@ -1,39 +1,18 @@
1
1
  import { z } from "zod";
2
2
  import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
3
+ import { cursorListInputSchema } from "../../../shared/cursor-list-tool.ts";
4
+ import { requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
3
5
  import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
4
6
  import { fetchWithTimeout, mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
5
7
  import {
6
8
  runReadOnlyMcpConnector,
7
9
  type ZodToolRegistrar,
8
10
  } from "../../../shared/run-read-only-mcp-connector.ts";
11
+ import { asRecord } from "../../../shared/search-filter.ts";
9
12
  import { filterTableauViews } from "./search-filter.ts";
10
13
 
11
- function trimTrailingSlash(s: string): string {
12
- return s.endsWith("/") ? s.slice(0, -1) : s;
13
- }
14
-
15
14
  function apiBase(): string {
16
- const v = process.env["TABLEAU_URL"]?.trim();
17
- if (v === undefined || v === "") {
18
- throw new Error("TABLEAU_URL is not set");
19
- }
20
- return trimTrailingSlash(v);
21
- }
22
-
23
- function patName(): string {
24
- const v = process.env["TABLEAU_PAT_NAME"]?.trim();
25
- if (v === undefined || v === "") {
26
- throw new Error("TABLEAU_PAT_NAME is not set");
27
- }
28
- return v;
29
- }
30
-
31
- function patSecret(): string {
32
- const v = process.env["TABLEAU_PAT_SECRET"]?.trim();
33
- if (v === undefined || v === "") {
34
- throw new Error("TABLEAU_PAT_SECRET is not set");
35
- }
36
- return v;
15
+ return requiredBaseUrl("TABLEAU_URL");
37
16
  }
38
17
 
39
18
  interface SigninResult {
@@ -48,8 +27,8 @@ async function tableauSignin(): Promise<SigninResult> {
48
27
  headers: { "Content-Type": "application/json", Accept: "application/json" },
49
28
  body: JSON.stringify({
50
29
  credentials: {
51
- personalAccessTokenName: patName(),
52
- personalAccessTokenSecret: patSecret(),
30
+ personalAccessTokenName: requiredEnv("TABLEAU_PAT_NAME"),
31
+ personalAccessTokenSecret: requiredEnv("TABLEAU_PAT_SECRET"),
53
32
  site: { contentUrl: "" },
54
33
  },
55
34
  }),
@@ -58,22 +37,9 @@ async function tableauSignin(): Promise<SigninResult> {
58
37
  if (!res.ok) {
59
38
  throw new Error(`Tableau signin ${String(res.status)}: ${text.slice(0, 400)}`);
60
39
  }
61
- const parsed = JSON.parse(text) as unknown;
62
- const root =
63
- parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
64
- ? (parsed as Record<string, unknown>)
65
- : null;
66
- const creds = root?.["credentials"];
67
- const credsObj =
68
- creds !== null && typeof creds === "object" && !Array.isArray(creds)
69
- ? (creds as Record<string, unknown>)
70
- : null;
40
+ const credsObj = asRecord(asRecord(JSON.parse(text) as unknown)?.["credentials"]);
71
41
  const token = typeof credsObj?.["token"] === "string" ? credsObj["token"] : null;
72
- const site = credsObj?.["site"];
73
- const siteObj =
74
- site !== null && typeof site === "object" && !Array.isArray(site)
75
- ? (site as Record<string, unknown>)
76
- : null;
42
+ const siteObj = asRecord(credsObj?.["site"]);
77
43
  const siteId = typeof siteObj?.["id"] === "string" ? siteObj["id"] : null;
78
44
  if (token === null || siteId === null) {
79
45
  throw new Error("Tableau signin response missing credentials.token or credentials.site.id");
@@ -101,21 +67,9 @@ async function listViews(
101
67
  if (!res.ok) {
102
68
  throw new Error(`Tableau views ${String(res.status)}: ${text.slice(0, 400)}`);
103
69
  }
104
- const parsed = JSON.parse(text) as unknown;
105
- const root =
106
- parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
107
- ? (parsed as Record<string, unknown>)
108
- : null;
109
- const views = root?.["views"];
110
- const viewsObj =
111
- views !== null && typeof views === "object" && !Array.isArray(views)
112
- ? (views as Record<string, unknown>)
113
- : null;
114
- const pagination = root?.["pagination"];
115
- const paginationObj =
116
- pagination !== null && typeof pagination === "object" && !Array.isArray(pagination)
117
- ? (pagination as Record<string, unknown>)
118
- : null;
70
+ const root = asRecord(JSON.parse(text) as unknown);
71
+ const viewsObj = asRecord(root?.["views"]);
72
+ const paginationObj = asRecord(root?.["pagination"]);
119
73
  return {
120
74
  views: Array.isArray(viewsObj?.["view"]) ? (viewsObj["view"] as unknown[]) : [],
121
75
  totalAvailable: Number(paginationObj?.["totalAvailable"]) || 0,
@@ -162,10 +116,7 @@ export function registerTableauTools(reg: ZodToolRegistrar, server: unknown): vo
162
116
  reg(
163
117
  "tableau_list",
164
118
  "List Tableau views/dashboards (`GET /api/3.4/sites/{siteId}/views`). Requires a PAT sign-in first. Paginated (1-based): `cursor` (page number) + `limit` (default 200, max 500) → `{ items, nextCursor }`.",
165
- z.object({
166
- cursor: z.string().nullable().optional(),
167
- limit: z.number().int().min(1).max(500).optional(),
168
- }),
119
+ cursorListInputSchema(),
169
120
  async (p) => {
170
121
  const pageSize = p.limit ?? 200;
171
122
  // 1-based: null / "" / "0" / non-numeric / negative / fractional all resolve to page 1
@@ -189,10 +140,7 @@ export function registerTableauTools(reg: ZodToolRegistrar, server: unknown): vo
189
140
  const { token, siteId } = await tableauSignin();
190
141
  const { views } = await listViews(token, siteId);
191
142
  const found = views.find((v) => {
192
- const obj =
193
- v !== null && typeof v === "object" && !Array.isArray(v)
194
- ? (v as Record<string, unknown>)
195
- : null;
143
+ const obj = asRecord(v);
196
144
  return obj?.["luid"] === p.id || obj?.["id"] === p.id;
197
145
  });
198
146
  if (found === undefined) {
@@ -1,257 +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
- fetchBearerAuthorizedJson,
7
- resolveUrlWithBase,
8
- } from "../../../shared/fetch-bearer-json.ts";
9
- import {
10
- createRegisterSimpleTool,
11
- createZodToolRegistrar,
12
- type McpListResult,
13
- mcpJsonResultIfOk,
14
- requireProcessEnv,
15
- } from "../../../shared/mcp-tool-kit.ts";
16
- import { teamsBotSendActivity } from "./bot-send.ts";
17
-
18
- const GRAPH = "https://graph.microsoft.com/v1.0";
19
-
20
- async function graphRequest(
21
- token: string,
22
- pathOrUrl: string,
23
- init?: RequestInit,
24
- ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
25
- const url = resolveUrlWithBase(GRAPH, pathOrUrl);
26
- return fetchBearerAuthorizedJson(url, token, init);
27
- }
28
-
29
- async function teamsPagedGraph(
30
- token: string,
31
- nextLink: string | undefined,
32
- initialPath: string,
33
- ): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
34
- const path = nextLink !== undefined && nextLink !== "" ? nextLink : initialPath;
35
- return graphRequest(token, path);
36
- }
37
-
38
- function graphListResult(r: {
39
- ok: boolean;
40
- status: number;
41
- json: unknown;
42
- text: string;
43
- }): McpListResult {
44
- return mcpJsonResultIfOk("Graph", r, 200);
45
- }
3
+ import { registerTeamsTools } from "./tools.ts";
46
4
 
47
5
  const server = new McpServer({ name: "nimbus-teams", version: "0.1.0" });
48
6
 
49
- const registerSimpleTool = createRegisterSimpleTool(server);
50
-
51
- import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
52
-
53
- const reg = createZodToolRegistrar(registerSimpleTool);
54
-
55
- /**
56
- * Every MUTATING teams tool goes through here. Outside the gateway this adds the
57
- * consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
58
- * the gateway it is a pass-through, because executor.ts (I2) is the gate there.
59
- */
60
- const registerWriteTool = createWriteToolRegistrar(server, {
61
- connector: "teams",
62
- scopeEnv: "NIMBUS_MCP_TEAMS_WRITE_SCOPE",
63
- scopeKinds: ["channel", "chat"],
64
- });
65
-
66
- const teamsTeamListSchema = z.object({
67
- top: z.number().int().min(1).max(100).optional(),
68
- nextLink: z.url().optional(),
69
- });
70
-
71
- reg(
72
- "teams_team_list",
73
- "List Microsoft Teams the signed-in user has joined (pagination via nextLink).",
74
- teamsTeamListSchema,
75
- async (parsed) => {
76
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
77
- const initial = `/me/joinedTeams?$top=${String(parsed.top ?? 50)}`;
78
- const r = await teamsPagedGraph(token, parsed.nextLink, initial);
79
- return graphListResult(r);
80
- },
81
- );
82
-
83
- const teamsChannelListSchema = z.object({
84
- teamId: z.string().min(1),
85
- top: z.number().int().min(1).max(100).optional(),
86
- nextLink: z.url().optional(),
87
- });
88
-
89
- reg(
90
- "teams_channel_list",
91
- "List channels in a team (standard + private the app can read).",
92
- teamsChannelListSchema,
93
- async (parsed) => {
94
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
95
- const tid = encodeURIComponent(parsed.teamId);
96
- const initial = `/teams/${tid}/channels?$top=${String(parsed.top ?? 50)}`;
97
- const r = await teamsPagedGraph(token, parsed.nextLink, initial);
98
- return graphListResult(r);
99
- },
100
- );
101
-
102
- const teamsChannelMessagesSchema = z.object({
103
- teamId: z.string().min(1),
104
- channelId: z.string().min(1),
105
- top: z.number().int().min(1).max(50).optional(),
106
- nextLink: z.url().optional(),
107
- });
108
-
109
- reg(
110
- "teams_channel_messages",
111
- "List recent messages in a team channel (not delta; for interactive reads).",
112
- teamsChannelMessagesSchema,
113
- async (parsed) => {
114
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
115
- const tid = encodeURIComponent(parsed.teamId);
116
- const cid = encodeURIComponent(parsed.channelId);
117
- const initial = `/teams/${tid}/channels/${cid}/messages?$top=${String(parsed.top ?? 25)}`;
118
- const r = await teamsPagedGraph(token, parsed.nextLink, initial);
119
- return graphListResult(r);
120
- },
121
- );
122
-
123
- const teamsChatListSchema = z.object({
124
- top: z.number().int().min(1).max(50).optional(),
125
- nextLink: z.url().optional(),
126
- });
127
-
128
- reg(
129
- "teams_chat_list",
130
- "List 1:1 and group chats for the signed-in user.",
131
- teamsChatListSchema,
132
- async (parsed) => {
133
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
134
- const initial = `/me/chats?$top=${String(parsed.top ?? 25)}`;
135
- const r = await teamsPagedGraph(token, parsed.nextLink, initial);
136
- return graphListResult(r);
137
- },
138
- );
139
-
140
- const teamsChatMessagesSchema = z.object({
141
- chatId: z.string().min(1),
142
- top: z.number().int().min(1).max(50).optional(),
143
- nextLink: z.url().optional(),
144
- });
145
-
146
- reg("teams_chat_messages", "List messages in a chat.", teamsChatMessagesSchema, async (parsed) => {
147
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
148
- const id = encodeURIComponent(parsed.chatId);
149
- const initial = `/chats/${id}/messages?$top=${String(parsed.top ?? 25)}`;
150
- const r = await teamsPagedGraph(token, parsed.nextLink, initial);
151
- return graphListResult(r);
152
- });
153
-
154
- const teamsMessagePostSchema = z.object({
155
- teamId: z.string().min(1),
156
- channelId: z.string().min(1),
157
- body: z.string().min(1),
158
- contentType: z.enum(["text", "html"]).optional(),
159
- });
160
-
161
- registerWriteTool(
162
- "teams_message_post",
163
- {
164
- mutates: "teams.message.post",
165
- recoverable: true,
166
- scopeTargetOf: (p) => ({ kind: "channel", value: `${p.teamId}/${p.channelId}` }),
167
- },
168
- "Post a message to a team channel.",
169
- teamsMessagePostSchema,
170
- async (parsed) => {
171
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
172
- const tid = encodeURIComponent(parsed.teamId);
173
- const cid = encodeURIComponent(parsed.channelId);
174
- const ct = parsed.contentType ?? "text";
175
- const r = await graphRequest(token, `/teams/${tid}/channels/${cid}/messages`, {
176
- method: "POST",
177
- headers: { "Content-Type": "application/json" },
178
- body: JSON.stringify({
179
- body: {
180
- contentType: ct === "html" ? "html" : "text",
181
- content: parsed.body,
182
- },
183
- }),
184
- });
185
- return graphListResult(r);
186
- },
187
- );
188
-
189
- const teamsMessagePostChatSchema = z.object({
190
- chatId: z.string().min(1),
191
- body: z.string().min(1),
192
- contentType: z.enum(["text", "html"]).optional(),
193
- });
194
-
195
- reg(
196
- "teams_message_post_chat",
197
- "Post a message to a chat (requires HITL teams.message.postChat).",
198
- teamsMessagePostChatSchema,
199
- async (parsed) => {
200
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
201
- const id = encodeURIComponent(parsed.chatId);
202
- const ct = parsed.contentType ?? "text";
203
- const r = await graphRequest(token, `/chats/${id}/messages`, {
204
- method: "POST",
205
- headers: { "Content-Type": "application/json" },
206
- body: JSON.stringify({
207
- body: {
208
- contentType: ct === "html" ? "html" : "text",
209
- content: parsed.body,
210
- },
211
- }),
212
- });
213
- return graphListResult(r);
214
- },
215
- );
216
-
217
- // --- ChatOps operational tools (Slice 5) -------------------------------------------------------
218
- // `teams_chat_post` uses bot app credentials (Bot Framework), distinct from the user Graph token.
219
- // Only `chatops/reply-dispatcher.ts` + `chatops/transport/` reference `teams_chat_post` (static D17).
220
-
221
- const teamsUserInfoSchema = z.object({ userId: z.string().min(1) });
222
- reg(
223
- "teams_user_info",
224
- "Fetch a Teams/AAD user (incl. mail/userPrincipalName) by id (ChatOps identity mapping).",
225
- teamsUserInfoSchema,
226
- async (parsed) => {
227
- const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
228
- const r = await teamsPagedGraph(
229
- token,
230
- undefined,
231
- `/users/${encodeURIComponent(parsed.userId)}`,
232
- );
233
- return graphListResult(r);
234
- },
235
- );
236
-
237
- const teamsChatPostSchema = z.object({
238
- conversationId: z.string().min(1),
239
- text: z.string().min(1),
240
- });
241
- registerWriteTool(
242
- "teams_chat_post",
243
- {
244
- mutates: "teams.chat.post",
245
- recoverable: true,
246
- scopeTargetOf: (p) => ({ kind: "chat", value: p.conversationId }),
247
- },
248
- "Post an operational bot message to a Teams conversation (ChatOps reply surface; bot app creds).",
249
- teamsChatPostSchema,
250
- async (parsed) => {
251
- const r = await teamsBotSendActivity(parsed.conversationId, parsed.text);
252
- return graphListResult(r);
253
- },
254
- );
7
+ registerTeamsTools(server);
255
8
 
256
9
  const transport = new StdioServerTransport();
257
10
  await server.connect(transport);