@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.
- package/README.md +14 -4
- package/connectors/airflow/src/search-filter.ts +2 -15
- package/connectors/airflow/src/server.ts +2 -87
- package/connectors/airflow/src/tools.ts +74 -0
- package/connectors/apple/src/adapters.ts +326 -0
- package/connectors/apple/src/apple-mail-core.ts +4 -8
- package/connectors/apple/src/calendar-tools.ts +28 -8
- package/connectors/apple/src/server.ts +23 -512
- package/connectors/apple/src/tools.ts +18 -44
- package/connectors/argocd/src/server.ts +25 -38
- package/connectors/athena/src/tools.ts +15 -34
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +197 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +133 -0
- package/connectors/bigeye/src/search-filter.ts +4 -15
- package/connectors/bigeye/src/server.ts +38 -38
- package/connectors/bigquery/src/tools.ts +1 -26
- package/connectors/bitbucket/src/server.ts +2 -244
- package/connectors/bitbucket/src/tools.ts +241 -0
- package/connectors/bitrise/src/server.ts +2 -79
- package/connectors/bitrise/src/tools.ts +76 -0
- package/connectors/canva/src/search-filter.ts +2 -11
- package/connectors/canva/src/server.ts +2 -73
- package/connectors/canva/src/tools.ts +66 -0
- package/connectors/circleci/src/server.ts +2 -176
- package/connectors/circleci/src/tools.ts +193 -0
- package/connectors/cloud-logging/src/tools.ts +14 -27
- package/connectors/cloudwatch/src/tools.ts +15 -34
- package/connectors/codemagic/src/server.ts +2 -70
- package/connectors/codemagic/src/tools.ts +71 -0
- package/connectors/confluence/src/server.ts +2 -301
- package/connectors/confluence/src/tools.ts +320 -0
- package/connectors/dagster/src/server.ts +2 -214
- package/connectors/dagster/src/tools.ts +187 -0
- package/connectors/databricks/src/search-filter.ts +7 -7
- package/connectors/databricks/src/server.ts +2 -77
- package/connectors/databricks/src/tools.ts +66 -0
- package/connectors/datadog/src/server.ts +2 -48
- package/connectors/datadog/src/tools.ts +48 -0
- package/connectors/dataprofile/src/profile.ts +16 -46
- package/connectors/dbt/src/server.ts +2 -92
- package/connectors/dbt/src/tools.ts +80 -0
- package/connectors/dependencytrack/src/server.ts +2 -76
- package/connectors/dependencytrack/src/tools.ts +65 -0
- package/connectors/discord/src/server.ts +2 -85
- package/connectors/discord/src/tools.ts +82 -0
- package/connectors/elasticsearch/src/tools.ts +7 -37
- package/connectors/fastmail/src/jmap-client.ts +202 -0
- package/connectors/fastmail/src/jmap-core.ts +8 -21
- package/connectors/fastmail/src/server.ts +2 -187
- package/connectors/fastmail/src/tools.ts +4 -23
- package/connectors/figma/src/search-filter.ts +2 -11
- package/connectors/figma/src/server.ts +2 -113
- package/connectors/figma/src/tools.ts +99 -0
- package/connectors/firebase/src/server.ts +2 -90
- package/connectors/firebase/src/token.ts +2 -5
- package/connectors/firebase/src/tools.ts +106 -0
- package/connectors/flagsmith/src/server.ts +2 -98
- package/connectors/flagsmith/src/tools.ts +95 -0
- package/connectors/flux/src/server.ts +52 -53
- package/connectors/gcp/src/server.ts +2 -132
- package/connectors/gcp/src/tools.ts +123 -0
- package/connectors/github/src/server.ts +2 -328
- package/connectors/github/src/tools.ts +311 -0
- package/connectors/github-actions/src/server.ts +2 -205
- package/connectors/github-actions/src/tools.ts +209 -0
- package/connectors/gitlab/src/server.ts +2 -384
- package/connectors/gitlab/src/tools.ts +392 -0
- package/connectors/gmail/src/server.ts +2 -270
- package/connectors/gmail/src/tools.ts +209 -0
- package/connectors/google-drive/src/server.ts +2 -524
- package/connectors/google-drive/src/tools.ts +529 -0
- package/connectors/google-meet/src/server.ts +2 -82
- package/connectors/google-meet/src/tools.ts +87 -0
- package/connectors/google-photos/src/server.ts +2 -132
- package/connectors/google-photos/src/tools.ts +138 -0
- package/connectors/grafana/src/server.ts +2 -53
- package/connectors/grafana/src/tools.ts +53 -0
- package/connectors/great-expectations/src/gx-parse.ts +14 -45
- package/connectors/greenhouse/src/server.ts +2 -63
- package/connectors/greenhouse/src/tools.ts +68 -0
- package/connectors/hubspot/src/server.ts +2 -77
- package/connectors/hubspot/src/tools.ts +70 -0
- package/connectors/iac/src/server.ts +2 -137
- package/connectors/iac/src/tools.ts +152 -0
- package/connectors/imap/src/imap-core.ts +29 -82
- package/connectors/imap/src/server.ts +33 -220
- package/connectors/intercom/src/server.ts +2 -61
- package/connectors/intercom/src/tools.ts +58 -0
- package/connectors/jenkins/src/jenkins-api.ts +2 -6
- package/connectors/jenkins/src/server.ts +2 -215
- package/connectors/jenkins/src/tools.ts +240 -0
- package/connectors/jira/src/server.ts +2 -301
- package/connectors/jira/src/tools.ts +325 -0
- package/connectors/kubernetes/src/server.ts +2 -155
- package/connectors/kubernetes/src/tools.ts +159 -0
- package/connectors/launchdarkly/src/server.ts +2 -75
- package/connectors/launchdarkly/src/tools.ts +72 -0
- package/connectors/lever/src/search-filter.ts +4 -12
- package/connectors/lever/src/server.ts +2 -60
- package/connectors/lever/src/tools.ts +61 -0
- package/connectors/linear/src/server.ts +2 -459
- package/connectors/linear/src/tools.ts +482 -0
- package/connectors/localdb/src/sql-scan.ts +13 -41
- package/connectors/localdb/src/tools.ts +1 -1
- package/connectors/looker/src/search-filter.ts +2 -15
- package/connectors/looker/src/server.ts +35 -70
- package/connectors/mendeley/src/server.ts +2 -55
- package/connectors/mendeley/src/tools.ts +54 -0
- package/connectors/mercury/src/server.ts +2 -57
- package/connectors/mercury/src/tools.ts +50 -0
- package/connectors/metabase/src/search-filter.ts +4 -15
- package/connectors/metabase/src/server.ts +2 -79
- package/connectors/metabase/src/tools.ts +64 -0
- package/connectors/miro/src/server.ts +2 -72
- package/connectors/miro/src/tools.ts +65 -0
- package/connectors/mlflow/src/search-filter.ts +3 -7
- package/connectors/mlflow/src/server.ts +20 -40
- package/connectors/monte-carlo/src/server.ts +50 -57
- package/connectors/netlify/src/search-filter.ts +6 -14
- package/connectors/netlify/src/server.ts +2 -60
- package/connectors/netlify/src/tools.ts +53 -0
- package/connectors/newrelic/src/server.ts +2 -40
- package/connectors/newrelic/src/tools.ts +37 -0
- package/connectors/notion/src/server.ts +2 -335
- package/connectors/notion/src/tools.ts +344 -0
- package/connectors/obsidian/src/server.ts +2 -425
- package/connectors/obsidian/src/tools.ts +466 -0
- package/connectors/onedrive/src/server.ts +2 -249
- package/connectors/onedrive/src/tools.ts +253 -0
- package/connectors/outlook/src/server.ts +2 -351
- package/connectors/outlook/src/tools.ts +357 -0
- package/connectors/pagerduty/src/server.ts +2 -132
- package/connectors/pagerduty/src/tools.ts +126 -0
- package/connectors/pipedrive/src/server.ts +2 -58
- package/connectors/pipedrive/src/tools.ts +63 -0
- package/connectors/powerbi/src/search-filter.ts +2 -15
- package/connectors/powerbi/src/server.ts +43 -68
- package/connectors/prefect/src/search-filter.ts +3 -16
- package/connectors/prefect/src/server.ts +2 -90
- package/connectors/prefect/src/tools.ts +83 -0
- package/connectors/protonmail/src/mail-core.ts +16 -75
- package/connectors/protonmail/src/server.ts +34 -214
- package/connectors/raindrop/src/server.ts +2 -103
- package/connectors/raindrop/src/tools.ts +103 -0
- package/connectors/ramp/src/server.ts +2 -105
- package/connectors/ramp/src/tools.ts +98 -0
- package/connectors/readwise/src/server.ts +2 -88
- package/connectors/readwise/src/tools.ts +81 -0
- package/connectors/sagemaker/src/tools.ts +19 -38
- package/connectors/salesforce/src/search-filter.ts +4 -11
- package/connectors/salesforce/src/server.ts +2 -90
- package/connectors/salesforce/src/tools.ts +75 -0
- package/connectors/semgrep/src/search-filter.ts +3 -10
- package/connectors/semgrep/src/server.ts +2 -104
- package/connectors/semgrep/src/tools.ts +92 -0
- package/connectors/sentry/src/server.ts +2 -58
- package/connectors/sentry/src/tools.ts +53 -0
- package/connectors/slack/src/server.ts +2 -352
- package/connectors/slack/src/tools.ts +376 -0
- package/connectors/snowflake/src/search-filter.ts +4 -19
- package/connectors/snowflake/src/server.ts +6 -25
- package/connectors/snyk/src/server.ts +2 -140
- package/connectors/snyk/src/tools.ts +136 -0
- package/connectors/sonarqube/src/server.ts +2 -111
- package/connectors/sonarqube/src/tools.ts +98 -0
- package/connectors/stackoverflow/src/server.ts +2 -77
- package/connectors/stackoverflow/src/tools.ts +66 -0
- package/connectors/storybook/src/storybook-parse.ts +3 -6
- package/connectors/stripe/src/server.ts +2 -61
- package/connectors/stripe/src/tools.ts +54 -0
- package/connectors/superset/src/search-filter.ts +4 -15
- package/connectors/superset/src/server.ts +2 -115
- package/connectors/superset/src/tools.ts +98 -0
- package/connectors/tableau/src/search-filter.ts +2 -15
- package/connectors/tableau/src/server.ts +13 -65
- package/connectors/teams/src/server.ts +2 -249
- package/connectors/teams/src/tools.ts +283 -0
- package/connectors/testflight/src/server.ts +2 -70
- package/connectors/testflight/src/tools.ts +79 -0
- package/connectors/vercel/src/search-filter.ts +2 -10
- package/connectors/vercel/src/server.ts +2 -75
- package/connectors/vercel/src/tools.ts +73 -0
- package/connectors/vertex-ai/src/tools.ts +23 -33
- package/connectors/wiz/src/search-filter.ts +6 -30
- package/connectors/wiz/src/server.ts +2 -188
- package/connectors/wiz/src/tools.ts +168 -0
- package/connectors/zendesk/src/server.ts +2 -73
- package/connectors/zendesk/src/tools.ts +60 -0
- package/connectors/zoom/src/search-filter.ts +1 -7
- package/connectors/zoom/src/server.ts +2 -94
- package/connectors/zoom/src/tools.ts +93 -0
- package/connectors/zotero/src/server.ts +2 -78
- package/connectors/zotero/src/tools.ts +67 -0
- package/package.json +14 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +13 -6
- package/shared/cli-json-kit.ts +150 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/cursor-list-tool.ts +29 -0
- package/shared/env-json-api.ts +209 -0
- package/shared/fetch-json-text.ts +27 -0
- package/shared/github-rest.ts +62 -0
- package/shared/graphql-json.ts +60 -0
- package/shared/imap-mail-core.ts +91 -0
- package/shared/imap-tool-kit.ts +57 -23
- package/shared/imapflow-adapter.ts +417 -0
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders, requiredEnv } 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 { filterSemgrepFindings } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const SEMGREP_API = "https://semgrep.dev/api/v1";
|
|
9
|
+
const SEVERITIES = ["critical", "high", "medium", "low", "info"] as const;
|
|
10
|
+
const STATUSES = ["open", "ignored", "fixed", "removed"] as const;
|
|
11
|
+
|
|
12
|
+
const semgrepGet = createJsonGetter({
|
|
13
|
+
base: SEMGREP_API,
|
|
14
|
+
label: "Semgrep",
|
|
15
|
+
headers: envAuthHeaders({ env: "SEMGREP_TOKEN" }),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function requireSlug(): string {
|
|
19
|
+
return requiredEnv("SEMGREP_DEPLOYMENT_SLUG");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
23
|
+
export const SEMGREP_TOOL_NAMES = ["semgrep_get", "semgrep_list", "semgrep_search"] as const;
|
|
24
|
+
|
|
25
|
+
export function registerSemgrepTools(reg: ZodToolRegistrar): void {
|
|
26
|
+
reg(
|
|
27
|
+
"semgrep_list",
|
|
28
|
+
"List Semgrep deployments, or open findings for the configured deployment. When called without filters, returns the user's deployments (`/deployments`). Provide a `severity` or `status` filter to query findings under the deployment slug from `SEMGREP_DEPLOYMENT_SLUG` env.",
|
|
29
|
+
z.object({
|
|
30
|
+
severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
|
|
31
|
+
status: z.array(z.enum(STATUSES)).min(1).optional(),
|
|
32
|
+
repository: z.string().min(1).optional(),
|
|
33
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
34
|
+
}),
|
|
35
|
+
async (p) => {
|
|
36
|
+
if (p.severity === undefined && p.status === undefined && p.repository === undefined) {
|
|
37
|
+
return jsonResult(await semgrepGet("/deployments"));
|
|
38
|
+
}
|
|
39
|
+
const slug = requireSlug();
|
|
40
|
+
const search = new URLSearchParams({
|
|
41
|
+
page_size: String(p.limit ?? 100),
|
|
42
|
+
});
|
|
43
|
+
if (p.severity !== undefined && p.severity.length > 0) {
|
|
44
|
+
search.set("severities", p.severity.join(","));
|
|
45
|
+
}
|
|
46
|
+
if (p.status !== undefined && p.status.length > 0) {
|
|
47
|
+
search.set("statuses", p.status.join(","));
|
|
48
|
+
}
|
|
49
|
+
if (p.repository !== undefined) {
|
|
50
|
+
search.set("repos", p.repository);
|
|
51
|
+
}
|
|
52
|
+
const path = `/deployments/${encodeURIComponent(slug)}/findings?${search.toString()}`;
|
|
53
|
+
return jsonResult(await semgrepGet(path));
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
reg(
|
|
58
|
+
"semgrep_get",
|
|
59
|
+
"Fetch one Semgrep finding by id from the configured deployment. Returns the matching finding envelope; throws when no match is found.",
|
|
60
|
+
z.object({
|
|
61
|
+
findingId: z.string().min(1),
|
|
62
|
+
}),
|
|
63
|
+
async (p) => {
|
|
64
|
+
const slug = requireSlug();
|
|
65
|
+
const search = new URLSearchParams({ ids: p.findingId, page_size: "1" });
|
|
66
|
+
const path = `/deployments/${encodeURIComponent(slug)}/findings?${search.toString()}`;
|
|
67
|
+
const root = await semgrepGet(path);
|
|
68
|
+
const findings = (root as { findings?: unknown[] } | null)?.findings;
|
|
69
|
+
if (!Array.isArray(findings) || findings.length === 0) {
|
|
70
|
+
throw new Error(`Semgrep: finding ${p.findingId} not found`);
|
|
71
|
+
}
|
|
72
|
+
return jsonResult(findings[0]);
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
reg(
|
|
77
|
+
"semgrep_search",
|
|
78
|
+
"Substring search across the deployment's open findings. Matches the query against `rule_name`, `rule_message`, `location.file_path`, and `repository.name` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
79
|
+
searchToolInputSchema(200),
|
|
80
|
+
async (p) => {
|
|
81
|
+
const slug = requireSlug();
|
|
82
|
+
const search = new URLSearchParams({
|
|
83
|
+
statuses: "open",
|
|
84
|
+
page_size: "500",
|
|
85
|
+
});
|
|
86
|
+
const path = `/deployments/${encodeURIComponent(slug)}/findings?${search.toString()}`;
|
|
87
|
+
const root = await semgrepGet(path);
|
|
88
|
+
const findings = (root as { findings?: unknown[] } | null)?.findings;
|
|
89
|
+
return matchesResult(findings, filterSemgrepFindings, p);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -1,66 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createRegisterSimpleTool,
|
|
7
|
-
createZodToolRegistrar,
|
|
8
|
-
mcpJsonResult as jsonResult,
|
|
9
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
10
|
-
|
|
11
|
-
function apiRoot(): string {
|
|
12
|
-
const u = process.env["SENTRY_URL"]?.trim() || "https://sentry.io";
|
|
13
|
-
return `${u.replace(/\/$/, "")}/api/0`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function org(): string {
|
|
17
|
-
const o = process.env["SENTRY_ORG_SLUG"]?.trim();
|
|
18
|
-
if (o === undefined || o === "") {
|
|
19
|
-
throw new Error("SENTRY_ORG_SLUG is not set");
|
|
20
|
-
}
|
|
21
|
-
return o;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function headers(): Record<string, string> {
|
|
25
|
-
const t = process.env["SENTRY_AUTH_TOKEN"]?.trim();
|
|
26
|
-
if (t === undefined || t === "") {
|
|
27
|
-
throw new Error("SENTRY_AUTH_TOKEN is not set");
|
|
28
|
-
}
|
|
29
|
-
return { Authorization: `Bearer ${t}`, Accept: "application/json" };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function sentryGet(path: string): Promise<unknown> {
|
|
33
|
-
const res = await fetch(`${apiRoot()}${path}`, { headers: headers() });
|
|
34
|
-
const text = await res.text();
|
|
35
|
-
if (!res.ok) {
|
|
36
|
-
throw new Error(`Sentry ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
37
|
-
}
|
|
38
|
-
return JSON.parse(text) as unknown;
|
|
39
|
-
}
|
|
3
|
+
import { registerSentryTools } from "./tools.ts";
|
|
40
4
|
|
|
41
5
|
const mcp = new McpServer({ name: "nimbus-sentry", version: "0.1.0" });
|
|
42
|
-
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
43
|
-
|
|
44
|
-
reg(
|
|
45
|
-
"sentry_issue_list",
|
|
46
|
-
"List unresolved issues for a project.",
|
|
47
|
-
z.object({ projectSlug: z.string().min(1), limit: z.number().int().min(1).max(100).optional() }),
|
|
48
|
-
async (p) => {
|
|
49
|
-
const lim = p.limit ?? 20;
|
|
50
|
-
return jsonResult(
|
|
51
|
-
await sentryGet(
|
|
52
|
-
`/projects/${org()}/${p.projectSlug}/issues/?query=is:unresolved&limit=${String(lim)}`,
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
6
|
|
|
58
|
-
|
|
59
|
-
"sentry_release_list",
|
|
60
|
-
"List releases for a project.",
|
|
61
|
-
z.object({ projectSlug: z.string().min(1) }),
|
|
62
|
-
async (p) => jsonResult(await sentryGet(`/projects/${org()}/${p.projectSlug}/releases/`)),
|
|
63
|
-
);
|
|
7
|
+
registerSentryTools(mcp);
|
|
64
8
|
|
|
65
9
|
const transport = new StdioServerTransport();
|
|
66
10
|
await mcp.connect(transport);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders, requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import {
|
|
4
|
+
createRegisterSimpleTool,
|
|
5
|
+
createZodToolRegistrar,
|
|
6
|
+
mcpJsonResult as jsonResult,
|
|
7
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
8
|
+
|
|
9
|
+
function apiRoot(): string {
|
|
10
|
+
const u = process.env["SENTRY_URL"]?.trim() || "https://sentry.io";
|
|
11
|
+
return `${u.replace(/\/$/, "")}/api/0`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function org(): string {
|
|
15
|
+
return requiredEnv("SENTRY_ORG_SLUG");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sentryGet = createJsonGetter({
|
|
19
|
+
base: apiRoot,
|
|
20
|
+
label: "Sentry",
|
|
21
|
+
headers: envAuthHeaders({ env: "SENTRY_AUTH_TOKEN" }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
25
|
+
export const SENTRY_TOOL_NAMES = ["sentry_issue_list", "sentry_release_list"] as const;
|
|
26
|
+
|
|
27
|
+
export function registerSentryTools(server: { tool: (...args: never) => unknown }): void {
|
|
28
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
29
|
+
|
|
30
|
+
reg(
|
|
31
|
+
"sentry_issue_list",
|
|
32
|
+
"List unresolved issues for a project.",
|
|
33
|
+
z.object({
|
|
34
|
+
projectSlug: z.string().min(1),
|
|
35
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
36
|
+
}),
|
|
37
|
+
async (p) => {
|
|
38
|
+
const lim = p.limit ?? 20;
|
|
39
|
+
return jsonResult(
|
|
40
|
+
await sentryGet(
|
|
41
|
+
`/projects/${org()}/${p.projectSlug}/issues/?query=is:unresolved&limit=${String(lim)}`,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
reg(
|
|
48
|
+
"sentry_release_list",
|
|
49
|
+
"List releases for a project.",
|
|
50
|
+
z.object({ projectSlug: z.string().min(1) }),
|
|
51
|
+
async (p) => jsonResult(await sentryGet(`/projects/${org()}/${p.projectSlug}/releases/`)),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -1,360 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createRegisterSimpleTool,
|
|
7
|
-
createZodToolRegistrar,
|
|
8
|
-
mcpJsonResult as jsonResult,
|
|
9
|
-
type McpListResult,
|
|
10
|
-
putOptionalBoolean,
|
|
11
|
-
putOptionalNonEmptyString,
|
|
12
|
-
requireProcessEnv,
|
|
13
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
14
|
-
|
|
15
|
-
type SlackApiRecord = Record<string, unknown>;
|
|
16
|
-
|
|
17
|
-
async function slackApi(
|
|
18
|
-
token: string,
|
|
19
|
-
method: string,
|
|
20
|
-
body: Record<string, unknown>,
|
|
21
|
-
): Promise<{ ok: boolean; json: SlackApiRecord; text: string }> {
|
|
22
|
-
const res = await fetch(`https://slack.com/api/${method}`, {
|
|
23
|
-
method: "POST",
|
|
24
|
-
headers: {
|
|
25
|
-
Authorization: `Bearer ${token}`,
|
|
26
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
27
|
-
},
|
|
28
|
-
body: JSON.stringify(body),
|
|
29
|
-
});
|
|
30
|
-
const text = await res.text();
|
|
31
|
-
let parsed: unknown;
|
|
32
|
-
try {
|
|
33
|
-
parsed = JSON.parse(text) as unknown;
|
|
34
|
-
} catch {
|
|
35
|
-
return { ok: false, json: {}, text };
|
|
36
|
-
}
|
|
37
|
-
const json =
|
|
38
|
-
parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
39
|
-
? (parsed as SlackApiRecord)
|
|
40
|
-
: {};
|
|
41
|
-
const okField = json["ok"];
|
|
42
|
-
return { ok: okField === true && res.ok, json, text };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function slackInvokeJsonWithToken(
|
|
46
|
-
token: string,
|
|
47
|
-
method: string,
|
|
48
|
-
body: Record<string, unknown>,
|
|
49
|
-
errorLabel: string,
|
|
50
|
-
): Promise<McpListResult> {
|
|
51
|
-
const res = await slackApi(token, method, body);
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
throw new Error(`${errorLabel}: ${res.text.slice(0, 400)}`);
|
|
54
|
-
}
|
|
55
|
-
return jsonResult(res.json);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function slackInvokeJson(
|
|
59
|
-
method: string,
|
|
60
|
-
body: Record<string, unknown>,
|
|
61
|
-
errorLabel: string,
|
|
62
|
-
): Promise<McpListResult> {
|
|
63
|
-
return slackInvokeJsonWithToken(
|
|
64
|
-
requireProcessEnv("SLACK_USER_ACCESS_TOKEN"),
|
|
65
|
-
method,
|
|
66
|
-
body,
|
|
67
|
-
errorLabel,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const slackConversationsHistorySchema = z.object({
|
|
72
|
-
channel: z.string().min(1),
|
|
73
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
74
|
-
cursor: z.string().optional(),
|
|
75
|
-
oldest: z.string().optional(),
|
|
76
|
-
inclusive: z.boolean().optional(),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
type SlackConversationsHistoryParsed = z.infer<typeof slackConversationsHistorySchema>;
|
|
80
|
-
|
|
81
|
-
function buildConversationsHistoryBody(
|
|
82
|
-
parsed: SlackConversationsHistoryParsed,
|
|
83
|
-
): Record<string, unknown> {
|
|
84
|
-
const body: Record<string, unknown> = {
|
|
85
|
-
channel: parsed.channel,
|
|
86
|
-
limit: parsed.limit ?? 50,
|
|
87
|
-
};
|
|
88
|
-
putOptionalNonEmptyString(body, "cursor", parsed.cursor);
|
|
89
|
-
putOptionalNonEmptyString(body, "oldest", parsed.oldest);
|
|
90
|
-
putOptionalBoolean(body, "inclusive", parsed.inclusive);
|
|
91
|
-
return body;
|
|
92
|
-
}
|
|
3
|
+
import { registerSlackTools } from "./tools.ts";
|
|
93
4
|
|
|
94
5
|
const server = new McpServer({ name: "nimbus-slack", version: "0.1.0" });
|
|
95
6
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
99
|
-
|
|
100
|
-
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Every MUTATING slack tool goes through here. Outside the gateway this adds the
|
|
104
|
-
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
105
|
-
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
106
|
-
*/
|
|
107
|
-
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
108
|
-
connector: "slack",
|
|
109
|
-
scopeEnv: "NIMBUS_MCP_SLACK_WRITE_SCOPE",
|
|
110
|
-
scopeKinds: ["channel"],
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
const slackChannelListSchema = z.object({
|
|
114
|
-
types: z.string().optional(),
|
|
115
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
116
|
-
cursor: z.string().optional(),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
reg(
|
|
120
|
-
"slack_channel_list",
|
|
121
|
-
"List channels the user is a member of (public, private, mpim, im).",
|
|
122
|
-
slackChannelListSchema,
|
|
123
|
-
async (parsed) => {
|
|
124
|
-
const body: Record<string, unknown> = {
|
|
125
|
-
types: parsed.types ?? "public_channel,private_channel,mpim,im",
|
|
126
|
-
limit: parsed.limit ?? 200,
|
|
127
|
-
exclude_archived: true,
|
|
128
|
-
};
|
|
129
|
-
putOptionalNonEmptyString(body, "cursor", parsed.cursor);
|
|
130
|
-
return slackInvokeJson("conversations.list", body, "Slack conversations.list");
|
|
131
|
-
},
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
reg(
|
|
135
|
-
"slack_channel_history",
|
|
136
|
-
"Fetch message history for a channel, DM, or mpim.",
|
|
137
|
-
slackConversationsHistorySchema,
|
|
138
|
-
async (parsed) =>
|
|
139
|
-
slackInvokeJson(
|
|
140
|
-
"conversations.history",
|
|
141
|
-
buildConversationsHistoryBody(parsed),
|
|
142
|
-
"Slack conversations.history",
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const slackThreadRepliesSchema = z.object({
|
|
147
|
-
channel: z.string().min(1),
|
|
148
|
-
ts: z.string().min(1),
|
|
149
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
150
|
-
cursor: z.string().optional(),
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
reg(
|
|
154
|
-
"slack_thread_replies",
|
|
155
|
-
"Fetch replies in a thread (channel + thread parent ts).",
|
|
156
|
-
slackThreadRepliesSchema,
|
|
157
|
-
async (parsed) => {
|
|
158
|
-
const body: Record<string, unknown> = {
|
|
159
|
-
channel: parsed.channel,
|
|
160
|
-
ts: parsed.ts,
|
|
161
|
-
limit: parsed.limit ?? 50,
|
|
162
|
-
};
|
|
163
|
-
putOptionalNonEmptyString(body, "cursor", parsed.cursor);
|
|
164
|
-
return slackInvokeJson("conversations.replies", body, "Slack conversations.replies");
|
|
165
|
-
},
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const slackDmListSchema = z.object({
|
|
169
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
170
|
-
cursor: z.string().optional(),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
reg(
|
|
174
|
-
"slack_dm_list",
|
|
175
|
-
"List direct message conversations (im + mpim).",
|
|
176
|
-
slackDmListSchema,
|
|
177
|
-
async (parsed) => {
|
|
178
|
-
const body: Record<string, unknown> = {
|
|
179
|
-
types: "im,mpim",
|
|
180
|
-
limit: parsed.limit ?? 200,
|
|
181
|
-
exclude_archived: true,
|
|
182
|
-
};
|
|
183
|
-
putOptionalNonEmptyString(body, "cursor", parsed.cursor);
|
|
184
|
-
return slackInvokeJson("conversations.list", body, "Slack dm list");
|
|
185
|
-
},
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
reg(
|
|
189
|
-
"slack_dm_history",
|
|
190
|
-
"Fetch DM / mpim history (same as channel history; convenience alias).",
|
|
191
|
-
slackConversationsHistorySchema,
|
|
192
|
-
async (parsed) =>
|
|
193
|
-
slackInvokeJson(
|
|
194
|
-
"conversations.history",
|
|
195
|
-
buildConversationsHistoryBody(parsed),
|
|
196
|
-
"Slack dm history",
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
const slackUserListSchema = z.object({
|
|
201
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
202
|
-
cursor: z.string().optional(),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
reg(
|
|
206
|
-
"slack_user_list",
|
|
207
|
-
"List users in the workspace (paginated).",
|
|
208
|
-
slackUserListSchema,
|
|
209
|
-
async (parsed) => {
|
|
210
|
-
const body: Record<string, unknown> = { limit: parsed.limit ?? 100 };
|
|
211
|
-
putOptionalNonEmptyString(body, "cursor", parsed.cursor);
|
|
212
|
-
return slackInvokeJson("users.list", body, "Slack users.list");
|
|
213
|
-
},
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const slackUserGetSchema = z.object({ user: z.string().min(1) });
|
|
217
|
-
|
|
218
|
-
reg("slack_user_get", "Get a single user profile by ID.", slackUserGetSchema, async (parsed) =>
|
|
219
|
-
slackInvokeJson("users.info", { user: parsed.user }, "Slack users.info"),
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const slackSearchSchema = z.object({
|
|
223
|
-
query: z.string().min(1),
|
|
224
|
-
count: z.number().int().min(1).max(100).optional(),
|
|
225
|
-
page: z.number().int().min(1).optional(),
|
|
226
|
-
sort: z.enum(["timestamp", "score"]).optional(),
|
|
227
|
-
sort_dir: z.enum(["asc", "desc"]).optional(),
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
reg("slack_search", "Search messages (workspace search).", slackSearchSchema, async (parsed) => {
|
|
231
|
-
const body: Record<string, unknown> = {
|
|
232
|
-
query: parsed.query,
|
|
233
|
-
count: parsed.count ?? 20,
|
|
234
|
-
page: parsed.page ?? 1,
|
|
235
|
-
};
|
|
236
|
-
if (parsed.sort !== undefined) {
|
|
237
|
-
body["sort"] = parsed.sort;
|
|
238
|
-
}
|
|
239
|
-
if (parsed.sort_dir !== undefined) {
|
|
240
|
-
body["sort_dir"] = parsed.sort_dir;
|
|
241
|
-
}
|
|
242
|
-
return slackInvokeJson("search.messages", body, "Slack search.messages");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const slackMessagePostSchema = z.object({
|
|
246
|
-
channel: z.string().min(1),
|
|
247
|
-
text: z.string().min(1),
|
|
248
|
-
thread_ts: z.string().optional(),
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
registerWriteTool(
|
|
252
|
-
"slack_message_post",
|
|
253
|
-
{
|
|
254
|
-
mutates: "slack.message.post",
|
|
255
|
-
recoverable: true,
|
|
256
|
-
scopeTargetOf: (p) => ({ kind: "channel", value: p.channel }),
|
|
257
|
-
},
|
|
258
|
-
"Post a message to a channel.",
|
|
259
|
-
slackMessagePostSchema,
|
|
260
|
-
async (parsed) => {
|
|
261
|
-
const body: Record<string, unknown> = {
|
|
262
|
-
channel: parsed.channel,
|
|
263
|
-
text: parsed.text,
|
|
264
|
-
};
|
|
265
|
-
putOptionalNonEmptyString(body, "thread_ts", parsed.thread_ts);
|
|
266
|
-
return slackInvokeJson("chat.postMessage", body, "Slack chat.postMessage");
|
|
267
|
-
},
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
const slackMessagePostDmSchema = z.object({
|
|
271
|
-
user_ids: z.string().min(1),
|
|
272
|
-
text: z.string().min(1),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
reg(
|
|
276
|
-
"slack_message_post_dm",
|
|
277
|
-
"Open or find a DM with user id(s) and send a message (requires HITL slack.message.post).",
|
|
278
|
-
slackMessagePostDmSchema,
|
|
279
|
-
async (parsed) => {
|
|
280
|
-
const token = requireProcessEnv("SLACK_USER_ACCESS_TOKEN");
|
|
281
|
-
const open = await slackApi(token, "conversations.open", {
|
|
282
|
-
users: parsed.user_ids,
|
|
283
|
-
return_im: true,
|
|
284
|
-
});
|
|
285
|
-
if (!open.ok) {
|
|
286
|
-
throw new Error(`Slack conversations.open: ${open.text.slice(0, 400)}`);
|
|
287
|
-
}
|
|
288
|
-
const ch = open.json["channel"];
|
|
289
|
-
const chRec =
|
|
290
|
-
ch !== null && typeof ch === "object" && !Array.isArray(ch)
|
|
291
|
-
? (ch as SlackApiRecord)
|
|
292
|
-
: undefined;
|
|
293
|
-
const channelId = chRec !== undefined && typeof chRec["id"] === "string" ? chRec["id"] : "";
|
|
294
|
-
if (channelId === "") {
|
|
295
|
-
throw new Error("Slack conversations.open: missing channel id");
|
|
296
|
-
}
|
|
297
|
-
const post = await slackApi(token, "chat.postMessage", {
|
|
298
|
-
channel: channelId,
|
|
299
|
-
text: parsed.text,
|
|
300
|
-
});
|
|
301
|
-
if (!post.ok) {
|
|
302
|
-
throw new Error(`Slack chat.postMessage (dm): ${post.text.slice(0, 400)}`);
|
|
303
|
-
}
|
|
304
|
-
return jsonResult({ open: open.json, post: post.json });
|
|
305
|
-
},
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
// --- ChatOps operational tools (Slice 5) -------------------------------------------------------
|
|
309
|
-
// These use the bot/app tokens (xoxb-/xapp-), distinct from the user token the read tools use.
|
|
310
|
-
// Only `chatops/reply-dispatcher.ts` + `chatops/transport/` reference `slack_chat_post` (static D17).
|
|
311
|
-
|
|
312
|
-
const slackUserInfoSchema = z.object({ user: z.string().min(1) });
|
|
313
|
-
reg(
|
|
314
|
-
"slack_user_info",
|
|
315
|
-
"Fetch a Slack user's profile (incl. email) by user id (ChatOps identity mapping; bot token).",
|
|
316
|
-
slackUserInfoSchema,
|
|
317
|
-
async (parsed) =>
|
|
318
|
-
slackInvokeJsonWithToken(
|
|
319
|
-
requireProcessEnv("SLACK_BOT_TOKEN"),
|
|
320
|
-
"users.info",
|
|
321
|
-
{ user: parsed.user },
|
|
322
|
-
"Slack users.info",
|
|
323
|
-
),
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
const slackChatPostSchema = z.object({ channel: z.string().min(1), text: z.string().min(1) });
|
|
327
|
-
registerWriteTool(
|
|
328
|
-
"slack_chat_post",
|
|
329
|
-
{
|
|
330
|
-
mutates: "slack.chat.post",
|
|
331
|
-
recoverable: true,
|
|
332
|
-
scopeTargetOf: (p) => ({ kind: "channel", value: p.channel }),
|
|
333
|
-
},
|
|
334
|
-
"Post an operational bot message to a channel (ChatOps reply surface; bot token).",
|
|
335
|
-
slackChatPostSchema,
|
|
336
|
-
async (parsed) =>
|
|
337
|
-
slackInvokeJsonWithToken(
|
|
338
|
-
requireProcessEnv("SLACK_BOT_TOKEN"),
|
|
339
|
-
"chat.postMessage",
|
|
340
|
-
{ channel: parsed.channel, text: parsed.text },
|
|
341
|
-
"Slack chat.postMessage",
|
|
342
|
-
),
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
const slackSocketOpenSchema = z.object({});
|
|
346
|
-
reg(
|
|
347
|
-
"slack_socket_open",
|
|
348
|
-
"Open a Slack Socket Mode connection and return the short-lived wss:// URL (app-level token).",
|
|
349
|
-
slackSocketOpenSchema,
|
|
350
|
-
async () =>
|
|
351
|
-
slackInvokeJsonWithToken(
|
|
352
|
-
requireProcessEnv("SLACK_APP_TOKEN"), // xapp-… (Socket Mode app-level token)
|
|
353
|
-
"apps.connections.open",
|
|
354
|
-
{},
|
|
355
|
-
"Slack apps.connections.open",
|
|
356
|
-
),
|
|
357
|
-
);
|
|
7
|
+
registerSlackTools(server);
|
|
358
8
|
|
|
359
9
|
const transport = new StdioServerTransport();
|
|
360
10
|
await server.connect(transport);
|