@nimbus-dev/connectors 0.2.1 → 0.2.3
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 +19 -7
- 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 +26 -43
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +199 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +134 -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 +19 -35
- package/connectors/cloudwatch/src/tools.ts +20 -38
- 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 +153 -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 +160 -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 +21 -40
- 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 +14 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +601 -34
- package/shared/cli-json-kit.ts +191 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/consent-kit.ts +81 -26
- 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/nimbus-spawn.ts +84 -27
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/safe-cli-arg.ts +139 -17
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/shared/windows-batch-args.ts +273 -0
- package/shared/write-budget.ts +100 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
emailToolSchemas,
|
|
4
|
+
outgoingMail,
|
|
5
|
+
registerEmailConnectorTools,
|
|
6
|
+
} from "../../../shared/imap-tool-kit.ts";
|
|
6
7
|
import { mcpJsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
7
8
|
import {
|
|
8
9
|
type DraftAppender,
|
|
@@ -11,19 +12,11 @@ import {
|
|
|
11
12
|
formatAddress,
|
|
12
13
|
} from "./apple-mail-core.ts";
|
|
13
14
|
import type { CalDavClient } from "./caldav-core.ts";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const draftArgs = z.object({
|
|
21
|
-
to: headerLine({ min: 1 }),
|
|
22
|
-
subject: headerLine({ min: 1, max: 998 }),
|
|
23
|
-
body: z.string().max(1_000_000),
|
|
24
|
-
cc: headerLine().optional(),
|
|
25
|
-
bcc: headerLine().optional(),
|
|
26
|
-
});
|
|
15
|
+
import {
|
|
16
|
+
APPLE_WRITE_SCOPE,
|
|
17
|
+
type CalendarToolConfig,
|
|
18
|
+
registerAppleCalendarTools,
|
|
19
|
+
} from "./calendar-tools.ts";
|
|
27
20
|
|
|
28
21
|
// ---------------------------------------------------------------------------
|
|
29
22
|
// Tool descriptions
|
|
@@ -65,13 +58,10 @@ export function registerAppleTools(
|
|
|
65
58
|
): void {
|
|
66
59
|
const { client, mailer, draftAppender, calendar, now, calendarConfig } = params;
|
|
67
60
|
|
|
68
|
-
//
|
|
69
|
-
const registerWriteTool = createWriteToolRegistrar(server,
|
|
70
|
-
connector: "apple",
|
|
71
|
-
scopeEnv: "NIMBUS_MCP_APPLE_WRITE_SCOPE",
|
|
72
|
-
scopeKinds: ["recipient"],
|
|
73
|
-
});
|
|
61
|
+
// ONE registrar for every apple write, mail and calendar alike: see APPLE_WRITE_SCOPE.
|
|
62
|
+
const registerWriteTool = createWriteToolRegistrar(server, APPLE_WRITE_SCOPE);
|
|
74
63
|
|
|
64
|
+
// The four shared email tools (list/get/search/mail_send) via the shared kit.
|
|
75
65
|
registerEmailConnectorTools({
|
|
76
66
|
server,
|
|
77
67
|
registerWriteTool,
|
|
@@ -92,27 +82,10 @@ export function registerAppleTools(
|
|
|
92
82
|
scopeTargetOf: (p) => ({ kind: "recipient", value: p.to }),
|
|
93
83
|
},
|
|
94
84
|
"Save a new email to the iCloud Mail Drafts folder via IMAP APPEND.",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
to: string;
|
|
100
|
-
subject: string;
|
|
101
|
-
body: string;
|
|
102
|
-
cc?: string;
|
|
103
|
-
bcc?: string;
|
|
104
|
-
} = {
|
|
105
|
-
to: parsed.data.to,
|
|
106
|
-
subject: parsed.data.subject,
|
|
107
|
-
body: parsed.data.body,
|
|
108
|
-
};
|
|
109
|
-
if (parsed.data.cc !== undefined && parsed.data.cc !== "") {
|
|
110
|
-
input.cc = parsed.data.cc;
|
|
111
|
-
}
|
|
112
|
-
if (parsed.data.bcc !== undefined && parsed.data.bcc !== "") {
|
|
113
|
-
input.bcc = parsed.data.bcc;
|
|
114
|
-
}
|
|
115
|
-
const result = await draftAppender.appendDraft(input);
|
|
85
|
+
// A draft takes exactly what a send does.
|
|
86
|
+
emailToolSchemas.sendArgs,
|
|
87
|
+
async (args) => {
|
|
88
|
+
const result = await draftAppender.appendDraft(outgoingMail(args));
|
|
116
89
|
return mcpJsonResult({ item: result });
|
|
117
90
|
},
|
|
118
91
|
);
|
|
@@ -122,6 +95,7 @@ export function registerAppleTools(
|
|
|
122
95
|
calendar,
|
|
123
96
|
now,
|
|
124
97
|
config: calendarConfig,
|
|
98
|
+
registerWriteTool,
|
|
125
99
|
});
|
|
126
100
|
}
|
|
127
101
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import {
|
|
4
|
+
createJsonGetter,
|
|
5
|
+
createJsonPoster,
|
|
6
|
+
envAuthHeaders,
|
|
7
|
+
type JsonApiConfig,
|
|
8
|
+
requiredBaseUrl,
|
|
9
|
+
} from "../../../shared/env-json-api.ts";
|
|
3
10
|
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
11
|
import { fetchWithTimeout, mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
12
|
import {
|
|
@@ -8,47 +15,24 @@ import {
|
|
|
8
15
|
} from "../../../shared/run-read-only-mcp-connector.ts";
|
|
9
16
|
import { filterArgocdApplications } from "./search-filter.ts";
|
|
10
17
|
|
|
11
|
-
function trimTrailingSlash(s: string): string {
|
|
12
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
18
|
function apiBase(): string {
|
|
16
|
-
|
|
17
|
-
if (v === undefined || v === "") {
|
|
18
|
-
throw new Error("ARGOCD_URL is not set");
|
|
19
|
-
}
|
|
20
|
-
return `${trimTrailingSlash(v)}/api/v1`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function authHeader(): Record<string, string> {
|
|
24
|
-
const t = process.env["ARGOCD_TOKEN"]?.trim();
|
|
25
|
-
if (t === undefined || t === "") {
|
|
26
|
-
throw new Error("ARGOCD_TOKEN is not set");
|
|
27
|
-
}
|
|
28
|
-
return { Authorization: `Bearer ${t}`, Accept: "application/json" };
|
|
19
|
+
return `${requiredBaseUrl("ARGOCD_URL")}/api/v1`;
|
|
29
20
|
}
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
/**
|
|
23
|
+
* One config for reads and the mutating requests alike. `fetchWithTimeout`, not the global
|
|
24
|
+
* fetch: this is a self-hosted control plane, and one that stops answering must fail the tool
|
|
25
|
+
* call rather than hang it.
|
|
26
|
+
*/
|
|
27
|
+
const api: JsonApiConfig = {
|
|
28
|
+
base: apiBase,
|
|
29
|
+
label: "ArgoCD",
|
|
30
|
+
headers: envAuthHeaders({ env: "ARGOCD_TOKEN" }),
|
|
31
|
+
fetch: fetchWithTimeout,
|
|
32
|
+
};
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { ...authHeader(), "Content-Type": "application/json" },
|
|
44
|
-
body: JSON.stringify(body),
|
|
45
|
-
});
|
|
46
|
-
const text = await res.text();
|
|
47
|
-
if (!res.ok) {
|
|
48
|
-
throw new Error(`ArgoCD ${path} ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
49
|
-
}
|
|
50
|
-
return text === "" ? {} : (JSON.parse(text) as unknown);
|
|
51
|
-
}
|
|
34
|
+
const agGet = createJsonGetter(api);
|
|
35
|
+
const agPost = createJsonPoster(api);
|
|
52
36
|
|
|
53
37
|
function applicationsFrom(root: unknown): unknown[] {
|
|
54
38
|
if (Array.isArray(root)) {
|
|
@@ -63,7 +47,10 @@ export function registerArgocdTools(reg: ZodToolRegistrar, server: unknown): voi
|
|
|
63
47
|
// kit needs the real server, which the helper now passes through as its second argument.
|
|
64
48
|
const registerWriteTool = createWriteToolRegistrar(server as ConsentServer, {
|
|
65
49
|
connector: "argocd",
|
|
66
|
-
|
|
50
|
+
// NIMBUS_MCP_<SERVICE>_WRITE_SCOPE, as docs/configuration.md documents for every connector.
|
|
51
|
+
// This read NIMBUS_MCP_APP_WRITE_SCOPE (the scope KIND, not the service), so the documented
|
|
52
|
+
// variable was ignored and every argocd write refused as out of scope.
|
|
53
|
+
scopeEnv: "NIMBUS_MCP_ARGOCD_WRITE_SCOPE",
|
|
67
54
|
scopeKinds: ["app"],
|
|
68
55
|
});
|
|
69
56
|
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
asArray,
|
|
4
|
+
awsCliArg,
|
|
5
|
+
createCliJsonRunner,
|
|
6
|
+
isRecord,
|
|
7
|
+
strField,
|
|
8
|
+
} from "../../../shared/cli-json-kit.ts";
|
|
2
9
|
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
10
|
import { nimbusSpawn } from "../../../shared/nimbus-spawn.ts";
|
|
4
11
|
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
-
import { isSafeCliArg } from "../../../shared/safe-cli-arg.ts";
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* A catalog / database / table name passed as a value to the `aws athena` CLI.
|
|
9
|
-
* Rejected at the schema boundary if it begins with `-` (argv flag
|
|
10
|
-
* contains control characters
|
|
15
|
+
* Rejected at the schema boundary (`awsCliArg`) if it begins with `-` (argv flag
|
|
16
|
+
* smuggling), contains control characters, or starts with a prefix that makes the
|
|
17
|
+
* CLI read the value from a file or a URL instead — `file://<path>` as a table name
|
|
18
|
+
* would send that file to AWS, and an error can quote it back.
|
|
11
19
|
*/
|
|
12
|
-
const cliArg = z
|
|
13
|
-
.string()
|
|
14
|
-
.min(1)
|
|
15
|
-
.refine(isSafeCliArg, { message: 'must not start with "-" or contain control characters' });
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Amazon Athena (Tier-3, metadata-only) MCP tool surface. ALL tools index
|
|
@@ -27,40 +31,19 @@ export const ATHENA_TOOL_NAMES = ["athena_list", "athena_get", "athena_search"]
|
|
|
27
31
|
|
|
28
32
|
const PAGE = "200";
|
|
29
33
|
|
|
30
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
31
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function strField(r: Record<string, unknown>, key: string): string {
|
|
35
|
-
const v = r[key];
|
|
36
|
-
return typeof v === "string" ? v : "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function asArray(parsed: unknown, key: string): unknown[] {
|
|
40
|
-
if (!isRecord(parsed)) {
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
const arr = parsed[key];
|
|
44
|
-
return Array.isArray(arr) ? arr : [];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
34
|
/**
|
|
48
35
|
* Run `aws athena <args> --output json` and parse stdout. The AWS CLI reads
|
|
49
36
|
* AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION / AWS_PROFILE
|
|
50
37
|
* natively from the process env injected at spawn time. Throws on non-zero exit.
|
|
51
38
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
["aws", "athena", ...args, "--output", "json"],
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
throw new Error(`aws athena ${args[0] ?? ""} failed: ${err.slice(0, 400)}`);
|
|
61
|
-
}
|
|
62
|
-
return out === "" ? {} : (JSON.parse(out) as unknown);
|
|
63
|
-
}
|
|
39
|
+
const athenaCli = createCliJsonRunner(
|
|
40
|
+
{
|
|
41
|
+
argv: (args) => ["aws", "athena", ...args, "--output", "json"],
|
|
42
|
+
label: "aws athena",
|
|
43
|
+
emptyResult: {},
|
|
44
|
+
},
|
|
45
|
+
nimbusSpawn,
|
|
46
|
+
);
|
|
64
47
|
|
|
65
48
|
function catalogMatches(entry: unknown, q: string): boolean {
|
|
66
49
|
if (!isRecord(entry)) {
|
|
@@ -89,8 +72,8 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
|
|
|
89
72
|
"athena_list",
|
|
90
73
|
"List Athena data catalogs, the databases in a catalog, or the tables in a database — METADATA ONLY. With no arguments, lists data catalogs (`aws athena list-data-catalogs`). With `catalog` set, lists that catalog's databases (`aws athena list-databases`). With `catalog` + `database` set, lists that database's table metadata (`aws athena list-table-metadata`) — each entry carries `Name`, `TableType`, `Columns`, and `PartitionKeys`. Never runs a query or returns row/cell data.",
|
|
91
74
|
z.object({
|
|
92
|
-
catalog:
|
|
93
|
-
database:
|
|
75
|
+
catalog: awsCliArg.optional(),
|
|
76
|
+
database: awsCliArg.optional(),
|
|
94
77
|
}),
|
|
95
78
|
async (p) => {
|
|
96
79
|
if (p.catalog === undefined) {
|
|
@@ -119,9 +102,9 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
|
|
|
119
102
|
"athena_get",
|
|
120
103
|
"Fetch one Athena table's schema + METADATA (`aws athena get-table-metadata`). Returns the table object including `Columns` (column names + types only), `PartitionKeys`, `TableType`, `Parameters`, and timestamps. No cell values or query results are returned — schema/metadata only.",
|
|
121
104
|
z.object({
|
|
122
|
-
catalog:
|
|
123
|
-
database:
|
|
124
|
-
table:
|
|
105
|
+
catalog: awsCliArg,
|
|
106
|
+
database: awsCliArg,
|
|
107
|
+
table: awsCliArg,
|
|
125
108
|
}),
|
|
126
109
|
async (p) => {
|
|
127
110
|
return jsonResult(
|
|
@@ -142,8 +125,8 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
|
|
|
142
125
|
"athena_search",
|
|
143
126
|
"Substring search over Athena catalog, database, and table NAMES (case-insensitive) — METADATA ONLY. With no `catalog`, searches data-catalog names. With `catalog` set (no `database`), searches that catalog's database names. With `catalog` + `database` set, searches that database's table names. Returns a `{ matches: [...] }` envelope of metadata entries. Never runs a query or samples row data.",
|
|
144
127
|
z.object({
|
|
145
|
-
catalog:
|
|
146
|
-
database:
|
|
128
|
+
catalog: awsCliArg.optional(),
|
|
129
|
+
database: awsCliArg.optional(),
|
|
147
130
|
query: z.string().min(1),
|
|
148
131
|
}),
|
|
149
132
|
async (p) => {
|
|
@@ -1,191 +1,10 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
createRegisterSimpleTool,
|
|
10
|
-
createZodToolRegistrar,
|
|
11
|
-
mcpJsonResult as jsonResult,
|
|
12
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
13
|
-
import { runCliJson, runCliOk } from "../../../shared/run-cli-json.ts";
|
|
14
|
-
|
|
15
|
-
function awsEnv(): Record<string, string | undefined> {
|
|
16
|
-
const e = { ...process.env } as Record<string, string | undefined>;
|
|
17
|
-
const ak = process.env["AWS_ACCESS_KEY_ID"]?.trim();
|
|
18
|
-
const sk = process.env["AWS_SECRET_ACCESS_KEY"]?.trim();
|
|
19
|
-
const rg = process.env["AWS_DEFAULT_REGION"]?.trim();
|
|
20
|
-
const profile = process.env["AWS_PROFILE"]?.trim();
|
|
21
|
-
if (ak !== undefined && ak !== "") {
|
|
22
|
-
e["AWS_ACCESS_KEY_ID"] = ak;
|
|
23
|
-
}
|
|
24
|
-
if (sk !== undefined && sk !== "") {
|
|
25
|
-
e["AWS_SECRET_ACCESS_KEY"] = sk;
|
|
26
|
-
}
|
|
27
|
-
if (rg !== undefined && rg !== "") {
|
|
28
|
-
e["AWS_DEFAULT_REGION"] = rg;
|
|
29
|
-
}
|
|
30
|
-
if (profile !== undefined && profile !== "") {
|
|
31
|
-
e["AWS_PROFILE"] = profile;
|
|
32
|
-
}
|
|
33
|
-
return e;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function awsJson(args: string[]): Promise<unknown> {
|
|
37
|
-
const cmd = ["aws", ...args, "--output", "json"];
|
|
38
|
-
const r = await runCliJson(cmd, awsEnv());
|
|
39
|
-
if (!r.ok) {
|
|
40
|
-
throw new Error(r.message);
|
|
41
|
-
}
|
|
42
|
-
return r.data ?? {};
|
|
43
|
-
}
|
|
3
|
+
import { registerAwsTools } from "./tools.ts";
|
|
44
4
|
|
|
45
5
|
const mcp = new McpServer({ name: "nimbus-aws", version: "0.1.0" });
|
|
46
6
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Every MUTATING aws tool goes through here. Outside the gateway this adds the
|
|
53
|
-
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
54
|
-
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
55
|
-
*/
|
|
56
|
-
const registerWriteTool = createWriteToolRegistrar(mcp, {
|
|
57
|
-
connector: "aws",
|
|
58
|
-
scopeEnv: "NIMBUS_MCP_AWS_WRITE_SCOPE",
|
|
59
|
-
scopeKinds: ["cluster", "function"],
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
reg(
|
|
63
|
-
"aws_ecs_service_list",
|
|
64
|
-
"List ECS services in a cluster.",
|
|
65
|
-
z.object({ cluster: z.string().min(1) }),
|
|
66
|
-
async (p) => jsonResult(await awsJson(["ecs", "list-services", "--cluster", p.cluster])),
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
reg("aws_lambda_list", "List Lambda functions (first page).", z.object({}), async () =>
|
|
70
|
-
jsonResult(await awsJson(["lambda", "list-functions"])),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
registerWriteTool(
|
|
74
|
-
"aws_ecs_service_update",
|
|
75
|
-
{
|
|
76
|
-
mutates: "aws.ecs.service.update",
|
|
77
|
-
recoverable: true,
|
|
78
|
-
scopeTargetOf: (p) => ({ kind: "cluster", value: p.cluster }),
|
|
79
|
-
},
|
|
80
|
-
"Update ECS service (e.g. new task definition).",
|
|
81
|
-
z.object({
|
|
82
|
-
cluster: z.string().min(1),
|
|
83
|
-
service: z.string().min(1),
|
|
84
|
-
taskDefinition: z.string().min(1),
|
|
85
|
-
}),
|
|
86
|
-
async (p) => {
|
|
87
|
-
const cmd = [
|
|
88
|
-
"aws",
|
|
89
|
-
"ecs",
|
|
90
|
-
"update-service",
|
|
91
|
-
"--cluster",
|
|
92
|
-
p.cluster,
|
|
93
|
-
"--service",
|
|
94
|
-
p.service,
|
|
95
|
-
"--task-definition",
|
|
96
|
-
p.taskDefinition,
|
|
97
|
-
"--force-new-deployment",
|
|
98
|
-
];
|
|
99
|
-
const r = await runCliOk(cmd, awsEnv());
|
|
100
|
-
if (!r.ok) {
|
|
101
|
-
throw new Error(r.message);
|
|
102
|
-
}
|
|
103
|
-
return jsonResult({ ok: true });
|
|
104
|
-
},
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
registerWriteTool(
|
|
108
|
-
"aws_lambda_invoke",
|
|
109
|
-
{
|
|
110
|
-
mutates: "aws.lambda.invoke",
|
|
111
|
-
recoverable: true,
|
|
112
|
-
scopeTargetOf: (p) => ({ kind: "function", value: p.functionName }),
|
|
113
|
-
},
|
|
114
|
-
"Invoke a Lambda function.",
|
|
115
|
-
z.object({
|
|
116
|
-
functionName: z.string().min(1),
|
|
117
|
-
payloadJson: z.string().optional(),
|
|
118
|
-
}),
|
|
119
|
-
async (p) => {
|
|
120
|
-
const dir = mkdtempSync(join(tmpdir(), "nimbus-aws-lambda-"));
|
|
121
|
-
const outFile = join(dir, "response.json");
|
|
122
|
-
if (p.payloadJson !== undefined && p.payloadJson !== "") {
|
|
123
|
-
const pf = join(dir, "payload.json");
|
|
124
|
-
writeFileSync(pf, p.payloadJson, "utf8");
|
|
125
|
-
const cmd = [
|
|
126
|
-
"aws",
|
|
127
|
-
"lambda",
|
|
128
|
-
"invoke",
|
|
129
|
-
"--function-name",
|
|
130
|
-
p.functionName,
|
|
131
|
-
"--payload",
|
|
132
|
-
`file://${pf}`,
|
|
133
|
-
outFile,
|
|
134
|
-
];
|
|
135
|
-
const r = await runCliOk(cmd, awsEnv());
|
|
136
|
-
if (!r.ok) {
|
|
137
|
-
throw new Error(r.message);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
const r = await runCliOk(
|
|
141
|
-
["aws", "lambda", "invoke", "--function-name", p.functionName, outFile],
|
|
142
|
-
awsEnv(),
|
|
143
|
-
);
|
|
144
|
-
if (!r.ok) {
|
|
145
|
-
throw new Error(r.message);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
let body: unknown;
|
|
149
|
-
try {
|
|
150
|
-
body = JSON.parse(readFileSync(outFile, "utf8")) as unknown;
|
|
151
|
-
} catch {
|
|
152
|
-
body = { ok: true };
|
|
153
|
-
}
|
|
154
|
-
return jsonResult(body);
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
reg(
|
|
159
|
-
"aws_ec2_instance_stop",
|
|
160
|
-
"Stop EC2 instances. HITL.",
|
|
161
|
-
z.object({ instanceIds: z.string().min(1) }),
|
|
162
|
-
async (p) => {
|
|
163
|
-
const r = await runCliOk(
|
|
164
|
-
["aws", "ec2", "stop-instances", "--instance-ids", p.instanceIds],
|
|
165
|
-
awsEnv(),
|
|
166
|
-
);
|
|
167
|
-
if (!r.ok) {
|
|
168
|
-
throw new Error(r.message);
|
|
169
|
-
}
|
|
170
|
-
return jsonResult({ ok: true });
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
reg(
|
|
175
|
-
"aws_ec2_instance_start",
|
|
176
|
-
"Start EC2 instances. HITL.",
|
|
177
|
-
z.object({ instanceIds: z.string().min(1) }),
|
|
178
|
-
async (p) => {
|
|
179
|
-
const r = await runCliOk(
|
|
180
|
-
["aws", "ec2", "start-instances", "--instance-ids", p.instanceIds],
|
|
181
|
-
awsEnv(),
|
|
182
|
-
);
|
|
183
|
-
if (!r.ok) {
|
|
184
|
-
throw new Error(r.message);
|
|
185
|
-
}
|
|
186
|
-
return jsonResult({ ok: true });
|
|
187
|
-
},
|
|
188
|
-
);
|
|
7
|
+
registerAwsTools(mcp);
|
|
189
8
|
|
|
190
9
|
const transport = new StdioServerTransport();
|
|
191
10
|
await mcp.connect(transport);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { awsCliArg } from "../../../shared/cli-json-kit.ts";
|
|
6
|
+
import type { ConsentServer } from "../../../shared/consent-kit.ts";
|
|
7
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
8
|
+
import {
|
|
9
|
+
createRegisterSimpleTool,
|
|
10
|
+
createZodToolRegistrar,
|
|
11
|
+
mcpJsonResult as jsonResult,
|
|
12
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
13
|
+
import { runCliJsonThrowing, runCliOkThrowing } from "../../../shared/run-cli-json.ts";
|
|
14
|
+
|
|
15
|
+
function awsEnv(): Record<string, string | undefined> {
|
|
16
|
+
const e = { ...process.env } as Record<string, string | undefined>;
|
|
17
|
+
const ak = process.env["AWS_ACCESS_KEY_ID"]?.trim();
|
|
18
|
+
const sk = process.env["AWS_SECRET_ACCESS_KEY"]?.trim();
|
|
19
|
+
const rg = process.env["AWS_DEFAULT_REGION"]?.trim();
|
|
20
|
+
const profile = process.env["AWS_PROFILE"]?.trim();
|
|
21
|
+
if (ak !== undefined && ak !== "") {
|
|
22
|
+
e["AWS_ACCESS_KEY_ID"] = ak;
|
|
23
|
+
}
|
|
24
|
+
if (sk !== undefined && sk !== "") {
|
|
25
|
+
e["AWS_SECRET_ACCESS_KEY"] = sk;
|
|
26
|
+
}
|
|
27
|
+
if (rg !== undefined && rg !== "") {
|
|
28
|
+
e["AWS_DEFAULT_REGION"] = rg;
|
|
29
|
+
}
|
|
30
|
+
if (profile !== undefined && profile !== "") {
|
|
31
|
+
e["AWS_PROFILE"] = profile;
|
|
32
|
+
}
|
|
33
|
+
return e;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function awsJson(args: string[]): Promise<unknown> {
|
|
37
|
+
const cmd = ["aws", ...args, "--output", "json"];
|
|
38
|
+
return (await runCliJsonThrowing(cmd, awsEnv())) ?? {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
42
|
+
export const AWS_TOOL_NAMES = [
|
|
43
|
+
"aws_ecs_service_list",
|
|
44
|
+
"aws_lambda_list",
|
|
45
|
+
"aws_ecs_service_update",
|
|
46
|
+
"aws_lambda_invoke",
|
|
47
|
+
"aws_ec2_instance_stop",
|
|
48
|
+
"aws_ec2_instance_start",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
export function registerAwsTools(
|
|
52
|
+
server: ConsentServer & { tool: (...args: never) => unknown },
|
|
53
|
+
): void {
|
|
54
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Every MUTATING aws tool goes through here. Outside the gateway this adds the
|
|
58
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
59
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
60
|
+
*/
|
|
61
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
62
|
+
connector: "aws",
|
|
63
|
+
scopeEnv: "NIMBUS_MCP_AWS_WRITE_SCOPE",
|
|
64
|
+
scopeKinds: ["cluster", "function", "instance"],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
reg(
|
|
68
|
+
"aws_ecs_service_list",
|
|
69
|
+
"List ECS services in a cluster.",
|
|
70
|
+
z.object({ cluster: awsCliArg }),
|
|
71
|
+
async (p) => jsonResult(await awsJson(["ecs", "list-services", "--cluster", p.cluster])),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
reg("aws_lambda_list", "List Lambda functions (first page).", z.object({}), async () =>
|
|
75
|
+
jsonResult(await awsJson(["lambda", "list-functions"])),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
registerWriteTool(
|
|
79
|
+
"aws_ecs_service_update",
|
|
80
|
+
{
|
|
81
|
+
mutates: "aws.ecs.service.update",
|
|
82
|
+
recoverable: true,
|
|
83
|
+
scopeTargetOf: (p) => ({ kind: "cluster", value: p.cluster }),
|
|
84
|
+
},
|
|
85
|
+
"Update ECS service (e.g. new task definition).",
|
|
86
|
+
z.object({
|
|
87
|
+
cluster: awsCliArg,
|
|
88
|
+
service: awsCliArg,
|
|
89
|
+
taskDefinition: awsCliArg,
|
|
90
|
+
}),
|
|
91
|
+
async (p) => {
|
|
92
|
+
const cmd = [
|
|
93
|
+
"aws",
|
|
94
|
+
"ecs",
|
|
95
|
+
"update-service",
|
|
96
|
+
"--cluster",
|
|
97
|
+
p.cluster,
|
|
98
|
+
"--service",
|
|
99
|
+
p.service,
|
|
100
|
+
"--task-definition",
|
|
101
|
+
p.taskDefinition,
|
|
102
|
+
"--force-new-deployment",
|
|
103
|
+
];
|
|
104
|
+
await runCliOkThrowing(cmd, awsEnv());
|
|
105
|
+
return jsonResult({ ok: true });
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
registerWriteTool(
|
|
110
|
+
"aws_lambda_invoke",
|
|
111
|
+
{
|
|
112
|
+
mutates: "aws.lambda.invoke",
|
|
113
|
+
recoverable: true,
|
|
114
|
+
scopeTargetOf: (p) => ({ kind: "function", value: p.functionName }),
|
|
115
|
+
},
|
|
116
|
+
"Invoke a Lambda function.",
|
|
117
|
+
z.object({
|
|
118
|
+
functionName: awsCliArg,
|
|
119
|
+
// Written to a temp file the CLI is pointed at with file://, never passed as an argument.
|
|
120
|
+
payloadJson: z.string().optional(),
|
|
121
|
+
}),
|
|
122
|
+
async (p) => {
|
|
123
|
+
// The CLI reads the payload from a file and writes the response to one. Both are the
|
|
124
|
+
// caller's data, so the directory holding them is removed however the call ends — it used
|
|
125
|
+
// to be left in the temp dir on every invocation.
|
|
126
|
+
const dir = mkdtempSync(join(tmpdir(), "nimbus-aws-lambda-"));
|
|
127
|
+
try {
|
|
128
|
+
const outFile = join(dir, "response.json");
|
|
129
|
+
if (p.payloadJson !== undefined && p.payloadJson !== "") {
|
|
130
|
+
const pf = join(dir, "payload.json");
|
|
131
|
+
writeFileSync(pf, p.payloadJson, "utf8");
|
|
132
|
+
const cmd = [
|
|
133
|
+
"aws",
|
|
134
|
+
"lambda",
|
|
135
|
+
"invoke",
|
|
136
|
+
"--function-name",
|
|
137
|
+
p.functionName,
|
|
138
|
+
"--payload",
|
|
139
|
+
`file://${pf}`,
|
|
140
|
+
outFile,
|
|
141
|
+
];
|
|
142
|
+
await runCliOkThrowing(cmd, awsEnv());
|
|
143
|
+
} else {
|
|
144
|
+
await runCliOkThrowing(
|
|
145
|
+
["aws", "lambda", "invoke", "--function-name", p.functionName, outFile],
|
|
146
|
+
awsEnv(),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
let body: unknown;
|
|
150
|
+
try {
|
|
151
|
+
body = JSON.parse(readFileSync(outFile, "utf8")) as unknown;
|
|
152
|
+
} catch {
|
|
153
|
+
body = { ok: true };
|
|
154
|
+
}
|
|
155
|
+
return jsonResult(body);
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync(dir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// The two EC2 actions are WRITES. They were registered as reads, which in standalone mode offered
|
|
163
|
+
// them to every client with no consent prompt, scope check, budget or audit record.
|
|
164
|
+
registerWriteTool(
|
|
165
|
+
"aws_ec2_instance_stop",
|
|
166
|
+
{
|
|
167
|
+
mutates: "aws.ec2.instance.stop",
|
|
168
|
+
recoverable: true,
|
|
169
|
+
scopeTargetOf: (p) => ({ kind: "instance", value: p.instanceIds }),
|
|
170
|
+
},
|
|
171
|
+
"Stop EC2 instances.",
|
|
172
|
+
z.object({ instanceIds: awsCliArg }),
|
|
173
|
+
async (p) => {
|
|
174
|
+
await runCliOkThrowing(
|
|
175
|
+
["aws", "ec2", "stop-instances", "--instance-ids", p.instanceIds],
|
|
176
|
+
awsEnv(),
|
|
177
|
+
);
|
|
178
|
+
return jsonResult({ ok: true });
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
registerWriteTool(
|
|
183
|
+
"aws_ec2_instance_start",
|
|
184
|
+
{
|
|
185
|
+
mutates: "aws.ec2.instance.start",
|
|
186
|
+
recoverable: true,
|
|
187
|
+
scopeTargetOf: (p) => ({ kind: "instance", value: p.instanceIds }),
|
|
188
|
+
},
|
|
189
|
+
"Start EC2 instances.",
|
|
190
|
+
z.object({ instanceIds: awsCliArg }),
|
|
191
|
+
async (p) => {
|
|
192
|
+
await runCliOkThrowing(
|
|
193
|
+
["aws", "ec2", "start-instances", "--instance-ids", p.instanceIds],
|
|
194
|
+
awsEnv(),
|
|
195
|
+
);
|
|
196
|
+
return jsonResult({ ok: true });
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
}
|