edgegate-mcp 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +7 -1
  2. package/dist/client.d.ts +19 -1
  3. package/dist/client.js +34 -0
  4. package/dist/client.js.map +1 -1
  5. package/dist/server.js +64 -0
  6. package/dist/server.js.map +1 -1
  7. package/dist/tools/check_byo_bucket.d.ts +12 -0
  8. package/dist/tools/check_byo_bucket.js +89 -0
  9. package/dist/tools/check_byo_bucket.js.map +1 -0
  10. package/dist/tools/check_status.d.ts +2 -2
  11. package/dist/tools/check_status.js +6 -1
  12. package/dist/tools/check_status.js.map +1 -1
  13. package/dist/tools/compare_runs.d.ts +2 -2
  14. package/dist/tools/create_pipeline.d.ts +4 -4
  15. package/dist/tools/disconnect_byo_bucket.d.ts +12 -0
  16. package/dist/tools/disconnect_byo_bucket.js +90 -0
  17. package/dist/tools/disconnect_byo_bucket.js.map +1 -0
  18. package/dist/tools/export_run_report.d.ts +2 -2
  19. package/dist/tools/export_run_report.js +4 -1
  20. package/dist/tools/export_run_report.js.map +1 -1
  21. package/dist/tools/get_audit_report.d.ts +2 -2
  22. package/dist/tools/get_audit_report.js +5 -1
  23. package/dist/tools/get_audit_report.js.map +1 -1
  24. package/dist/tools/get_byo_audit.d.ts +27 -0
  25. package/dist/tools/get_byo_audit.js +125 -0
  26. package/dist/tools/get_byo_audit.js.map +1 -0
  27. package/dist/tools/get_report.d.ts +1 -1
  28. package/dist/tools/list_devices.d.ts +25 -0
  29. package/dist/tools/list_devices.js +81 -0
  30. package/dist/tools/list_devices.js.map +1 -0
  31. package/dist/tools/register_byo_artifact.d.ts +27 -0
  32. package/dist/tools/register_byo_artifact.js +122 -0
  33. package/dist/tools/register_byo_artifact.js.map +1 -0
  34. package/dist/tools/register_byo_bucket.d.ts +24 -0
  35. package/dist/tools/register_byo_bucket.js +143 -0
  36. package/dist/tools/register_byo_bucket.js.map +1 -0
  37. package/dist/types.d.ts +98 -0
  38. package/dist/version.d.ts +2 -2
  39. package/dist/version.js +1 -1
  40. package/package.json +1 -1
  41. package/plugin.json +3 -2
  42. package/skills/edgegate-byo-storage.md +148 -0
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateError } from "../client.js";
3
+ export const registerByoBucketInputSchema = z.object({
4
+ workspace_id: z.string().uuid(),
5
+ role_arn: z
6
+ .string()
7
+ .regex(/^arn:aws:iam::\d{12}:role\/.+$/)
8
+ .describe("ARN of the IAM role EdgeGate's workers will assume to read your bucket. " +
9
+ "Created by the EdgeGate CloudFormation launch stack (or your equivalent " +
10
+ "Terraform module). Format: arn:aws:iam::<account-id>:role/<name>."),
11
+ bucket: z
12
+ .string()
13
+ .min(3)
14
+ .max(255)
15
+ .regex(/^[a-z0-9][a-z0-9\-.]{1,253}$/)
16
+ .describe("S3 bucket name (not the URI — just the bucket name)."),
17
+ region: z
18
+ .string()
19
+ .min(4)
20
+ .max(64)
21
+ .describe("AWS region the bucket lives in, e.g. us-east-1."),
22
+ kms_key_id: z
23
+ .string()
24
+ .max(2048)
25
+ .optional()
26
+ .describe("Optional KMS key ARN if the bucket uses SSE-KMS. The IAM role must " +
27
+ "have kms:Decrypt on this key."),
28
+ });
29
+ export async function registerByoBucketHandler(client, input) {
30
+ try {
31
+ const grant = await client.registerByoGrant(input.workspace_id, {
32
+ role_arn: input.role_arn,
33
+ bucket: input.bucket,
34
+ region: input.region,
35
+ kms_key_id: input.kms_key_id,
36
+ });
37
+ return successText(grant);
38
+ }
39
+ catch (err) {
40
+ if (err instanceof EdgeGateError) {
41
+ if (err.status === 402) {
42
+ return {
43
+ isError: true,
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: [
48
+ `BYO storage requires the **Enterprise plan**.`,
49
+ ``,
50
+ `Reach out to sales at https://edgegate.frozo.ai/enterprise ` +
51
+ `to enable BYO storage on this workspace. ` +
52
+ `Once enabled, re-run \`edgegate_register_byo_bucket\` with the ` +
53
+ `same role + bucket details.`,
54
+ ].join("\n"),
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ if (err.status === 409) {
60
+ // Do NOT auto-rotate — the existing grant may belong to a different
61
+ // role_arn the customer doesn't want overwritten by accident.
62
+ return {
63
+ isError: true,
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: [
68
+ `This workspace already has a BYO storage grant registered.`,
69
+ ``,
70
+ `Two safe paths forward:`,
71
+ `1. Inspect the existing grant with \`edgegate_check_byo_bucket\` ` +
72
+ `to confirm it's the one you intended.`,
73
+ `2. If you want to replace it: \`edgegate_disconnect_byo_bucket\` ` +
74
+ `first (will 409 if artifacts still reference it), then re-run ` +
75
+ `\`edgegate_register_byo_bucket\` with the new role/bucket.`,
76
+ ``,
77
+ `External-ID rotation (without replacing the grant) is available ` +
78
+ `via the dashboard at ` +
79
+ `https://edgegate.frozo.ai/workspace/${input.workspace_id}/settings#byo-storage.`,
80
+ ].join("\n"),
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ return {
86
+ isError: true,
87
+ content: [
88
+ {
89
+ type: "text",
90
+ text: `EdgeGate returned ${err.status}: ${err.detail}`,
91
+ },
92
+ ],
93
+ };
94
+ }
95
+ throw err;
96
+ }
97
+ }
98
+ function roleArnTail(roleArn) {
99
+ // arn:aws:iam::123456789012:role/edgegate-byo-storage-prod
100
+ // Render the trailing role name + last 4 of the account id so the user can
101
+ // confirm at a glance that they registered the right role.
102
+ const parts = roleArn.split(":");
103
+ if (parts.length < 6)
104
+ return roleArn;
105
+ const account = parts[4];
106
+ const roleName = parts.slice(5).join(":").replace(/^role\//, "");
107
+ const acctTail = account.length > 4 ? `…${account.slice(-4)}` : account;
108
+ return `${roleName} (acct ${acctTail})`;
109
+ }
110
+ function successText(grant) {
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: [
116
+ `Registered BYO storage grant for this workspace.`,
117
+ ``,
118
+ `- role: \`${roleArnTail(grant.role_arn)}\``,
119
+ `- bucket: \`${grant.bucket}\` (${grant.region})`,
120
+ grant.kms_key_id
121
+ ? `- kms key: \`${grant.kms_key_id}\``
122
+ : `- kms key: (none — SSE-S3 or no encryption)`,
123
+ `- status: **${grant.status}**`,
124
+ `- external_id: \`${grant.external_id}\``,
125
+ ``,
126
+ `**Action required if you haven't already:** add this External ID to ` +
127
+ `your IAM role's trust policy under \`Condition.StringEquals.sts:ExternalId\`. ` +
128
+ `Without it, AssumeRole will fail with BYO_ASSUME_ROLE_FAILED.`,
129
+ ``,
130
+ grant.status === "active"
131
+ ? `The readiness probe just passed. You can register your first artifact ` +
132
+ `with \`edgegate_register_byo_artifact\`.`
133
+ : grant.last_verify_error
134
+ ? `The readiness probe FAILED: \`${grant.last_verify_error}\`. Fix the ` +
135
+ `IAM/bucket/KMS configuration, then re-probe with ` +
136
+ `\`edgegate_check_byo_bucket\`.`
137
+ : `Probe outcome pending — run \`edgegate_check_byo_bucket\` to verify.`,
138
+ ].join("\n"),
139
+ },
140
+ ],
141
+ };
142
+ }
143
+ //# sourceMappingURL=register_byo_bucket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register_byo_bucket.js","sourceRoot":"","sources":["../../src/tools/register_byo_bucket.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -162,6 +162,19 @@ export interface APIKeyListItem {
162
162
  revoked_at: string | null;
163
163
  created_at: string;
164
164
  }
165
+ export type DeviceCategory = "smartphone" | "reference" | "compute" | "iot" | "automotive" | "xr" | "other";
166
+ export interface DeviceEntry {
167
+ /** Short alias EdgeGate uses (e.g. 'sm8650'). Stable across pipeline configs. */
168
+ id: string;
169
+ /** Canonical AI Hub device name (e.g. 'Samsung Galaxy S24 (Family)'). */
170
+ name: string;
171
+ /** Form-factor / use case bucket. */
172
+ category: DeviceCategory;
173
+ }
174
+ export interface DeviceListResponse {
175
+ devices: DeviceEntry[];
176
+ total: number;
177
+ }
165
178
  export type WorkspaceRole = "owner" | "admin" | "viewer";
166
179
  export interface Member {
167
180
  user_id: UUID;
@@ -269,3 +282,88 @@ export interface PromptPackCreateBody {
269
282
  version: string;
270
283
  content: PromptPackContent;
271
284
  }
285
+ /**
286
+ * Workspace's customer-owned S3 bucket grant. Returned by every grant
287
+ * endpoint (register / get / verify / rotate-external-id).
288
+ *
289
+ * `external_id` is shown in EVERY response — it's the value the customer
290
+ * has to paste into their IAM role trust policy's `sts:ExternalId`
291
+ * condition. We don't treat it like a secret because the trust policy
292
+ * already pins our AWS account as the only principal that can use it.
293
+ *
294
+ * `status` semantics: "active" = last probe passed; "failed" = last probe
295
+ * raised (with `last_verify_error` populated); "revoked" = grant was
296
+ * explicitly deleted (404 on /grants thereafter).
297
+ */
298
+ export interface ByoGrant {
299
+ id: UUID;
300
+ workspace_id: UUID;
301
+ role_arn: string;
302
+ external_id: UUID;
303
+ bucket: string;
304
+ region: string;
305
+ kms_key_id: string | null;
306
+ status: "active" | "revoked" | "failed";
307
+ last_verified_at: string | null;
308
+ last_verify_error: string | null;
309
+ created_at: string;
310
+ updated_at: string;
311
+ }
312
+ /**
313
+ * Request body for POST /v1/workspaces/{ws}/artifacts/byo.
314
+ * Registers an existing S3 URI in the customer's grant-registered bucket
315
+ * as an Artifact pointer. EdgeGate does NOT upload bytes — it HeadObjects
316
+ * the URI to confirm existence + capture size/etag.
317
+ */
318
+ export interface ByoArtifactRegisterRequest {
319
+ s3_uri: string;
320
+ expected_sha256?: string;
321
+ expected_size?: number;
322
+ kind?: string;
323
+ original_filename?: string;
324
+ }
325
+ /**
326
+ * One row from the workspace's append-only `byo_storage_audit` table.
327
+ * `aws_request_id` is the join key for cross-referencing the customer's
328
+ * own CloudTrail. Nullable fields are by design for events that don't
329
+ * produce them (verify_probe has no run_id/artifact_id, etc.).
330
+ */
331
+ export interface ByoAuditEntry {
332
+ id: number;
333
+ event_type: string;
334
+ aws_request_id: string;
335
+ role_arn: string;
336
+ bucket: string;
337
+ s3_key: string | null;
338
+ bytes_read: number | null;
339
+ worker_hostname: string | null;
340
+ outcome: string;
341
+ error_code: string | null;
342
+ artifact_id: UUID | null;
343
+ run_id: UUID | null;
344
+ ts: string;
345
+ }
346
+ /**
347
+ * Paginated audit-log page. `next_cursor === null` means the response
348
+ * contained the last page. Pass the value back as the `cursor` query
349
+ * param to fetch the next page.
350
+ */
351
+ export interface ByoAuditPage {
352
+ entries: ByoAuditEntry[];
353
+ next_cursor: number | null;
354
+ }
355
+ /**
356
+ * Returned by POST /artifacts and POST /artifacts/byo. Mirrors the
357
+ * backend `ArtifactResponse` schema. `storage_url` for BYO artifacts is
358
+ * `byo-s3://{bucket}/{key}` rather than the managed `s3://...` form.
359
+ */
360
+ export interface ArtifactResponse {
361
+ id: UUID;
362
+ kind: string;
363
+ sha256: string;
364
+ size_bytes: number;
365
+ original_filename: string | null;
366
+ storage_url: string;
367
+ created_at: string;
368
+ expires_at: string | null;
369
+ }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.7.0";
2
- export declare const USER_AGENT = "edgegate-mcp/0.7.0";
1
+ export declare const VERSION = "0.9.0";
2
+ export declare const USER_AGENT = "edgegate-mcp/0.9.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
- export const VERSION = "0.7.0";
1
+ export const VERSION = "0.9.0";
2
2
  export const USER_AGENT = `edgegate-mcp/${VERSION}`;
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edgegate-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "MCP server for EdgeGate — set up edge-AI regression gates from Claude Code, Cursor, or Claude Desktop.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edgegate",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Edge-AI regression gates from Claude Code — set up CI, run benchmarks, compare runs, export reports, and fetch audit bundles from any prompt.",
5
5
  "author": "Frozo / EdgeGate",
6
6
  "homepage": "https://edgegate.frozo.ai",
@@ -29,6 +29,7 @@
29
29
  "skills/edgegate-connect-huggingface.md",
30
30
  "skills/edgegate-connect-qaihub.md",
31
31
  "skills/edgegate-workspace-setup.md",
32
- "skills/edgegate-members.md"
32
+ "skills/edgegate-members.md",
33
+ "skills/edgegate-byo-storage.md"
33
34
  ]
34
35
  }
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: edgegate-byo-storage
3
+ description: Wire up BYO (bring-your-own) S3 storage for an Enterprise EdgeGate workspace — register the IAM role + bucket grant, verify the readiness probe, register the first artifact directly from the customer's bucket, and confirm the audit trail. Use when the user is on the Enterprise plan and wants model bytes to live in their own AWS account.
4
+ ---
5
+
6
+ # /edgegate-byo-storage
7
+
8
+ This is the **Enterprise BYO storage onboarding** flow. The whole point of
9
+ BYO is that model bytes never leave the customer's AWS account — EdgeGate's
10
+ workers AssumeRole into their account and read directly from their bucket.
11
+
12
+ Use this skill once per workspace, after the customer's security/IAM team
13
+ has provisioned the role + bucket.
14
+
15
+ ## When to use
16
+
17
+ - The workspace is on the **Enterprise plan** (BYO storage 402s otherwise).
18
+ - The user has (or wants to provision) their own S3 bucket and IAM role.
19
+ - The user is migrating an existing workspace from EdgeGate-managed storage
20
+ to BYO, or onboarding a brand-new Enterprise workspace.
21
+
22
+ If the workspace isn't Enterprise yet, send them to
23
+ <https://edgegate.frozo.ai/enterprise> first — none of the
24
+ `edgegate_*_byo_*` tools will work until BYO is enabled on the plan.
25
+
26
+ ## Pre-flight (AWS side — the user does this in their account)
27
+
28
+ You can't do this part for them, but you can hand them the exact spec:
29
+
30
+ 1. **Provision the IAM role.** The fastest path is the EdgeGate
31
+ CloudFormation Launch Stack — link them to it from
32
+ <https://edgegate.frozo.ai/workspace/{workspace_id}/settings#byo-storage>.
33
+ (A Terraform module is also published; ask the customer which their
34
+ security team prefers.) The stack creates:
35
+ - The IAM role with `sts:AssumeRole` trusted to EdgeGate's AWS account.
36
+ - The S3 bucket policy granting that role `s3:GetObject` + `ListBucket`
37
+ scoped to the bucket only.
38
+ - (Optional) KMS key policy granting `kms:Decrypt` if the bucket uses
39
+ SSE-KMS.
40
+ 2. **Capture** the `role_arn`, `bucket` name, `region`, and (if applicable)
41
+ `kms_key_id`. The trust policy's `sts:ExternalId` is left as a
42
+ placeholder — EdgeGate mints the real one in step 1 below and the
43
+ customer pastes it back.
44
+
45
+ If they want the raw IAM JSON instead of CloudFormation/Terraform, point
46
+ them at `docs/byo-storage-onboarding.md` in the backend repo or the
47
+ dashboard's "Show raw policies" link.
48
+
49
+ ## Steps
50
+
51
+ 1. **Register the grant.** Call
52
+ `edgegate_register_byo_bucket({ workspace_id, role_arn, bucket, region, kms_key_id? })`.
53
+ The response includes an `external_id` UUID — capture it.
54
+
55
+ 2. **Paste the External ID into the IAM role trust policy.** Tell the
56
+ user, in plain English: "Open your IAM role in the AWS console, edit
57
+ the trust relationship, and replace the placeholder ExternalId with
58
+ `<external_id>`." Until they do this, AssumeRole will fail with
59
+ `BYO_ASSUME_ROLE_FAILED`.
60
+
61
+ The CloudFormation stack supports passing the External ID as a
62
+ parameter — point them at that if they used the stack.
63
+
64
+ 3. **Verify the probe.** After the user confirms the trust policy edit
65
+ is saved, call `edgegate_check_byo_bucket({ workspace_id })`. Expect
66
+ `status: "active"`. If `status: "failed"`, the response's checklist
67
+ covers every typed `BYO_*` error code — read the `last_verify_error`
68
+ to the user, suggest the matching fix, then re-run `check`.
69
+
70
+ 4. **Register the first artifact.** Pick (or ask for) an existing model
71
+ in the bucket. Call:
72
+ ```
73
+ edgegate_register_byo_artifact({
74
+ workspace_id,
75
+ s3_uri: "s3://<bucket>/<key>.onnx",
76
+ expected_sha256: "<optional but recommended>",
77
+ })
78
+ ```
79
+ Capture the returned `artifact_id`. EdgeGate did NOT download the
80
+ bytes — it only HeadObject'd the URI to confirm the key exists and
81
+ capture size + etag. The storage URL in the response will start with
82
+ `byo-s3://` (not `s3://`) — that's how downstream tooling routes
83
+ reads through the BYO service rather than EdgeGate's managed S3.
84
+
85
+ 5. **Trigger the first run.** Run the artifact against an existing
86
+ pipeline so the customer sees the end-to-end flow work:
87
+ ```
88
+ edgegate_run_gate({
89
+ workspace_id,
90
+ pipeline_id: "<existing pipeline>",
91
+ model_artifact_id: "<artifact_id from step 4>",
92
+ })
93
+ ```
94
+ Poll with `edgegate_check_status` until it terminates.
95
+
96
+ 6. **Show them the audit trail.** Call
97
+ `edgegate_get_byo_audit({ workspace_id, run_id: "<run_id from step 5>" })`.
98
+ The table includes one row per S3 / STS call with the
99
+ `aws_request_id`. Tell the user: "Cross-reference these against your
100
+ own CloudTrail in the same time window — every read should match."
101
+ This is the trust handshake the customer's security team will ask
102
+ for.
103
+
104
+ 7. **Recap + next.** Summarize:
105
+ - Grant: `active`, bucket=`<name>`, region=`<region>`
106
+ - First artifact registered + first run executed
107
+ - Audit log accessible via `edgegate_get_byo_audit`
108
+
109
+ Next concrete actions:
110
+ - "Migrate more artifacts: `edgegate_register_byo_artifact` per s3_uri"
111
+ - "Schedule periodic audit pulls into your SIEM (we can stream via API)"
112
+ - "Rotate the External ID at any time via the dashboard"
113
+
114
+ ## Failure modes
115
+
116
+ - **Register grant → 402.** Workspace is not on Enterprise. Send them to
117
+ <https://edgegate.frozo.ai/enterprise>.
118
+ - **Register grant → 409.** A grant already exists. We deliberately do
119
+ NOT auto-rotate — the existing grant may belong to a role the customer
120
+ doesn't want overwritten. Inspect with `edgegate_check_byo_bucket`,
121
+ then either keep it or `edgegate_disconnect_byo_bucket` and re-register.
122
+ External-ID-only rotation is available via the dashboard.
123
+ - **Check probe → status=failed with BYO_ASSUME_ROLE_FAILED.** External
124
+ ID drift between EdgeGate and the role's trust policy. Re-paste the
125
+ current external_id (visible via `edgegate_check_byo_bucket` or the
126
+ dashboard) into the trust policy's `sts:ExternalId` condition.
127
+ - **Check probe → BYO_KMS_ACCESS_DENIED.** SSE-KMS bucket but the role
128
+ is missing `kms:Decrypt` on the key. Add the role principal to the
129
+ KMS key policy.
130
+ - **Check probe → BYO_REGION_MISMATCH.** The bucket lives in a different
131
+ region than the `region` you registered. Disconnect and re-register
132
+ with the correct region.
133
+ - **Register artifact → 400 bucket mismatch.** The s3_uri points at a
134
+ bucket that isn't this workspace's registered one. Cross-bucket
135
+ pointers are forbidden — register the right bucket (or move the
136
+ object).
137
+ - **Register artifact → 400 BYO_OBJECT_NOT_FOUND.** The key is mistyped
138
+ or the role's bucket policy denies `ListBucket`/`GetObject` on that
139
+ prefix. HeadObject failures pass through with the typed code.
140
+ - **Disconnect → 409.** Artifacts still reference the grant. The
141
+ response lists the safe paths forward (drop the artifacts first, or
142
+ rotate the External ID via dashboard if that's what you actually
143
+ wanted).
144
+ - **Mid-run revocation** (customer revokes the role mid-run): the
145
+ in-flight cells complete, the rest fail with
146
+ `BYO_ASSUME_ROLE_FAILED`, the run terminates as `failed` (not
147
+ `error`) with the partial-success cells preserved in the bundle.
148
+ This is by design — re-grant + re-run picks up cleanly.