@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
|
@@ -1,81 +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
|
-
const BASE = "https://api.hubapi.com";
|
|
8
|
-
const DEAL_PROPERTIES =
|
|
9
|
-
"dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate";
|
|
10
|
-
|
|
11
|
-
function apiToken(): string {
|
|
12
|
-
const t = process.env["HUBSPOT_TOKEN"]?.trim();
|
|
13
|
-
if (t === undefined || t === "") {
|
|
14
|
-
throw new Error("HUBSPOT_TOKEN is not set");
|
|
15
|
-
}
|
|
16
|
-
return t;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function authHeader(): Record<string, string> {
|
|
20
|
-
return { Authorization: `Bearer ${apiToken()}`, Accept: "application/json" };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function hubspotGet(path: string): Promise<unknown> {
|
|
24
|
-
const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
|
|
25
|
-
const text = await res.text();
|
|
26
|
-
if (!res.ok) {
|
|
27
|
-
throw new Error(`HubSpot ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
28
|
-
}
|
|
29
|
-
return JSON.parse(text) as unknown;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function dealsFrom(root: unknown): unknown[] {
|
|
33
|
-
const results = (root as { results?: unknown } | null)?.results;
|
|
34
|
-
return Array.isArray(results) ? results : [];
|
|
35
|
-
}
|
|
2
|
+
import { registerHubspotTools } from "./tools.ts";
|
|
36
3
|
|
|
37
4
|
await runReadOnlyMcpConnector("nimbus-hubspot", (reg) => {
|
|
38
|
-
reg
|
|
39
|
-
"hubspot_list",
|
|
40
|
-
"List the authenticated portal's HubSpot deals (GET /crm/v3/objects/deals?limit=N&properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the { results: [...], paging?: { next?: { after } } } envelope — `results` holds the deal objects, each shaped { id, properties: { dealname, amount, dealstage, pipeline, closedate, createdate, hs_lastmodifieddate }, createdAt, updatedAt }. `limit` (default 100, max 100) caps page_size of the single first-page request.",
|
|
41
|
-
z.object({
|
|
42
|
-
limit: z.number().int().min(1).max(100).optional(),
|
|
43
|
-
}),
|
|
44
|
-
async (p) => {
|
|
45
|
-
const params = new URLSearchParams({
|
|
46
|
-
limit: String(p.limit ?? 100),
|
|
47
|
-
properties: DEAL_PROPERTIES,
|
|
48
|
-
});
|
|
49
|
-
return jsonResult(await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`));
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
reg(
|
|
54
|
-
"hubspot_get",
|
|
55
|
-
"Fetch one HubSpot deal by its id (GET /crm/v3/objects/deals/{dealId}?properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the deal object directly (NOT wrapped in `results`). Throws when no deal with that id exists.",
|
|
56
|
-
z.object({
|
|
57
|
-
id: z.string().min(1),
|
|
58
|
-
}),
|
|
59
|
-
async (p) => {
|
|
60
|
-
const params = new URLSearchParams({ properties: DEAL_PROPERTIES });
|
|
61
|
-
return jsonResult(
|
|
62
|
-
await hubspotGet(`/crm/v3/objects/deals/${encodeURIComponent(p.id)}?${params.toString()}`),
|
|
63
|
-
);
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
reg(
|
|
68
|
-
"hubspot_search",
|
|
69
|
-
"**Substring search over the FIRST PAGE only** (up to 100 most-recently-listed deals) of the authenticated portal's HubSpot deals. This tool fetches GET /crm/v3/objects/deals?limit=100 once and matches the query locally (case-insensitive) against the deal name, deal stage, and pipeline. **Deals beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
|
|
70
|
-
searchToolInputSchema(100),
|
|
71
|
-
async (p) => {
|
|
72
|
-
const params = new URLSearchParams({ limit: "100", properties: DEAL_PROPERTIES });
|
|
73
|
-
const root = await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`);
|
|
74
|
-
const matches = filterHubspotDeals(dealsFrom(root), {
|
|
75
|
-
query: p.query,
|
|
76
|
-
limit: p.limit,
|
|
77
|
-
});
|
|
78
|
-
return jsonResult({ matches });
|
|
79
|
-
},
|
|
80
|
-
);
|
|
5
|
+
registerHubspotTools(reg);
|
|
81
6
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders } 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 { filterHubspotDeals } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BASE = "https://api.hubapi.com";
|
|
9
|
+
const DEAL_PROPERTIES =
|
|
10
|
+
"dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate";
|
|
11
|
+
|
|
12
|
+
const hubspotGet = createJsonGetter({
|
|
13
|
+
base: BASE,
|
|
14
|
+
label: "HubSpot",
|
|
15
|
+
headers: envAuthHeaders({ env: "HUBSPOT_TOKEN" }),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function dealsFrom(root: unknown): unknown[] {
|
|
19
|
+
const results = (root as { results?: unknown } | null)?.results;
|
|
20
|
+
return Array.isArray(results) ? results : [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
24
|
+
export const HUBSPOT_TOOL_NAMES = ["hubspot_get", "hubspot_list", "hubspot_search"] as const;
|
|
25
|
+
|
|
26
|
+
export function registerHubspotTools(reg: ZodToolRegistrar): void {
|
|
27
|
+
reg(
|
|
28
|
+
"hubspot_list",
|
|
29
|
+
"List the authenticated portal's HubSpot deals (GET /crm/v3/objects/deals?limit=N&properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the { results: [...], paging?: { next?: { after } } } envelope — `results` holds the deal objects, each shaped { id, properties: { dealname, amount, dealstage, pipeline, closedate, createdate, hs_lastmodifieddate }, createdAt, updatedAt }. `limit` (default 100, max 100) caps page_size of the single first-page request.",
|
|
30
|
+
z.object({
|
|
31
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
32
|
+
}),
|
|
33
|
+
async (p) => {
|
|
34
|
+
const params = new URLSearchParams({
|
|
35
|
+
limit: String(p.limit ?? 100),
|
|
36
|
+
properties: DEAL_PROPERTIES,
|
|
37
|
+
});
|
|
38
|
+
return jsonResult(await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`));
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
reg(
|
|
43
|
+
"hubspot_get",
|
|
44
|
+
"Fetch one HubSpot deal by its id (GET /crm/v3/objects/deals/{dealId}?properties=dealname,amount,dealstage,pipeline,closedate,createdate,hs_lastmodifieddate). Returns the deal object directly (NOT wrapped in `results`). Throws when no deal with that id exists.",
|
|
45
|
+
z.object({
|
|
46
|
+
id: z.string().min(1),
|
|
47
|
+
}),
|
|
48
|
+
async (p) => {
|
|
49
|
+
const params = new URLSearchParams({ properties: DEAL_PROPERTIES });
|
|
50
|
+
return jsonResult(
|
|
51
|
+
await hubspotGet(`/crm/v3/objects/deals/${encodeURIComponent(p.id)}?${params.toString()}`),
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
reg(
|
|
57
|
+
"hubspot_search",
|
|
58
|
+
"**Substring search over the FIRST PAGE only** (up to 100 most-recently-listed deals) of the authenticated portal's HubSpot deals. This tool fetches GET /crm/v3/objects/deals?limit=100 once and matches the query locally (case-insensitive) against the deal name, deal stage, and pipeline. **Deals beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
|
|
59
|
+
searchToolInputSchema(100),
|
|
60
|
+
async (p) => {
|
|
61
|
+
const params = new URLSearchParams({ limit: "100", properties: DEAL_PROPERTIES });
|
|
62
|
+
const root = await hubspotGet(`/crm/v3/objects/deals?${params.toString()}`);
|
|
63
|
+
const matches = filterHubspotDeals(dealsFrom(root), {
|
|
64
|
+
query: p.query,
|
|
65
|
+
limit: p.limit,
|
|
66
|
+
});
|
|
67
|
+
return jsonResult({ matches });
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -1,145 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
createRegisterSimpleTool,
|
|
6
|
-
createZodToolRegistrar,
|
|
7
|
-
mcpJsonResult as jsonResult,
|
|
8
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
9
|
-
import { runCliOkThrowing } from "../../../shared/run-cli-json.ts";
|
|
3
|
+
import { registerIacTools } from "./tools.ts";
|
|
10
4
|
|
|
11
5
|
const mcp = new McpServer({ name: "nimbus-iac", version: "0.1.0" });
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Every MUTATING iac tool goes through here. Outside the gateway this adds the
|
|
19
|
-
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
20
|
-
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
21
|
-
*/
|
|
22
|
-
const registerWriteTool = createWriteToolRegistrar(mcp, {
|
|
23
|
-
connector: "iac",
|
|
24
|
-
scopeEnv: "NIMBUS_MCP_IAC_WRITE_SCOPE",
|
|
25
|
-
scopeKinds: ["dir", "stack"],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const processEnv = process.env as Record<string, string | undefined>;
|
|
29
|
-
|
|
30
|
-
reg(
|
|
31
|
-
"iac_terraform_plan",
|
|
32
|
-
"Run terraform plan in a directory.",
|
|
33
|
-
z.object({ workingDirectory: z.string().min(1) }),
|
|
34
|
-
async (p) => {
|
|
35
|
-
await runCliOkThrowing(["terraform", "-chdir", p.workingDirectory, "plan", "-input=false"], {
|
|
36
|
-
...processEnv,
|
|
37
|
-
});
|
|
38
|
-
return jsonResult({ ok: true });
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
registerWriteTool(
|
|
43
|
-
"iac_terraform_apply",
|
|
44
|
-
{
|
|
45
|
-
mutates: "iac.terraform.apply",
|
|
46
|
-
recoverable: true,
|
|
47
|
-
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
48
|
-
},
|
|
49
|
-
"Run terraform apply.",
|
|
50
|
-
z.object({ workingDirectory: z.string().min(1) }),
|
|
51
|
-
async (p) => {
|
|
52
|
-
await runCliOkThrowing(
|
|
53
|
-
["terraform", "-chdir", p.workingDirectory, "apply", "-auto-approve", "-input=false"],
|
|
54
|
-
{ ...processEnv },
|
|
55
|
-
);
|
|
56
|
-
return jsonResult({ ok: true });
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
registerWriteTool(
|
|
61
|
-
"iac_terraform_destroy",
|
|
62
|
-
{
|
|
63
|
-
mutates: "iac.terraform.destroy",
|
|
64
|
-
// Destroying infrastructure cannot be undone from its own result, and there is nothing
|
|
65
|
-
// queryable to snapshot, so the identifying parameters ARE the pre-state: a record naming
|
|
66
|
-
// WHICH directory was destroyed still beats silence.
|
|
67
|
-
recoverable: false,
|
|
68
|
-
capturePreState: (p) => Promise.resolve({ workingDirectory: p.workingDirectory }),
|
|
69
|
-
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
70
|
-
},
|
|
71
|
-
"Run terraform destroy.",
|
|
72
|
-
z.object({ workingDirectory: z.string().min(1) }),
|
|
73
|
-
async (p) => {
|
|
74
|
-
await runCliOkThrowing(
|
|
75
|
-
["terraform", "-chdir", p.workingDirectory, "destroy", "-auto-approve", "-input=false"],
|
|
76
|
-
{ ...processEnv },
|
|
77
|
-
);
|
|
78
|
-
return jsonResult({ ok: true });
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
registerWriteTool(
|
|
83
|
-
"iac_cloudformation_deploy",
|
|
84
|
-
{
|
|
85
|
-
mutates: "iac.cloudformation.deploy",
|
|
86
|
-
recoverable: true,
|
|
87
|
-
scopeTargetOf: (p) => ({ kind: "stack", value: p.stackName }),
|
|
88
|
-
},
|
|
89
|
-
"Deploy a CloudFormation stack via AWS CLI.",
|
|
90
|
-
z.object({
|
|
91
|
-
stackName: z.string().min(1),
|
|
92
|
-
templateBody: z.string().min(1),
|
|
93
|
-
}),
|
|
94
|
-
async (p) => {
|
|
95
|
-
await runCliOkThrowing(
|
|
96
|
-
[
|
|
97
|
-
"aws",
|
|
98
|
-
"cloudformation",
|
|
99
|
-
"deploy",
|
|
100
|
-
"--stack-name",
|
|
101
|
-
p.stackName,
|
|
102
|
-
"--template-body",
|
|
103
|
-
p.templateBody,
|
|
104
|
-
"--capabilities",
|
|
105
|
-
"CAPABILITY_IAM",
|
|
106
|
-
],
|
|
107
|
-
{ ...processEnv },
|
|
108
|
-
);
|
|
109
|
-
return jsonResult({ ok: true });
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
reg(
|
|
114
|
-
"iac_pulumi_preview",
|
|
115
|
-
"Run pulumi preview in a stack directory.",
|
|
116
|
-
z.object({ workingDirectory: z.string().min(1) }),
|
|
117
|
-
async (p) => {
|
|
118
|
-
await runCliOkThrowing(
|
|
119
|
-
["pulumi", "preview", "--cwd", p.workingDirectory, "--non-interactive"],
|
|
120
|
-
{ ...processEnv },
|
|
121
|
-
);
|
|
122
|
-
return jsonResult({ ok: true });
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
registerWriteTool(
|
|
127
|
-
"iac_pulumi_up",
|
|
128
|
-
{
|
|
129
|
-
mutates: "iac.pulumi.up",
|
|
130
|
-
recoverable: true,
|
|
131
|
-
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
132
|
-
},
|
|
133
|
-
"Run pulumi up.",
|
|
134
|
-
z.object({ workingDirectory: z.string().min(1) }),
|
|
135
|
-
async (p) => {
|
|
136
|
-
await runCliOkThrowing(
|
|
137
|
-
["pulumi", "up", "--yes", "--cwd", p.workingDirectory, "--non-interactive"],
|
|
138
|
-
{ ...processEnv },
|
|
139
|
-
);
|
|
140
|
-
return jsonResult({ ok: true });
|
|
141
|
-
},
|
|
142
|
-
);
|
|
7
|
+
registerIacTools(mcp);
|
|
143
8
|
|
|
144
9
|
const transport = new StdioServerTransport();
|
|
145
10
|
await mcp.connect(transport);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ConsentServer } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
4
|
+
import {
|
|
5
|
+
createRegisterSimpleTool,
|
|
6
|
+
createZodToolRegistrar,
|
|
7
|
+
mcpJsonResult as jsonResult,
|
|
8
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
9
|
+
import { runCliOkThrowing } from "../../../shared/run-cli-json.ts";
|
|
10
|
+
|
|
11
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
12
|
+
export const IAC_TOOL_NAMES = [
|
|
13
|
+
"iac_terraform_plan",
|
|
14
|
+
"iac_terraform_apply",
|
|
15
|
+
"iac_terraform_destroy",
|
|
16
|
+
"iac_cloudformation_deploy",
|
|
17
|
+
"iac_pulumi_preview",
|
|
18
|
+
"iac_pulumi_up",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export function registerIacTools(
|
|
22
|
+
server: ConsentServer & { tool: (...args: never) => unknown },
|
|
23
|
+
): void {
|
|
24
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Every MUTATING iac tool goes through here. Outside the gateway this adds the
|
|
28
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
29
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
30
|
+
*/
|
|
31
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
32
|
+
connector: "iac",
|
|
33
|
+
scopeEnv: "NIMBUS_MCP_IAC_WRITE_SCOPE",
|
|
34
|
+
scopeKinds: ["dir", "stack"],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const processEnv = process.env as Record<string, string | undefined>;
|
|
38
|
+
|
|
39
|
+
reg(
|
|
40
|
+
"iac_terraform_plan",
|
|
41
|
+
"Run terraform plan in a directory.",
|
|
42
|
+
z.object({ workingDirectory: z.string().min(1) }),
|
|
43
|
+
async (p) => {
|
|
44
|
+
await runCliOkThrowing(["terraform", "-chdir", p.workingDirectory, "plan", "-input=false"], {
|
|
45
|
+
...processEnv,
|
|
46
|
+
});
|
|
47
|
+
return jsonResult({ ok: true });
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
registerWriteTool(
|
|
52
|
+
"iac_terraform_apply",
|
|
53
|
+
{
|
|
54
|
+
mutates: "iac.terraform.apply",
|
|
55
|
+
recoverable: true,
|
|
56
|
+
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
57
|
+
},
|
|
58
|
+
"Run terraform apply.",
|
|
59
|
+
z.object({ workingDirectory: z.string().min(1) }),
|
|
60
|
+
async (p) => {
|
|
61
|
+
await runCliOkThrowing(
|
|
62
|
+
["terraform", "-chdir", p.workingDirectory, "apply", "-auto-approve", "-input=false"],
|
|
63
|
+
{ ...processEnv },
|
|
64
|
+
);
|
|
65
|
+
return jsonResult({ ok: true });
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
registerWriteTool(
|
|
70
|
+
"iac_terraform_destroy",
|
|
71
|
+
{
|
|
72
|
+
mutates: "iac.terraform.destroy",
|
|
73
|
+
// Destroying infrastructure cannot be undone from its own result, and there is nothing
|
|
74
|
+
// queryable to snapshot, so the identifying parameters ARE the pre-state: a record naming
|
|
75
|
+
// WHICH directory was destroyed still beats silence.
|
|
76
|
+
recoverable: false,
|
|
77
|
+
capturePreState: (p) => Promise.resolve({ workingDirectory: p.workingDirectory }),
|
|
78
|
+
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
79
|
+
},
|
|
80
|
+
"Run terraform destroy.",
|
|
81
|
+
z.object({ workingDirectory: z.string().min(1) }),
|
|
82
|
+
async (p) => {
|
|
83
|
+
await runCliOkThrowing(
|
|
84
|
+
["terraform", "-chdir", p.workingDirectory, "destroy", "-auto-approve", "-input=false"],
|
|
85
|
+
{ ...processEnv },
|
|
86
|
+
);
|
|
87
|
+
return jsonResult({ ok: true });
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
registerWriteTool(
|
|
92
|
+
"iac_cloudformation_deploy",
|
|
93
|
+
{
|
|
94
|
+
mutates: "iac.cloudformation.deploy",
|
|
95
|
+
recoverable: true,
|
|
96
|
+
scopeTargetOf: (p) => ({ kind: "stack", value: p.stackName }),
|
|
97
|
+
},
|
|
98
|
+
"Deploy a CloudFormation stack via AWS CLI.",
|
|
99
|
+
z.object({
|
|
100
|
+
stackName: z.string().min(1),
|
|
101
|
+
templateBody: z.string().min(1),
|
|
102
|
+
}),
|
|
103
|
+
async (p) => {
|
|
104
|
+
await runCliOkThrowing(
|
|
105
|
+
[
|
|
106
|
+
"aws",
|
|
107
|
+
"cloudformation",
|
|
108
|
+
"deploy",
|
|
109
|
+
"--stack-name",
|
|
110
|
+
p.stackName,
|
|
111
|
+
"--template-body",
|
|
112
|
+
p.templateBody,
|
|
113
|
+
"--capabilities",
|
|
114
|
+
"CAPABILITY_IAM",
|
|
115
|
+
],
|
|
116
|
+
{ ...processEnv },
|
|
117
|
+
);
|
|
118
|
+
return jsonResult({ ok: true });
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
reg(
|
|
123
|
+
"iac_pulumi_preview",
|
|
124
|
+
"Run pulumi preview in a stack directory.",
|
|
125
|
+
z.object({ workingDirectory: z.string().min(1) }),
|
|
126
|
+
async (p) => {
|
|
127
|
+
await runCliOkThrowing(
|
|
128
|
+
["pulumi", "preview", "--cwd", p.workingDirectory, "--non-interactive"],
|
|
129
|
+
{ ...processEnv },
|
|
130
|
+
);
|
|
131
|
+
return jsonResult({ ok: true });
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
registerWriteTool(
|
|
136
|
+
"iac_pulumi_up",
|
|
137
|
+
{
|
|
138
|
+
mutates: "iac.pulumi.up",
|
|
139
|
+
recoverable: true,
|
|
140
|
+
scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
|
|
141
|
+
},
|
|
142
|
+
"Run pulumi up.",
|
|
143
|
+
z.object({ workingDirectory: z.string().min(1) }),
|
|
144
|
+
async (p) => {
|
|
145
|
+
await runCliOkThrowing(
|
|
146
|
+
["pulumi", "up", "--yes", "--cwd", p.workingDirectory, "--non-interactive"],
|
|
147
|
+
{ ...processEnv },
|
|
148
|
+
);
|
|
149
|
+
return jsonResult({ ok: true });
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -11,10 +11,28 @@
|
|
|
11
11
|
* preview — there is no surface to request `BODY[]` or an attachment part.
|
|
12
12
|
*
|
|
13
13
|
* The IMAP client and SMTP mailer are injected so tests never open real sockets.
|
|
14
|
+
*
|
|
15
|
+
* The shapes themselves now live in `shared/imap-mail-core.ts`: this connector
|
|
16
|
+
* and protonmail declared them separately and identically, which is the same
|
|
17
|
+
* duplication the tool layer had already been through. The `Imap*` aliases stay
|
|
18
|
+
* so this connector's own callers read as before.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
MailAddress,
|
|
23
|
+
MailAttachmentMeta,
|
|
24
|
+
MailClient,
|
|
25
|
+
MailEnvelope,
|
|
26
|
+
MailListOptions,
|
|
27
|
+
MailMessageMeta,
|
|
28
|
+
MailSearchOptions,
|
|
29
|
+
} from "../../../shared/imap-mail-core.ts";
|
|
17
30
|
|
|
31
|
+
export type {
|
|
32
|
+
SendMailInput,
|
|
33
|
+
SendMailResult,
|
|
34
|
+
SmtpMailer,
|
|
35
|
+
} from "../../../shared/imap-mail-core.ts";
|
|
18
36
|
export {
|
|
19
37
|
capPreview,
|
|
20
38
|
clampLimit,
|
|
@@ -25,86 +43,15 @@ export {
|
|
|
25
43
|
|
|
26
44
|
/** One parsed address (name optional). */
|
|
27
45
|
export type ImapAddress = MailAddress;
|
|
28
|
-
|
|
29
46
|
/** Message ENVELOPE — RFC 2822 header fields only. */
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
readonly to?: readonly ImapAddress[];
|
|
36
|
-
readonly cc?: readonly ImapAddress[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Attachment METADATA only — filename, size, mimetype. NEVER the bytes.
|
|
41
|
-
* Derived from BODYSTRUCTURE.
|
|
42
|
-
*/
|
|
43
|
-
export interface ImapAttachmentMeta {
|
|
44
|
-
readonly filename: string | null;
|
|
45
|
-
readonly sizeBytes: number | null;
|
|
46
|
-
readonly mimeType: string | null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A single mailbox message reduced to header + attachment-metadata + a capped
|
|
51
|
-
* text preview. This is the ONLY shape the connector ever materializes; it has
|
|
52
|
-
* no field that could carry attachment content or a full body.
|
|
53
|
-
*/
|
|
54
|
-
export interface ImapMessageMeta {
|
|
55
|
-
readonly uid: number;
|
|
56
|
-
readonly mailbox: string;
|
|
57
|
-
readonly uidValidity: string | null;
|
|
58
|
-
readonly envelope: ImapEnvelope;
|
|
59
|
-
readonly attachments: readonly ImapAttachmentMeta[];
|
|
60
|
-
/** Plain-text body preview, already capped to {@link PREVIEW_MAX_CHARS}. */
|
|
61
|
-
readonly preview: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
47
|
+
export type ImapEnvelope = MailEnvelope;
|
|
48
|
+
/** Attachment METADATA only — filename, size, mimetype. NEVER the bytes. */
|
|
49
|
+
export type ImapAttachmentMeta = MailAttachmentMeta;
|
|
50
|
+
/** Header + attachment-metadata + capped preview: the only shape materialized. */
|
|
51
|
+
export type ImapMessageMeta = MailMessageMeta;
|
|
64
52
|
/** Listing options. */
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/** Search options — searches header/envelope fields, never document/body content scans. */
|
|
71
|
-
export interface ImapSearchOptions {
|
|
72
|
-
readonly query: string;
|
|
73
|
-
readonly mailbox?: string;
|
|
74
|
-
readonly limit?: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Minimal IMAP client surface the connector depends on. Implemented for real by
|
|
79
|
-
* the imapflow adapter in `server.ts` and by a fake in tests. Deliberately
|
|
80
|
-
* exposes ONLY header/attachment-metadata + capped-preview reads.
|
|
81
|
-
*/
|
|
82
|
-
export interface ImapClient {
|
|
83
|
-
/** List the most recent messages (header + attachment metadata + preview). */
|
|
84
|
-
list(options: ImapListOptions): Promise<ImapMessageMeta[]>;
|
|
85
|
-
/** Fetch one message's header + attachment metadata + preview by uid. */
|
|
86
|
-
get(uid: number, mailbox?: string): Promise<ImapMessageMeta | null>;
|
|
87
|
-
/** Search by subject/from/to substring; returns matching message metas. */
|
|
88
|
-
search(options: ImapSearchOptions): Promise<ImapMessageMeta[]>;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Outgoing message for the SMTP send tool. */
|
|
92
|
-
export interface SendMailInput {
|
|
93
|
-
readonly to: string;
|
|
94
|
-
readonly subject: string;
|
|
95
|
-
readonly body: string;
|
|
96
|
-
readonly cc?: string;
|
|
97
|
-
readonly bcc?: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** Result of a send (message id assigned by the SMTP server, if any). */
|
|
101
|
-
export interface SendMailResult {
|
|
102
|
-
readonly messageId: string | null;
|
|
103
|
-
readonly accepted: readonly string[];
|
|
104
|
-
readonly rejected: readonly string[];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Minimal SMTP mailer surface; implemented for real over nodemailer. */
|
|
108
|
-
export interface SmtpMailer {
|
|
109
|
-
send(input: SendMailInput): Promise<SendMailResult>;
|
|
110
|
-
}
|
|
53
|
+
export type ImapListOptions = MailListOptions;
|
|
54
|
+
/** Search options — header/envelope fields only, never body-content scans. */
|
|
55
|
+
export type ImapSearchOptions = MailSearchOptions;
|
|
56
|
+
/** Minimal IMAP client surface the connector depends on. */
|
|
57
|
+
export type ImapClient = MailClient;
|