@veertu/anka-mcp 0.1.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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/dist/anka.d.ts +41 -0
  4. package/dist/anka.js +65 -0
  5. package/dist/anka.js.map +1 -0
  6. package/dist/auth.d.ts +10 -0
  7. package/dist/auth.js +43 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/config.d.ts +69 -0
  10. package/dist/config.js +98 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/controller.d.ts +73 -0
  13. package/dist/controller.js +125 -0
  14. package/dist/controller.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/log.d.ts +107 -0
  19. package/dist/log.js +254 -0
  20. package/dist/log.js.map +1 -0
  21. package/dist/security/host.d.ts +2 -0
  22. package/dist/security/host.js +9 -0
  23. package/dist/security/host.js.map +1 -0
  24. package/dist/security/rate-limit.d.ts +18 -0
  25. package/dist/security/rate-limit.js +71 -0
  26. package/dist/security/rate-limit.js.map +1 -0
  27. package/dist/security/sanitize.d.ts +6 -0
  28. package/dist/security/sanitize.js +33 -0
  29. package/dist/security/sanitize.js.map +1 -0
  30. package/dist/security/schemas.d.ts +9 -0
  31. package/dist/security/schemas.js +20 -0
  32. package/dist/security/schemas.js.map +1 -0
  33. package/dist/server.d.ts +3 -0
  34. package/dist/server.js +13 -0
  35. package/dist/server.js.map +1 -0
  36. package/dist/ssh-key.d.ts +23 -0
  37. package/dist/ssh-key.js +73 -0
  38. package/dist/ssh-key.js.map +1 -0
  39. package/dist/tokens/cleanup.d.ts +10 -0
  40. package/dist/tokens/cleanup.js +28 -0
  41. package/dist/tokens/cleanup.js.map +1 -0
  42. package/dist/tokens/ownership.d.ts +6 -0
  43. package/dist/tokens/ownership.js +31 -0
  44. package/dist/tokens/ownership.js.map +1 -0
  45. package/dist/tokens/schema.d.ts +3 -0
  46. package/dist/tokens/schema.js +35 -0
  47. package/dist/tokens/schema.js.map +1 -0
  48. package/dist/tokens/store.d.ts +45 -0
  49. package/dist/tokens/store.js +145 -0
  50. package/dist/tokens/store.js.map +1 -0
  51. package/dist/tools/controller/get-vm.d.ts +3 -0
  52. package/dist/tools/controller/get-vm.js +34 -0
  53. package/dist/tools/controller/get-vm.js.map +1 -0
  54. package/dist/tools/controller/index.d.ts +3 -0
  55. package/dist/tools/controller/index.js +12 -0
  56. package/dist/tools/controller/index.js.map +1 -0
  57. package/dist/tools/controller/list-templates.d.ts +1 -0
  58. package/dist/tools/controller/list-templates.js +21 -0
  59. package/dist/tools/controller/list-templates.js.map +1 -0
  60. package/dist/tools/controller/request-vm.d.ts +8 -0
  61. package/dist/tools/controller/request-vm.js +101 -0
  62. package/dist/tools/controller/request-vm.js.map +1 -0
  63. package/dist/tools/controller/results.d.ts +5 -0
  64. package/dist/tools/controller/results.js +23 -0
  65. package/dist/tools/controller/results.js.map +1 -0
  66. package/dist/tools/controller/terminate-vm.d.ts +3 -0
  67. package/dist/tools/controller/terminate-vm.js +32 -0
  68. package/dist/tools/controller/terminate-vm.js.map +1 -0
  69. package/dist/tools/define-tool.d.ts +34 -0
  70. package/dist/tools/define-tool.js +51 -0
  71. package/dist/tools/define-tool.js.map +1 -0
  72. package/dist/tools/index.d.ts +8 -0
  73. package/dist/tools/index.js +20 -0
  74. package/dist/tools/index.js.map +1 -0
  75. package/dist/tools/local/delete-vm.d.ts +3 -0
  76. package/dist/tools/local/delete-vm.js +20 -0
  77. package/dist/tools/local/delete-vm.js.map +1 -0
  78. package/dist/tools/local/index.d.ts +3 -0
  79. package/dist/tools/local/index.js +14 -0
  80. package/dist/tools/local/index.js.map +1 -0
  81. package/dist/tools/local/list-templates.d.ts +1 -0
  82. package/dist/tools/local/list-templates.js +17 -0
  83. package/dist/tools/local/list-templates.js.map +1 -0
  84. package/dist/tools/local/show-vm.d.ts +3 -0
  85. package/dist/tools/local/show-vm.js +23 -0
  86. package/dist/tools/local/show-vm.js.map +1 -0
  87. package/dist/tools/local/ssh-access.d.ts +3 -0
  88. package/dist/tools/local/ssh-access.js +68 -0
  89. package/dist/tools/local/ssh-access.js.map +1 -0
  90. package/dist/tools/local/start-vm.d.ts +7 -0
  91. package/dist/tools/local/start-vm.js +52 -0
  92. package/dist/tools/local/start-vm.js.map +1 -0
  93. package/dist/tools/local/vms.d.ts +62 -0
  94. package/dist/tools/local/vms.js +91 -0
  95. package/dist/tools/local/vms.js.map +1 -0
  96. package/dist/transports/admin.d.ts +3 -0
  97. package/dist/transports/admin.js +74 -0
  98. package/dist/transports/admin.js.map +1 -0
  99. package/dist/transports/http.d.ts +14 -0
  100. package/dist/transports/http.js +283 -0
  101. package/dist/transports/http.js.map +1 -0
  102. package/package.json +46 -0
