@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,145 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
|
+
import { nimbusSpawn } from "../../../shared/nimbus-spawn.ts";
|
|
4
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { isSafeCliArg } from "../../../shared/safe-cli-arg.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A value passed to the `gcloud ai` CLI (a Vertex AI region like `us-central1`
|
|
9
|
+
* or a model id). Rejected at the schema boundary if it begins with `-` (argv
|
|
10
|
+
* flag smuggling) or contains control characters. Exported so the contract test
|
|
11
|
+
* can assert the guard rejects a `-`-prefixed value.
|
|
12
|
+
*/
|
|
13
|
+
export const cliArg = z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.refine(isSafeCliArg, { message: 'must not start with "-" or contain control characters' });
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* GCP Vertex AI (Tier-3, no-row-data) MCP tool surface. ALL tools index Vertex
|
|
20
|
+
* AI model REGISTRY metadata only (model resource name, display name, version
|
|
21
|
+
* id, region, create/update timestamps). NONE call inference / prediction —
|
|
22
|
+
* there is NO `gcloud ai endpoints predict`, NO `explain`, NO `raw-predict`, NO
|
|
23
|
+
* batch-prediction output read. The names are introspected by the
|
|
24
|
+
* `assertNoRowDataTools` contract test, so they must never contain a row-data
|
|
25
|
+
* segment (predict/query/records/results/scan/sample/…).
|
|
26
|
+
*/
|
|
27
|
+
export const VERTEX_AI_TOOL_NAMES = [
|
|
28
|
+
"vertex_ai_list",
|
|
29
|
+
"vertex_ai_get",
|
|
30
|
+
"vertex_ai_search",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
const DEFAULT_REGION = "us-central1";
|
|
34
|
+
|
|
35
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
36
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function strField(r: Record<string, unknown>, key: string): string {
|
|
40
|
+
const v = r[key];
|
|
41
|
+
return typeof v === "string" ? v : "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function asArray(parsed: unknown): unknown[] {
|
|
45
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The configured GCP project id, if `GOOGLE_CLOUD_PROJECT` is set at spawn. */
|
|
49
|
+
function projectArgs(): string[] {
|
|
50
|
+
const project = process.env["GOOGLE_CLOUD_PROJECT"]?.trim();
|
|
51
|
+
return project !== undefined && project !== "" ? ["--project", project] : [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the Vertex AI region: an explicit tool argument wins, else the
|
|
56
|
+
* `VERTEX_AI_REGION` env injected at spawn, else `us-central1`. The result is a
|
|
57
|
+
* raw, unguarded string — callers must pass it through `cliArg`/`isSafeCliArg`
|
|
58
|
+
* (or the schema) before handing it to gcloud.
|
|
59
|
+
*/
|
|
60
|
+
function resolveRegion(explicit?: string): string {
|
|
61
|
+
const e = (explicit ?? process.env["VERTEX_AI_REGION"])?.trim();
|
|
62
|
+
return e !== undefined && e !== "" ? e : DEFAULT_REGION;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run `gcloud ai <args> --region <region> --format json` and parse stdout.
|
|
67
|
+
* gcloud reads Application Default Credentials from
|
|
68
|
+
* `GOOGLE_APPLICATION_CREDENTIALS` natively from the process env injected at
|
|
69
|
+
* spawn time. The `<region>` is `isSafeCliArg`-guarded before use (argv flag
|
|
70
|
+
* smuggling). Throws on non-zero exit.
|
|
71
|
+
*/
|
|
72
|
+
async function gcloudAi(args: string[], region: string): Promise<unknown> {
|
|
73
|
+
const regionPreview = region.slice(0, 64);
|
|
74
|
+
if (!isSafeCliArg(region)) {
|
|
75
|
+
throw new Error(`Invalid region: ${regionPreview}`);
|
|
76
|
+
}
|
|
77
|
+
const { code, stdout, stderr } = await nimbusSpawn(
|
|
78
|
+
["gcloud", "ai", ...args, "--region", region, ...projectArgs(), "--format", "json"],
|
|
79
|
+
{},
|
|
80
|
+
);
|
|
81
|
+
const out = stdout.trim();
|
|
82
|
+
const err = stderr.trim();
|
|
83
|
+
if (code !== 0) {
|
|
84
|
+
throw new Error(`gcloud ai ${args[0] ?? ""} failed: ${err.slice(0, 400)}`);
|
|
85
|
+
}
|
|
86
|
+
return out === "" ? [] : (JSON.parse(out) as unknown);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function modelMatches(entry: unknown, q: string): boolean {
|
|
90
|
+
if (!isRecord(entry)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const needle = q.toLowerCase();
|
|
94
|
+
return (
|
|
95
|
+
strField(entry, "displayName").toLowerCase().includes(needle) ||
|
|
96
|
+
strField(entry, "name").toLowerCase().includes(needle)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Register the read-only, metadata-only Vertex AI tools onto the given
|
|
102
|
+
* registrar. Shared between `server.ts` (live) and the contract test
|
|
103
|
+
* (introspection).
|
|
104
|
+
*/
|
|
105
|
+
export function registerVertexAiTools(reg: ZodToolRegistrar): void {
|
|
106
|
+
reg(
|
|
107
|
+
"vertex_ai_list",
|
|
108
|
+
"List GCP Vertex AI models in a region — METADATA ONLY (`gcloud ai models list`). Each entry carries the model `name` (resource path), `displayName`, `versionId`, `createTime`, and `updateTime`. Optional `region` defaults to the configured region (else us-central1). Never calls inference / prediction; never fetches model outputs.",
|
|
109
|
+
z.object({
|
|
110
|
+
region: cliArg.optional(),
|
|
111
|
+
}),
|
|
112
|
+
async (p) => {
|
|
113
|
+
const region = resolveRegion(p.region);
|
|
114
|
+
return jsonResult(asArray(await gcloudAi(["models", "list"], region)));
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
reg(
|
|
119
|
+
"vertex_ai_get",
|
|
120
|
+
"Fetch one GCP Vertex AI model's METADATA (`gcloud ai models describe <model> --region <region>`). Returns the model object including `name`, `displayName`, `versionId`, and create/update timestamps. The `modelId` and `region` are guarded against argv flag smuggling before use. No inference, prediction, or model-output data is returned — registry metadata only.",
|
|
121
|
+
z.object({
|
|
122
|
+
modelId: cliArg,
|
|
123
|
+
region: cliArg.optional(),
|
|
124
|
+
}),
|
|
125
|
+
async (p) => {
|
|
126
|
+
const region = resolveRegion(p.region);
|
|
127
|
+
return jsonResult(await gcloudAi(["models", "describe", p.modelId], region));
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
reg(
|
|
132
|
+
"vertex_ai_search",
|
|
133
|
+
"Substring search over GCP Vertex AI model display names + resource names (case-insensitive) — METADATA ONLY. Returns a `{ matches: [...] }` envelope of `gcloud ai models list` metadata entries matching the query. Optional `region` defaults to the configured region (else us-central1). Never calls inference / prediction.",
|
|
134
|
+
z.object({
|
|
135
|
+
query: z.string().min(1),
|
|
136
|
+
region: cliArg.optional(),
|
|
137
|
+
}),
|
|
138
|
+
async (p) => {
|
|
139
|
+
const region = resolveRegion(p.region);
|
|
140
|
+
const models = asArray(await gcloudAi(["models", "list"], region));
|
|
141
|
+
const matches = models.filter((m) => modelMatches(m, p.query));
|
|
142
|
+
return jsonResult({ matches });
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.wiz",
|
|
3
|
+
"displayName": "Wiz",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Wiz cloud security platform connector (CSPM findings, misconfigurations, toxic combinations). Read-only; surfaces severity, status, type, source rule, entity, projects, description into the local index. Wiz uses OAuth client_credentials auth; the connector fetches a fresh access token at startup. Regional users override `wiz.api_url` + `wiz.auth_url` vault keys; the default sandbox manifest covers the public US-east tenant. Self-hosted / regional endpoint expansion is a known Task 14 runtime-merge follow-up.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.app.wiz.io", "auth.app.wiz.io"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asObjectish,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
stringField,
|
|
6
|
+
} from "../../../shared/search-filter.ts";
|
|
7
|
+
|
|
8
|
+
export type WizSearchMatchOptions = SearchMatchOptions;
|
|
9
|
+
|
|
10
|
+
function nestedString(row: Record<string, unknown>, parent: string, key: string): string {
|
|
11
|
+
const p = row[parent];
|
|
12
|
+
if (p === null || typeof p !== "object") {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
return stringField(p as Record<string, unknown>, key);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function projectNames(row: Record<string, unknown>): string {
|
|
19
|
+
const projects = row["projects"];
|
|
20
|
+
if (!Array.isArray(projects)) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
const names: string[] = [];
|
|
24
|
+
for (const p of projects) {
|
|
25
|
+
if (p === null || typeof p !== "object") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const name = stringField(p as Record<string, unknown>, "name");
|
|
29
|
+
if (name !== "") {
|
|
30
|
+
names.push(name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return names.join(" ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
37
|
+
const row = asObjectish(item);
|
|
38
|
+
if (row === undefined) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return [
|
|
42
|
+
nestedString(row, "sourceRule", "name"),
|
|
43
|
+
stringField(row, "description"),
|
|
44
|
+
nestedString(row, "entity", "name"),
|
|
45
|
+
nestedString(row, "entity", "type"),
|
|
46
|
+
projectNames(row),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const filterWizIssues = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterWizIssues } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_API = "https://api.app.wiz.io/graphql";
|
|
9
|
+
const DEFAULT_AUTH = "https://auth.app.wiz.io/oauth/token";
|
|
10
|
+
const SEVERITIES = ["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
|
|
11
|
+
const STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] as const;
|
|
12
|
+
|
|
13
|
+
function apiUrl(): string {
|
|
14
|
+
const v = process.env["WIZ_API_URL"]?.trim();
|
|
15
|
+
return v === undefined || v === "" ? DEFAULT_API : v;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function authUrl(): string {
|
|
19
|
+
const v = process.env["WIZ_AUTH_URL"]?.trim();
|
|
20
|
+
return v === undefined || v === "" ? DEFAULT_AUTH : v;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function fetchAccessToken(): Promise<string> {
|
|
24
|
+
const clientId = process.env["WIZ_CLIENT_ID"]?.trim();
|
|
25
|
+
const clientSecret = process.env["WIZ_CLIENT_SECRET"]?.trim();
|
|
26
|
+
if (clientId === undefined || clientId === "") {
|
|
27
|
+
throw new Error("WIZ_CLIENT_ID is not set");
|
|
28
|
+
}
|
|
29
|
+
if (clientSecret === undefined || clientSecret === "") {
|
|
30
|
+
throw new Error("WIZ_CLIENT_SECRET is not set");
|
|
31
|
+
}
|
|
32
|
+
const body = new URLSearchParams({
|
|
33
|
+
grant_type: "client_credentials",
|
|
34
|
+
client_id: clientId,
|
|
35
|
+
client_secret: clientSecret,
|
|
36
|
+
audience: "wiz-api",
|
|
37
|
+
});
|
|
38
|
+
const res = await fetch(authUrl(), {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
41
|
+
body: body.toString(),
|
|
42
|
+
});
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`Wiz auth ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
46
|
+
}
|
|
47
|
+
const parsed = JSON.parse(text) as { access_token?: string };
|
|
48
|
+
if (typeof parsed.access_token !== "string" || parsed.access_token === "") {
|
|
49
|
+
throw new Error("Wiz auth: missing access_token in response");
|
|
50
|
+
}
|
|
51
|
+
return parsed.access_token;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let cachedToken: string | undefined;
|
|
55
|
+
async function getToken(): Promise<string> {
|
|
56
|
+
cachedToken ??= await fetchAccessToken();
|
|
57
|
+
return cachedToken;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function wizGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
|
|
61
|
+
const token = await getToken();
|
|
62
|
+
const res = await fetch(apiUrl(), {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
Accept: "application/json",
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({ query, variables }),
|
|
70
|
+
});
|
|
71
|
+
const text = await res.text();
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new Error(`Wiz ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
74
|
+
}
|
|
75
|
+
const parsed = JSON.parse(text) as { data?: T; errors?: unknown };
|
|
76
|
+
if (parsed.errors !== undefined) {
|
|
77
|
+
throw new Error(`Wiz GraphQL error: ${JSON.stringify(parsed.errors).slice(0, 400)}`);
|
|
78
|
+
}
|
|
79
|
+
if (parsed.data === undefined) {
|
|
80
|
+
throw new Error("Wiz GraphQL: response missing `data` field");
|
|
81
|
+
}
|
|
82
|
+
return parsed.data;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ISSUES_QUERY = `
|
|
86
|
+
query Issues($first: Int!, $after: String, $filterBy: IssueFilters) {
|
|
87
|
+
issues(first: $first, after: $after, filterBy: $filterBy) {
|
|
88
|
+
nodes {
|
|
89
|
+
id
|
|
90
|
+
sourceRule { id name }
|
|
91
|
+
severity
|
|
92
|
+
status
|
|
93
|
+
type
|
|
94
|
+
createdAt
|
|
95
|
+
updatedAt
|
|
96
|
+
resolvedAt
|
|
97
|
+
description
|
|
98
|
+
remediation
|
|
99
|
+
entity { id name type }
|
|
100
|
+
projects { id name slug }
|
|
101
|
+
}
|
|
102
|
+
pageInfo { hasNextPage endCursor }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const SINGLE_ISSUE_QUERY = `
|
|
108
|
+
query Issue($id: ID!) {
|
|
109
|
+
issue(id: $id) {
|
|
110
|
+
id
|
|
111
|
+
sourceRule { id name }
|
|
112
|
+
severity
|
|
113
|
+
status
|
|
114
|
+
type
|
|
115
|
+
createdAt
|
|
116
|
+
updatedAt
|
|
117
|
+
resolvedAt
|
|
118
|
+
description
|
|
119
|
+
remediation
|
|
120
|
+
entity { id name type }
|
|
121
|
+
projects { id name slug }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
interface IssuesPage {
|
|
127
|
+
readonly issues: {
|
|
128
|
+
readonly nodes: readonly unknown[];
|
|
129
|
+
readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await runReadOnlyMcpConnector("nimbus-wiz", (reg) => {
|
|
134
|
+
reg(
|
|
135
|
+
"wiz_list",
|
|
136
|
+
"List Wiz issues. Filters map to the `IssueFilters` GraphQL input — severity, status, and entity type are all optional. Defaults to open issues across all severities, capped at 100.",
|
|
137
|
+
z.object({
|
|
138
|
+
severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
|
|
139
|
+
status: z.array(z.enum(STATUSES)).min(1).optional(),
|
|
140
|
+
entityType: z.string().min(1).optional(),
|
|
141
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
142
|
+
}),
|
|
143
|
+
async (p) => {
|
|
144
|
+
const filterBy: Record<string, unknown> = {};
|
|
145
|
+
filterBy["status"] = p.status ?? ["OPEN"];
|
|
146
|
+
if (p.severity !== undefined && p.severity.length > 0) {
|
|
147
|
+
filterBy["severity"] = p.severity;
|
|
148
|
+
}
|
|
149
|
+
if (p.entityType !== undefined) {
|
|
150
|
+
filterBy["entityType"] = p.entityType;
|
|
151
|
+
}
|
|
152
|
+
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
153
|
+
first: p.limit ?? 100,
|
|
154
|
+
after: null,
|
|
155
|
+
filterBy,
|
|
156
|
+
});
|
|
157
|
+
return jsonResult(data.issues);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
reg(
|
|
162
|
+
"wiz_get",
|
|
163
|
+
"Fetch one Wiz issue by its id. Throws when no match is found.",
|
|
164
|
+
z.object({
|
|
165
|
+
issueId: z.string().min(1),
|
|
166
|
+
}),
|
|
167
|
+
async (p) => {
|
|
168
|
+
const data = await wizGraphql<{ issue: unknown }>(SINGLE_ISSUE_QUERY, {
|
|
169
|
+
id: p.issueId,
|
|
170
|
+
});
|
|
171
|
+
if (data.issue === null) {
|
|
172
|
+
throw new Error(`Wiz: issue ${p.issueId} not found`);
|
|
173
|
+
}
|
|
174
|
+
return jsonResult(data.issue);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
reg(
|
|
179
|
+
"wiz_search",
|
|
180
|
+
"Substring search across open issues. Matches the query against `sourceRule.name`, `description`, `entity.name`, `entity.type`, and project names (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
181
|
+
searchToolInputSchema(200),
|
|
182
|
+
async (p) => {
|
|
183
|
+
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
184
|
+
first: 500,
|
|
185
|
+
after: null,
|
|
186
|
+
filterBy: { status: ["OPEN"] },
|
|
187
|
+
});
|
|
188
|
+
const matches = filterWizIssues(data.issues.nodes, { query: p.query, limit: p.limit });
|
|
189
|
+
return jsonResult({ matches });
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.workday",
|
|
3
|
+
"displayName": "Workday",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Workday connector (read-only). Surfaces worker profile, job postings, and time-off requests from the Workday tenant as indexed items in the local index. Authenticates via OAuth2 authorization-code flow; the user supplies NIMBUS_OAUTH_WORKDAY_CLIENT_ID / _SECRET and NIMBUS_WORKDAY_TENANT. The tenant API host is injected at spawn via manifestWithExtraNetworkHosts.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": [],
|
|
11
|
+
"filesystem": {
|
|
12
|
+
"read": [],
|
|
13
|
+
"write": []
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"hitlRequired": [],
|
|
17
|
+
"syncInterval": 600,
|
|
18
|
+
"minNimbusVersion": "0.14.0"
|
|
19
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import {
|
|
4
|
+
fetchWithTimeout,
|
|
5
|
+
mcpJsonResult as jsonResult,
|
|
6
|
+
requireProcessEnv,
|
|
7
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
8
|
+
import {
|
|
9
|
+
runReadOnlyMcpConnector,
|
|
10
|
+
type ZodToolRegistrar,
|
|
11
|
+
} from "../../../shared/run-read-only-mcp-connector.ts";
|
|
12
|
+
import { filterWorkdayWorkers } from "./search-filter.ts";
|
|
13
|
+
|
|
14
|
+
function trimSlash(s: string): string {
|
|
15
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function apiBase(): string {
|
|
19
|
+
const host = trimSlash(requireProcessEnv("WORKDAY_TENANT_HOST"));
|
|
20
|
+
const tenant = requireProcessEnv("WORKDAY_TENANT");
|
|
21
|
+
// Workday Staffing REST: /ccx/api/staffing/v6/<tenant>/workers
|
|
22
|
+
return `${host}/ccx/api/staffing/v6/${encodeURIComponent(tenant)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function authHeader(): Record<string, string> {
|
|
26
|
+
return {
|
|
27
|
+
Authorization: `Bearer ${requireProcessEnv("WORKDAY_ACCESS_TOKEN")}`,
|
|
28
|
+
Accept: "application/json",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function wdGet(path: string): Promise<unknown> {
|
|
33
|
+
const res = await fetchWithTimeout(`${apiBase()}${path}`, { headers: authHeader() });
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`Workday ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
37
|
+
}
|
|
38
|
+
return JSON.parse(text) as unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function workersFrom(root: unknown): unknown[] {
|
|
42
|
+
const d = (root as { data?: unknown } | null)?.data;
|
|
43
|
+
return Array.isArray(d) ? d : [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function registerWorkdayTools(reg: ZodToolRegistrar): void {
|
|
47
|
+
reg(
|
|
48
|
+
"workday_list",
|
|
49
|
+
"List Workday workers (`GET /workers?limit=100`). Returns `{ data: [...] }` of worker objects (id, descriptor, title, team, department, location).",
|
|
50
|
+
z.object({ limit: z.number().int().min(1).max(100).optional() }),
|
|
51
|
+
async (p) => jsonResult(await wdGet(`/workers?limit=${p.limit ?? 100}`)),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
reg(
|
|
55
|
+
"workday_get",
|
|
56
|
+
"Fetch one Workday worker by id (`GET /workers/{id}`).",
|
|
57
|
+
z.object({ id: z.string().min(1) }),
|
|
58
|
+
async (p) => jsonResult(await wdGet(`/workers/${encodeURIComponent(p.id)}`)),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
reg(
|
|
62
|
+
"workday_search",
|
|
63
|
+
"Substring search across the first page of Workday workers (descriptor/title/team/department/location). Returns `{ matches: [...] }`.",
|
|
64
|
+
searchToolInputSchema(100),
|
|
65
|
+
async (p) => {
|
|
66
|
+
const root = await wdGet(`/workers?limit=100`);
|
|
67
|
+
return jsonResult({
|
|
68
|
+
matches: filterWorkdayWorkers(workersFrom(root), { query: p.query, limit: p.limit }),
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function startConnector(): Promise<void> {
|
|
75
|
+
await runReadOnlyMcpConnector("nimbus-workday", registerWorkdayTools);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (import.meta.main) await startConnector();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WORKDAY_TOOL_NAMES = ["workday_list", "workday_get", "workday_search"] as const;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.zendesk",
|
|
3
|
+
"displayName": "Zendesk",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Zendesk Support connector (read-only). Surfaces the user's Zendesk tickets as `zendesk:ticket` items in the local index — the subject, the plain-text description (the first comment), status (new/open/pending/hold/solved/closed), priority, type, requester/assignee/group/organization ids, tags, and the originating via-channel. Useful for correlating customer support history with code/PR changes (\"which tickets reference the checkout regression?\") without leaving Nimbus. HTTP Basic auth: the username is `<email>/token` and the password is the API token (`Authorization: Basic base64(<email>/token:<api_token>)`; never logged). Zendesk is per-tenant: the user supplies the full base URL `https://<subdomain>.zendesk.com` via the `zendesk.url` vault key, and that host is added to the sandbox network list at spawn time by `phase3AddZendeskMcp`, so the static manifest network list is intentionally empty. Zendesk cursor-paginates `/api/v2/tickets.json` (`page[size]=100`), following `meta.after_cursor` while `meta.has_more` is true. `created_at` / `updated_at` are ISO-8601 (converted to epoch-ms on index). v1 indexes tickets only — comments, users, organizations, and Help Center articles are deferred follow-ups; reply/solve/assign HITL writes are deferred.",
|
|
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,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fieldsFromKeys,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
} from "../../../shared/search-filter.ts";
|
|
6
|
+
|
|
7
|
+
export type ZendeskSearchMatchOptions = SearchMatchOptions;
|
|
8
|
+
|
|
9
|
+
export const filterZendeskTickets = makeQueryFilter(
|
|
10
|
+
fieldsFromKeys(["subject", "description", "status", "priority", "type"], { tags: true }),
|
|
11
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import {
|
|
4
|
+
encodeBasicAuthHeader,
|
|
5
|
+
mcpJsonResult as jsonResult,
|
|
6
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
7
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
8
|
+
import { filterZendeskTickets } from "./search-filter.ts";
|
|
9
|
+
|
|
10
|
+
function trimTrailingSlash(s: string): string {
|
|
11
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function baseUrl(): string {
|
|
15
|
+
const v = process.env["ZENDESK_URL"]?.trim();
|
|
16
|
+
if (v === undefined || v === "") {
|
|
17
|
+
throw new Error("ZENDESK_URL is not set");
|
|
18
|
+
}
|
|
19
|
+
return trimTrailingSlash(v);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function authHeader(): Record<string, string> {
|
|
23
|
+
const email = process.env["ZENDESK_EMAIL"]?.trim();
|
|
24
|
+
if (email === undefined || email === "") {
|
|
25
|
+
throw new Error("ZENDESK_EMAIL is not set");
|
|
26
|
+
}
|
|
27
|
+
const token = process.env["ZENDESK_API_TOKEN"]?.trim();
|
|
28
|
+
if (token === undefined || token === "") {
|
|
29
|
+
throw new Error("ZENDESK_API_TOKEN is not set");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
Authorization: encodeBasicAuthHeader(`${email}/token`, token),
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function zendeskGet(path: string): Promise<unknown> {
|
|
38
|
+
const res = await fetch(`${baseUrl()}${path}`, { headers: authHeader() });
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`Zendesk ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
42
|
+
}
|
|
43
|
+
return JSON.parse(text) as unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await runReadOnlyMcpConnector("nimbus-zendesk", (reg) => {
|
|
47
|
+
reg(
|
|
48
|
+
"zendesk_list",
|
|
49
|
+
"List the user's Zendesk tickets (`GET /api/v2/tickets.json?page[size]=100`). Returns the cursor-pagination envelope `{ tickets: [...], meta: { has_more, after_cursor }, links: { next } }` — `tickets` holds the ticket objects.",
|
|
50
|
+
z.object({}),
|
|
51
|
+
async () => {
|
|
52
|
+
return jsonResult(await zendeskGet(`/api/v2/tickets.json?page[size]=100`));
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
reg(
|
|
57
|
+
"zendesk_get",
|
|
58
|
+
"Fetch one Zendesk ticket by its numeric id (`GET /api/v2/tickets/{id}.json`). Returns the `{ ticket: {...} }` envelope. Throws when no match is found.",
|
|
59
|
+
z.object({
|
|
60
|
+
id: z.string().min(1),
|
|
61
|
+
}),
|
|
62
|
+
async (p) => {
|
|
63
|
+
return jsonResult(await zendeskGet(`/api/v2/tickets/${encodeURIComponent(p.id)}.json`));
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
reg(
|
|
68
|
+
"zendesk_search",
|
|
69
|
+
"Substring search across the user's Zendesk tickets (first page only). Matches the query against the ticket subject, description, status, priority, type, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
70
|
+
searchToolInputSchema(100),
|
|
71
|
+
async (p) => {
|
|
72
|
+
const root = await zendeskGet(`/api/v2/tickets.json?page[size]=100`);
|
|
73
|
+
const tickets = (root as { tickets?: unknown[] } | null)?.tickets;
|
|
74
|
+
return matchesResult(tickets, filterZendeskTickets, p);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.zoom",
|
|
3
|
+
"displayName": "Zoom",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Zoom REST API connector (read-only). Surfaces the authenticated user's scheduled meetings as `zoom:meeting` items AND cloud-recording transcripts as `zoom:transcript` items (prose-heavy embedding) in the local index via `GET /v2/users/me/meetings?type=scheduled` and `GET /v2/users/me/recordings`. Bearer OAuth auth (`Authorization: Bearer <token>`; short-lived access token refreshed by the Gateway via the 3-legged OAuth flow; never logged); fixed SaaS host api.zoom.us; transcript `download_url` fetches use the Bearer header — the token never appears in a logged URL. The recordings walk windowing keeps each request inside Zoom's ≤1-month constraint, and a skip-if-exists check on `<meeting_uuid>:<recording_file_id>` avoids re-downloading immutable transcripts.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.zoom.us"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoom's /v2/users/me/recordings requires (to - from) <= 1 month. We use 31
|
|
3
|
+
* days as the safe upper bound (Zoom's docs say "1 month" loosely; 31 covers
|
|
4
|
+
* any month). Returns nothing on success; throws a clear Error when the
|
|
5
|
+
* window is wrong so the LLM agent doesn't burn a round-trip on a 400.
|
|
6
|
+
* Review point 3.1.
|
|
7
|
+
*
|
|
8
|
+
* Lives in its own module (not server.ts) so it can be unit-tested without
|
|
9
|
+
* importing server.ts — which runs `runReadOnlyMcpConnector` at top level and
|
|
10
|
+
* would start a stdio transport on import. Mirrors the existing
|
|
11
|
+
* encode-meeting-path.ts / search-filter.ts extraction pattern.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_RECORDINGS_WINDOW_MS = 31 * 86_400_000;
|
|
14
|
+
|
|
15
|
+
export function validateZoomRecordingsWindow(from: string, to: string): void {
|
|
16
|
+
const fromMs = Date.parse(from);
|
|
17
|
+
const toMs = Date.parse(to);
|
|
18
|
+
if (!Number.isFinite(fromMs)) {
|
|
19
|
+
throw new TypeError(`Invalid 'from': ${from} is not an ISO-8601 date.`);
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(toMs)) {
|
|
22
|
+
throw new TypeError(`Invalid 'to': ${to} is not an ISO-8601 date.`);
|
|
23
|
+
}
|
|
24
|
+
if (toMs < fromMs) {
|
|
25
|
+
throw new Error(`'to' (${to}) must be >= 'from' (${from}).`);
|
|
26
|
+
}
|
|
27
|
+
const widthMs = toMs - fromMs;
|
|
28
|
+
if (widthMs > MAX_RECORDINGS_WINDOW_MS) {
|
|
29
|
+
const days = Math.round(widthMs / 86_400_000);
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Zoom requires (to - from) <= 1 month; got ${String(days)} days. Split the request into multiple <= 31-day windows.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|