@nimbus-dev/connectors 0.1.0
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/LICENSE +662 -0
- package/NOTICE +40 -0
- package/README.md +111 -0
- package/connectors/airflow/nimbus.extension.json +16 -0
- package/connectors/airflow/src/search-filter.ts +38 -0
- package/connectors/airflow/src/server.ts +91 -0
- package/connectors/apple/nimbus.extension.json +40 -0
- package/connectors/apple/src/apple-mail-core.ts +43 -0
- package/connectors/apple/src/caldav-core.ts +116 -0
- package/connectors/apple/src/calendar-tools.ts +278 -0
- package/connectors/apple/src/server.ts +541 -0
- package/connectors/apple/src/tools.ts +138 -0
- package/connectors/argocd/nimbus.extension.json +16 -0
- package/connectors/argocd/src/search-filter.ts +24 -0
- package/connectors/argocd/src/server.ts +158 -0
- package/connectors/athena/nimbus.extension.json +15 -0
- package/connectors/athena/src/server.ts +6 -0
- package/connectors/athena/src/tools.ts +181 -0
- package/connectors/aws/nimbus.extension.json +21 -0
- package/connectors/aws/src/server.ts +191 -0
- package/connectors/azure/nimbus.extension.json +15 -0
- package/connectors/azure/src/server.ts +139 -0
- package/connectors/bigeye/nimbus.extension.json +16 -0
- package/connectors/bigeye/src/search-filter.ts +22 -0
- package/connectors/bigeye/src/server.ts +161 -0
- package/connectors/bigquery/nimbus.extension.json +15 -0
- package/connectors/bigquery/src/server.ts +6 -0
- package/connectors/bigquery/src/tools.ts +175 -0
- package/connectors/bitbucket/nimbus.extension.json +15 -0
- package/connectors/bitbucket/src/server.ts +252 -0
- package/connectors/bitrise/nimbus.extension.json +15 -0
- package/connectors/bitrise/src/search-filter.ts +11 -0
- package/connectors/bitrise/src/server.ts +83 -0
- package/connectors/canva/nimbus.extension.json +15 -0
- package/connectors/canva/src/search-filter.ts +23 -0
- package/connectors/canva/src/server.ts +77 -0
- package/connectors/circleci/nimbus.extension.json +15 -0
- package/connectors/circleci/src/server.ts +184 -0
- package/connectors/cloud-logging/nimbus.extension.json +15 -0
- package/connectors/cloud-logging/src/server.ts +6 -0
- package/connectors/cloud-logging/src/tools.ts +115 -0
- package/connectors/cloudwatch/nimbus.extension.json +15 -0
- package/connectors/cloudwatch/src/server.ts +6 -0
- package/connectors/cloudwatch/src/tools.ts +144 -0
- package/connectors/codemagic/nimbus.extension.json +15 -0
- package/connectors/codemagic/src/search-filter.ts +11 -0
- package/connectors/codemagic/src/server.ts +74 -0
- package/connectors/confluence/nimbus.extension.json +15 -0
- package/connectors/confluence/src/kb-append.ts +25 -0
- package/connectors/confluence/src/server.ts +309 -0
- package/connectors/dagster/nimbus.extension.json +16 -0
- package/connectors/dagster/src/search-filter.ts +53 -0
- package/connectors/dagster/src/server.ts +218 -0
- package/connectors/databricks/nimbus.extension.json +16 -0
- package/connectors/databricks/src/search-filter.ts +28 -0
- package/connectors/databricks/src/server.ts +81 -0
- package/connectors/datadog/nimbus.extension.json +15 -0
- package/connectors/datadog/src/server.ts +56 -0
- package/connectors/dataprofile/nimbus.extension.json +16 -0
- package/connectors/dataprofile/src/profile.ts +298 -0
- package/connectors/dataprofile/src/server.ts +6 -0
- package/connectors/dataprofile/src/tools.ts +69 -0
- package/connectors/dbt/nimbus.extension.json +16 -0
- package/connectors/dbt/src/search-filter.ts +28 -0
- package/connectors/dbt/src/server.ts +96 -0
- package/connectors/dependencytrack/nimbus.extension.json +16 -0
- package/connectors/dependencytrack/src/search-filter.ts +24 -0
- package/connectors/dependencytrack/src/server.ts +80 -0
- package/connectors/discord/nimbus.extension.json +15 -0
- package/connectors/discord/src/server.ts +93 -0
- package/connectors/elasticsearch/nimbus.extension.json +16 -0
- package/connectors/elasticsearch/src/server.ts +6 -0
- package/connectors/elasticsearch/src/tools.ts +110 -0
- package/connectors/fastmail/nimbus.extension.json +16 -0
- package/connectors/fastmail/src/jmap-core.ts +86 -0
- package/connectors/fastmail/src/server.ts +194 -0
- package/connectors/fastmail/src/tools.ts +143 -0
- package/connectors/figma/nimbus.extension.json +15 -0
- package/connectors/figma/src/search-filter.ts +24 -0
- package/connectors/figma/src/server.ts +117 -0
- package/connectors/firebase/nimbus.extension.json +15 -0
- package/connectors/firebase/src/search-filter.ts +30 -0
- package/connectors/firebase/src/server.ts +94 -0
- package/connectors/firebase/src/token.ts +41 -0
- package/connectors/flagsmith/nimbus.extension.json +15 -0
- package/connectors/flagsmith/src/search-filter.ts +29 -0
- package/connectors/flagsmith/src/server.ts +102 -0
- package/connectors/flux/nimbus.extension.json +16 -0
- package/connectors/flux/src/search-filter.ts +42 -0
- package/connectors/flux/src/server.ts +182 -0
- package/connectors/gcp/nimbus.extension.json +15 -0
- package/connectors/gcp/src/server.ts +140 -0
- package/connectors/github/nimbus.extension.json +15 -0
- package/connectors/github/src/server.ts +347 -0
- package/connectors/github-actions/nimbus.extension.json +15 -0
- package/connectors/github-actions/src/server.ts +213 -0
- package/connectors/gitlab/nimbus.extension.json +15 -0
- package/connectors/gitlab/src/server.ts +392 -0
- package/connectors/gmail/nimbus.extension.json +15 -0
- package/connectors/gmail/src/server.ts +277 -0
- package/connectors/google-drive/nimbus.extension.json +15 -0
- package/connectors/google-drive/src/drive-query.ts +3 -0
- package/connectors/google-drive/src/server.ts +531 -0
- package/connectors/google-meet/nimbus.extension.json +15 -0
- package/connectors/google-meet/src/server.ts +89 -0
- package/connectors/google-photos/nimbus.extension.json +15 -0
- package/connectors/google-photos/src/server.ts +139 -0
- package/connectors/grafana/nimbus.extension.json +15 -0
- package/connectors/grafana/src/server.ts +61 -0
- package/connectors/great-expectations/nimbus.extension.json +16 -0
- package/connectors/great-expectations/src/gx-parse.ts +292 -0
- package/connectors/great-expectations/src/server.ts +6 -0
- package/connectors/great-expectations/src/tools.ts +91 -0
- package/connectors/greenhouse/nimbus.extension.json +15 -0
- package/connectors/greenhouse/src/search-filter.ts +65 -0
- package/connectors/greenhouse/src/server.ts +67 -0
- package/connectors/hubspot/nimbus.extension.json +15 -0
- package/connectors/hubspot/src/search-filter.ts +28 -0
- package/connectors/hubspot/src/server.ts +81 -0
- package/connectors/iac/nimbus.extension.json +14 -0
- package/connectors/iac/src/server.ts +145 -0
- package/connectors/imap/nimbus.extension.json +16 -0
- package/connectors/imap/src/imap-core.ts +110 -0
- package/connectors/imap/src/server.ts +232 -0
- package/connectors/imap/src/tools.ts +40 -0
- package/connectors/intercom/nimbus.extension.json +15 -0
- package/connectors/intercom/src/search-filter.ts +46 -0
- package/connectors/intercom/src/server.ts +65 -0
- package/connectors/jenkins/nimbus.extension.json +15 -0
- package/connectors/jenkins/src/jenkins-api.ts +156 -0
- package/connectors/jenkins/src/server.ts +223 -0
- package/connectors/jira/nimbus.extension.json +15 -0
- package/connectors/jira/src/server.ts +309 -0
- package/connectors/kubernetes/nimbus.extension.json +15 -0
- package/connectors/kubernetes/src/server.ts +163 -0
- package/connectors/launchdarkly/nimbus.extension.json +15 -0
- package/connectors/launchdarkly/src/search-filter.ts +31 -0
- package/connectors/launchdarkly/src/server.ts +79 -0
- package/connectors/lever/nimbus.extension.json +15 -0
- package/connectors/lever/src/search-filter.ts +35 -0
- package/connectors/lever/src/server.ts +64 -0
- package/connectors/linear/nimbus.extension.json +15 -0
- package/connectors/linear/src/server.ts +467 -0
- package/connectors/localdb/nimbus.extension.json +16 -0
- package/connectors/localdb/src/server.ts +6 -0
- package/connectors/localdb/src/sql-scan.ts +158 -0
- package/connectors/localdb/src/tools.ts +67 -0
- package/connectors/looker/nimbus.extension.json +16 -0
- package/connectors/looker/src/search-filter.ts +22 -0
- package/connectors/looker/src/server.ts +254 -0
- package/connectors/mendeley/nimbus.extension.json +15 -0
- package/connectors/mendeley/src/search-filter.ts +68 -0
- package/connectors/mendeley/src/server.ts +59 -0
- package/connectors/mercury/nimbus.extension.json +15 -0
- package/connectors/mercury/src/search-filter.ts +11 -0
- package/connectors/mercury/src/server.ts +61 -0
- package/connectors/metabase/nimbus.extension.json +16 -0
- package/connectors/metabase/src/search-filter.ts +22 -0
- package/connectors/metabase/src/server.ts +83 -0
- package/connectors/miro/nimbus.extension.json +15 -0
- package/connectors/miro/src/search-filter.ts +24 -0
- package/connectors/miro/src/server.ts +76 -0
- package/connectors/mlflow/nimbus.extension.json +16 -0
- package/connectors/mlflow/src/search-filter.ts +38 -0
- package/connectors/mlflow/src/server.ts +167 -0
- package/connectors/monte-carlo/nimbus.extension.json +16 -0
- package/connectors/monte-carlo/src/search-filter.ts +11 -0
- package/connectors/monte-carlo/src/server.ts +241 -0
- package/connectors/netlify/nimbus.extension.json +15 -0
- package/connectors/netlify/src/search-filter.ts +37 -0
- package/connectors/netlify/src/server.ts +64 -0
- package/connectors/newrelic/nimbus.extension.json +15 -0
- package/connectors/newrelic/src/server.ts +48 -0
- package/connectors/notion/nimbus.extension.json +15 -0
- package/connectors/notion/src/kb-append.ts +23 -0
- package/connectors/notion/src/server.ts +343 -0
- package/connectors/obsidian/nimbus.extension.json +19 -0
- package/connectors/obsidian/src/server.ts +433 -0
- package/connectors/onedrive/nimbus.extension.json +15 -0
- package/connectors/onedrive/src/server.ts +256 -0
- package/connectors/outlook/nimbus.extension.json +15 -0
- package/connectors/outlook/src/server.ts +358 -0
- package/connectors/outlook/src/tool-scope-policy.ts +53 -0
- package/connectors/pagerduty/nimbus.extension.json +15 -0
- package/connectors/pagerduty/src/server.ts +140 -0
- package/connectors/pipedrive/nimbus.extension.json +15 -0
- package/connectors/pipedrive/src/search-filter.ts +11 -0
- package/connectors/pipedrive/src/server.ts +62 -0
- package/connectors/powerbi/nimbus.extension.json +16 -0
- package/connectors/powerbi/src/search-filter.ts +22 -0
- package/connectors/powerbi/src/server.ts +249 -0
- package/connectors/prefect/nimbus.extension.json +16 -0
- package/connectors/prefect/src/search-filter.ts +40 -0
- package/connectors/prefect/src/server.ts +94 -0
- package/connectors/protonmail/nimbus.extension.json +16 -0
- package/connectors/protonmail/src/mail-core.ts +96 -0
- package/connectors/protonmail/src/server.ts +231 -0
- package/connectors/protonmail/src/tools.ts +45 -0
- package/connectors/raindrop/nimbus.extension.json +15 -0
- package/connectors/raindrop/src/search-filter.ts +20 -0
- package/connectors/raindrop/src/server.ts +107 -0
- package/connectors/ramp/nimbus.extension.json +15 -0
- package/connectors/ramp/src/search-filter.ts +37 -0
- package/connectors/ramp/src/server.ts +109 -0
- package/connectors/readwise/nimbus.extension.json +15 -0
- package/connectors/readwise/src/search-filter.ts +64 -0
- package/connectors/readwise/src/server.ts +92 -0
- package/connectors/sagemaker/nimbus.extension.json +15 -0
- package/connectors/sagemaker/src/server.ts +6 -0
- package/connectors/sagemaker/src/tools.ts +121 -0
- package/connectors/salesforce/nimbus.extension.json +15 -0
- package/connectors/salesforce/src/search-filter.ts +23 -0
- package/connectors/salesforce/src/server.ts +94 -0
- package/connectors/semgrep/nimbus.extension.json +15 -0
- package/connectors/semgrep/src/search-filter.ts +31 -0
- package/connectors/semgrep/src/server.ts +108 -0
- package/connectors/sentry/nimbus.extension.json +15 -0
- package/connectors/sentry/src/server.ts +66 -0
- package/connectors/slack/nimbus.extension.json +15 -0
- package/connectors/slack/src/server.ts +360 -0
- package/connectors/snowflake/nimbus.extension.json +16 -0
- package/connectors/snowflake/src/search-filter.ts +26 -0
- package/connectors/snowflake/src/server.ts +236 -0
- package/connectors/snyk/nimbus.extension.json +15 -0
- package/connectors/snyk/src/search-filter.ts +28 -0
- package/connectors/snyk/src/server.ts +144 -0
- package/connectors/sonarqube/nimbus.extension.json +15 -0
- package/connectors/sonarqube/src/search-filter.ts +28 -0
- package/connectors/sonarqube/src/server.ts +115 -0
- package/connectors/stackoverflow/nimbus.extension.json +15 -0
- package/connectors/stackoverflow/src/search-filter.ts +52 -0
- package/connectors/stackoverflow/src/server.ts +81 -0
- package/connectors/storybook/nimbus.extension.json +16 -0
- package/connectors/storybook/src/server.ts +6 -0
- package/connectors/storybook/src/storybook-parse.ts +67 -0
- package/connectors/storybook/src/tools.ts +68 -0
- package/connectors/stripe/nimbus.extension.json +15 -0
- package/connectors/stripe/src/search-filter.ts +19 -0
- package/connectors/stripe/src/server.ts +65 -0
- package/connectors/superset/nimbus.extension.json +16 -0
- package/connectors/superset/src/search-filter.ts +22 -0
- package/connectors/superset/src/server.ts +119 -0
- package/connectors/tableau/nimbus.extension.json +16 -0
- package/connectors/tableau/src/search-filter.ts +22 -0
- package/connectors/tableau/src/server.ts +248 -0
- package/connectors/teams/nimbus.extension.json +20 -0
- package/connectors/teams/src/bot-send.ts +72 -0
- package/connectors/teams/src/server.ts +257 -0
- package/connectors/testflight/nimbus.extension.json +15 -0
- package/connectors/testflight/src/jwt.ts +27 -0
- package/connectors/testflight/src/search-filter.ts +30 -0
- package/connectors/testflight/src/server.ts +74 -0
- package/connectors/vercel/nimbus.extension.json +15 -0
- package/connectors/vercel/src/search-filter.ts +34 -0
- package/connectors/vercel/src/server.ts +79 -0
- package/connectors/vertex-ai/nimbus.extension.json +15 -0
- package/connectors/vertex-ai/src/server.ts +6 -0
- package/connectors/vertex-ai/src/tools.ts +145 -0
- package/connectors/wiz/nimbus.extension.json +15 -0
- package/connectors/wiz/src/search-filter.ts +50 -0
- package/connectors/wiz/src/server.ts +192 -0
- package/connectors/workday/nimbus.extension.json +19 -0
- package/connectors/workday/src/search-filter.ts +5 -0
- package/connectors/workday/src/server.ts +78 -0
- package/connectors/workday/src/tools.ts +1 -0
- package/connectors/zendesk/nimbus.extension.json +16 -0
- package/connectors/zendesk/src/search-filter.ts +11 -0
- package/connectors/zendesk/src/server.ts +77 -0
- package/connectors/zoom/nimbus.extension.json +15 -0
- package/connectors/zoom/src/encode-meeting-path.ts +5 -0
- package/connectors/zoom/src/recordings-window.ts +34 -0
- package/connectors/zoom/src/search-filter.ts +42 -0
- package/connectors/zoom/src/server.ts +98 -0
- package/connectors/zotero/nimbus.extension.json +15 -0
- package/connectors/zotero/src/search-filter.ts +81 -0
- package/connectors/zotero/src/server.ts +82 -0
- package/package.json +152 -0
- package/shared/atlassian-json-fetch.ts +42 -0
- package/shared/audit-chain.ts +133 -0
- package/shared/connector-mode.ts +60 -0
- package/shared/consent-kit.ts +323 -0
- package/shared/fetch-bearer-json.ts +18 -0
- package/shared/header-safe.ts +28 -0
- package/shared/imap-bodystructure.ts +113 -0
- package/shared/imap-mail-core.ts +57 -0
- package/shared/imap-tool-kit.ts +293 -0
- package/shared/join-api-path.ts +18 -0
- package/shared/kb-markdown.ts +142 -0
- package/shared/mcp-search-tool.ts +37 -0
- package/shared/mcp-tool-kit.ts +35 -0
- package/shared/nimbus-spawn.ts +128 -0
- package/shared/rest-tool-kit.ts +18 -0
- package/shared/run-cli-json.ts +55 -0
- package/shared/run-read-only-mcp-connector.ts +59 -0
- package/shared/safe-cli-arg.ts +55 -0
- package/shared/search-filter.ts +140 -0
- package/shared/strip-trailing-slashes.ts +7 -0
- package/shared/write-scope.ts +58 -0
- package/standalone/src/bin.ts +19 -0
- package/standalone/src/launcher.ts +186 -0
- package/standalone/tsconfig.json +8 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchesResult } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterSnykAggregatedIssues } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const SNYK_API = "https://api.snyk.io";
|
|
8
|
+
|
|
9
|
+
function authHeader(): Record<string, string> {
|
|
10
|
+
const t = process.env["SNYK_TOKEN"]?.trim();
|
|
11
|
+
if (t === undefined || t === "") {
|
|
12
|
+
throw new Error("SNYK_TOKEN is not set");
|
|
13
|
+
}
|
|
14
|
+
return { Authorization: `token ${t}`, Accept: "application/json" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function snykGet(path: string): Promise<unknown> {
|
|
18
|
+
const res = await fetch(`${SNYK_API}${path}`, { headers: authHeader() });
|
|
19
|
+
const text = await res.text();
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(`Snyk ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(text) as unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function snykPost(path: string, body: unknown): Promise<unknown> {
|
|
27
|
+
const res = await fetch(`${SNYK_API}${path}`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { ...authHeader(), "Content-Type": "application/json" },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
});
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Snyk ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
35
|
+
}
|
|
36
|
+
if (text.trim() === "") {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
return JSON.parse(text) as unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await runReadOnlyMcpConnector("nimbus-snyk", (reg) => {
|
|
43
|
+
reg(
|
|
44
|
+
"snyk_list",
|
|
45
|
+
"List Snyk projects for an org, or aggregated issues for a project. When projectId is omitted, returns the org's project list. Severities default to all four (critical/high/medium/low).",
|
|
46
|
+
z.object({
|
|
47
|
+
orgId: z.string().min(1),
|
|
48
|
+
projectId: z.string().min(1).optional(),
|
|
49
|
+
severities: z
|
|
50
|
+
.array(z.enum(["critical", "high", "medium", "low"]))
|
|
51
|
+
.min(1)
|
|
52
|
+
.optional(),
|
|
53
|
+
limit: z.number().int().min(1).max(1000).optional(),
|
|
54
|
+
}),
|
|
55
|
+
async (p) => {
|
|
56
|
+
if (p.projectId === undefined) {
|
|
57
|
+
return jsonResult(await snykGet(`/v1/org/${encodeURIComponent(p.orgId)}/projects`));
|
|
58
|
+
}
|
|
59
|
+
const severities = p.severities ?? ["critical", "high", "medium", "low"];
|
|
60
|
+
const body = {
|
|
61
|
+
filters: {
|
|
62
|
+
severities,
|
|
63
|
+
types: ["vuln", "license"],
|
|
64
|
+
ignored: false,
|
|
65
|
+
patched: false,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const path = `/v1/org/${encodeURIComponent(p.orgId)}/project/${encodeURIComponent(p.projectId)}/aggregated-issues`;
|
|
69
|
+
const data = await snykPost(path, body);
|
|
70
|
+
if (p.limit !== undefined && data !== null && typeof data === "object") {
|
|
71
|
+
const issues = (data as { issues?: unknown[] }).issues;
|
|
72
|
+
if (Array.isArray(issues) && issues.length > p.limit) {
|
|
73
|
+
return jsonResult({
|
|
74
|
+
...(data as Record<string, unknown>),
|
|
75
|
+
issues: issues.slice(0, p.limit),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return jsonResult(data);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
reg(
|
|
84
|
+
"snyk_get",
|
|
85
|
+
"Fetch one Snyk issue by id from an org + project. Returns the aggregated-issue envelope for the matching `issue.id`; throws when no match is found.",
|
|
86
|
+
z.object({
|
|
87
|
+
orgId: z.string().min(1),
|
|
88
|
+
projectId: z.string().min(1),
|
|
89
|
+
issueId: z.string().min(1),
|
|
90
|
+
}),
|
|
91
|
+
async (p) => {
|
|
92
|
+
const body = {
|
|
93
|
+
filters: {
|
|
94
|
+
severities: ["critical", "high", "medium", "low"],
|
|
95
|
+
types: ["vuln", "license"],
|
|
96
|
+
ignored: false,
|
|
97
|
+
patched: false,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const path = `/v1/org/${encodeURIComponent(p.orgId)}/project/${encodeURIComponent(p.projectId)}/aggregated-issues`;
|
|
101
|
+
const root = await snykPost(path, body);
|
|
102
|
+
const issues = (root as { issues?: unknown[] } | null)?.issues;
|
|
103
|
+
if (!Array.isArray(issues)) {
|
|
104
|
+
throw new TypeError(`Snyk: project ${p.projectId} returned no issues envelope`);
|
|
105
|
+
}
|
|
106
|
+
const match = issues.find((it) => {
|
|
107
|
+
if (it === null || typeof it !== "object") {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const id = (it as { id?: unknown }).id;
|
|
111
|
+
return typeof id === "string" && id === p.issueId;
|
|
112
|
+
});
|
|
113
|
+
if (match === undefined) {
|
|
114
|
+
throw new Error(`Snyk: issue ${p.issueId} not found in project ${p.projectId}`);
|
|
115
|
+
}
|
|
116
|
+
return jsonResult(match);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
reg(
|
|
121
|
+
"snyk_search",
|
|
122
|
+
"Substring search across a project's aggregated issues. Matches the query against `issueData.title`, `issueData.cve`, and `pkgName` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
123
|
+
z.object({
|
|
124
|
+
orgId: z.string().min(1),
|
|
125
|
+
projectId: z.string().min(1),
|
|
126
|
+
query: z.string().min(1),
|
|
127
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
128
|
+
}),
|
|
129
|
+
async (p) => {
|
|
130
|
+
const body = {
|
|
131
|
+
filters: {
|
|
132
|
+
severities: ["critical", "high", "medium", "low"],
|
|
133
|
+
types: ["vuln", "license"],
|
|
134
|
+
ignored: false,
|
|
135
|
+
patched: false,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const path = `/v1/org/${encodeURIComponent(p.orgId)}/project/${encodeURIComponent(p.projectId)}/aggregated-issues`;
|
|
139
|
+
const root = await snykPost(path, body);
|
|
140
|
+
const issues = (root as { issues?: unknown[] } | null)?.issues;
|
|
141
|
+
return matchesResult(issues, filterSnykAggregatedIssues, p);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.sonarqube",
|
|
3
|
+
"displayName": "SonarQube / SonarCloud",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "SonarQube + SonarCloud REST connector (issues, projects). Read-only; surfaces severity, type (BUG/VULNERABILITY/CODE_SMELL), rule, file/line into the local index. Self-hosted SonarQube via `sonarqube.url` is a known Task 14 runtime-merge follow-up — the SaaS host (sonarcloud.io) is the only one in the static sandbox manifest today.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["sonarcloud.io"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asObjectish,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
stringField,
|
|
6
|
+
} from "../../../shared/search-filter.ts";
|
|
7
|
+
|
|
8
|
+
export type SonarSearchMatchOptions = SearchMatchOptions;
|
|
9
|
+
|
|
10
|
+
function stringArray(value: unknown): string[] {
|
|
11
|
+
return Array.isArray(value) ? value.filter((t): t is string => typeof t === "string") : [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
15
|
+
const row = asObjectish(item);
|
|
16
|
+
if (row === undefined) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const tags = stringArray(row["tags"]);
|
|
20
|
+
return [
|
|
21
|
+
stringField(row, "message"),
|
|
22
|
+
stringField(row, "rule"),
|
|
23
|
+
stringField(row, "component"),
|
|
24
|
+
tags.join(" "),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const filterSonarIssues = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchesResult } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterSonarIssues } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_API = "https://sonarcloud.io";
|
|
8
|
+
const ISSUE_TYPES = ["BUG", "VULNERABILITY", "CODE_SMELL"] as const;
|
|
9
|
+
const OPEN_STATUSES = ["OPEN", "CONFIRMED", "REOPENED"] as const;
|
|
10
|
+
|
|
11
|
+
function stripTrailingSlashes(s: string): string {
|
|
12
|
+
let end = s.length;
|
|
13
|
+
while (end > 0 && s.codePointAt(end - 1) === 47) end -= 1;
|
|
14
|
+
return s.slice(0, end);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function apiBase(): string {
|
|
18
|
+
const raw = process.env["SONARQUBE_URL"]?.trim();
|
|
19
|
+
if (raw === undefined || raw === "") {
|
|
20
|
+
return DEFAULT_API;
|
|
21
|
+
}
|
|
22
|
+
return stripTrailingSlashes(raw);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function authHeader(): Record<string, string> {
|
|
26
|
+
const t = process.env["SONARQUBE_TOKEN"]?.trim();
|
|
27
|
+
if (t === undefined || t === "") {
|
|
28
|
+
throw new Error("SONARQUBE_TOKEN is not set");
|
|
29
|
+
}
|
|
30
|
+
return { Authorization: `Bearer ${t}`, Accept: "application/json" };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function sonarGet(path: string): Promise<unknown> {
|
|
34
|
+
const res = await fetch(`${apiBase()}${path}`, { headers: authHeader() });
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(`SonarQube ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
38
|
+
}
|
|
39
|
+
return JSON.parse(text) as unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await runReadOnlyMcpConnector("nimbus-sonarqube", (reg) => {
|
|
43
|
+
reg(
|
|
44
|
+
"sonarqube_list",
|
|
45
|
+
"List SonarQube projects, or issues for a project. When projectKey is omitted, returns the project list (SonarCloud requires `SONARQUBE_ORGANIZATION` env; self-hosted ignores it). When projectKey is provided, returns open issues (status OPEN/CONFIRMED/REOPENED) for that project.",
|
|
46
|
+
z.object({
|
|
47
|
+
projectKey: z.string().min(1).optional(),
|
|
48
|
+
types: z.array(z.enum(ISSUE_TYPES)).min(1).optional(),
|
|
49
|
+
severities: z
|
|
50
|
+
.array(z.enum(["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"]))
|
|
51
|
+
.min(1)
|
|
52
|
+
.optional(),
|
|
53
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
54
|
+
}),
|
|
55
|
+
async (p) => {
|
|
56
|
+
if (p.projectKey === undefined) {
|
|
57
|
+
const org = process.env["SONARQUBE_ORGANIZATION"]?.trim();
|
|
58
|
+
const search = new URLSearchParams({ qualifiers: "TRK", ps: "100" });
|
|
59
|
+
if (org !== undefined && org !== "") {
|
|
60
|
+
search.set("organization", org);
|
|
61
|
+
}
|
|
62
|
+
return jsonResult(await sonarGet(`/api/components/search?${search.toString()}`));
|
|
63
|
+
}
|
|
64
|
+
const types = p.types ?? ISSUE_TYPES;
|
|
65
|
+
const search = new URLSearchParams({
|
|
66
|
+
componentKeys: p.projectKey,
|
|
67
|
+
types: types.join(","),
|
|
68
|
+
statuses: OPEN_STATUSES.join(","),
|
|
69
|
+
ps: String(p.limit ?? 100),
|
|
70
|
+
});
|
|
71
|
+
if (p.severities !== undefined && p.severities.length > 0) {
|
|
72
|
+
search.set("severities", p.severities.join(","));
|
|
73
|
+
}
|
|
74
|
+
return jsonResult(await sonarGet(`/api/issues/search?${search.toString()}`));
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
reg(
|
|
79
|
+
"sonarqube_get",
|
|
80
|
+
"Fetch one SonarQube issue by key. Returns the matching issue envelope from `/api/issues/search?issues=<key>`; throws when no match is found.",
|
|
81
|
+
z.object({
|
|
82
|
+
issueKey: z.string().min(1),
|
|
83
|
+
}),
|
|
84
|
+
async (p) => {
|
|
85
|
+
const search = new URLSearchParams({ issues: p.issueKey, ps: "1" });
|
|
86
|
+
const root = await sonarGet(`/api/issues/search?${search.toString()}`);
|
|
87
|
+
const issues = (root as { issues?: unknown[] } | null)?.issues;
|
|
88
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
89
|
+
throw new Error(`SonarQube: issue ${p.issueKey} not found`);
|
|
90
|
+
}
|
|
91
|
+
return jsonResult(issues[0]);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
reg(
|
|
96
|
+
"sonarqube_search",
|
|
97
|
+
"Substring search across a project's open issues. Matches the query against `message`, `rule`, `component`, and `tags` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
98
|
+
z.object({
|
|
99
|
+
projectKey: z.string().min(1),
|
|
100
|
+
query: z.string().min(1),
|
|
101
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
102
|
+
}),
|
|
103
|
+
async (p) => {
|
|
104
|
+
const search = new URLSearchParams({
|
|
105
|
+
componentKeys: p.projectKey,
|
|
106
|
+
types: ISSUE_TYPES.join(","),
|
|
107
|
+
statuses: OPEN_STATUSES.join(","),
|
|
108
|
+
ps: "500",
|
|
109
|
+
});
|
|
110
|
+
const root = await sonarGet(`/api/issues/search?${search.toString()}`);
|
|
111
|
+
const issues = (root as { issues?: unknown[] } | null)?.issues;
|
|
112
|
+
return matchesResult(issues, filterSonarIssues, p);
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.stackoverflow",
|
|
3
|
+
"displayName": "Stack Overflow for Teams",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Stack Overflow for Teams connector (read-only). Surfaces the team's private Q&A `questions` as `stackoverflow:question` items in the local index — the title, the question body, the tags, the score / view count / answer count, the answered flag, and the asking user. Useful for answering knowledge questions (\"how do we handle retries?\", \"who knows about the billing pipeline?\") without leaving Nimbus. Bearer API-token auth (`Authorization: Bearer <token>` — a Stack Overflow for Teams Personal Access Token; never logged) plus the `Accept: application/json` request header. The SaaS host (api.stackoverflowteams.com) is fixed; the team slug is URL-encoded into the request path. The v3 API page-paginates the `/v3/teams/<team>/questions` endpoint by page number (1-based, page_size=100), returning the `{ items, totalCount, pageSize, page, totalPages, sort, order }` envelope. `creationDate` / `lastActivityDate` / `lastEditDate` are ISO-8601 (converted to epoch-ms on index). `webUrl` is the per-question canonical URL. Answers, articles, tags-as-items, and users-as-items are deferred follow-ups; v1 indexes questions only.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.stackoverflowteams.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asObjectish,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
stringField,
|
|
6
|
+
} from "../../../shared/search-filter.ts";
|
|
7
|
+
|
|
8
|
+
export type StackOverflowSearchMatchOptions = SearchMatchOptions;
|
|
9
|
+
|
|
10
|
+
function tagText(row: Record<string, unknown>): string {
|
|
11
|
+
const tags = row["tags"];
|
|
12
|
+
if (!Array.isArray(tags)) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
const names: string[] = [];
|
|
16
|
+
for (const t of tags) {
|
|
17
|
+
if (typeof t === "string") {
|
|
18
|
+
names.push(t);
|
|
19
|
+
} else if (t !== null && typeof t === "object") {
|
|
20
|
+
const name = (t as Record<string, unknown>)["name"];
|
|
21
|
+
if (typeof name === "string") {
|
|
22
|
+
names.push(name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return names.join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ownerName(row: Record<string, unknown>): string {
|
|
30
|
+
const owner = row["owner"];
|
|
31
|
+
if (owner === null || typeof owner !== "object") {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
const name = (owner as Record<string, unknown>)["name"];
|
|
35
|
+
return typeof name === "string" ? name : "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
39
|
+
const row = asObjectish(item);
|
|
40
|
+
if (row === undefined) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return [
|
|
44
|
+
stringField(row, "title"),
|
|
45
|
+
stringField(row, "body"),
|
|
46
|
+
stringField(row, "bodyMarkdown"),
|
|
47
|
+
tagText(row),
|
|
48
|
+
ownerName(row),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const filterStackOverflowQuestions = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterStackOverflowQuestions } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const BASE = "https://api.stackoverflowteams.com";
|
|
8
|
+
|
|
9
|
+
function apiToken(): string {
|
|
10
|
+
const t = process.env["STACKOVERFLOW_TOKEN"]?.trim();
|
|
11
|
+
if (t === undefined || t === "") {
|
|
12
|
+
throw new Error("STACKOVERFLOW_TOKEN is not set");
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function teamSlug(): string {
|
|
18
|
+
const t = process.env["STACKOVERFLOW_TEAM"]?.trim();
|
|
19
|
+
if (t === undefined || t === "") {
|
|
20
|
+
throw new Error("STACKOVERFLOW_TEAM is not set");
|
|
21
|
+
}
|
|
22
|
+
return t;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function authHeader(): Record<string, string> {
|
|
26
|
+
return { Authorization: `Bearer ${apiToken()}`, Accept: "application/json" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function stackOverflowGet(path: string): Promise<unknown> {
|
|
30
|
+
const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`Stack Overflow ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
34
|
+
}
|
|
35
|
+
return JSON.parse(text) as unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function questionsBasePath(): string {
|
|
39
|
+
return `/v3/teams/${encodeURIComponent(teamSlug())}/questions`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await runReadOnlyMcpConnector("nimbus-stackoverflow", (reg) => {
|
|
43
|
+
reg(
|
|
44
|
+
"stackoverflow_list",
|
|
45
|
+
"List the team's Stack Overflow for Teams questions (`GET /v3/teams/<team>/questions?page=1&pagesize=100&sort=creation&order=desc`). Returns the `{ items: [...], totalCount, pageSize, page, totalPages, sort, order }` envelope — `items` holds the question objects.",
|
|
46
|
+
z.object({}),
|
|
47
|
+
async () => {
|
|
48
|
+
return jsonResult(
|
|
49
|
+
await stackOverflowGet(
|
|
50
|
+
`${questionsBasePath()}?page=1&pagesize=100&sort=creation&order=desc`,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
reg(
|
|
57
|
+
"stackoverflow_get",
|
|
58
|
+
"Fetch one Stack Overflow for Teams question by its id (`GET /v3/teams/<team>/questions/{id}`). Returns the question object directly (NOT wrapped in an `{ items }` envelope). Throws when no match is found.",
|
|
59
|
+
z.object({
|
|
60
|
+
id: z.string().min(1),
|
|
61
|
+
}),
|
|
62
|
+
async (p) => {
|
|
63
|
+
return jsonResult(
|
|
64
|
+
await stackOverflowGet(`${questionsBasePath()}/${encodeURIComponent(p.id)}`),
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
reg(
|
|
70
|
+
"stackoverflow_search",
|
|
71
|
+
"Substring search across the team's Stack Overflow for Teams questions (first page only). Matches the query against the question title, body, tags, and the asking user's name (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
72
|
+
searchToolInputSchema(100),
|
|
73
|
+
async (p) => {
|
|
74
|
+
const root = await stackOverflowGet(
|
|
75
|
+
`${questionsBasePath()}?page=1&pagesize=100&sort=creation&order=desc`,
|
|
76
|
+
);
|
|
77
|
+
const items = (root as { items?: unknown[] } | null)?.items;
|
|
78
|
+
return matchesResult(items, filterStackOverflowQuestions, p);
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.storybook",
|
|
3
|
+
"displayName": "Storybook",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Storybook component/story manifest connector (Tier-5 local class). Indexes the story-level metadata from a local Storybook manifest — the index.json (Storybook v7+) or legacy stories.json (v6) that `storybook build` writes to disk — as `storybook:story` items: story id, component title, story name, import path, tags, and entry type. This gives design-system component coverage and lets you recall 'which stories cover the Button component'. It is a pure filesystem read of one JSON manifest under the configured Storybook output directory: NO browser is launched, NO dev server is contacted, and NO component code is executed. The user points the connector at their Storybook output dir (e.g. storybook-static) via the non-secret path `storybook.dir`; read tools are `storybook_list`, `storybook_get`, `storybook_search`. The configured directory arrives as the scoped environment variable STORYBOOK_DIR and is added to the sandbox filesystem.read allow-list at spawn time. No network access.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": [],
|
|
11
|
+
"filesystem": { "read": [], "write": [] }
|
|
12
|
+
},
|
|
13
|
+
"hitlRequired": [],
|
|
14
|
+
"syncInterval": 600,
|
|
15
|
+
"minNimbusVersion": "0.2.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { parseStorybookIndex, type StorybookStory } from "@nimbus-dev/sdk";
|
|
4
|
+
|
|
5
|
+
export type { StorybookStory };
|
|
6
|
+
export { parseStorybookIndex };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local-only reader for a Storybook component/story manifest. This module is the
|
|
10
|
+
* MCP-server-side counterpart of the gateway's `mapStorybookStoryToItem` mapper:
|
|
11
|
+
* it reads the `index.json` (v7+) or legacy `stories.json` (v6) manifest that
|
|
12
|
+
* `storybook build` writes to disk and returns story-level METADATA.
|
|
13
|
+
*
|
|
14
|
+
* No browser, no dev-server connection, no code execution — a pure filesystem
|
|
15
|
+
* read of one JSON manifest under the configured Storybook output dir.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
19
|
+
const MANIFEST_NAMES = ["index.json", "stories.json"] as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the configured Storybook dir from the environment. Throws when unset so
|
|
23
|
+
* the MCP tools surface a clear error rather than reading an empty path.
|
|
24
|
+
*/
|
|
25
|
+
export function storybookDir(): string {
|
|
26
|
+
const dir = process.env["STORYBOOK_DIR"]?.trim();
|
|
27
|
+
if (dir === undefined || dir === "") {
|
|
28
|
+
throw new Error("STORYBOOK_DIR is not set");
|
|
29
|
+
}
|
|
30
|
+
return resolve(dir);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readManifest(root: string): Promise<unknown> {
|
|
34
|
+
for (const name of MANIFEST_NAMES) {
|
|
35
|
+
try {
|
|
36
|
+
const buf = await readFile(join(root, name));
|
|
37
|
+
if (buf.byteLength > MAX_FILE_BYTES) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return JSON.parse(buf.toString("utf8")) as unknown;
|
|
41
|
+
} catch {
|
|
42
|
+
// try the next candidate name
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Read + parse all stories from the manifest under the configured dir. */
|
|
49
|
+
export async function loadStories(): Promise<StorybookStory[]> {
|
|
50
|
+
const parsed = await readManifest(storybookDir());
|
|
51
|
+
return parsed === null ? [] : parseStorybookIndex(parsed);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Substring search over story id / component title / story name / tags. */
|
|
55
|
+
export function filterStories(stories: readonly StorybookStory[], query: string): StorybookStory[] {
|
|
56
|
+
const q = query.trim().toLowerCase();
|
|
57
|
+
if (q === "") {
|
|
58
|
+
return [...stories];
|
|
59
|
+
}
|
|
60
|
+
return stories.filter(
|
|
61
|
+
(s) =>
|
|
62
|
+
s.id.toLowerCase().includes(q) ||
|
|
63
|
+
(s.title ?? "").toLowerCase().includes(q) ||
|
|
64
|
+
(s.name ?? "").toLowerCase().includes(q) ||
|
|
65
|
+
s.tags.some((t) => t.toLowerCase().includes(q)),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
4
|
+
import { filterStories, loadStories, type StorybookStory } from "./storybook-parse.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Storybook MCP tool surface. Reads the local Storybook manifest (index.json /
|
|
8
|
+
* stories.json) and returns story-level metadata — a pure filesystem read. No
|
|
9
|
+
* browser, no dev-server connection, no code execution.
|
|
10
|
+
*/
|
|
11
|
+
export const STORYBOOK_TOOL_NAMES = [
|
|
12
|
+
"storybook_list",
|
|
13
|
+
"storybook_get",
|
|
14
|
+
"storybook_search",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
function toEnvelope(s: StorybookStory): Record<string, unknown> {
|
|
18
|
+
return {
|
|
19
|
+
id: s.id,
|
|
20
|
+
title: s.title,
|
|
21
|
+
name: s.name,
|
|
22
|
+
importPath: s.importPath,
|
|
23
|
+
tags: [...s.tags],
|
|
24
|
+
type: s.entryType,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register the read-only Storybook tools onto the given registrar. Shared
|
|
30
|
+
* between `server.ts` (live) and the contract test (introspection).
|
|
31
|
+
*/
|
|
32
|
+
export function registerStorybookTools(reg: ZodToolRegistrar): void {
|
|
33
|
+
reg(
|
|
34
|
+
"storybook_list",
|
|
35
|
+
"List stories from the local Storybook manifest (index.json / stories.json). Returns each story's id, component title, story name, import path, tags, and type. A pure filesystem read — no browser or dev server is contacted.",
|
|
36
|
+
z.object({ limit: z.number().int().min(1).max(2000).optional() }),
|
|
37
|
+
async (p) => {
|
|
38
|
+
const all = await loadStories();
|
|
39
|
+
const limit = p.limit ?? 500;
|
|
40
|
+
return jsonResult({ items: all.slice(0, limit).map(toEnvelope) });
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
reg(
|
|
45
|
+
"storybook_get",
|
|
46
|
+
"Fetch one story by its Storybook id (e.g. 'components-button--primary'). Returns the story's metadata, or null if absent.",
|
|
47
|
+
z.object({ id: z.string().min(1).max(512) }),
|
|
48
|
+
async (p) => {
|
|
49
|
+
const all = await loadStories();
|
|
50
|
+
const found = all.find((s) => s.id === p.id) ?? null;
|
|
51
|
+
return jsonResult(found === null ? { item: null } : { item: toEnvelope(found) });
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
reg(
|
|
56
|
+
"storybook_search",
|
|
57
|
+
"Substring search over stories (matches id, component title, story name, and tags). Returns the same view as storybook_list.",
|
|
58
|
+
z.object({
|
|
59
|
+
query: z.string().min(1).max(500),
|
|
60
|
+
limit: z.number().int().min(1).max(2000).optional(),
|
|
61
|
+
}),
|
|
62
|
+
async (p) => {
|
|
63
|
+
const all = await loadStories();
|
|
64
|
+
const matches = filterStories(all, p.query).slice(0, p.limit ?? 500);
|
|
65
|
+
return jsonResult({ matches: matches.map(toEnvelope) });
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.stripe",
|
|
3
|
+
"displayName": "Stripe",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Stripe billing connector (read-only). Surfaces the user's recent invoices as `stripe:invoice` items in the local index — invoice number, status (draft/open/paid/void/uncollectible), amount due/paid + currency, customer id/name/email, the hosted invoice + PDF URLs, the subscription id, and the created / due / billing-period dates. Useful for answering billing questions (\"which invoices are still open?\", \"what did ACME pay last month?\") without leaving Nimbus. Bearer secret-key auth. The SaaS host (api.stripe.com) is fixed. Stripe timestamps are epoch seconds (converted to ms on index). Customers / charges / payment intents / disputes / subscription events and the `stripe.refund.create` write are deferred follow-ups.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.stripe.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fieldsFromKeys,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
} from "../../../shared/search-filter.ts";
|
|
6
|
+
|
|
7
|
+
export type StripeSearchMatchOptions = SearchMatchOptions;
|
|
8
|
+
|
|
9
|
+
export const filterStripeInvoices = makeQueryFilter(
|
|
10
|
+
fieldsFromKeys([
|
|
11
|
+
"id",
|
|
12
|
+
"number",
|
|
13
|
+
"status",
|
|
14
|
+
"customer",
|
|
15
|
+
"customer_name",
|
|
16
|
+
"customer_email",
|
|
17
|
+
"description",
|
|
18
|
+
]),
|
|
19
|
+
);
|