@@ -0,0 +1,6 @@
1
+ /** Require the current MCP credential to own the given controller instance. */
2
+ export declare function requireControllerInstanceAccess(instanceId: string): void;
3
+ /** Register a newly created controller instance under the current MCP credential. */
4
+ export declare function registerControllerInstance(instanceId: string): void;
5
+ /** Remove instance ownership after successful termination. */
6
+ export declare function releaseControllerInstance(instanceId: string): void;
@@ -0,0 +1,31 @@
1
+ import { getRequestContext } from "../log.js";
2
+ import { getTokenStore } from "./store.js";
3
+ const SKIP_OWNERSHIP_CREDENTIAL_IDS = new Set(["anonymous"]);
4
+ /** Require the current MCP credential to own the given controller instance. */
5
+ export function requireControllerInstanceAccess(instanceId) {
6
+ const ctx = getRequestContext();
7
+ if (!ctx?.credentialId) {
8
+ throw new Error("Missing credential context");
9
+ }
10
+ if (SKIP_OWNERSHIP_CREDENTIAL_IDS.has(ctx.credentialId)) {
11
+ return;
12
+ }
13
+ getTokenStore().assertInstanceOwned(ctx.credentialId, instanceId);
14
+ }
15
+ /** Register a newly created controller instance under the current MCP credential. */
16
+ export function registerControllerInstance(instanceId) {
17
+ const ctx = getRequestContext();
18
+ if (!ctx?.credentialId || SKIP_OWNERSHIP_CREDENTIAL_IDS.has(ctx.credentialId)) {
19
+ return;
20
+ }
21
+ getTokenStore().registerInstance(ctx.credentialId, instanceId);
22
+ }
23
+ /** Remove instance ownership after successful termination. */
24
+ export function releaseControllerInstance(instanceId) {
25
+ const ctx = getRequestContext();
26
+ if (!ctx?.credentialId || SKIP_OWNERSHIP_CREDENTIAL_IDS.has(ctx.credentialId)) {
27
+ return;
28
+ }
29
+ getTokenStore().releaseInstance(ctx.credentialId, instanceId);
30
+ }
31
+ //# sourceMappingURL=ownership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownership.js","sourceRoot":"","sources":["../../src/tokens/ownership.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAE7D,+EAA+E;AAC/E,MAAM,UAAU,+BAA+B,CAAC,UAAkB;IAChE,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,6BAA6B,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACxD,OAAO;IACT,CAAC;IACD,aAAa,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AACpE,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,0BAA0B,CAAC,UAAkB;IAC3D,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,GAAG,EAAE,YAAY,IAAI,6BAA6B,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9E,OAAO;IACT,CAAC;IACD,aAAa,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AACjE,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,yBAAyB,CAAC,UAAkB;IAC1D,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,GAAG,EAAE,YAAY,IAAI,6BAA6B,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9E,OAAO;IACT,CAAC;IACD,aAAa,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type Database from "better-sqlite3";
2
+ /** Apply idempotent schema migrations and enable foreign keys. */
3
+ export declare function migrateSchema(db: Database.Database): void;
@@ -0,0 +1,35 @@
1
+ const MIGRATIONS = [
2
+ `CREATE TABLE IF NOT EXISTS tokens (
3
+ id TEXT PRIMARY KEY,
4
+ label TEXT NOT NULL DEFAULT '',
5
+ token_hash TEXT NOT NULL UNIQUE,
6
+ created_at TEXT NOT NULL,
7
+ revoked_at TEXT
8
+ )`,
9
+ `CREATE TABLE IF NOT EXISTS instances (
10
+ instance_id TEXT PRIMARY KEY,
11
+ credential_id TEXT NOT NULL REFERENCES tokens(id),
12
+ created_at TEXT NOT NULL
13
+ )`,
14
+ `CREATE INDEX IF NOT EXISTS idx_instances_credential_id ON instances(credential_id)`
15
+ ];
16
+ function instancesColumnNames(db) {
17
+ const rows = db.prepare("PRAGMA table_info(instances)").all();
18
+ return rows.map((row) => row.name);
19
+ }
20
+ /** Apply idempotent schema migrations and enable foreign keys. */
21
+ export function migrateSchema(db) {
22
+ db.pragma("foreign_keys = ON");
23
+ for (const sql of MIGRATIONS) {
24
+ db.exec(sql);
25
+ }
26
+ const columns = instancesColumnNames(db);
27
+ if (columns.includes("token_id") && !columns.includes("credential_id")) {
28
+ db.exec("ALTER TABLE instances RENAME COLUMN token_id TO credential_id");
29
+ }
30
+ if (columns.includes("token_id")) {
31
+ db.exec("DROP INDEX IF EXISTS idx_instances_token_id");
32
+ }
33
+ db.exec("CREATE INDEX IF NOT EXISTS idx_instances_credential_id ON instances(credential_id)");
34
+ }
35
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/tokens/schema.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAG;IACjB;;;;;;IAME;IACF;;;;IAIE;IACF,oFAAoF;CACrF,CAAC;AAEF,SAAS,oBAAoB,CAAC,EAAqB;IACjD,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,GAAG,EAAwB,CAAC;IACpF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,EAAqB;IACjD,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACvE,EAAE,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IACzD,CAAC;IACD,EAAE,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAC;AAChG,CAAC"}
@@ -0,0 +1,45 @@
1
+ export declare const LEGACY_CREDENTIAL_ID = "legacy";
2
+ export interface TokenRecord {
3
+ id: string;
4
+ label: string;
5
+ createdAt: string;
6
+ revoked: boolean;
7
+ instanceCount: number;
8
+ }
9
+ export interface ValidatedToken {
10
+ id: string;
11
+ label: string;
12
+ }
13
+ export interface RevokeResult {
14
+ instanceIds: string[];
15
+ }
16
+ export declare class TokenStore {
17
+ private readonly db;
18
+ constructor(dbPath: string);
19
+ close(): void;
20
+ /** Ensure a synthetic row exists for the legacy env bearer (FK target for instances). */
21
+ ensureLegacyCredential(): void;
22
+ hasActiveTokens(): boolean;
23
+ createToken(label?: string): {
24
+ id: string;
25
+ label: string;
26
+ token: string;
27
+ };
28
+ listTokens(): TokenRecord[];
29
+ getTokenById(id: string): {
30
+ id: string;
31
+ label: string;
32
+ revoked: boolean;
33
+ } | null;
34
+ revokeToken(id: string): RevokeResult;
35
+ validateToken(plaintext: string): ValidatedToken | null;
36
+ listInstancesForCredential(credentialId: string): string[];
37
+ registerInstance(credentialId: string, instanceId: string): void;
38
+ assertInstanceOwned(credentialId: string, instanceId: string): void;
39
+ releaseInstance(credentialId: string, instanceId: string): void;
40
+ deleteInstancesForCredential(credentialId: string): void;
41
+ }
42
+ export declare function initTokenStore(dbPath: string): TokenStore;
43
+ export declare function getTokenStore(): TokenStore;
44
+ /** @internal Test helper to reset the singleton. */
45
+ export declare function resetTokenStoreForTests(): void;
@@ -0,0 +1,145 @@
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import Database from "better-sqlite3";
3
+ import { migrateSchema } from "./schema.js";
4
+ export const LEGACY_CREDENTIAL_ID = "legacy";
5
+ function hashToken(plaintext) {
6
+ return createHash("sha256").update(plaintext).digest("hex");
7
+ }
8
+ function nowIso() {
9
+ return new Date().toISOString();
10
+ }
11
+ export class TokenStore {
12
+ db;
13
+ constructor(dbPath) {
14
+ this.db = new Database(dbPath);
15
+ migrateSchema(this.db);
16
+ }
17
+ close() {
18
+ this.db.close();
19
+ }
20
+ /** Ensure a synthetic row exists for the legacy env bearer (FK target for instances). */
21
+ ensureLegacyCredential() {
22
+ this.db
23
+ .prepare(`INSERT INTO tokens (id, label, token_hash, created_at, revoked_at)
24
+ VALUES (?, '', '__legacy__', ?, NULL)
25
+ ON CONFLICT(id) DO NOTHING`)
26
+ .run(LEGACY_CREDENTIAL_ID, nowIso());
27
+ }
28
+ hasActiveTokens() {
29
+ const row = this.db
30
+ .prepare(`SELECT COUNT(*) AS count FROM tokens WHERE revoked_at IS NULL AND token_hash != '__legacy__'`)
31
+ .get();
32
+ return row.count > 0;
33
+ }
34
+ createToken(label = "") {
35
+ const id = randomUUID();
36
+ const token = randomBytes(32).toString("hex");
37
+ const createdAt = nowIso();
38
+ const trimmedLabel = label.trim();
39
+ const insert = this.db.transaction(() => {
40
+ this.db
41
+ .prepare(`INSERT INTO tokens (id, label, token_hash, created_at, revoked_at)
42
+ VALUES (?, ?, ?, ?, NULL)`)
43
+ .run(id, trimmedLabel, hashToken(token), createdAt);
44
+ });
45
+ insert();
46
+ return { id, label: trimmedLabel, token };
47
+ }
48
+ listTokens() {
49
+ const rows = this.db
50
+ .prepare(`SELECT t.id, t.label, t.created_at, t.revoked_at,
51
+ COUNT(i.instance_id) AS instance_count
52
+ FROM tokens t
53
+ LEFT JOIN instances i ON i.credential_id = t.id
54
+ WHERE t.token_hash != '__legacy__'
55
+ GROUP BY t.id
56
+ ORDER BY t.created_at ASC`)
57
+ .all();
58
+ return rows.map((row) => ({
59
+ id: row.id,
60
+ label: row.label,
61
+ createdAt: row.created_at,
62
+ revoked: row.revoked_at !== null,
63
+ instanceCount: row.instance_count
64
+ }));
65
+ }
66
+ getTokenById(id) {
67
+ const row = this.db
68
+ .prepare(`SELECT id, label, revoked_at FROM tokens WHERE id = ? AND token_hash != '__legacy__'`)
69
+ .get(id);
70
+ if (!row)
71
+ return null;
72
+ return { id: row.id, label: row.label, revoked: row.revoked_at !== null };
73
+ }
74
+ revokeToken(id) {
75
+ const existing = this.getTokenById(id);
76
+ if (!existing) {
77
+ throw new Error(`Token not found: ${id}`);
78
+ }
79
+ if (existing.revoked) {
80
+ throw new Error(`Token already revoked: ${id}`);
81
+ }
82
+ const instanceRows = this.listInstancesForCredential(id);
83
+ const revoke = this.db.transaction(() => {
84
+ this.db
85
+ .prepare(`UPDATE tokens SET revoked_at = ? WHERE id = ?`)
86
+ .run(nowIso(), id);
87
+ });
88
+ revoke();
89
+ return { instanceIds: instanceRows };
90
+ }
91
+ validateToken(plaintext) {
92
+ const row = this.db
93
+ .prepare(`SELECT id, label FROM tokens WHERE token_hash = ? AND revoked_at IS NULL`)
94
+ .get(hashToken(plaintext));
95
+ if (!row || row.id === LEGACY_CREDENTIAL_ID)
96
+ return null;
97
+ return { id: row.id, label: row.label };
98
+ }
99
+ listInstancesForCredential(credentialId) {
100
+ const rows = this.db
101
+ .prepare(`SELECT instance_id FROM instances WHERE credential_id = ? ORDER BY created_at ASC`)
102
+ .all(credentialId);
103
+ return rows.map((row) => row.instance_id);
104
+ }
105
+ registerInstance(credentialId, instanceId) {
106
+ this.db
107
+ .prepare(`INSERT INTO instances (instance_id, credential_id, created_at) VALUES (?, ?, ?)`)
108
+ .run(instanceId, credentialId, nowIso());
109
+ }
110
+ assertInstanceOwned(credentialId, instanceId) {
111
+ const row = this.db
112
+ .prepare(`SELECT credential_id FROM instances WHERE instance_id = ?`)
113
+ .get(instanceId);
114
+ if (!row || row.credential_id !== credentialId) {
115
+ throw new Error("Instance not owned by this credential");
116
+ }
117
+ }
118
+ releaseInstance(credentialId, instanceId) {
119
+ this.db
120
+ .prepare(`DELETE FROM instances WHERE instance_id = ? AND credential_id = ?`)
121
+ .run(instanceId, credentialId);
122
+ }
123
+ deleteInstancesForCredential(credentialId) {
124
+ this.db.prepare(`DELETE FROM instances WHERE credential_id = ?`).run(credentialId);
125
+ }
126
+ }
127
+ let sharedStore;
128
+ export function initTokenStore(dbPath) {
129
+ if (sharedStore)
130
+ sharedStore.close();
131
+ sharedStore = new TokenStore(dbPath);
132
+ return sharedStore;
133
+ }
134
+ export function getTokenStore() {
135
+ if (!sharedStore) {
136
+ throw new Error("Token store not initialized");
137
+ }
138
+ return sharedStore;
139
+ }
140
+ /** @internal Test helper to reset the singleton. */
141
+ export function resetTokenStoreForTests() {
142
+ sharedStore?.close();
143
+ sharedStore = undefined;
144
+ }
145
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/tokens/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const controllerGetVmTool: import("../define-tool.js").ToolDefinition<{
2
+ instance_id: import("zod").ZodString;
3
+ }>;
@@ -0,0 +1,34 @@
1
+ import { controller, extractSshEndpoint } from "../../controller.js";
2
+ import { uuidLike } from "../../security/schemas.js";
3
+ import { requireControllerInstanceAccess } from "../../tokens/ownership.js";
4
+ import { defineTool, jsonResult } from "../define-tool.js";
5
+ import { runControllerTool } from "./results.js";
6
+ export const controllerGetVmTool = defineTool({
7
+ name: "controller_get_vm",
8
+ config: {
9
+ title: "Get controller VM status",
10
+ description: "Get the current state of a controller VM instance, including SSH endpoint " +
11
+ "details (host, forwarded port, username) once it is reachable. Use the private " +
12
+ "key returned by controller_request_vm to connect.",
13
+ inputSchema: {
14
+ instance_id: uuidLike.describe("The instance id returned by controller_request_vm.")
15
+ },
16
+ annotations: { title: "Get controller VM status", readOnlyHint: true, openWorldHint: true }
17
+ },
18
+ handler: async ({ instance_id }) => runControllerTool(async () => {
19
+ try {
20
+ requireControllerInstanceAccess(instance_id);
21
+ }
22
+ catch {
23
+ return jsonResult({ ok: false, error: "Instance not owned by this credential" }, true);
24
+ }
25
+ const instance = await controller.getVm(instance_id);
26
+ return jsonResult({
27
+ instance_id,
28
+ instance_state: instance.instance_state,
29
+ vm_status: instance.vminfo?.status ?? null,
30
+ ssh: extractSshEndpoint(instance.vminfo) ?? null
31
+ });
32
+ })
33
+ });
34
+ //# sourceMappingURL=get-vm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-vm.js","sourceRoot":"","sources":["../../../src/tools/controller/get-vm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAyB,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;IAC5C,IAAI,EAAE,mBAAmB;IACzB,MAAM,EAAE;QACN,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,4EAA4E;YAC5E,iFAAiF;YACjF,mDAAmD;QACrD,WAAW,EAAE;YACX,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,oDAAoD,CAAC;SACrF;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KAC5F;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CACjC,iBAAiB,CAAC,KAAK,IAAI,EAAE;QAC3B,IAAI,CAAC;YACH,+BAA+B,CAAC,WAAW,CAAC,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uCAAuC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACrD,OAAO,UAAU,CAAC;YAChB,WAAW;YACX,cAAc,EAAE,QAAQ,CAAC,cAAc;YACvC,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI;YAC1C,GAAG,EAAE,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI;SACjD,CAAC,CAAC;IACL,CAAC,CAAC;CACL,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../define-tool.js";
2
+ /** Tools exposed when the controller backend is enabled. */
3
+ export declare const controllerTools: ToolDefinition<any>[];
@@ -0,0 +1,12 @@
1
+ import { controllerListTemplatesTool } from "./list-templates.js";
2
+ import { controllerRequestVmTool } from "./request-vm.js";
3
+ import { controllerGetVmTool } from "./get-vm.js";
4
+ import { controllerTerminateVmTool } from "./terminate-vm.js";
5
+ /** Tools exposed when the controller backend is enabled. */
6
+ export const controllerTools = [
7
+ controllerListTemplatesTool,
8
+ controllerRequestVmTool,
9
+ controllerGetVmTool,
10
+ controllerTerminateVmTool
11
+ ];
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/controller/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAA0B;IACpD,2BAA2B;IAC3B,uBAAuB;IACvB,mBAAmB;IACnB,yBAAyB;CAC1B,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const controllerListTemplatesTool: import("../define-tool.js").ToolDefinition<{}>;
@@ -0,0 +1,21 @@
1
+ import { controller } from "../../controller.js";
2
+ import { defineTool, jsonResult } from "../define-tool.js";
3
+ import { runControllerTool } from "./results.js";
4
+ export const controllerListTemplatesTool = defineTool({
5
+ name: "controller_list_templates",
6
+ config: {
7
+ title: "List controller templates",
8
+ description: "List the VM templates available in the Anka Build Cloud registry. Use this " +
9
+ "to find the template `vmid` (and optionally a tag) to pass to " +
10
+ "controller_request_vm.",
11
+ inputSchema: {},
12
+ annotations: { title: "List controller templates", readOnlyHint: true, openWorldHint: true }
13
+ },
14
+ handler: async () => runControllerTool(async () => {
15
+ const templates = await controller.listTemplates();
16
+ return jsonResult({
17
+ templates: templates.map((t) => ({ id: t.id, name: t.name, arch: t.arch }))
18
+ });
19
+ })
20
+ });
21
+ //# sourceMappingURL=list-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-templates.js","sourceRoot":"","sources":["../../../src/tools/controller/list-templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAU,CAAC;IACpD,IAAI,EAAE,2BAA2B;IACjC,MAAM,EAAE;QACN,KAAK,EAAE,2BAA2B;QAClC,WAAW,EACT,6EAA6E;YAC7E,gEAAgE;YAChE,wBAAwB;QAC1B,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KAC7F;IACD,OAAO,EAAE,KAAK,IAAI,EAAE,CAClB,iBAAiB,CAAC,KAAK,IAAI,EAAE;QAC3B,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,CAAC;QACnD,OAAO,UAAU,CAAC;YAChB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SAC5E,CAAC,CAAC;IACL,CAAC,CAAC;CACL,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ export declare const controllerRequestVmTool: import("../define-tool.js").ToolDefinition<{
3
+ vmid: z.ZodString;
4
+ tag: z.ZodOptional<z.ZodString>;
5
+ name: z.ZodOptional<z.ZodString>;
6
+ externalId: z.ZodOptional<z.ZodString>;
7
+ addSshPortForward: z.ZodOptional<z.ZodBoolean>;
8
+ }>;
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ import { config } from "../../config.js";
3
+ import { controller, extractSshEndpoint, isSshReady } from "../../controller.js";
4
+ import { optionalBoundedString, uuidLike } from "../../security/schemas.js";
5
+ import { buildAuthorizedKeysStartupScript, buildSshCommand, encodeStartupScript, generateSshKeypair, probeSshAuth } from "../../ssh-key.js";
6
+ import { buildControllerExternalId } from "../../log.js";
7
+ import { registerControllerInstance } from "../../tokens/ownership.js";
8
+ import { defineTool, jsonResult } from "../define-tool.js";
9
+ import { controllerError } from "./results.js";
10
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11
+ /** Instance states that mean the VM will never become ready. */
12
+ const FAILED_STATES = new Set(["Error", "Failed", "Terminated", "Terminating", "Stopped"]);
13
+ export const controllerRequestVmTool = defineTool({
14
+ name: "controller_request_vm",
15
+ config: {
16
+ title: "Request a VM from the controller",
17
+ description: "Start one VM instance from a template on the Anka Build Cloud Controller, wait " +
18
+ "until it is running and the temporary SSH key authenticates over the forwarded port, " +
19
+ "then return the connection details (host, forwarded SSH port, username, private key path, " +
20
+ "ssh command). A temporary ed25519 key is generated and installed on the VM via the " +
21
+ "controller startup_script. The template must have port forwarding for the SSH guest port " +
22
+ "(default 22), or set addSshPortForward to add it. The agent then opens the SSH connection itself.",
23
+ inputSchema: {
24
+ vmid: uuidLike.describe("UUID of the template to start (from controller_list_templates)."),
25
+ tag: optionalBoundedString.optional().describe("Optional template tag. Defaults to the latest tag."),
26
+ name: optionalBoundedString.optional().describe("Optional name for the instance."),
27
+ externalId: optionalBoundedString
28
+ .optional()
29
+ .describe("Optional extra reference appended to the auto-generated external_id " +
30
+ "(which always records MCP client, IP, user-agent, and session)."),
31
+ addSshPortForward: z
32
+ .boolean()
33
+ .optional()
34
+ .describe("Add an SSH port-forward rule even if the template lacks one. Defaults to true.")
35
+ },
36
+ annotations: { title: "Request a VM from the controller", openWorldHint: true }
37
+ },
38
+ handler: async ({ vmid, tag, name, externalId, addSshPortForward = true }) => {
39
+ try {
40
+ const { privateKeyPath, publicKey } = await generateSshKeypair();
41
+ const startupScript = encodeStartupScript(buildAuthorizedKeysStartupScript(publicKey));
42
+ const instanceId = await controller.startVm({
43
+ vmid,
44
+ tag,
45
+ name,
46
+ externalId: buildControllerExternalId(externalId),
47
+ addSshPortForward,
48
+ startupScript
49
+ });
50
+ registerControllerInstance(instanceId);
51
+ const deadline = Date.now() + config.controllerStartTimeoutMs;
52
+ let instance;
53
+ while (Date.now() < deadline) {
54
+ instance = await controller.getVm(instanceId);
55
+ if (isSshReady(instance)) {
56
+ const endpoint = extractSshEndpoint(instance.vminfo);
57
+ const sshVerified = !config.controllerSshProbeEnabled ||
58
+ (await probeSshAuth({
59
+ privateKeyPath,
60
+ host: endpoint.host,
61
+ port: endpoint.port,
62
+ user: endpoint.username
63
+ }));
64
+ if (sshVerified) {
65
+ return jsonResult({
66
+ instance_id: instanceId,
67
+ instance_state: instance.instance_state,
68
+ ssh: {
69
+ ...endpoint,
70
+ private_key_path: privateKeyPath,
71
+ command: buildSshCommand({
72
+ privateKeyPath,
73
+ host: endpoint.host,
74
+ port: endpoint.port,
75
+ user: endpoint.username
76
+ })
77
+ }
78
+ });
79
+ }
80
+ }
81
+ if (instance.instance_state && FAILED_STATES.has(instance.instance_state)) {
82
+ return jsonResult({
83
+ instance_id: instanceId,
84
+ instance_state: instance.instance_state,
85
+ error: `Instance entered terminal state "${instance.instance_state}" before becoming SSH-ready.`
86
+ }, true);
87
+ }
88
+ await sleep(config.controllerPollIntervalMs);
89
+ }
90
+ return jsonResult({
91
+ instance_id: instanceId,
92
+ instance_state: instance?.instance_state,
93
+ error: `Timed out after ${config.controllerStartTimeoutMs}ms waiting for the VM to become SSH-ready. The instance was started; use controller_get_vm to keep checking or controller_terminate_vm to clean up.`
94
+ }, true);
95
+ }
96
+ catch (error) {
97
+ return jsonResult({ ok: false, error: controllerError(error) }, true);
98
+ }
99
+ }
100
+ });
101
+ //# sourceMappingURL=request-vm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-vm.js","sourceRoot":"","sources":["../../../src/tools/controller/request-vm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ /** Extract an agent-safe error string from a controller failure. */
3
+ export declare function controllerError(error: unknown): string;
4
+ /** Run an async controller operation and return a narrow tool result on failure. */
5
+ export declare function runControllerTool(fn: () => Promise<CallToolResult>): Promise<CallToolResult>;
@@ -0,0 +1,23 @@
1
+ import { ControllerError } from "../../controller.js";
2
+ import { sanitizeControllerError, sanitizeUnknownError } from "../../security/sanitize.js";
3
+ import { jsonResult } from "../define-tool.js";
4
+ /** Extract an agent-safe error string from a controller failure. */
5
+ export function controllerError(error) {
6
+ if (error instanceof ControllerError) {
7
+ return sanitizeControllerError(error.message);
8
+ }
9
+ if (error instanceof Error) {
10
+ return sanitizeControllerError(error.message);
11
+ }
12
+ return sanitizeUnknownError(error);
13
+ }
14
+ /** Run an async controller operation and return a narrow tool result on failure. */
15
+ export async function runControllerTool(fn) {
16
+ try {
17
+ return await fn();
18
+ }
19
+ catch (error) {
20
+ return jsonResult({ ok: false, error: controllerError(error) }, true);
21
+ }
22
+ }
23
+ //# sourceMappingURL=results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"results.js","sourceRoot":"","sources":["../../../src/tools/controller/results.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,oEAAoE;AACpE,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,OAAO,uBAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,uBAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAiC;IAEjC,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const controllerTerminateVmTool: import("../define-tool.js").ToolDefinition<{
2
+ instance_id: import("zod").ZodString;
3
+ }>;
@@ -0,0 +1,32 @@
1
+ import { controller } from "../../controller.js";
2
+ import { uuidLike } from "../../security/schemas.js";
3
+ import { releaseControllerInstance, requireControllerInstanceAccess } from "../../tokens/ownership.js";
4
+ import { defineTool, jsonResult } from "../define-tool.js";
5
+ import { runControllerTool } from "./results.js";
6
+ export const controllerTerminateVmTool = defineTool({
7
+ name: "controller_terminate_vm",
8
+ config: {
9
+ title: "Terminate a controller VM",
10
+ description: "Terminate a running controller VM instance by its instance id.",
11
+ inputSchema: {
12
+ instance_id: uuidLike.describe("The instance id to terminate.")
13
+ },
14
+ annotations: {
15
+ title: "Terminate a controller VM",
16
+ destructiveHint: true,
17
+ openWorldHint: true
18
+ }
19
+ },
20
+ handler: async ({ instance_id }) => runControllerTool(async () => {
21
+ try {
22
+ requireControllerInstanceAccess(instance_id);
23
+ }
24
+ catch {
25
+ return jsonResult({ ok: false, error: "Instance not owned by this credential" }, true);
26
+ }
27
+ await controller.terminateVm(instance_id);
28
+ releaseControllerInstance(instance_id);
29
+ return jsonResult({ instance_id, terminated: true });
30
+ })
31
+ });
32
+ //# sourceMappingURL=terminate-vm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminate-vm.js","sourceRoot":"","sources":["../../../src/tools/controller/terminate-vm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EACL,yBAAyB,EACzB,+BAA+B,EAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;IAClD,IAAI,EAAE,yBAAyB;IAC/B,MAAM,EAAE;QACN,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,gEAAgE;QAC7E,WAAW,EAAE;YACX,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,+BAA+B,CAAC;SAChE;QACD,WAAW,EAAE;YACX,KAAK,EAAE,2BAA2B;YAClC,eAAe,EAAE,IAAI;YACrB,aAAa,EAAE,IAAI;SACpB;KACF;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CACjC,iBAAiB,CAAC,KAAK,IAAI,EAAE;QAC3B,IAAI,CAAC;YACH,+BAA+B,CAAC,WAAW,CAAC,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uCAAuC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC1C,yBAAyB,CAAC,WAAW,CAAC,CAAC;QACvC,OAAO,UAAU,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC;CACL,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { McpServer, ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { ZodRawShape } from "zod";
4
+ interface ToolConfig<InputArgs extends ZodRawShape> {
5
+ title?: string;
6
+ description: string;
7
+ inputSchema?: InputArgs;
8
+ annotations?: {
9
+ title?: string;
10
+ readOnlyHint?: boolean;
11
+ destructiveHint?: boolean;
12
+ idempotentHint?: boolean;
13
+ openWorldHint?: boolean;
14
+ };
15
+ }
16
+ /** A self-contained, registrable MCP tool. */
17
+ export interface ToolDefinition<InputArgs extends ZodRawShape = ZodRawShape> {
18
+ name: string;
19
+ config: ToolConfig<InputArgs>;
20
+ handler: ToolCallback<InputArgs>;
21
+ register(server: McpServer): void;
22
+ }
23
+ /**
24
+ * Declare an MCP tool in one place. Add the returned object to the `tools`
25
+ * array in `tools/index.ts` and it is wired up automatically.
26
+ */
27
+ export declare function defineTool<InputArgs extends ZodRawShape>(spec: {
28
+ name: string;
29
+ config: ToolConfig<InputArgs>;
30
+ handler: ToolCallback<InputArgs>;
31
+ }): ToolDefinition<InputArgs>;
32
+ /** Serialize a value into a CallToolResult with a single JSON text block. */
33
+ export declare function jsonResult(value: unknown, isError?: boolean): CallToolResult;
34
+ export {};