@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,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.powerbi",
|
|
3
|
+
"displayName": "Power BI",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Power BI connector. Indexes Power BI reports as `powerbi:dashboard` items in the local index and exposes three read-only tools to the Nimbus agent (`powerbi_list`, `powerbi_get`, `powerbi_search`) plus two HITL-gated write tools: `powerbi_dataset_refresh` (trigger a dataset refresh; groupId optional, omit for My Workspace) and `powerbi_dataflow_refresh` (trigger a dataflow refresh). Uses Azure AD client-credentials flow (tenant_id/client_id/client_secret) to mint an access token, then calls the Power BI REST API. Hosts are fixed well-known endpoints (login.microsoftonline.com for AAD tokens, api.powerbi.com for the API); the static manifest network list declares both hosts directly — no per-tenant host derivation is needed.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["login.microsoftonline.com", "api.powerbi.com"],
|
|
11
|
+
"filesystem": { "read": [], "write": [] }
|
|
12
|
+
},
|
|
13
|
+
"hitlRequired": ["write"],
|
|
14
|
+
"syncInterval": 600,
|
|
15
|
+
"minNimbusVersion": "0.5.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asRecord,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
} from "../../../shared/search-filter.ts";
|
|
6
|
+
|
|
7
|
+
export type PowerBiSearchMatchOptions = SearchMatchOptions;
|
|
8
|
+
|
|
9
|
+
function stringAt(row: Record<string, unknown>, key: string): string {
|
|
10
|
+
const v = row[key];
|
|
11
|
+
return typeof v === "string" ? v : "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
15
|
+
const row = asRecord(item);
|
|
16
|
+
if (row === undefined) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return [stringAt(row, "name"), stringAt(row, "description")];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const filterPowerBiReports = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { fetchWithTimeout, mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import {
|
|
6
|
+
runReadOnlyMcpConnector,
|
|
7
|
+
type ZodToolRegistrar,
|
|
8
|
+
} from "../../../shared/run-read-only-mcp-connector.ts";
|
|
9
|
+
import { filterPowerBiReports } from "./search-filter.ts";
|
|
10
|
+
|
|
11
|
+
function requireEnv(name: string): string {
|
|
12
|
+
const v = process.env[name]?.trim();
|
|
13
|
+
if (v === undefined || v === "") {
|
|
14
|
+
throw new Error(`${name} is not set`);
|
|
15
|
+
}
|
|
16
|
+
return v;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function fetchAccessToken(
|
|
20
|
+
tenantId: string,
|
|
21
|
+
clientId: string,
|
|
22
|
+
clientSecret: string,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
25
|
+
const body =
|
|
26
|
+
`grant_type=client_credentials` +
|
|
27
|
+
`&client_id=${encodeURIComponent(clientId)}` +
|
|
28
|
+
`&client_secret=${encodeURIComponent(clientSecret)}` +
|
|
29
|
+
`&scope=${encodeURIComponent("https://analysis.windows.net/powerbi/api/.default")}`;
|
|
30
|
+
const res = await fetchWithTimeout(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
34
|
+
Accept: "application/json",
|
|
35
|
+
},
|
|
36
|
+
body,
|
|
37
|
+
});
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`Power BI token error ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
41
|
+
}
|
|
42
|
+
const parsed = JSON.parse(text) as unknown;
|
|
43
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
44
|
+
throw new Error("Power BI token response: unexpected shape");
|
|
45
|
+
}
|
|
46
|
+
const token = (parsed as Record<string, unknown>)["access_token"];
|
|
47
|
+
if (typeof token !== "string" || token === "") {
|
|
48
|
+
throw new Error("Power BI token response: missing access_token");
|
|
49
|
+
}
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const POWERBI_API_BASE = "https://api.powerbi.com";
|
|
54
|
+
|
|
55
|
+
/** Mint an AAD access token from the connector's client-credentials env. */
|
|
56
|
+
async function accessToken(): Promise<string> {
|
|
57
|
+
return fetchAccessToken(
|
|
58
|
+
requireEnv("POWERBI_TENANT_ID"),
|
|
59
|
+
requireEnv("POWERBI_CLIENT_ID"),
|
|
60
|
+
requireEnv("POWERBI_CLIENT_SECRET"),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function asRec(value: unknown): Record<string, unknown> | undefined {
|
|
65
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
66
|
+
? (value as Record<string, unknown>)
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function listReports(accessToken: string): Promise<unknown[]> {
|
|
71
|
+
const res = await fetchWithTimeout("https://api.powerbi.com/v1.0/myorg/reports", {
|
|
72
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" },
|
|
73
|
+
});
|
|
74
|
+
const text = await res.text();
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
throw new Error(`Power BI reports error ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
77
|
+
}
|
|
78
|
+
const value = asRec(JSON.parse(text) as unknown)?.["value"];
|
|
79
|
+
return Array.isArray(value) ? value : [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Fetch a dataset's table names for lineage (`GET /datasets/{id}/tables`); empty on any failure. */
|
|
83
|
+
async function fetchDatasetTables(accessToken: string, datasetId: string): Promise<string[]> {
|
|
84
|
+
const res = await fetchWithTimeout(
|
|
85
|
+
`https://api.powerbi.com/v1.0/myorg/datasets/${encodeURIComponent(datasetId)}/tables`,
|
|
86
|
+
{ headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" } },
|
|
87
|
+
);
|
|
88
|
+
if (!res.ok) return [];
|
|
89
|
+
const value = asRec(JSON.parse(await res.text()) as unknown)?.["value"];
|
|
90
|
+
if (!Array.isArray(value)) return [];
|
|
91
|
+
const out: string[] = [];
|
|
92
|
+
for (const item of value) {
|
|
93
|
+
const name = asRec(item)?.["name"];
|
|
94
|
+
if (typeof name === "string" && name !== "") out.push(name);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Attach each report's dataset-table refs in-session, so the gateway never makes a second credentialed call. */
|
|
100
|
+
async function expandReport(accessToken: string, report: unknown): Promise<unknown> {
|
|
101
|
+
const r = asRec(report);
|
|
102
|
+
if (r === undefined) return report;
|
|
103
|
+
const datasetId = r["datasetId"];
|
|
104
|
+
const datasetTables =
|
|
105
|
+
typeof datasetId === "string" && datasetId !== ""
|
|
106
|
+
? await fetchDatasetTables(accessToken, datasetId)
|
|
107
|
+
: [];
|
|
108
|
+
return { ...r, datasetTables };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): void {
|
|
112
|
+
// Despite the read-only helper's name, this connector exposes write tools. The consent
|
|
113
|
+
// kit needs the real server, which the helper now passes through as its second argument.
|
|
114
|
+
const registerWriteTool = createWriteToolRegistrar(server as ConsentServer, {
|
|
115
|
+
connector: "powerbi",
|
|
116
|
+
scopeEnv: "NIMBUS_MCP_POWERBI_WRITE_SCOPE",
|
|
117
|
+
scopeKinds: ["workspace"],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
reg(
|
|
121
|
+
"powerbi_list",
|
|
122
|
+
"List Power BI reports (`GET /v1.0/myorg/reports`), each expanded with its dataset-table refs for lineage. The reports endpoint returns the full org list in one response and has no reliable server paging, so this is a single fetch returning ALL reports with `nextCursor: null` (`cursor`/`limit` are accepted for `_list` API symmetry but never truncate).",
|
|
123
|
+
z.object({
|
|
124
|
+
cursor: z.string().nullable().optional(),
|
|
125
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
126
|
+
}),
|
|
127
|
+
async (_p) => {
|
|
128
|
+
const tenantId = requireEnv("POWERBI_TENANT_ID");
|
|
129
|
+
const clientId = requireEnv("POWERBI_CLIENT_ID");
|
|
130
|
+
const clientSecret = requireEnv("POWERBI_CLIENT_SECRET");
|
|
131
|
+
const accessToken = await fetchAccessToken(tenantId, clientId, clientSecret);
|
|
132
|
+
// Return EVERY report: slicing to `limit` would silently drop reports (nextCursor is null, so
|
|
133
|
+
// the gateway drain stops) and lose them from the index for orgs with many reports.
|
|
134
|
+
const reports = await listReports(accessToken);
|
|
135
|
+
const items = await Promise.all(reports.map((r) => expandReport(accessToken, r)));
|
|
136
|
+
return jsonResult({ items, nextCursor: null });
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
reg(
|
|
141
|
+
"powerbi_get",
|
|
142
|
+
"Fetch one Power BI report by its id. Throws when the report is not found.",
|
|
143
|
+
z.object({
|
|
144
|
+
id: z.string().min(1),
|
|
145
|
+
}),
|
|
146
|
+
async (p) => {
|
|
147
|
+
const tenantId = requireEnv("POWERBI_TENANT_ID");
|
|
148
|
+
const clientId = requireEnv("POWERBI_CLIENT_ID");
|
|
149
|
+
const clientSecret = requireEnv("POWERBI_CLIENT_SECRET");
|
|
150
|
+
const accessToken = await fetchAccessToken(tenantId, clientId, clientSecret);
|
|
151
|
+
const reports = await listReports(accessToken);
|
|
152
|
+
const found = reports.find((r) => {
|
|
153
|
+
if (r === null || typeof r !== "object" || Array.isArray(r)) return false;
|
|
154
|
+
return (r as Record<string, unknown>)["id"] === p.id;
|
|
155
|
+
});
|
|
156
|
+
if (found === undefined) {
|
|
157
|
+
throw new Error(`Power BI report not found: ${p.id}`);
|
|
158
|
+
}
|
|
159
|
+
return jsonResult(found);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
reg(
|
|
164
|
+
"powerbi_search",
|
|
165
|
+
"Substring search across Power BI reports. Matches the query (case-insensitive) against report name and description. Returns a `{ matches: [...] }` envelope.",
|
|
166
|
+
searchToolInputSchema(200),
|
|
167
|
+
async (p) => {
|
|
168
|
+
const tenantId = requireEnv("POWERBI_TENANT_ID");
|
|
169
|
+
const clientId = requireEnv("POWERBI_CLIENT_ID");
|
|
170
|
+
const clientSecret = requireEnv("POWERBI_CLIENT_SECRET");
|
|
171
|
+
const accessToken = await fetchAccessToken(tenantId, clientId, clientSecret);
|
|
172
|
+
const reports = await listReports(accessToken);
|
|
173
|
+
const matches = filterPowerBiReports(reports, { query: p.query, limit: p.limit });
|
|
174
|
+
return jsonResult({ matches });
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
registerWriteTool(
|
|
179
|
+
"powerbi_dataset_refresh",
|
|
180
|
+
{
|
|
181
|
+
mutates: "powerbi.dataset.refresh",
|
|
182
|
+
recoverable: true,
|
|
183
|
+
scopeTargetOf: (p) => ({ kind: "workspace", value: p.groupId ?? "my-workspace" }),
|
|
184
|
+
},
|
|
185
|
+
"Trigger a dataset refresh. groupId optional (omit for My Workspace). Async (202).",
|
|
186
|
+
// groupId is nullish: indexed dashboard metadata stores `null` for "My Workspace" reports, and
|
|
187
|
+
// Zod `.optional()` would reject a literal null — `.nullish()` accepts both null and undefined.
|
|
188
|
+
z.object({ groupId: z.string().min(1).nullish(), datasetId: z.string().min(1) }),
|
|
189
|
+
async (p) => {
|
|
190
|
+
const token = await accessToken();
|
|
191
|
+
// Narrow to a non-empty string in a local so encodeURIComponent sees `string` (not nullish).
|
|
192
|
+
const group =
|
|
193
|
+
p.groupId === undefined || p.groupId === null || p.groupId === "" ? undefined : p.groupId;
|
|
194
|
+
const datasetUrl =
|
|
195
|
+
group === undefined
|
|
196
|
+
? `${POWERBI_API_BASE}/v1.0/myorg/datasets/${encodeURIComponent(p.datasetId)}/refreshes`
|
|
197
|
+
: `${POWERBI_API_BASE}/v1.0/myorg/groups/${encodeURIComponent(group)}/datasets/${encodeURIComponent(p.datasetId)}/refreshes`;
|
|
198
|
+
const res = await fetchWithTimeout(datasetUrl, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
201
|
+
body: JSON.stringify({ notifyOption: "NoNotification" }),
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Power BI dataset refresh ${String(res.status)}: ${(await res.text()).slice(0, 400)}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return jsonResult({
|
|
209
|
+
status: "queued",
|
|
210
|
+
...(group === undefined ? {} : { groupId: group }),
|
|
211
|
+
datasetId: p.datasetId,
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
registerWriteTool(
|
|
217
|
+
"powerbi_dataflow_refresh",
|
|
218
|
+
{
|
|
219
|
+
mutates: "powerbi.dataflow.refresh",
|
|
220
|
+
recoverable: true,
|
|
221
|
+
scopeTargetOf: (p) => ({ kind: "workspace", value: p.groupId ?? "my-workspace" }),
|
|
222
|
+
},
|
|
223
|
+
"Trigger a dataflow refresh. Async (202).",
|
|
224
|
+
z.object({ groupId: z.string().min(1), dataflowId: z.string().min(1) }),
|
|
225
|
+
async (p) => {
|
|
226
|
+
const token = await accessToken();
|
|
227
|
+
const url = `${POWERBI_API_BASE}/v1.0/myorg/groups/${encodeURIComponent(p.groupId)}/dataflows/${encodeURIComponent(p.dataflowId)}/refreshes`;
|
|
228
|
+
const res = await fetchWithTimeout(url, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
231
|
+
body: JSON.stringify({ notifyOption: "NoNotification" }),
|
|
232
|
+
});
|
|
233
|
+
if (!res.ok) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Power BI dataflow refresh ${String(res.status)}: ${(await res.text()).slice(0, 400)}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return jsonResult({ status: "queued", groupId: p.groupId, dataflowId: p.dataflowId });
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Exported so the bundled-connector registry can start this server explicitly: `import.meta.main`
|
|
244
|
+
// is false under an import, and the module must stay importable by tests without connecting stdio.
|
|
245
|
+
export async function startConnector(): Promise<void> {
|
|
246
|
+
await runReadOnlyMcpConnector("nimbus-powerbi", registerPowerBiTools);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (import.meta.main) await startConnector();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.prefect",
|
|
3
|
+
"displayName": "Prefect",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Prefect connector (read-only). Surfaces the user's data-orchestration deployments from a Prefect Cloud workspace or a self-hosted Prefect Server as prefect:deployment items in the local index — the deployment id, name, flow id, description, tags, paused flag, work pool, work queue, schedule, status, and created/updated timestamps. Useful for answering orchestration questions (\"which deployments are paused?\", \"what's the schedule for the nightly flow?\") without leaving Nimbus. Bearer-token auth (Prefect API key; required for Cloud, may be empty for self-hosted Server but still injected for uniform wiring). Prefect has no universal SaaS host: the per-tenant workspace API root is supplied via the prefect api_url vault key and added to the sandbox network list at spawn time by phase3AddPrefectMcp, so the static manifest network list is intentionally empty. The list endpoint is a POST filter: POST <api_url>/deployments/filter with a JSON body { limit, offset, sort } returns a bare JSON array of deployments; the connector walks a single forward offset pass per cycle, capped. v1 indexes deployments only — individual flow runs and task runs are a deferred follow-up. No write tools.",
|
|
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,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asObjectish,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
stringField,
|
|
6
|
+
} from "../../../shared/search-filter.ts";
|
|
7
|
+
|
|
8
|
+
export type PrefectSearchMatchOptions = SearchMatchOptions;
|
|
9
|
+
|
|
10
|
+
/** Prefect deployment tags are a bare string array (not `{name}` objects). */
|
|
11
|
+
function tagNames(row: Record<string, unknown>): string {
|
|
12
|
+
const tags = row["tags"];
|
|
13
|
+
if (!Array.isArray(tags)) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const names: string[] = [];
|
|
17
|
+
for (const t of tags) {
|
|
18
|
+
if (typeof t === "string" && t !== "") {
|
|
19
|
+
names.push(t);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return names.join(" ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
26
|
+
const row = asObjectish(item);
|
|
27
|
+
if (row === undefined) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return [
|
|
31
|
+
stringField(row, "name"),
|
|
32
|
+
stringField(row, "description"),
|
|
33
|
+
stringField(row, "work_pool_name"),
|
|
34
|
+
stringField(row, "work_queue_name"),
|
|
35
|
+
stringField(row, "status"),
|
|
36
|
+
tagNames(row),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const filterPrefectDeployments = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterPrefectDeployments } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
function trimTrailingSlash(s: string): string {
|
|
8
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function apiBase(): string {
|
|
12
|
+
const v = process.env["PREFECT_API_URL"]?.trim();
|
|
13
|
+
if (v === undefined || v === "") {
|
|
14
|
+
throw new Error("PREFECT_API_URL is not set");
|
|
15
|
+
}
|
|
16
|
+
return trimTrailingSlash(v);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function authHeaders(): Record<string, string> {
|
|
20
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
21
|
+
// Prefect Cloud requires a Bearer API key; self-hosted Prefect Server may run
|
|
22
|
+
// keyless, so an empty key is tolerated (the header is simply omitted).
|
|
23
|
+
const apiKey = process.env["PREFECT_API_KEY"]?.trim();
|
|
24
|
+
if (apiKey !== undefined && apiKey !== "") {
|
|
25
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
26
|
+
}
|
|
27
|
+
return headers;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function prefectGet(path: string): Promise<unknown> {
|
|
31
|
+
const res = await fetch(`${apiBase()}${path}`, { headers: authHeaders() });
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Prefect ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(text) as unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function prefectListDeployments(offset: number, limit: number): Promise<unknown> {
|
|
40
|
+
const res = await fetch(`${apiBase()}/deployments/filter`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { ...authHeaders(), "Content-Type": "application/json" },
|
|
43
|
+
body: JSON.stringify({ limit, offset, sort: "CREATED_DESC" }),
|
|
44
|
+
});
|
|
45
|
+
const text = await res.text();
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
throw new Error(`Prefect ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
48
|
+
}
|
|
49
|
+
return JSON.parse(text) as unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deploymentsFrom(root: unknown): unknown[] {
|
|
53
|
+
return Array.isArray(root) ? root : [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await runReadOnlyMcpConnector("nimbus-prefect", (reg) => {
|
|
57
|
+
reg(
|
|
58
|
+
"prefect_list",
|
|
59
|
+
"List Prefect deployments (POST <api_url>/deployments/filter with a JSON body { limit, offset, sort: CREATED_DESC }). Prefect's list endpoints are POST-with-body filters, not GET query-string endpoints; the response is a bare JSON array of deployment objects. The optional offset arg (default 0) selects the page start; limit is fixed at 100.",
|
|
60
|
+
z.object({
|
|
61
|
+
offset: z.number().int().min(0).optional(),
|
|
62
|
+
}),
|
|
63
|
+
async (p) => {
|
|
64
|
+
const offset = p.offset ?? 0;
|
|
65
|
+
const root = await prefectListDeployments(offset, 100);
|
|
66
|
+
return jsonResult({ items: deploymentsFrom(root) });
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
reg(
|
|
71
|
+
"prefect_get",
|
|
72
|
+
"Fetch one Prefect deployment by its id (GET <api_url>/deployments/{id}). The id is the deployment UUID string. Returns the deployment object directly (NOT wrapped in an array). Throws when no deployment with that id exists.",
|
|
73
|
+
z.object({
|
|
74
|
+
id: z.string().min(1),
|
|
75
|
+
}),
|
|
76
|
+
async (p) => {
|
|
77
|
+
return jsonResult(await prefectGet(`/deployments/${encodeURIComponent(p.id)}`));
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
reg(
|
|
82
|
+
"prefect_search",
|
|
83
|
+
"Substring search across the first page of Prefect deployments. Matches the query (case-insensitive) against the deployment name, description, work pool, work queue, status, and tags. Returns a matches envelope.",
|
|
84
|
+
searchToolInputSchema(100),
|
|
85
|
+
async (p) => {
|
|
86
|
+
const root = await prefectListDeployments(0, 100);
|
|
87
|
+
const matches = filterPrefectDeployments(deploymentsFrom(root), {
|
|
88
|
+
query: p.query,
|
|
89
|
+
limit: p.limit,
|
|
90
|
+
});
|
|
91
|
+
return jsonResult({ matches });
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.protonmail",
|
|
3
|
+
"displayName": "ProtonMail (Bridge)",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "ProtonMail email connector via ProtonMail Bridge (Tier-4 EMAIL class). ProtonMail is end-to-end encrypted, so the local ProtonMail Bridge app decrypts mail on the user's machine and exposes a standard IMAP/SMTP interface on the loopback interface (127.0.0.1:1143 IMAP / 127.0.0.1:1025 SMTP) with Bridge-generated credentials. This connector indexes the user's mailbox over that local IMAP listener as `protonmail:email` items — message HEADERS (subject, from, to/cc, date, message-id), a short capped plain-text body PREVIEW (~2 KB / 2000 chars), and attachment METADATA only (filename, size, mimetype). It NEVER downloads or parses attachment bytes and never fetches the full message body. Read tools (`protonmail_list`, `protonmail_get`, `protonmail_search`) plus one HITL-gated write tool (`protonmail_mail_send`) that sends mail over the Bridge SMTP relay. Bridge credentials arrive as scoped environment variables injected at spawn time (PROTONMAIL_HOST/PROTONMAIL_PORT/PROTONMAIL_USERNAME/PROTONMAIL_PASSWORD and PROTONMAIL_SMTP_*) — the connector never reads the vault. Because Bridge listens on the loopback interface with a self-signed certificate, the connection uses STARTTLS without certificate verification (it never leaves 127.0.0.1); the concrete 127.0.0.1:1143 + 127.0.0.1:1025 host:port entries are added to the sandbox network allow-list at spawn time by phase3AddProtonmailMcp.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": [],
|
|
11
|
+
"filesystem": { "read": [], "write": [] }
|
|
12
|
+
},
|
|
13
|
+
"hitlRequired": ["write"],
|
|
14
|
+
"syncInterval": 300,
|
|
15
|
+
"minNimbusVersion": "0.2.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProtonMail (via ProtonMail Bridge) connector core — transport-agnostic logic
|
|
3
|
+
* for the read tools (`protonmail_list`, `protonmail_get`, `protonmail_search`)
|
|
4
|
+
* and the HITL-gated send tool (`protonmail_mail_send`). ProtonMail Bridge
|
|
5
|
+
* exposes a standard IMAP/SMTP interface on the loopback interface, so this is
|
|
6
|
+
* the same shape as the generic IMAP connector core.
|
|
7
|
+
*
|
|
8
|
+
* HARD SCOPE CONSTRAINT (security): this connector indexes HEADERS + a short
|
|
9
|
+
* capped plain-text body PREVIEW + attachment METADATA only. It NEVER downloads
|
|
10
|
+
* or parses attachment bytes and never fetches the full message body.
|
|
11
|
+
*
|
|
12
|
+
* The IMAP client and SMTP mailer are injected so tests never open real sockets.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { MailAddress } from "../../../shared/imap-mail-core.ts";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
capPreview,
|
|
19
|
+
clampLimit,
|
|
20
|
+
formatAddress,
|
|
21
|
+
PREVIEW_FETCH_BYTES,
|
|
22
|
+
PREVIEW_MAX_CHARS,
|
|
23
|
+
} from "../../../shared/imap-mail-core.ts";
|
|
24
|
+
export type { MailAddress };
|
|
25
|
+
|
|
26
|
+
/** Message ENVELOPE — RFC 2822 header fields only. */
|
|
27
|
+
export interface MailEnvelope {
|
|
28
|
+
readonly date?: Date | string | null;
|
|
29
|
+
readonly subject?: string | null;
|
|
30
|
+
readonly messageId?: string | null;
|
|
31
|
+
readonly from?: readonly MailAddress[];
|
|
32
|
+
readonly to?: readonly MailAddress[];
|
|
33
|
+
readonly cc?: readonly MailAddress[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Attachment METADATA only — filename, size, mimetype. NEVER the bytes. */
|
|
37
|
+
export interface MailAttachmentMeta {
|
|
38
|
+
readonly filename: string | null;
|
|
39
|
+
readonly sizeBytes: number | null;
|
|
40
|
+
readonly mimeType: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A single mailbox message reduced to header + attachment-metadata + a capped
|
|
45
|
+
* text preview. This is the ONLY shape the connector ever materializes.
|
|
46
|
+
*/
|
|
47
|
+
export interface MailMessageMeta {
|
|
48
|
+
readonly uid: number;
|
|
49
|
+
readonly mailbox: string;
|
|
50
|
+
readonly uidValidity: string | null;
|
|
51
|
+
readonly envelope: MailEnvelope;
|
|
52
|
+
readonly attachments: readonly MailAttachmentMeta[];
|
|
53
|
+
/** Plain-text body preview, already capped to {@link PREVIEW_MAX_CHARS}. */
|
|
54
|
+
readonly preview: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface MailListOptions {
|
|
58
|
+
readonly mailbox?: string;
|
|
59
|
+
readonly limit?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MailSearchOptions {
|
|
63
|
+
readonly query: string;
|
|
64
|
+
readonly mailbox?: string;
|
|
65
|
+
readonly limit?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Minimal IMAP client surface the connector depends on. Implemented for real by
|
|
70
|
+
* the imapflow adapter in `server.ts` and by a fake in tests.
|
|
71
|
+
*/
|
|
72
|
+
export interface MailClient {
|
|
73
|
+
list(options: MailListOptions): Promise<MailMessageMeta[]>;
|
|
74
|
+
get(uid: number, mailbox?: string): Promise<MailMessageMeta | null>;
|
|
75
|
+
search(options: MailSearchOptions): Promise<MailMessageMeta[]>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Outgoing message for the SMTP send tool. */
|
|
79
|
+
export interface SendMailInput {
|
|
80
|
+
readonly to: string;
|
|
81
|
+
readonly subject: string;
|
|
82
|
+
readonly body: string;
|
|
83
|
+
readonly cc?: string;
|
|
84
|
+
readonly bcc?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface SendMailResult {
|
|
88
|
+
readonly messageId: string | null;
|
|
89
|
+
readonly accepted: readonly string[];
|
|
90
|
+
readonly rejected: readonly string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Minimal SMTP mailer surface; implemented for real over nodemailer. */
|
|
94
|
+
export interface SmtpMailer {
|
|
95
|
+
send(input: SendMailInput): Promise<SendMailResult>;
|
|
96
|
+
}
|