edgegate-mcp 0.4.1 → 0.7.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 (62) hide show
  1. package/README.md +20 -2
  2. package/dist/client.d.ts +26 -1
  3. package/dist/client.js +52 -0
  4. package/dist/client.js.map +1 -1
  5. package/dist/server.js +125 -0
  6. package/dist/server.js.map +1 -1
  7. package/dist/tools/change_member_role.d.ts +18 -0
  8. package/dist/tools/change_member_role.js +61 -0
  9. package/dist/tools/change_member_role.js.map +1 -0
  10. package/dist/tools/connect_huggingface.d.ts +15 -0
  11. package/dist/tools/connect_huggingface.js +83 -0
  12. package/dist/tools/connect_huggingface.js.map +1 -0
  13. package/dist/tools/connect_qaihub.d.ts +15 -0
  14. package/dist/tools/connect_qaihub.js +62 -0
  15. package/dist/tools/connect_qaihub.js.map +1 -0
  16. package/dist/tools/create_api_key.d.ts +18 -0
  17. package/dist/tools/create_api_key.js +73 -0
  18. package/dist/tools/create_api_key.js.map +1 -0
  19. package/dist/tools/create_pipeline.d.ts +30 -0
  20. package/dist/tools/create_pipeline.js +31 -0
  21. package/dist/tools/create_pipeline.js.map +1 -1
  22. package/dist/tools/create_workspace.d.ts +12 -0
  23. package/dist/tools/create_workspace.js +51 -0
  24. package/dist/tools/create_workspace.js.map +1 -0
  25. package/dist/tools/disconnect_huggingface.d.ts +12 -0
  26. package/dist/tools/disconnect_huggingface.js +44 -0
  27. package/dist/tools/disconnect_huggingface.js.map +1 -0
  28. package/dist/tools/disconnect_qaihub.d.ts +12 -0
  29. package/dist/tools/disconnect_qaihub.js +42 -0
  30. package/dist/tools/disconnect_qaihub.js.map +1 -0
  31. package/dist/tools/get_huggingface_integration.d.ts +12 -0
  32. package/dist/tools/get_huggingface_integration.js +52 -0
  33. package/dist/tools/get_huggingface_integration.js.map +1 -0
  34. package/dist/tools/get_qaihub_integration.d.ts +12 -0
  35. package/dist/tools/get_qaihub_integration.js +52 -0
  36. package/dist/tools/get_qaihub_integration.js.map +1 -0
  37. package/dist/tools/invite_member.d.ts +18 -0
  38. package/dist/tools/invite_member.js +85 -0
  39. package/dist/tools/invite_member.js.map +1 -0
  40. package/dist/tools/list_api_keys.d.ts +12 -0
  41. package/dist/tools/list_api_keys.js +51 -0
  42. package/dist/tools/list_api_keys.js.map +1 -0
  43. package/dist/tools/list_members.d.ts +12 -0
  44. package/dist/tools/list_members.js +43 -0
  45. package/dist/tools/list_members.js.map +1 -0
  46. package/dist/tools/remove_member.d.ts +15 -0
  47. package/dist/tools/remove_member.js +64 -0
  48. package/dist/tools/remove_member.js.map +1 -0
  49. package/dist/tools/revoke_api_key.d.ts +15 -0
  50. package/dist/tools/revoke_api_key.js +49 -0
  51. package/dist/tools/revoke_api_key.js.map +1 -0
  52. package/dist/types.d.ts +84 -0
  53. package/dist/version.d.ts +2 -2
  54. package/dist/version.js +1 -1
  55. package/package.json +1 -1
  56. package/plugin.json +6 -2
  57. package/skills/edgegate-connect-huggingface.md +64 -0
  58. package/skills/edgegate-connect-qaihub.md +56 -0
  59. package/skills/edgegate-import.md +2 -2
  60. package/skills/edgegate-init.md +17 -0
  61. package/skills/edgegate-members.md +51 -0
  62. package/skills/edgegate-workspace-setup.md +74 -0
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateClient } from "../client.js";
3
+ import type { ToolResult } from "./setup_workspace.js";
4
+ export declare const listApiKeysInputSchema: z.ZodObject<{
5
+ workspace_id: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ workspace_id: string;
8
+ }, {
9
+ workspace_id: string;
10
+ }>;
11
+ export type ListApiKeysInput = z.infer<typeof listApiKeysInputSchema>;
12
+ export declare function listApiKeysHandler(client: EdgeGateClient, input: ListApiKeysInput): Promise<ToolResult>;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateError } from "../client.js";
3
+ export const listApiKeysInputSchema = z.object({
4
+ workspace_id: z.string().uuid(),
5
+ });
6
+ export async function listApiKeysHandler(client, input) {
7
+ try {
8
+ const keys = await client.listApiKeys(input.workspace_id);
9
+ if (keys.length === 0) {
10
+ return {
11
+ content: [
12
+ {
13
+ type: "text",
14
+ text: "No API keys in this workspace yet. Create one with `edgegate_create_api_key`.",
15
+ },
16
+ ],
17
+ };
18
+ }
19
+ return {
20
+ content: [{ type: "text", text: formatTable(keys) }],
21
+ };
22
+ }
23
+ catch (err) {
24
+ if (err instanceof EdgeGateError) {
25
+ return {
26
+ isError: true,
27
+ content: [{ type: "text", text: `EdgeGate returned ${err.status}: ${err.detail}` }],
28
+ };
29
+ }
30
+ throw err;
31
+ }
32
+ }
33
+ function formatTable(keys) {
34
+ const lines = [
35
+ `Found ${keys.length} API key${keys.length === 1 ? "" : "s"} in this workspace:`,
36
+ ``,
37
+ `| name | id | prefix...suffix | status | last used |`,
38
+ `|---|---|---|---|---|`,
39
+ ];
40
+ for (const k of keys) {
41
+ const status = k.revoked_at
42
+ ? "revoked"
43
+ : k.expires_at && new Date(k.expires_at) < new Date()
44
+ ? "expired"
45
+ : "active";
46
+ lines.push(`| ${k.name} | \`${k.id}\` | \`${k.prefix}...${k.suffix}\` | ${status} | ${k.last_used_at ?? "never"} |`);
47
+ }
48
+ lines.push(``, `Revoke a key with \`edgegate_revoke_api_key({ workspace_id, key_id })\`. The plaintext is never recoverable — if it was lost, rotate by creating a new key and revoking the old.`);
49
+ return lines.join("\n");
50
+ }
51
+ //# sourceMappingURL=list_api_keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_api_keys.js","sourceRoot":"","sources":["../../src/tools/list_api_keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAkB,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;CAChC,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAsB,EACtB,KAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,+EAA+E;qBACtF;iBACF;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;SACrD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YACjC,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;aACpF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAsB;IACzC,MAAM,KAAK,GAAa;QACtB,SAAS,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,qBAAqB;QAChF,EAAE;QACF,sDAAsD;QACtD,uBAAuB;KACxB,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU;YACzB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,IAAI,EAAE;gBACnD,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,QAAQ,CAAC;QACf,KAAK,CAAC,IAAI,CACR,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,QAAQ,MAAM,MAAM,CAAC,CAAC,YAAY,IAAI,OAAO,IAAI,CACzG,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CACR,EAAE,EACF,kLAAkL,CACnL,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateClient } from "../client.js";
3
+ import type { ToolResult } from "./setup_workspace.js";
4
+ export declare const listMembersInputSchema: z.ZodObject<{
5
+ workspace_id: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ workspace_id: string;
8
+ }, {
9
+ workspace_id: string;
10
+ }>;
11
+ export type ListMembersInput = z.infer<typeof listMembersInputSchema>;
12
+ export declare function listMembersHandler(client: EdgeGateClient, input: ListMembersInput): Promise<ToolResult>;
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateError } from "../client.js";
3
+ export const listMembersInputSchema = z.object({
4
+ workspace_id: z.string().uuid(),
5
+ });
6
+ export async function listMembersHandler(client, input) {
7
+ try {
8
+ const members = await client.listMembers(input.workspace_id);
9
+ if (members.length === 0) {
10
+ return {
11
+ content: [
12
+ { type: "text", text: "No members in this workspace yet (which should be impossible — the owner is always a member)." },
13
+ ],
14
+ };
15
+ }
16
+ return {
17
+ content: [{ type: "text", text: formatTable(members) }],
18
+ };
19
+ }
20
+ catch (err) {
21
+ if (err instanceof EdgeGateError) {
22
+ return {
23
+ isError: true,
24
+ content: [{ type: "text", text: `EdgeGate returned ${err.status}: ${err.detail}` }],
25
+ };
26
+ }
27
+ throw err;
28
+ }
29
+ }
30
+ function formatTable(members) {
31
+ const lines = [
32
+ `Found ${members.length} member${members.length === 1 ? "" : "s"} in this workspace:`,
33
+ ``,
34
+ `| email | role | user_id |`,
35
+ `|---|---|---|`,
36
+ ];
37
+ for (const m of members) {
38
+ lines.push(`| ${m.email} | ${m.role} | \`${m.user_id}\` |`);
39
+ }
40
+ lines.push(``, `Invite with \`edgegate_invite_member\`, change role with \`edgegate_change_member_role\`, remove with \`edgegate_remove_member\`. Roles: \`owner\` (full control), \`admin\` (manage pipelines + runs, can't change billing or delete workspace), \`viewer\` (read-only).`);
41
+ return lines.join("\n");
42
+ }
43
+ //# sourceMappingURL=list_members.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_members.js","sourceRoot":"","sources":["../../src/tools/list_members.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAkB,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;CAChC,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAsB,EACtB,KAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+FAA+F,EAAE;iBACxH;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;SACxD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YACjC,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;aACpF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,OAAiB;IACpC,MAAM,KAAK,GAAa;QACtB,SAAS,OAAO,CAAC,MAAM,UAAU,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,qBAAqB;QACrF,EAAE;QACF,4BAA4B;QAC5B,eAAe;KAChB,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,CAAC,IAAI,CACR,EAAE,EACF,2QAA2Q,CAC5Q,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateClient } from "../client.js";
3
+ import type { ToolResult } from "./setup_workspace.js";
4
+ export declare const removeMemberInputSchema: z.ZodObject<{
5
+ workspace_id: z.ZodString;
6
+ user_id: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ workspace_id: string;
9
+ user_id: string;
10
+ }, {
11
+ workspace_id: string;
12
+ user_id: string;
13
+ }>;
14
+ export type RemoveMemberInput = z.infer<typeof removeMemberInputSchema>;
15
+ export declare function removeMemberHandler(client: EdgeGateClient, input: RemoveMemberInput): Promise<ToolResult>;
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateError } from "../client.js";
3
+ export const removeMemberInputSchema = z.object({
4
+ workspace_id: z.string().uuid(),
5
+ user_id: z
6
+ .string()
7
+ .uuid()
8
+ .describe("Target member's user_id (from `edgegate_list_members`). The user immediately " +
9
+ "loses access to the workspace. Their pipelines and runs are preserved — " +
10
+ "this only removes the membership row."),
11
+ });
12
+ export async function removeMemberHandler(client, input) {
13
+ try {
14
+ await client.removeMember(input.workspace_id, input.user_id);
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: [
20
+ `Removed user \`${input.user_id}\` from this workspace.`,
21
+ ``,
22
+ `Pipelines and runs they created are preserved. Re-invite any time with \`edgegate_invite_member\` if it was a mistake.`,
23
+ ].join("\n"),
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ catch (err) {
29
+ if (err instanceof EdgeGateError) {
30
+ if (err.status === 400) {
31
+ // Most likely "Cannot remove the last owner from workspace".
32
+ return {
33
+ isError: true,
34
+ content: [
35
+ {
36
+ type: "text",
37
+ text: `Member removal rejected: ${err.detail}\n\n` +
38
+ `If you're trying to remove the last owner, promote another member ` +
39
+ `to owner first via \`edgegate_change_member_role\`, then remove ` +
40
+ `the original owner.`,
41
+ },
42
+ ],
43
+ };
44
+ }
45
+ if (err.status === 404) {
46
+ return {
47
+ isError: true,
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: `User \`${input.user_id}\` is not a member of this workspace.`,
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ return {
57
+ isError: true,
58
+ content: [{ type: "text", text: `EdgeGate returned ${err.status}: ${err.detail}` }],
59
+ };
60
+ }
61
+ throw err;
62
+ }
63
+ }
64
+ //# sourceMappingURL=remove_member.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove_member.js","sourceRoot":"","sources":["../../src/tools/remove_member.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAkB,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IAC/B,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,IAAI,EAAE;SACN,QAAQ,CACP,+EAA+E;QAC7E,0EAA0E;QAC1E,uCAAuC,CAC1C;CACJ,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAsB,EACtB,KAAwB;IAExB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE;wBACJ,kBAAkB,KAAK,CAAC,OAAO,yBAAyB;wBACxD,EAAE;wBACF,wHAAwH;qBACzH,CAAC,IAAI,CAAC,IAAI,CAAC;iBACb;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,6DAA6D;gBAC7D,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EACF,4BAA4B,GAAG,CAAC,MAAM,MAAM;gCAC5C,oEAAoE;gCACpE,kEAAkE;gCAClE,qBAAqB;yBACxB;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,UAAU,KAAK,CAAC,OAAO,uCAAuC;yBACrE;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;aACpF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateClient } from "../client.js";
3
+ import type { ToolResult } from "./setup_workspace.js";
4
+ export declare const revokeApiKeyInputSchema: z.ZodObject<{
5
+ workspace_id: z.ZodString;
6
+ key_id: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ workspace_id: string;
9
+ key_id: string;
10
+ }, {
11
+ workspace_id: string;
12
+ key_id: string;
13
+ }>;
14
+ export type RevokeApiKeyInput = z.infer<typeof revokeApiKeyInputSchema>;
15
+ export declare function revokeApiKeyHandler(client: EdgeGateClient, input: RevokeApiKeyInput): Promise<ToolResult>;
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { EdgeGateError } from "../client.js";
3
+ export const revokeApiKeyInputSchema = z.object({
4
+ workspace_id: z.string().uuid(),
5
+ key_id: z
6
+ .string()
7
+ .uuid()
8
+ .describe("UUID of the key to revoke (the `id` field from `edgegate_list_api_keys`). " +
9
+ "This is destructive and immediate — any CI job or client still using " +
10
+ "the plaintext will fail authentication on the next request."),
11
+ });
12
+ export async function revokeApiKeyHandler(client, input) {
13
+ try {
14
+ await client.revokeApiKey(input.workspace_id, input.key_id);
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: [
20
+ `Revoked API key \`${input.key_id}\`.`,
21
+ ``,
22
+ `The key is now rejected for all future requests. The audit trail (last_used_at, revoked_at) is preserved on the row — you can still see it in \`edgegate_list_api_keys\`.`,
23
+ ].join("\n"),
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ catch (err) {
29
+ if (err instanceof EdgeGateError) {
30
+ if (err.status === 404) {
31
+ return {
32
+ isError: true,
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `API key \`${input.key_id}\` not found in this workspace (or already revoked).`,
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ return {
42
+ isError: true,
43
+ content: [{ type: "text", text: `EdgeGate returned ${err.status}: ${err.detail}` }],
44
+ };
45
+ }
46
+ throw err;
47
+ }
48
+ }
49
+ //# sourceMappingURL=revoke_api_key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke_api_key.js","sourceRoot":"","sources":["../../src/tools/revoke_api_key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAkB,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IAC/B,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,IAAI,EAAE;SACN,QAAQ,CACP,4EAA4E;QAC1E,uEAAuE;QACvE,6DAA6D,CAChE;CACJ,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAsB,EACtB,KAAwB;IAExB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE;wBACJ,qBAAqB,KAAK,CAAC,MAAM,KAAK;wBACtC,EAAE;wBACF,2KAA2K;qBAC5K,CAAC,IAAI,CAAC,IAAI,CAAC;iBACb;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,aAAa,KAAK,CAAC,MAAM,sDAAsD;yBACtF;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;aACpF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,20 @@
1
1
  export type UUID = string;
2
+ /** Supported dtypes for AI Hub compile input tensors. */
3
+ export type InputSpecDtype = "float32" | "float16" | "int64" | "int32" | "bool";
4
+ /**
5
+ * Explicit shape + dtype for one named model input.
6
+ * Passed as `input_specs` on pipeline create/update to override the default
7
+ * AI Hub auto-detect or PR-#40 auto-resolve behaviour.
8
+ *
9
+ * Example (BERT-family):
10
+ * { shape: [1, 128], dtype: "int64" }
11
+ */
12
+ export interface InputSpec {
13
+ /** Tensor shape (1–8 positive integers). */
14
+ shape: number[];
15
+ /** Element dtype. Defaults to "float32" when omitted. */
16
+ dtype: InputSpecDtype;
17
+ }
2
18
  export interface Workspace {
3
19
  id: UUID;
4
20
  name: string;
@@ -84,6 +100,74 @@ export interface WorkflowTemplate {
84
100
  api_url: string;
85
101
  secret_names: string[];
86
102
  }
103
+ /**
104
+ * Returned by GET /integrations/huggingface — status without the token.
105
+ * The plaintext token is never echoed; only the last 4 chars + lifecycle
106
+ * fields are visible after the initial connect/rotate call.
107
+ */
108
+ export interface HuggingFaceIntegrationStatus {
109
+ id: UUID;
110
+ provider: "huggingface";
111
+ status: "active" | "disabled";
112
+ token_last4: string;
113
+ created_at: string;
114
+ updated_at: string;
115
+ }
116
+ /**
117
+ * Returned by POST /integrations/huggingface and the rotate endpoint.
118
+ * Includes the whoami account name/type so the caller can confirm the
119
+ * right account was connected, without leaking the secret.
120
+ */
121
+ export interface HuggingFaceConnectResponse extends HuggingFaceIntegrationStatus {
122
+ account_name: string;
123
+ account_type: string;
124
+ }
125
+ /**
126
+ * Returned by GET / POST / PUT on /integrations/qaihub.
127
+ * The plaintext token is never echoed; only token_last4 is visible.
128
+ */
129
+ export interface QaihubIntegration {
130
+ id: UUID;
131
+ provider: "qaihub";
132
+ status: "active" | "disabled";
133
+ token_last4: string;
134
+ created_at: string;
135
+ updated_at: string;
136
+ }
137
+ /**
138
+ * Returned by POST /workspaces/{ws}/api-keys.
139
+ * `plaintext` is the only time the full key is visible — the caller must
140
+ * persist it immediately; the backend stores only a bcrypt hash.
141
+ */
142
+ export interface APIKeyCreatedResponse {
143
+ id: UUID;
144
+ plaintext: string;
145
+ name: string;
146
+ prefix: string;
147
+ suffix: string;
148
+ created_at: string;
149
+ expires_at: string | null;
150
+ }
151
+ /**
152
+ * Returned by GET /workspaces/{ws}/api-keys (one row per key).
153
+ * Includes lifecycle fields but never the plaintext or the hash.
154
+ */
155
+ export interface APIKeyListItem {
156
+ id: UUID;
157
+ name: string;
158
+ prefix: string;
159
+ suffix: string;
160
+ last_used_at: string | null;
161
+ expires_at: string | null;
162
+ revoked_at: string | null;
163
+ created_at: string;
164
+ }
165
+ export type WorkspaceRole = "owner" | "admin" | "viewer";
166
+ export interface Member {
167
+ user_id: UUID;
168
+ email: string;
169
+ role: WorkspaceRole;
170
+ }
87
171
  /** @deprecated Not used — audit-report endpoint does not exist; use RunBundle instead. */
88
172
  export interface AuditReport {
89
173
  url?: string;
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.4.1";
2
- export declare const USER_AGENT = "edgegate-mcp/0.4.1";
1
+ export declare const VERSION = "0.7.0";
2
+ export declare const USER_AGENT = "edgegate-mcp/0.7.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
- export const VERSION = "0.4.1";
1
+ export const VERSION = "0.7.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.4.1",
3
+ "version": "0.7.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.4.1",
3
+ "version": "0.7.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",
@@ -25,6 +25,10 @@
25
25
  "skills/edgegate-compare.md",
26
26
  "skills/edgegate-export.md",
27
27
  "skills/edgegate-import.md",
28
- "skills/edgegate-promptpacks.md"
28
+ "skills/edgegate-promptpacks.md",
29
+ "skills/edgegate-connect-huggingface.md",
30
+ "skills/edgegate-connect-qaihub.md",
31
+ "skills/edgegate-workspace-setup.md",
32
+ "skills/edgegate-members.md"
29
33
  ]
30
34
  }
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: edgegate-connect-huggingface
3
+ description: Connect a personal HuggingFace token to the active workspace so EdgeGate can import private, gated, or Qualcomm-org repos. Walk the user through generating a token, confirming the account, and remembering that rotation is non-disruptive.
4
+ ---
5
+
6
+ # /edgegate-connect-huggingface
7
+
8
+ Use this skill when the user wants to import a HuggingFace model that the
9
+ anonymous endpoint can't reach — common cases:
10
+
11
+ - `qualcomm/*` (Qualcomm's own optimized model org)
12
+ - `Intel/*` and many `Xenova/*` repos
13
+ - The user's own private repository
14
+ - Any gated model (Llama family, some image models, etc.) the user has access to
15
+
16
+ The workspace integration encrypts the token at rest using the same KMS as the
17
+ AI Hub token; it is never echoed in plaintext after the initial connect.
18
+
19
+ ## Steps
20
+
21
+ 1. **Confirm the active workspace.** If you don't already know which workspace
22
+ the user is operating on, call `edgegate_setup_workspace` first.
23
+
24
+ 2. **Check whether a token is already connected.** Call
25
+ `edgegate_get_huggingface_integration`. If a token is already active,
26
+ confirm with the user whether they want to **rotate** (replace) it before
27
+ asking for a new one.
28
+
29
+ 3. **Walk the user through generating a token** if they don't already have one.
30
+ - Open <https://huggingface.co/settings/tokens>
31
+ - Click **Create new token**
32
+ - Default scope is `Read` — that's enough for the import flow
33
+ - Copy the token. It starts with `hf_…` and is shown exactly once
34
+
35
+ 4. **Call `edgegate_connect_huggingface`** with the token. The tool validates
36
+ it against HF's `whoami` endpoint before storing, so a typo'd or revoked
37
+ token surfaces as a clean 400 with guidance — not a silent failure later
38
+ during import.
39
+
40
+ 5. **Confirm.** The tool response includes the HF `account_name` and
41
+ `account_type`. Confirm that matches the user's expectation — for example,
42
+ if they meant to use their org account but the response shows their
43
+ personal handle, offer to rotate with the right token.
44
+
45
+ 6. **Move on.** Tell the user the integration is live and that they can now
46
+ call `edgegate_import_huggingface_model` against any repo their token can
47
+ read (including the Qualcomm org).
48
+
49
+ ## Failure modes
50
+
51
+ - **400 "does not look like a HuggingFace token"** — they pasted something
52
+ that doesn't start with `hf_` or `api_`. Direct them back to
53
+ <https://huggingface.co/settings/tokens>.
54
+ - **400 "HuggingFace rejected the token"** — token is real-shaped but HF
55
+ returned 401. Common causes: typo, copied truncated value, token revoked.
56
+ Suggest regenerating.
57
+ - **409 conflict** — the tool auto-rotates on conflict, so this shouldn't
58
+ bubble up. If it does, the rotation also failed; surface the error.
59
+
60
+ ## Removing the integration
61
+
62
+ If the user wants to remove the token (offboarding, key rotation policy,
63
+ account change), call `edgegate_disconnect_huggingface`. The encrypted token
64
+ is deleted; future imports fall back to anonymous access.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: edgegate-connect-qaihub
3
+ description: Connect a Qualcomm AI Hub API token to the active EdgeGate workspace so runs can compile and profile models on real Snapdragon devices. Required before any pipeline can actually execute.
4
+ ---
5
+
6
+ # /edgegate-connect-qaihub
7
+
8
+ Use this skill when the user is setting up a new EdgeGate workspace, has just
9
+ created one with `edgegate_create_workspace`, or is seeing runs fail with
10
+ `NO_AIHUB_TOKEN`.
11
+
12
+ Qualcomm AI Hub is the device cloud EdgeGate uses behind the scenes — every
13
+ compile + profile + inference job runs against a real Snapdragon device there.
14
+ Without a token connected, the worker can't talk to Hub, so runs that reach the
15
+ worker fail fast.
16
+
17
+ ## Steps
18
+
19
+ 1. **Confirm the active workspace.** If you don't already know which workspace,
20
+ call `edgegate_setup_workspace` first.
21
+
22
+ 2. **Check whether a token is already connected.** Call
23
+ `edgegate_get_qaihub_integration`. If the response says **active** with a
24
+ `token_last4`, ask the user whether they want to **rotate** (replace) the
25
+ existing token before continuing. If the response is 404, they need to
26
+ connect for the first time.
27
+
28
+ 3. **Walk the user through generating a Qualcomm AI Hub token** if they don't
29
+ already have one:
30
+ - Open <https://app.aihub.qualcomm.com/account/api-token>
31
+ - Click **Generate new token** (or copy the existing one if shown)
32
+ - The token is a long alphanumeric string; copy it
33
+
34
+ 4. **Call `edgegate_connect_qaihub`** with the token. The backend stores it
35
+ under envelope encryption using the workspace KMS — the plaintext is
36
+ never returned again, and only `token_last4` is visible afterwards.
37
+
38
+ 5. **Confirm + next step.** Tell the user the integration is live. If this is
39
+ their first connection, suggest:
40
+ - `edgegate_create_promptpack` (if they need a new promptpack)
41
+ - `edgegate_create_pipeline` to define their first regression gate
42
+
43
+ ## Failure modes
44
+
45
+ - **Already exists (409 conflict)** — the tool transparently rotates instead
46
+ of failing, so the user shouldn't see this. If it does bubble up, the
47
+ rotation also failed and the backend error message comes through.
48
+ - **500 from Qualcomm AI Hub itself** — usually a transient outage at Hub
49
+ (`https://app.aihub.qualcomm.com`). Ask the user to retry in a minute.
50
+
51
+ ## Removing the integration
52
+
53
+ If the user wants to disconnect (offboarding, key rotation policy, account
54
+ change), call `edgegate_disconnect_qaihub`. The encrypted token is deleted
55
+ and new runs in the workspace fail with `NO_AIHUB_TOKEN` until a fresh
56
+ token is connected.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: edgegate-import
3
- description: Import a public Hugging Face model (ONNX) into EdgeGate. Use when the user says "import model from huggingface", "pull this HF model", or references an "<owner>/<name>" repo they want to gate.
3
+ description: Import a Hugging Face model (ONNX) into EdgeGate. Use when the user says "import model from huggingface", "pull this HF model", or references an "<owner>/<name>" repo they want to gate. Supports both anonymous (public repos) and personal-token (private / gated / qualcomm-org repos via /edgegate-connect-huggingface) flows.
4
4
  ---
5
5
 
6
6
  # /edgegate-import
@@ -45,5 +45,5 @@ Use this skill when the user says any of:
45
45
  ## Failure modes
46
46
 
47
47
  - **"no ONNX file found"** — The repo doesn't contain a pre-built ONNX. EdgeGate v1 only supports repos with a pre-built ONNX file. Point the user to the dashboard upload flow for converting their own model: `https://edgegate.frozo.ai/workspace/<id>/models`.
48
- - **"private repo"** — EdgeGate v1 only imports public HuggingFace repos. Ask the user to make the repo public or use the direct upload flow instead.
48
+ - **"private repo" / 401 from HuggingFace** — The anonymous endpoint can't read the repo (gated org like `qualcomm/*`, the user's own private repo, or a gated Llama family model). Offer to run `/edgegate-connect-huggingface` to attach a personal HF token to the workspace; once connected the same import call will succeed.
49
49
  - **402 — plan limit** — Direct the user to `https://edgegate.frozo.ai/pricing` to upgrade.
@@ -13,6 +13,10 @@ Goal of this skill: take the user from "I have a model file" to "every PR is aut
13
13
 
14
14
  1. **Confirm workspace.** Call `edgegate_setup_workspace` with no args. Present the list. Ask the user which one to use; if they say "the first one", pick `result[0].id`. Confirm before continuing.
15
15
 
16
+ If the list is empty OR the user wants a new workspace for this project, route to `/edgegate-workspace-setup` instead — that flow creates the workspace, connects Qualcomm AI Hub, optionally mints an API key for CI, and optionally invites teammates, all from chat.
17
+
18
+ If they have a workspace but it's missing the Qualcomm AI Hub integration (runs would fail with `NO_AIHUB_TOKEN`), run `/edgegate-connect-qaihub` first.
19
+
16
20
  2. **Define the pipeline.** Ask the user:
17
21
  - "Which model file do you want to gate? (path or artifact_id)"
18
22
  - "Which Snapdragon devices? (default: Samsung Galaxy S24, Galaxy S23)"
@@ -20,6 +24,8 @@ Goal of this skill: take the user from "I have a model file" to "every PR is aut
20
24
 
21
25
  If they hand you a file path (e.g. `./model.onnx`), tell them they need to upload it via the dashboard first to get an artifact_id (the MCP tool does not handle uploads in v1.0). Link: `https://edgegate.frozo.ai/workspace/{workspace_id}/models`.
22
26
 
27
+ If they hand you a HuggingFace repo id (e.g. `microsoft/resnet-50`), call `edgegate_import_huggingface_model` to import it and get an artifact_id back. If the repo is private / gated / from the Qualcomm org, the import will 401 — offer to run `/edgegate-connect-huggingface` to attach a personal HF token to the workspace, then retry the import.
28
+
23
29
  If they hand you an artifact_id, proceed to `edgegate_create_pipeline`.
24
30
 
25
31
  3. **Trigger the first run.** Call `edgegate_run_gate` with the workspace_id + new pipeline_id. Tell the user the run_id. Note that runs take 3-5 min per device.
@@ -28,6 +34,17 @@ Goal of this skill: take the user from "I have a model file" to "every PR is aut
28
34
 
29
35
  5. **Confirm.** Tell the user what's now set up: workspace `<name>`, pipeline `<name>`, run `<id>` in flight, and (if applicable) GitHub Action wired.
30
36
 
37
+ ## Input shape overrides (`input_specs`)
38
+
39
+ If creating a pipeline for a text or audio model, the backend auto-resolves dynamic shapes
40
+ (defaults: batch=1, sequence=128). If those defaults don't fit — long-context LLM, custom
41
+ audio model, or mixed-input model — pass `input_specs` explicitly with the right shape per input.
42
+
43
+ Examples:
44
+ - Long-context BERT (seq_len=512): `{ input_ids: { shape: [1, 512], dtype: "int64" }, attention_mask: { shape: [1, 512], dtype: "int64" } }`
45
+ - Audio model (mel-spectrogram): `{ mel_features: { shape: [1, 80, 3000], dtype: "float32" } }`
46
+ - Image model: omit entirely — the backend reads static shapes from the ONNX file.
47
+
31
48
  ## Failure modes
32
49
 
33
50
  - **No workspaces.** The API key may have been revoked. Direct the user to `https://edgegate.frozo.ai/workspace/<id>/settings#api-keys` to generate a fresh key.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: edgegate-members
3
+ description: List, invite, change role of, or remove EdgeGate workspace members. Use for any "add Alice as admin", "show me who's on this workspace", "Bob left the team", or similar membership management.
4
+ ---
5
+
6
+ # /edgegate-members
7
+
8
+ Composes the four member tools into a single skill the LLM can route any
9
+ membership-management request through.
10
+
11
+ ## Roles
12
+
13
+ | Role | Can do | Can NOT do |
14
+ |---|---|---|
15
+ | `owner` | Everything: members, billing, delete workspace, AI Hub connect, pipelines, runs | (nothing — full power) |
16
+ | `admin` | Pipelines, runs, integrations, members (except adding owners) | Billing, delete workspace, downgrade self |
17
+ | `viewer` | Read pipelines, runs, reports, members | Anything write |
18
+
19
+ ## Steps by intent
20
+
21
+ ### "Show me the members" / "Who's on this workspace?"
22
+ Call `edgegate_list_members({ workspace_id })`. Returns email + role + user_id.
23
+
24
+ ### "Add Alice as admin"
25
+ 1. Call `edgegate_invite_member({ workspace_id, user_email, role })`.
26
+ 2. If the response is 404, Alice doesn't have an EdgeGate account yet — tell
27
+ the user Alice needs to register at <https://edgegate.frozo.ai/register>
28
+ first, then re-run.
29
+ 3. If 409, Alice is already a member — offer to update her role with
30
+ `/edgegate-members → change role` instead.
31
+
32
+ ### "Make Bob an owner" / "Downgrade Carol to viewer"
33
+ 1. Get Bob's user_id from `edgegate_list_members` if not already known.
34
+ 2. Call `edgegate_change_member_role({ workspace_id, user_id, role })`.
35
+ 3. If the response is 400 with "Cannot remove the last owner", explain to
36
+ the user: they need to promote another member to owner first, then come
37
+ back to downgrade the original owner.
38
+
39
+ ### "Remove Dave"
40
+ 1. Get Dave's user_id.
41
+ 2. Confirm with the user (this is destructive — Dave loses access
42
+ immediately). Mention that Dave's pipelines and runs are preserved.
43
+ 3. Call `edgegate_remove_member({ workspace_id, user_id })`.
44
+ 4. Same last-owner guard as above applies.
45
+
46
+ ## Failure modes
47
+
48
+ - **403** on invite / change / remove — the caller's own role is too low. The
49
+ detail message names the required role.
50
+ - **404** on user lookup — the email or user_id doesn't exist in EdgeGate.
51
+ - **plan_limit_exceeded** on invite — workspace seat cap; direct to pricing.