@nimbus-dev/connectors 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/connectors/airflow/src/search-filter.ts +2 -15
- package/connectors/airflow/src/server.ts +2 -87
- package/connectors/airflow/src/tools.ts +74 -0
- package/connectors/apple/src/adapters.ts +326 -0
- package/connectors/apple/src/apple-mail-core.ts +4 -8
- package/connectors/apple/src/calendar-tools.ts +28 -8
- package/connectors/apple/src/server.ts +23 -512
- package/connectors/apple/src/tools.ts +18 -44
- package/connectors/argocd/src/server.ts +25 -38
- package/connectors/athena/src/tools.ts +15 -34
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +197 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +133 -0
- package/connectors/bigeye/src/search-filter.ts +4 -15
- package/connectors/bigeye/src/server.ts +38 -38
- package/connectors/bigquery/src/tools.ts +1 -26
- package/connectors/bitbucket/src/server.ts +2 -244
- package/connectors/bitbucket/src/tools.ts +241 -0
- package/connectors/bitrise/src/server.ts +2 -79
- package/connectors/bitrise/src/tools.ts +76 -0
- package/connectors/canva/src/search-filter.ts +2 -11
- package/connectors/canva/src/server.ts +2 -73
- package/connectors/canva/src/tools.ts +66 -0
- package/connectors/circleci/src/server.ts +2 -176
- package/connectors/circleci/src/tools.ts +193 -0
- package/connectors/cloud-logging/src/tools.ts +14 -27
- package/connectors/cloudwatch/src/tools.ts +15 -34
- package/connectors/codemagic/src/server.ts +2 -70
- package/connectors/codemagic/src/tools.ts +71 -0
- package/connectors/confluence/src/server.ts +2 -301
- package/connectors/confluence/src/tools.ts +320 -0
- package/connectors/dagster/src/server.ts +2 -214
- package/connectors/dagster/src/tools.ts +187 -0
- package/connectors/databricks/src/search-filter.ts +7 -7
- package/connectors/databricks/src/server.ts +2 -77
- package/connectors/databricks/src/tools.ts +66 -0
- package/connectors/datadog/src/server.ts +2 -48
- package/connectors/datadog/src/tools.ts +48 -0
- package/connectors/dataprofile/src/profile.ts +16 -46
- package/connectors/dbt/src/server.ts +2 -92
- package/connectors/dbt/src/tools.ts +80 -0
- package/connectors/dependencytrack/src/server.ts +2 -76
- package/connectors/dependencytrack/src/tools.ts +65 -0
- package/connectors/discord/src/server.ts +2 -85
- package/connectors/discord/src/tools.ts +82 -0
- package/connectors/elasticsearch/src/tools.ts +7 -37
- package/connectors/fastmail/src/jmap-client.ts +202 -0
- package/connectors/fastmail/src/jmap-core.ts +8 -21
- package/connectors/fastmail/src/server.ts +2 -187
- package/connectors/fastmail/src/tools.ts +4 -23
- package/connectors/figma/src/search-filter.ts +2 -11
- package/connectors/figma/src/server.ts +2 -113
- package/connectors/figma/src/tools.ts +99 -0
- package/connectors/firebase/src/server.ts +2 -90
- package/connectors/firebase/src/token.ts +2 -5
- package/connectors/firebase/src/tools.ts +106 -0
- package/connectors/flagsmith/src/server.ts +2 -98
- package/connectors/flagsmith/src/tools.ts +95 -0
- package/connectors/flux/src/server.ts +52 -53
- package/connectors/gcp/src/server.ts +2 -132
- package/connectors/gcp/src/tools.ts +123 -0
- package/connectors/github/src/server.ts +2 -328
- package/connectors/github/src/tools.ts +311 -0
- package/connectors/github-actions/src/server.ts +2 -205
- package/connectors/github-actions/src/tools.ts +209 -0
- package/connectors/gitlab/src/server.ts +2 -384
- package/connectors/gitlab/src/tools.ts +392 -0
- package/connectors/gmail/src/server.ts +2 -270
- package/connectors/gmail/src/tools.ts +209 -0
- package/connectors/google-drive/src/server.ts +2 -524
- package/connectors/google-drive/src/tools.ts +529 -0
- package/connectors/google-meet/src/server.ts +2 -82
- package/connectors/google-meet/src/tools.ts +87 -0
- package/connectors/google-photos/src/server.ts +2 -132
- package/connectors/google-photos/src/tools.ts +138 -0
- package/connectors/grafana/src/server.ts +2 -53
- package/connectors/grafana/src/tools.ts +53 -0
- package/connectors/great-expectations/src/gx-parse.ts +14 -45
- package/connectors/greenhouse/src/server.ts +2 -63
- package/connectors/greenhouse/src/tools.ts +68 -0
- package/connectors/hubspot/src/server.ts +2 -77
- package/connectors/hubspot/src/tools.ts +70 -0
- package/connectors/iac/src/server.ts +2 -137
- package/connectors/iac/src/tools.ts +152 -0
- package/connectors/imap/src/imap-core.ts +29 -82
- package/connectors/imap/src/server.ts +33 -220
- package/connectors/intercom/src/server.ts +2 -61
- package/connectors/intercom/src/tools.ts +58 -0
- package/connectors/jenkins/src/jenkins-api.ts +2 -6
- package/connectors/jenkins/src/server.ts +2 -215
- package/connectors/jenkins/src/tools.ts +240 -0
- package/connectors/jira/src/server.ts +2 -301
- package/connectors/jira/src/tools.ts +325 -0
- package/connectors/kubernetes/src/server.ts +2 -155
- package/connectors/kubernetes/src/tools.ts +159 -0
- package/connectors/launchdarkly/src/server.ts +2 -75
- package/connectors/launchdarkly/src/tools.ts +72 -0
- package/connectors/lever/src/search-filter.ts +4 -12
- package/connectors/lever/src/server.ts +2 -60
- package/connectors/lever/src/tools.ts +61 -0
- package/connectors/linear/src/server.ts +2 -459
- package/connectors/linear/src/tools.ts +482 -0
- package/connectors/localdb/src/sql-scan.ts +13 -41
- package/connectors/localdb/src/tools.ts +1 -1
- package/connectors/looker/src/search-filter.ts +2 -15
- package/connectors/looker/src/server.ts +35 -70
- package/connectors/mendeley/src/server.ts +2 -55
- package/connectors/mendeley/src/tools.ts +54 -0
- package/connectors/mercury/src/server.ts +2 -57
- package/connectors/mercury/src/tools.ts +50 -0
- package/connectors/metabase/src/search-filter.ts +4 -15
- package/connectors/metabase/src/server.ts +2 -79
- package/connectors/metabase/src/tools.ts +64 -0
- package/connectors/miro/src/server.ts +2 -72
- package/connectors/miro/src/tools.ts +65 -0
- package/connectors/mlflow/src/search-filter.ts +3 -7
- package/connectors/mlflow/src/server.ts +20 -40
- package/connectors/monte-carlo/src/server.ts +50 -57
- package/connectors/netlify/src/search-filter.ts +6 -14
- package/connectors/netlify/src/server.ts +2 -60
- package/connectors/netlify/src/tools.ts +53 -0
- package/connectors/newrelic/src/server.ts +2 -40
- package/connectors/newrelic/src/tools.ts +37 -0
- package/connectors/notion/src/server.ts +2 -335
- package/connectors/notion/src/tools.ts +344 -0
- package/connectors/obsidian/src/server.ts +2 -425
- package/connectors/obsidian/src/tools.ts +466 -0
- package/connectors/onedrive/src/server.ts +2 -249
- package/connectors/onedrive/src/tools.ts +253 -0
- package/connectors/outlook/src/server.ts +2 -351
- package/connectors/outlook/src/tools.ts +357 -0
- package/connectors/pagerduty/src/server.ts +2 -132
- package/connectors/pagerduty/src/tools.ts +126 -0
- package/connectors/pipedrive/src/server.ts +2 -58
- package/connectors/pipedrive/src/tools.ts +63 -0
- package/connectors/powerbi/src/search-filter.ts +2 -15
- package/connectors/powerbi/src/server.ts +43 -68
- package/connectors/prefect/src/search-filter.ts +3 -16
- package/connectors/prefect/src/server.ts +2 -90
- package/connectors/prefect/src/tools.ts +83 -0
- package/connectors/protonmail/src/mail-core.ts +16 -75
- package/connectors/protonmail/src/server.ts +34 -214
- package/connectors/raindrop/src/server.ts +2 -103
- package/connectors/raindrop/src/tools.ts +103 -0
- package/connectors/ramp/src/server.ts +2 -105
- package/connectors/ramp/src/tools.ts +98 -0
- package/connectors/readwise/src/server.ts +2 -88
- package/connectors/readwise/src/tools.ts +81 -0
- package/connectors/sagemaker/src/tools.ts +19 -38
- package/connectors/salesforce/src/search-filter.ts +4 -11
- package/connectors/salesforce/src/server.ts +2 -90
- package/connectors/salesforce/src/tools.ts +75 -0
- package/connectors/semgrep/src/search-filter.ts +3 -10
- package/connectors/semgrep/src/server.ts +2 -104
- package/connectors/semgrep/src/tools.ts +92 -0
- package/connectors/sentry/src/server.ts +2 -58
- package/connectors/sentry/src/tools.ts +53 -0
- package/connectors/slack/src/server.ts +2 -352
- package/connectors/slack/src/tools.ts +376 -0
- package/connectors/snowflake/src/search-filter.ts +4 -19
- package/connectors/snowflake/src/server.ts +6 -25
- package/connectors/snyk/src/server.ts +2 -140
- package/connectors/snyk/src/tools.ts +136 -0
- package/connectors/sonarqube/src/server.ts +2 -111
- package/connectors/sonarqube/src/tools.ts +98 -0
- package/connectors/stackoverflow/src/server.ts +2 -77
- package/connectors/stackoverflow/src/tools.ts +66 -0
- package/connectors/storybook/src/storybook-parse.ts +3 -6
- package/connectors/stripe/src/server.ts +2 -61
- package/connectors/stripe/src/tools.ts +54 -0
- package/connectors/superset/src/search-filter.ts +4 -15
- package/connectors/superset/src/server.ts +2 -115
- package/connectors/superset/src/tools.ts +98 -0
- package/connectors/tableau/src/search-filter.ts +2 -15
- package/connectors/tableau/src/server.ts +13 -65
- package/connectors/teams/src/server.ts +2 -249
- package/connectors/teams/src/tools.ts +283 -0
- package/connectors/testflight/src/server.ts +2 -70
- package/connectors/testflight/src/tools.ts +79 -0
- package/connectors/vercel/src/search-filter.ts +2 -10
- package/connectors/vercel/src/server.ts +2 -75
- package/connectors/vercel/src/tools.ts +73 -0
- package/connectors/vertex-ai/src/tools.ts +23 -33
- package/connectors/wiz/src/search-filter.ts +6 -30
- package/connectors/wiz/src/server.ts +2 -188
- package/connectors/wiz/src/tools.ts +168 -0
- package/connectors/zendesk/src/server.ts +2 -73
- package/connectors/zendesk/src/tools.ts +60 -0
- package/connectors/zoom/src/search-filter.ts +1 -7
- package/connectors/zoom/src/server.ts +2 -94
- package/connectors/zoom/src/tools.ts +93 -0
- package/connectors/zotero/src/server.ts +2 -78
- package/connectors/zotero/src/tools.ts +67 -0
- package/package.json +15 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +13 -6
- package/shared/cli-json-kit.ts +150 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/cursor-list-tool.ts +29 -0
- package/shared/env-json-api.ts +209 -0
- package/shared/fetch-json-text.ts +27 -0
- package/shared/github-rest.ts +62 -0
- package/shared/graphql-json.ts +60 -0
- package/shared/imap-mail-core.ts +91 -0
- package/shared/imap-tool-kit.ts +57 -23
- package/shared/imapflow-adapter.ts +417 -0
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterPipedriveDeals } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BASE = "https://api.pipedrive.com";
|
|
9
|
+
|
|
10
|
+
/** Pipedrive takes its token in the query string, not a header. */
|
|
11
|
+
function withToken(path: string): string {
|
|
12
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
13
|
+
return `${BASE}${path}${sep}api_token=${encodeURIComponent(requiredEnv("PIPEDRIVE_TOKEN"))}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function pipedriveGet(path: string, resourceLabel: string): Promise<unknown> {
|
|
17
|
+
const res = await fetch(withToken(path), { headers: { Accept: "application/json" } });
|
|
18
|
+
const text = await res.text();
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
throw new Error(`Pipedrive ${resourceLabel} ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
21
|
+
}
|
|
22
|
+
return JSON.parse(text) as unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
26
|
+
export const PIPEDRIVE_TOOL_NAMES = [
|
|
27
|
+
"pipedrive_list",
|
|
28
|
+
"pipedrive_get",
|
|
29
|
+
"pipedrive_search",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export function registerPipedriveTools(reg: ZodToolRegistrar): void {
|
|
33
|
+
reg(
|
|
34
|
+
"pipedrive_list",
|
|
35
|
+
"List the user's Pipedrive deals (`GET /v1/deals?limit=100`). Returns the `{ success, data: [...], additional_data: { pagination } }` envelope — `data` holds the deal objects (and may be null when there are no deals).",
|
|
36
|
+
z.object({}),
|
|
37
|
+
async () => {
|
|
38
|
+
return jsonResult(await pipedriveGet(`/v1/deals?limit=100`, "deals"));
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
reg(
|
|
43
|
+
"pipedrive_get",
|
|
44
|
+
"Fetch one Pipedrive deal by its id (`GET /v1/deals/{id}`). Returns the `{ success, data: {...} }` envelope. Throws when no match is found.",
|
|
45
|
+
z.object({
|
|
46
|
+
id: z.string().min(1),
|
|
47
|
+
}),
|
|
48
|
+
async (p) => {
|
|
49
|
+
return jsonResult(await pipedriveGet(`/v1/deals/${encodeURIComponent(p.id)}`, "deal"));
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
reg(
|
|
54
|
+
"pipedrive_search",
|
|
55
|
+
"Substring search across the user's Pipedrive deals (first page only). Matches the query against the deal title, status, organization name, person name, owner name, and label (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
56
|
+
searchToolInputSchema(100),
|
|
57
|
+
async (p) => {
|
|
58
|
+
const root = await pipedriveGet(`/v1/deals?limit=100`, "deals");
|
|
59
|
+
const data = (root as { data?: unknown } | null)?.data;
|
|
60
|
+
return matchesResult(data, filterPipedriveDeals, p);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
asRecord,
|
|
3
2
|
makeQueryFilter,
|
|
3
|
+
recordFieldsFromKeys,
|
|
4
4
|
type SearchMatchOptions,
|
|
5
5
|
} from "../../../shared/search-filter.ts";
|
|
6
6
|
|
|
7
7
|
export type PowerBiSearchMatchOptions = SearchMatchOptions;
|
|
8
8
|
|
|
9
|
-
|
|
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);
|
|
9
|
+
export const filterPowerBiReports = makeQueryFilter(recordFieldsFromKeys(["name", "description"]));
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import { cursorListInputSchema } from "../../../shared/cursor-list-tool.ts";
|
|
4
|
+
import { requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
5
|
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
6
|
import { fetchWithTimeout, mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
7
|
import {
|
|
6
8
|
runReadOnlyMcpConnector,
|
|
7
9
|
type ZodToolRegistrar,
|
|
8
10
|
} from "../../../shared/run-read-only-mcp-connector.ts";
|
|
11
|
+
import { asRecord } from "../../../shared/search-filter.ts";
|
|
9
12
|
import { filterPowerBiReports } from "./search-filter.ts";
|
|
10
13
|
|
|
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
14
|
async function fetchAccessToken(
|
|
20
15
|
tenantId: string,
|
|
21
16
|
clientId: string,
|
|
@@ -39,11 +34,11 @@ async function fetchAccessToken(
|
|
|
39
34
|
if (!res.ok) {
|
|
40
35
|
throw new Error(`Power BI token error ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
41
36
|
}
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
37
|
+
const root = asRecord(JSON.parse(text) as unknown);
|
|
38
|
+
if (root === undefined) {
|
|
44
39
|
throw new Error("Power BI token response: unexpected shape");
|
|
45
40
|
}
|
|
46
|
-
const token =
|
|
41
|
+
const token = root["access_token"];
|
|
47
42
|
if (typeof token !== "string" || token === "") {
|
|
48
43
|
throw new Error("Power BI token response: missing access_token");
|
|
49
44
|
}
|
|
@@ -55,18 +50,12 @@ const POWERBI_API_BASE = "https://api.powerbi.com";
|
|
|
55
50
|
/** Mint an AAD access token from the connector's client-credentials env. */
|
|
56
51
|
async function accessToken(): Promise<string> {
|
|
57
52
|
return fetchAccessToken(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
requiredEnv("POWERBI_TENANT_ID"),
|
|
54
|
+
requiredEnv("POWERBI_CLIENT_ID"),
|
|
55
|
+
requiredEnv("POWERBI_CLIENT_SECRET"),
|
|
61
56
|
);
|
|
62
57
|
}
|
|
63
58
|
|
|
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
59
|
async function listReports(accessToken: string): Promise<unknown[]> {
|
|
71
60
|
const res = await fetchWithTimeout("https://api.powerbi.com/v1.0/myorg/reports", {
|
|
72
61
|
headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" },
|
|
@@ -75,7 +64,7 @@ async function listReports(accessToken: string): Promise<unknown[]> {
|
|
|
75
64
|
if (!res.ok) {
|
|
76
65
|
throw new Error(`Power BI reports error ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
77
66
|
}
|
|
78
|
-
const value =
|
|
67
|
+
const value = asRecord(JSON.parse(text) as unknown)?.["value"];
|
|
79
68
|
return Array.isArray(value) ? value : [];
|
|
80
69
|
}
|
|
81
70
|
|
|
@@ -86,11 +75,11 @@ async function fetchDatasetTables(accessToken: string, datasetId: string): Promi
|
|
|
86
75
|
{ headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" } },
|
|
87
76
|
);
|
|
88
77
|
if (!res.ok) return [];
|
|
89
|
-
const value =
|
|
78
|
+
const value = asRecord(JSON.parse(await res.text()) as unknown)?.["value"];
|
|
90
79
|
if (!Array.isArray(value)) return [];
|
|
91
80
|
const out: string[] = [];
|
|
92
81
|
for (const item of value) {
|
|
93
|
-
const name =
|
|
82
|
+
const name = asRecord(item)?.["name"];
|
|
94
83
|
if (typeof name === "string" && name !== "") out.push(name);
|
|
95
84
|
}
|
|
96
85
|
return out;
|
|
@@ -98,7 +87,7 @@ async function fetchDatasetTables(accessToken: string, datasetId: string): Promi
|
|
|
98
87
|
|
|
99
88
|
/** Attach each report's dataset-table refs in-session, so the gateway never makes a second credentialed call. */
|
|
100
89
|
async function expandReport(accessToken: string, report: unknown): Promise<unknown> {
|
|
101
|
-
const r =
|
|
90
|
+
const r = asRecord(report);
|
|
102
91
|
if (r === undefined) return report;
|
|
103
92
|
const datasetId = r["datasetId"];
|
|
104
93
|
const datasetTables =
|
|
@@ -108,6 +97,27 @@ async function expandReport(accessToken: string, report: unknown): Promise<unkno
|
|
|
108
97
|
return { ...r, datasetTables };
|
|
109
98
|
}
|
|
110
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Queue a refresh by POSTing `{ notifyOption: "NoNotification" }` to a `…/refreshes` URL. Power BI
|
|
102
|
+
* answers 202 and refreshes asynchronously; a failure throws `Power BI <what> refresh <status>`.
|
|
103
|
+
*/
|
|
104
|
+
async function queueRefresh(
|
|
105
|
+
accessToken: string,
|
|
106
|
+
url: string,
|
|
107
|
+
what: "dataset" | "dataflow",
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
const res = await fetchWithTimeout(url, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify({ notifyOption: "NoNotification" }),
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Power BI ${what} refresh ${String(res.status)}: ${(await res.text()).slice(0, 400)}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): void {
|
|
112
122
|
// Despite the read-only helper's name, this connector exposes write tools. The consent
|
|
113
123
|
// kit needs the real server, which the helper now passes through as its second argument.
|
|
@@ -120,19 +130,13 @@ export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): vo
|
|
|
120
130
|
reg(
|
|
121
131
|
"powerbi_list",
|
|
122
132
|
"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
|
-
|
|
124
|
-
cursor: z.string().nullable().optional(),
|
|
125
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
126
|
-
}),
|
|
133
|
+
cursorListInputSchema(),
|
|
127
134
|
async (_p) => {
|
|
128
|
-
const
|
|
129
|
-
const clientId = requireEnv("POWERBI_CLIENT_ID");
|
|
130
|
-
const clientSecret = requireEnv("POWERBI_CLIENT_SECRET");
|
|
131
|
-
const accessToken = await fetchAccessToken(tenantId, clientId, clientSecret);
|
|
135
|
+
const token = await accessToken();
|
|
132
136
|
// Return EVERY report: slicing to `limit` would silently drop reports (nextCursor is null, so
|
|
133
137
|
// the gateway drain stops) and lose them from the index for orgs with many reports.
|
|
134
|
-
const reports = await listReports(
|
|
135
|
-
const items = await Promise.all(reports.map((r) => expandReport(
|
|
138
|
+
const reports = await listReports(token);
|
|
139
|
+
const items = await Promise.all(reports.map((r) => expandReport(token, r)));
|
|
136
140
|
return jsonResult({ items, nextCursor: null });
|
|
137
141
|
},
|
|
138
142
|
);
|
|
@@ -144,15 +148,8 @@ export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): vo
|
|
|
144
148
|
id: z.string().min(1),
|
|
145
149
|
}),
|
|
146
150
|
async (p) => {
|
|
147
|
-
const
|
|
148
|
-
const
|
|
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
|
-
});
|
|
151
|
+
const reports = await listReports(await accessToken());
|
|
152
|
+
const found = reports.find((r) => asRecord(r)?.["id"] === p.id);
|
|
156
153
|
if (found === undefined) {
|
|
157
154
|
throw new Error(`Power BI report not found: ${p.id}`);
|
|
158
155
|
}
|
|
@@ -165,11 +162,7 @@ export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): vo
|
|
|
165
162
|
"Substring search across Power BI reports. Matches the query (case-insensitive) against report name and description. Returns a `{ matches: [...] }` envelope.",
|
|
166
163
|
searchToolInputSchema(200),
|
|
167
164
|
async (p) => {
|
|
168
|
-
const
|
|
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);
|
|
165
|
+
const reports = await listReports(await accessToken());
|
|
173
166
|
const matches = filterPowerBiReports(reports, { query: p.query, limit: p.limit });
|
|
174
167
|
return jsonResult({ matches });
|
|
175
168
|
},
|
|
@@ -195,16 +188,7 @@ export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): vo
|
|
|
195
188
|
group === undefined
|
|
196
189
|
? `${POWERBI_API_BASE}/v1.0/myorg/datasets/${encodeURIComponent(p.datasetId)}/refreshes`
|
|
197
190
|
: `${POWERBI_API_BASE}/v1.0/myorg/groups/${encodeURIComponent(group)}/datasets/${encodeURIComponent(p.datasetId)}/refreshes`;
|
|
198
|
-
|
|
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
|
-
}
|
|
191
|
+
await queueRefresh(token, datasetUrl, "dataset");
|
|
208
192
|
return jsonResult({
|
|
209
193
|
status: "queued",
|
|
210
194
|
...(group === undefined ? {} : { groupId: group }),
|
|
@@ -225,16 +209,7 @@ export function registerPowerBiTools(reg: ZodToolRegistrar, server: unknown): vo
|
|
|
225
209
|
async (p) => {
|
|
226
210
|
const token = await accessToken();
|
|
227
211
|
const url = `${POWERBI_API_BASE}/v1.0/myorg/groups/${encodeURIComponent(p.groupId)}/dataflows/${encodeURIComponent(p.dataflowId)}/refreshes`;
|
|
228
|
-
|
|
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
|
-
}
|
|
212
|
+
await queueRefresh(token, url, "dataflow");
|
|
238
213
|
return jsonResult({ status: "queued", groupId: p.groupId, dataflowId: p.dataflowId });
|
|
239
214
|
},
|
|
240
215
|
);
|
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
asObjectish,
|
|
3
3
|
makeQueryFilter,
|
|
4
|
+
nonEmptyStringsText,
|
|
4
5
|
type SearchMatchOptions,
|
|
5
6
|
stringField,
|
|
6
7
|
} from "../../../shared/search-filter.ts";
|
|
7
8
|
|
|
8
9
|
export type PrefectSearchMatchOptions = SearchMatchOptions;
|
|
9
10
|
|
|
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
11
|
function fieldsOf(item: unknown): readonly string[] | null {
|
|
26
12
|
const row = asObjectish(item);
|
|
27
13
|
if (row === undefined) {
|
|
@@ -33,7 +19,8 @@ function fieldsOf(item: unknown): readonly string[] | null {
|
|
|
33
19
|
stringField(row, "work_pool_name"),
|
|
34
20
|
stringField(row, "work_queue_name"),
|
|
35
21
|
stringField(row, "status"),
|
|
36
|
-
|
|
22
|
+
// Prefect deployment tags are a bare string array (not `{name}` objects).
|
|
23
|
+
nonEmptyStringsText(row, "tags"),
|
|
37
24
|
];
|
|
38
25
|
}
|
|
39
26
|
|
|
@@ -1,94 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
-
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
-
import {
|
|
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
|
-
}
|
|
2
|
+
import { registerPrefectTools } from "./tools.ts";
|
|
55
3
|
|
|
56
4
|
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
|
-
);
|
|
5
|
+
registerPrefectTools(reg);
|
|
94
6
|
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, requiredBaseUrl } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterPrefectDeployments } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
function apiBase(): string {
|
|
9
|
+
return requiredBaseUrl("PREFECT_API_URL");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function authHeaders(): Record<string, string> {
|
|
13
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
14
|
+
// Prefect Cloud requires a Bearer API key; self-hosted Prefect Server may run
|
|
15
|
+
// keyless, so an empty key is tolerated (the header is simply omitted).
|
|
16
|
+
const apiKey = process.env["PREFECT_API_KEY"]?.trim();
|
|
17
|
+
if (apiKey !== undefined && apiKey !== "") {
|
|
18
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
19
|
+
}
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const prefectGet = createJsonGetter({ base: apiBase, label: "Prefect", headers: authHeaders });
|
|
24
|
+
|
|
25
|
+
async function prefectListDeployments(offset: number, limit: number): Promise<unknown> {
|
|
26
|
+
const res = await fetch(`${apiBase()}/deployments/filter`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { ...authHeaders(), "Content-Type": "application/json" },
|
|
29
|
+
body: JSON.stringify({ limit, offset, sort: "CREATED_DESC" }),
|
|
30
|
+
});
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`Prefect ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
34
|
+
}
|
|
35
|
+
return JSON.parse(text) as unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function deploymentsFrom(root: unknown): unknown[] {
|
|
39
|
+
return Array.isArray(root) ? root : [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
43
|
+
export const PREFECT_TOOL_NAMES = ["prefect_get", "prefect_list", "prefect_search"] as const;
|
|
44
|
+
|
|
45
|
+
export function registerPrefectTools(reg: ZodToolRegistrar): void {
|
|
46
|
+
reg(
|
|
47
|
+
"prefect_list",
|
|
48
|
+
"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.",
|
|
49
|
+
z.object({
|
|
50
|
+
offset: z.number().int().min(0).optional(),
|
|
51
|
+
}),
|
|
52
|
+
async (p) => {
|
|
53
|
+
const offset = p.offset ?? 0;
|
|
54
|
+
const root = await prefectListDeployments(offset, 100);
|
|
55
|
+
return jsonResult({ items: deploymentsFrom(root) });
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
reg(
|
|
60
|
+
"prefect_get",
|
|
61
|
+
"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.",
|
|
62
|
+
z.object({
|
|
63
|
+
id: z.string().min(1),
|
|
64
|
+
}),
|
|
65
|
+
async (p) => {
|
|
66
|
+
return jsonResult(await prefectGet(`/deployments/${encodeURIComponent(p.id)}`));
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
reg(
|
|
71
|
+
"prefect_search",
|
|
72
|
+
"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.",
|
|
73
|
+
searchToolInputSchema(100),
|
|
74
|
+
async (p) => {
|
|
75
|
+
const root = await prefectListDeployments(0, 100);
|
|
76
|
+
const matches = filterPrefectDeployments(deploymentsFrom(root), {
|
|
77
|
+
query: p.query,
|
|
78
|
+
limit: p.limit,
|
|
79
|
+
});
|
|
80
|
+
return jsonResult({ matches });
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -10,10 +10,24 @@
|
|
|
10
10
|
* or parses attachment bytes and never fetches the full message body.
|
|
11
11
|
*
|
|
12
12
|
* The IMAP client and SMTP mailer are injected so tests never open real sockets.
|
|
13
|
+
*
|
|
14
|
+
* "The same shape as the generic IMAP connector core" used to be a comment
|
|
15
|
+
* above a second copy of that shape. It is now a re-export of it, from
|
|
16
|
+
* `shared/imap-mail-core.ts`.
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
export type {
|
|
20
|
+
MailAddress,
|
|
21
|
+
MailAttachmentMeta,
|
|
22
|
+
MailClient,
|
|
23
|
+
MailEnvelope,
|
|
24
|
+
MailListOptions,
|
|
25
|
+
MailMessageMeta,
|
|
26
|
+
MailSearchOptions,
|
|
27
|
+
SendMailInput,
|
|
28
|
+
SendMailResult,
|
|
29
|
+
SmtpMailer,
|
|
30
|
+
} from "../../../shared/imap-mail-core.ts";
|
|
17
31
|
export {
|
|
18
32
|
capPreview,
|
|
19
33
|
clampLimit,
|
|
@@ -21,76 +35,3 @@ export {
|
|
|
21
35
|
PREVIEW_FETCH_BYTES,
|
|
22
36
|
PREVIEW_MAX_CHARS,
|
|
23
37
|
} 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
|
-
}
|