@nimbus-dev/connectors 0.2.2 → 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 CHANGED
@@ -21,7 +21,9 @@ Run standalone, a connector gives you:
21
21
  parameters in front of you, and does nothing unless you approve.
22
22
  - **A write-scope allow-list**, enforced by the server and unreachable by the model.
23
23
  - **A mutation budget** per session, which caps a runaway agent loop.
24
- - **A local, hash-chained, append-only audit log**, when `NIMBUS_MCP_AUDIT_LOG` is set.
24
+ - **A local, hash-chained, append-only audit log**, when `NIMBUS_MCP_AUDIT_LOG` is set — one file
25
+ can serve every connector you run, within the bounds
26
+ [Configuration](./docs/configuration.md#the-audit-log) lists.
25
27
 
26
28
  It does **not** give you the process sandbox, OS-keychain credential storage, the egress ledger, or
27
29
  owner-controlled consent. Those are properties of the Nimbus gateway and no published package can
@@ -62,8 +64,8 @@ Full matrix, versions tested, and the provenance of each claim:
62
64
  ```
63
65
 
64
66
  Credentials come from the environment. There is no Vault outside the gateway, so whoever writes this
65
- config holds the secret. Every variable, and the two behaviours that look like bugs and are not, are
66
- in [Configuration](./docs/configuration.md).
67
+ config holds the secret. Every variable, and the three behaviours that look like bugs and are not,
68
+ are in [Configuration](./docs/configuration.md).
67
69
 
68
70
  Each connector has its own README at `connectors/<id>/README.md`. Most list their tools and
69
71
  credentials, but some — `github`, `slack` and `jira` among them — are still only a short gateway
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import {
3
3
  asArray,
4
- cliArg,
4
+ awsCliArg,
5
5
  createCliJsonRunner,
6
6
  isRecord,
7
7
  strField,
@@ -12,8 +12,10 @@ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connect
12
12
 
13
13
  /**
14
14
  * A catalog / database / table name passed as a value to the `aws athena` CLI.
15
- * Rejected at the schema boundary if it begins with `-` (argv flag smuggling) or
16
- * 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.
17
19
  */
18
20
 
19
21
  /**
@@ -70,8 +72,8 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
70
72
  "athena_list",
71
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.",
72
74
  z.object({
73
- catalog: cliArg.optional(),
74
- database: cliArg.optional(),
75
+ catalog: awsCliArg.optional(),
76
+ database: awsCliArg.optional(),
75
77
  }),
76
78
  async (p) => {
77
79
  if (p.catalog === undefined) {
@@ -100,9 +102,9 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
100
102
  "athena_get",
101
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.",
102
104
  z.object({
103
- catalog: cliArg,
104
- database: cliArg,
105
- table: cliArg,
105
+ catalog: awsCliArg,
106
+ database: awsCliArg,
107
+ table: awsCliArg,
106
108
  }),
107
109
  async (p) => {
108
110
  return jsonResult(
@@ -123,8 +125,8 @@ export function registerAthenaTools(reg: ZodToolRegistrar): void {
123
125
  "athena_search",
124
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.",
125
127
  z.object({
126
- catalog: cliArg.optional(),
127
- database: cliArg.optional(),
128
+ catalog: awsCliArg.optional(),
129
+ database: awsCliArg.optional(),
128
130
  query: z.string().min(1),
129
131
  }),
130
132
  async (p) => {
@@ -2,6 +2,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { z } from "zod";
5
+ import { awsCliArg } from "../../../shared/cli-json-kit.ts";
5
6
  import type { ConsentServer } from "../../../shared/consent-kit.ts";
6
7
  import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
7
8
  import {
@@ -66,7 +67,7 @@ export function registerAwsTools(
66
67
  reg(
67
68
  "aws_ecs_service_list",
68
69
  "List ECS services in a cluster.",
69
- z.object({ cluster: z.string().min(1) }),
70
+ z.object({ cluster: awsCliArg }),
70
71
  async (p) => jsonResult(await awsJson(["ecs", "list-services", "--cluster", p.cluster])),
71
72
  );
72
73
 
@@ -83,9 +84,9 @@ export function registerAwsTools(
83
84
  },
84
85
  "Update ECS service (e.g. new task definition).",
85
86
  z.object({
86
- cluster: z.string().min(1),
87
- service: z.string().min(1),
88
- taskDefinition: z.string().min(1),
87
+ cluster: awsCliArg,
88
+ service: awsCliArg,
89
+ taskDefinition: awsCliArg,
89
90
  }),
90
91
  async (p) => {
91
92
  const cmd = [
@@ -114,7 +115,8 @@ export function registerAwsTools(
114
115
  },
115
116
  "Invoke a Lambda function.",
116
117
  z.object({
117
- functionName: z.string().min(1),
118
+ functionName: awsCliArg,
119
+ // Written to a temp file the CLI is pointed at with file://, never passed as an argument.
118
120
  payloadJson: z.string().optional(),
119
121
  }),
120
122
  async (p) => {
@@ -167,7 +169,7 @@ export function registerAwsTools(
167
169
  scopeTargetOf: (p) => ({ kind: "instance", value: p.instanceIds }),
168
170
  },
169
171
  "Stop EC2 instances.",
170
- z.object({ instanceIds: z.string().min(1) }),
172
+ z.object({ instanceIds: awsCliArg }),
171
173
  async (p) => {
172
174
  await runCliOkThrowing(
173
175
  ["aws", "ec2", "stop-instances", "--instance-ids", p.instanceIds],
@@ -185,7 +187,7 @@ export function registerAwsTools(
185
187
  scopeTargetOf: (p) => ({ kind: "instance", value: p.instanceIds }),
186
188
  },
187
189
  "Start EC2 instances.",
188
- z.object({ instanceIds: z.string().min(1) }),
190
+ z.object({ instanceIds: awsCliArg }),
189
191
  async (p) => {
190
192
  await runCliOkThrowing(
191
193
  ["aws", "ec2", "start-instances", "--instance-ids", p.instanceIds],
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { azCliArg } from "../../../shared/cli-json-kit.ts";
2
3
  import type { ConsentServer } from "../../../shared/consent-kit.ts";
3
4
  import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
4
5
  import {
@@ -44,8 +45,8 @@ export function registerAzureTools(
44
45
  "azure_app_service_list",
45
46
  "List App Services in a resource group.",
46
47
  z.object({
47
- subscriptionId: z.string().min(1),
48
- resourceGroup: z.string().min(1),
48
+ subscriptionId: azCliArg,
49
+ resourceGroup: azCliArg,
49
50
  }),
50
51
  async (p) =>
51
52
  jsonResult(
@@ -69,9 +70,9 @@ export function registerAzureTools(
69
70
  },
70
71
  "Restart an App Service.",
71
72
  z.object({
72
- subscriptionId: z.string().min(1),
73
- resourceGroup: z.string().min(1),
74
- name: z.string().min(1),
73
+ subscriptionId: azCliArg,
74
+ resourceGroup: azCliArg,
75
+ name: azCliArg,
75
76
  }),
76
77
  async (p) => {
77
78
  await runCliOkThrowing(
@@ -101,10 +102,10 @@ export function registerAzureTools(
101
102
  },
102
103
  "Scale an AKS node pool.",
103
104
  z.object({
104
- subscriptionId: z.string().min(1),
105
- resourceGroup: z.string().min(1),
106
- clusterName: z.string().min(1),
107
- poolName: z.string().min(1),
105
+ subscriptionId: azCliArg,
106
+ resourceGroup: azCliArg,
107
+ clusterName: azCliArg,
108
+ poolName: azCliArg,
108
109
  nodeCount: z.number().int().min(0),
109
110
  }),
110
111
  async (p) => {
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import {
3
+ cliArg,
3
4
  createCliJsonRunner,
4
5
  gcloudProjectArgs,
5
6
  isRecord,
@@ -8,7 +9,6 @@ import {
8
9
  import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
9
10
  import { nimbusSpawn } from "../../../shared/nimbus-spawn.ts";
10
11
  import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
11
- import { assertSafeCliArg } from "../../../shared/safe-cli-arg.ts";
12
12
 
13
13
  /**
14
14
  * GCP Cloud Logging (Tier-3, no-row-data) MCP tool surface. ALL tools index log
@@ -75,15 +75,12 @@ export function registerCloudLoggingTools(reg: ZodToolRegistrar): void {
75
75
  "cloud_logging_get",
76
76
  "Fetch one Cloud Logging routing sink's configuration METADATA (`gcloud logging sinks describe <sink>`). Returns the sink's `name`, `destination`, `filter`, `description`, `disabled` flag, and create/update timestamps. No log-entry contents are returned — metadata only.",
77
77
  z.object({
78
- sinkName: z.string().min(1),
78
+ // A bare positional to `gcloud logging sinks describe`, so it is refused here, at the
79
+ // schema, if gcloud would misread it: a value starting with "-" would be parsed as a flag.
80
+ sinkName: cliArg,
79
81
  }),
80
82
  async (p) => {
81
- // `sinkName` is a bare positional to `gcloud logging sinks describe`; guard
82
- // against argv flag smuggling (a value beginning with "-" would be parsed as
83
- // a gcloud flag).
84
- return jsonResult(
85
- await gcloudLogging(["sinks", "describe", assertSafeCliArg(p.sinkName, "sinkName")]),
86
- );
83
+ return jsonResult(await gcloudLogging(["sinks", "describe", p.sinkName]));
87
84
  },
88
85
  );
89
86
 
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import {
3
3
  asArray,
4
- cliArg,
4
+ awsCliArg,
5
5
  createCliJsonRunner,
6
6
  isRecord,
7
7
  strField,
@@ -12,8 +12,9 @@ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connect
12
12
 
13
13
  /**
14
14
  * A log-group name / prefix passed as a value to the `aws logs` CLI. Rejected at
15
- * the schema boundary if it begins with `-` (argv flag smuggling) or contains
16
- * control characters.
15
+ * the schema boundary (`awsCliArg`) if it begins with `-` (argv flag smuggling),
16
+ * contains control characters, or starts with a prefix that makes the CLI read the
17
+ * value from a file or a URL instead.
17
18
  */
18
19
 
19
20
  /**
@@ -65,7 +66,7 @@ export function registerCloudwatchTools(reg: ZodToolRegistrar): void {
65
66
  "cloudwatch_list",
66
67
  "List CloudWatch log GROUPS — METADATA ONLY (`aws logs describe-log-groups`). Each entry carries `logGroupName`, `arn`, `creationTime`, `retentionInDays`, `storedBytes`, and `metricFilterCount`. Optionally filter by a `prefix` on the log-group name. Never fetches log-event contents / row data.",
67
68
  z.object({
68
- prefix: cliArg.optional(),
69
+ prefix: awsCliArg.optional(),
69
70
  }),
70
71
  async (p) => {
71
72
  const args = ["describe-log-groups", "--limit", LIMIT];
@@ -80,7 +81,7 @@ export function registerCloudwatchTools(reg: ZodToolRegistrar): void {
80
81
  "cloudwatch_get",
81
82
  "Fetch one CloudWatch log group's METADATA plus a stream summary. Returns the matching `describe-log-groups` entry (name, ARN, retention, stored bytes, metric-filter count) and the group's `describe-log-streams` METADATA (stream names, last-event timestamps, stored bytes) ordered by last activity. No log-event contents are returned — metadata only.",
82
83
  z.object({
83
- logGroupName: cliArg,
84
+ logGroupName: awsCliArg,
84
85
  }),
85
86
  async (p) => {
86
87
  const group = await logsCli([
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { gcloudEnv } from "../../../shared/cli-json-kit.ts";
2
+ import { cliArg, gcloudEnv } from "../../../shared/cli-json-kit.ts";
3
3
  import type { ConsentServer } from "../../../shared/consent-kit.ts";
4
4
  import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
5
5
  import {
@@ -40,7 +40,7 @@ export function registerGcpTools(
40
40
  reg(
41
41
  "gcp_cloud_run_service_list",
42
42
  "List Cloud Run services in a region.",
43
- z.object({ projectId: z.string().min(1), region: z.string().min(1) }),
43
+ z.object({ projectId: cliArg, region: cliArg }),
44
44
  async (p) =>
45
45
  jsonResult(
46
46
  await gcloudJson([
@@ -62,10 +62,10 @@ export function registerGcpTools(
62
62
  },
63
63
  "Deploy a container image to Cloud Run.",
64
64
  z.object({
65
- projectId: z.string().min(1),
66
- region: z.string().min(1),
67
- service: z.string().min(1),
68
- image: z.string().min(1),
65
+ projectId: cliArg,
66
+ region: cliArg,
67
+ service: cliArg,
68
+ image: cliArg,
69
69
  }),
70
70
  async (p) => {
71
71
  await runCliOkThrowing(
@@ -94,11 +94,11 @@ export function registerGcpTools(
94
94
  },
95
95
  "Restart a GKE deployment rollout via kubectl (uses current cluster credentials).",
96
96
  z.object({
97
- projectId: z.string().min(1),
98
- location: z.string().min(1),
99
- cluster: z.string().min(1),
100
- namespace: z.string().min(1),
101
- deployment: z.string().min(1),
97
+ projectId: cliArg,
98
+ location: cliArg,
99
+ cluster: cliArg,
100
+ namespace: cliArg,
101
+ deployment: cliArg,
102
102
  }),
103
103
  async (p) => {
104
104
  await runCliOkThrowing(
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { awsCliArg, awsCliDocument, cliArg } from "../../../shared/cli-json-kit.ts";
2
3
  import type { ConsentServer } from "../../../shared/consent-kit.ts";
3
4
  import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
4
5
  import {
@@ -39,7 +40,7 @@ export function registerIacTools(
39
40
  reg(
40
41
  "iac_terraform_plan",
41
42
  "Run terraform plan in a directory.",
42
- z.object({ workingDirectory: z.string().min(1) }),
43
+ z.object({ workingDirectory: cliArg }),
43
44
  async (p) => {
44
45
  await runCliOkThrowing(["terraform", "-chdir", p.workingDirectory, "plan", "-input=false"], {
45
46
  ...processEnv,
@@ -56,7 +57,7 @@ export function registerIacTools(
56
57
  scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
57
58
  },
58
59
  "Run terraform apply.",
59
- z.object({ workingDirectory: z.string().min(1) }),
60
+ z.object({ workingDirectory: cliArg }),
60
61
  async (p) => {
61
62
  await runCliOkThrowing(
62
63
  ["terraform", "-chdir", p.workingDirectory, "apply", "-auto-approve", "-input=false"],
@@ -78,7 +79,7 @@ export function registerIacTools(
78
79
  scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
79
80
  },
80
81
  "Run terraform destroy.",
81
- z.object({ workingDirectory: z.string().min(1) }),
82
+ z.object({ workingDirectory: cliArg }),
82
83
  async (p) => {
83
84
  await runCliOkThrowing(
84
85
  ["terraform", "-chdir", p.workingDirectory, "destroy", "-auto-approve", "-input=false"],
@@ -97,8 +98,8 @@ export function registerIacTools(
97
98
  },
98
99
  "Deploy a CloudFormation stack via AWS CLI.",
99
100
  z.object({
100
- stackName: z.string().min(1),
101
- templateBody: z.string().min(1),
101
+ stackName: awsCliArg,
102
+ templateBody: awsCliDocument,
102
103
  }),
103
104
  async (p) => {
104
105
  await runCliOkThrowing(
@@ -122,7 +123,7 @@ export function registerIacTools(
122
123
  reg(
123
124
  "iac_pulumi_preview",
124
125
  "Run pulumi preview in a stack directory.",
125
- z.object({ workingDirectory: z.string().min(1) }),
126
+ z.object({ workingDirectory: cliArg }),
126
127
  async (p) => {
127
128
  await runCliOkThrowing(
128
129
  ["pulumi", "preview", "--cwd", p.workingDirectory, "--non-interactive"],
@@ -140,7 +141,7 @@ export function registerIacTools(
140
141
  scopeTargetOf: (p) => ({ kind: "dir", value: p.workingDirectory }),
141
142
  },
142
143
  "Run pulumi up.",
143
- z.object({ workingDirectory: z.string().min(1) }),
144
+ z.object({ workingDirectory: cliArg }),
144
145
  async (p) => {
145
146
  await runCliOkThrowing(
146
147
  ["pulumi", "up", "--yes", "--cwd", p.workingDirectory, "--non-interactive"],
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { cliArg } from "../../../shared/cli-json-kit.ts";
2
3
  import type { ConsentServer } from "../../../shared/consent-kit.ts";
3
4
  import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
4
5
  import {
@@ -34,7 +35,7 @@ async function kubectlJson(rest: string[]): Promise<unknown> {
34
35
  return runCliJsonThrowing(cmd, kubeEnv());
35
36
  }
36
37
 
37
- const optionalNamespaceSchema = z.object({ namespace: z.string().min(1).optional() });
38
+ const optionalNamespaceSchema = z.object({ namespace: cliArg.optional() });
38
39
 
39
40
  async function k8sListNamespacedResource(p: { namespace?: string | undefined }, resource: string) {
40
41
  const ns = p.namespace ?? "default";
@@ -92,9 +93,9 @@ export function registerKubernetesTools(
92
93
  },
93
94
  "Restart a rollout (e.g. deployment).",
94
95
  z.object({
95
- namespace: z.string().min(1).optional(),
96
- resourceType: z.string().min(1),
97
- name: z.string().min(1),
96
+ namespace: cliArg.optional(),
97
+ resourceType: cliArg,
98
+ name: cliArg,
98
99
  }),
99
100
  async (p) => {
100
101
  const ns = p.namespace ?? "default";
@@ -117,8 +118,8 @@ export function registerKubernetesTools(
117
118
  },
118
119
  "Delete a pod.",
119
120
  z.object({
120
- namespace: z.string().min(1).optional(),
121
- podName: z.string().min(1),
121
+ namespace: cliArg.optional(),
122
+ podName: cliArg,
122
123
  }),
123
124
  async (p) => {
124
125
  const ns = p.namespace ?? "default";
@@ -137,8 +138,8 @@ export function registerKubernetesTools(
137
138
  },
138
139
  "Scale a deployment.",
139
140
  z.object({
140
- namespace: z.string().min(1).optional(),
141
- deploymentName: z.string().min(1),
141
+ namespace: cliArg.optional(),
142
+ deploymentName: cliArg,
142
143
  replicas: z.number().int().min(0),
143
144
  }),
144
145
  async (p) => {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import {
3
3
  asArray,
4
- cliArg,
4
+ awsCliArg,
5
5
  createCliJsonRunner,
6
6
  isRecord,
7
7
  strField,
@@ -13,9 +13,9 @@ import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connect
13
13
  /**
14
14
  * The argv-injection guard every SageMaker model name passes through on its way to the
15
15
  * `aws sagemaker` CLI. Re-exported so this connector's own test can assert the guard rejects a
16
- * `-`-prefixed value.
16
+ * `-`-prefixed value, and one the CLI would replace by a file's contents (`file://<path>`).
17
17
  */
18
- export { cliArg };
18
+ export { awsCliArg };
19
19
 
20
20
  /**
21
21
  * Amazon SageMaker (Tier-3, metadata-only) MCP tool surface. ALL tools index
@@ -65,7 +65,7 @@ export function registerSagemakerTools(reg: ZodToolRegistrar): void {
65
65
  "sagemaker_list",
66
66
  "List Amazon SageMaker models — METADATA ONLY (`aws sagemaker list-models`). Each entry carries `ModelName`, `ModelArn`, and `CreationTime`. Optionally filter by a `nameContains` substring on the model name. Never invokes an endpoint and never fetches inference / training / model-artifact data.",
67
67
  z.object({
68
- nameContains: cliArg.optional(),
68
+ nameContains: awsCliArg.optional(),
69
69
  }),
70
70
  async (p) => {
71
71
  const args = ["list-models", "--max-results", PAGE];
@@ -80,7 +80,7 @@ export function registerSagemakerTools(reg: ZodToolRegistrar): void {
80
80
  "sagemaker_get",
81
81
  "Fetch one Amazon SageMaker model's METADATA (`aws sagemaker describe-model`). Returns the model object including `ModelName`, `ModelArn`, `PrimaryContainer` (container `Image` reference + `ModelDataUrl` S3 pointer — a URI string, NOT the model bytes), `ExecutionRoleArn`, and `CreationTime`. No inference, training, or model-artifact data is returned — registry metadata only.",
82
82
  z.object({
83
- modelName: cliArg,
83
+ modelName: awsCliArg,
84
84
  }),
85
85
  async (p) => {
86
86
  return jsonResult(await sagemakerCli(["describe-model", "--model-name", p.modelName]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-dev/connectors",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },