@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.
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/anka.d.ts +41 -0
- package/dist/anka.js +65 -0
- package/dist/anka.js.map +1 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +43 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/controller.d.ts +73 -0
- package/dist/controller.js +125 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +107 -0
- package/dist/log.js +254 -0
- package/dist/log.js.map +1 -0
- package/dist/security/host.d.ts +2 -0
- package/dist/security/host.js +9 -0
- package/dist/security/host.js.map +1 -0
- package/dist/security/rate-limit.d.ts +18 -0
- package/dist/security/rate-limit.js +71 -0
- package/dist/security/rate-limit.js.map +1 -0
- package/dist/security/sanitize.d.ts +6 -0
- package/dist/security/sanitize.js +33 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/schemas.d.ts +9 -0
- package/dist/security/schemas.js +20 -0
- package/dist/security/schemas.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +13 -0
- package/dist/server.js.map +1 -0
- package/dist/ssh-key.d.ts +23 -0
- package/dist/ssh-key.js +73 -0
- package/dist/ssh-key.js.map +1 -0
- package/dist/tokens/cleanup.d.ts +10 -0
- package/dist/tokens/cleanup.js +28 -0
- package/dist/tokens/cleanup.js.map +1 -0
- package/dist/tokens/ownership.d.ts +6 -0
- package/dist/tokens/ownership.js +31 -0
- package/dist/tokens/ownership.js.map +1 -0
- package/dist/tokens/schema.d.ts +3 -0
- package/dist/tokens/schema.js +35 -0
- package/dist/tokens/schema.js.map +1 -0
- package/dist/tokens/store.d.ts +45 -0
- package/dist/tokens/store.js +145 -0
- package/dist/tokens/store.js.map +1 -0
- package/dist/tools/controller/get-vm.d.ts +3 -0
- package/dist/tools/controller/get-vm.js +34 -0
- package/dist/tools/controller/get-vm.js.map +1 -0
- package/dist/tools/controller/index.d.ts +3 -0
- package/dist/tools/controller/index.js +12 -0
- package/dist/tools/controller/index.js.map +1 -0
- package/dist/tools/controller/list-templates.d.ts +1 -0
- package/dist/tools/controller/list-templates.js +21 -0
- package/dist/tools/controller/list-templates.js.map +1 -0
- package/dist/tools/controller/request-vm.d.ts +8 -0
- package/dist/tools/controller/request-vm.js +101 -0
- package/dist/tools/controller/request-vm.js.map +1 -0
- package/dist/tools/controller/results.d.ts +5 -0
- package/dist/tools/controller/results.js +23 -0
- package/dist/tools/controller/results.js.map +1 -0
- package/dist/tools/controller/terminate-vm.d.ts +3 -0
- package/dist/tools/controller/terminate-vm.js +32 -0
- package/dist/tools/controller/terminate-vm.js.map +1 -0
- package/dist/tools/define-tool.d.ts +34 -0
- package/dist/tools/define-tool.js +51 -0
- package/dist/tools/define-tool.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +20 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/local/delete-vm.d.ts +3 -0
- package/dist/tools/local/delete-vm.js +20 -0
- package/dist/tools/local/delete-vm.js.map +1 -0
- package/dist/tools/local/index.d.ts +3 -0
- package/dist/tools/local/index.js +14 -0
- package/dist/tools/local/index.js.map +1 -0
- package/dist/tools/local/list-templates.d.ts +1 -0
- package/dist/tools/local/list-templates.js +17 -0
- package/dist/tools/local/list-templates.js.map +1 -0
- package/dist/tools/local/show-vm.d.ts +3 -0
- package/dist/tools/local/show-vm.js +23 -0
- package/dist/tools/local/show-vm.js.map +1 -0
- package/dist/tools/local/ssh-access.d.ts +3 -0
- package/dist/tools/local/ssh-access.js +68 -0
- package/dist/tools/local/ssh-access.js.map +1 -0
- package/dist/tools/local/start-vm.d.ts +7 -0
- package/dist/tools/local/start-vm.js +52 -0
- package/dist/tools/local/start-vm.js.map +1 -0
- package/dist/tools/local/vms.d.ts +62 -0
- package/dist/tools/local/vms.js +91 -0
- package/dist/tools/local/vms.js.map +1 -0
- package/dist/transports/admin.d.ts +3 -0
- package/dist/transports/admin.js +74 -0
- package/dist/transports/admin.js.map +1 -0
- package/dist/transports/http.d.ts +14 -0
- package/dist/transports/http.js +283 -0
- package/dist/transports/http.js.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Veertu Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# anka-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for [Anka](https://veertu.com/) macOS virtualization. It runs as a **streamable HTTP server** with bearer-token auth and exposes two curated, purpose-built tool sets that auto-enable based on configuration:
|
|
4
|
+
|
|
5
|
+
- **Controller backend** - talks to the [Anka Build Cloud Controller](https://docs.veertu.com/anka/anka-build-cloud/working-with-controller-and-api/) REST API to request a VM from a fleet and hand back SSH connection details. No `anka` CLI is used.
|
|
6
|
+
- **Local backend** - drives the local `anka` CLI with a small, safe set of lifecycle commands, guarded by a running-VM limit.
|
|
7
|
+
|
|
8
|
+
There is intentionally no generic "run any anka command" tool.
|
|
9
|
+
|
|
10
|
+
## Backends and when they enable
|
|
11
|
+
|
|
12
|
+
| Backend | Enabled when | Tools |
|
|
13
|
+
| ---------- | --------------------------------------------------------- | ----- |
|
|
14
|
+
| Controller | `ANKA_CONTROLLER_URL` is set | `controller_list_templates`, `controller_request_vm`, `controller_get_vm`, `controller_terminate_vm` |
|
|
15
|
+
| Local | `ANKA_LOCAL=on`, or `auto` when no controller is configured (detects the `anka` CLI) | `local_list_templates`, `local_start_vm`, `local_show_vm`, `local_ssh_access`, `local_delete_vm` |
|
|
16
|
+
|
|
17
|
+
When `ANKA_CONTROLLER_URL` is set, the local backend defaults to **off** so only controller tools are exposed. Set `ANKA_LOCAL=on` or `ANKA_LOCAL=auto` to enable both. The server refuses to start if neither backend is enabled.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Node.js >= 18
|
|
22
|
+
- For the local backend: the `anka` CLI installed and on `PATH` (or point `ANKA_BIN` at it)
|
|
23
|
+
- For the controller backend: network access to an Anka Build Cloud Controller
|
|
24
|
+
|
|
25
|
+
## Install and run
|
|
26
|
+
|
|
27
|
+
### From npm
|
|
28
|
+
|
|
29
|
+
Requires Node.js >= 18.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# run without a global install
|
|
33
|
+
npx @veertu/anka-mcp
|
|
34
|
+
|
|
35
|
+
# or install globally
|
|
36
|
+
npm install -g @veertu/anka-mcp
|
|
37
|
+
anka-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Set auth and backend env vars before starting (see [Configuration](#configuration)). Example:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
|
|
44
|
+
echo "MCP_AUTH_TOKEN=$MCP_AUTH_TOKEN"
|
|
45
|
+
anka-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From source (contributors)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run build
|
|
53
|
+
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
|
|
54
|
+
echo "MCP_AUTH_TOKEN=$MCP_AUTH_TOKEN"
|
|
55
|
+
npm start
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For **multi-client** deployments, use the admin API instead of a single shared token:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export MCP_ADMIN_TOKEN="$(openssl rand -hex 32)"
|
|
62
|
+
export ANKA_CONTROLLER_URL="http://your-controller:8090"
|
|
63
|
+
npm start
|
|
64
|
+
|
|
65
|
+
# Create a per-client MCP token (plaintext shown once):
|
|
66
|
+
curl -s -X POST "http://localhost:9111/admin/tokens" \
|
|
67
|
+
-H "Authorization: Bearer $MCP_ADMIN_TOKEN" \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-d '{"label":"team-a"}'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The endpoint is served at `http://<host>:<port>/mcp`. For local dev without auth (never expose beyond localhost):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
MCP_ALLOW_NO_AUTH=1 npm run dev
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Use-case 1: Controller fleet
|
|
79
|
+
|
|
80
|
+
The agent asks the MCP server for a VM; the server generates a temporary SSH key, passes it to the VM via the controller `startup_script` (with `startup_script_condition: 1` so the script runs immediately, before networking), waits until the controller reports the instance started and an SSH auth probe with that key succeeds over the forwarded port, then returns the host IP, forwarded SSH port, private key path, and a ready-to-use `ssh` command. The agent then SSHes in itself.
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart LR
|
|
84
|
+
agent["AI agent"] -->|"controller_request_vm {vmid}"| mcp["anka-mcp"]
|
|
85
|
+
mcp -->|"POST /api/v1/vm"| ctl["Controller API"]
|
|
86
|
+
mcp -->|"poll GET /api/v1/vm?id="| ctl
|
|
87
|
+
mcp -->|"{host, port, username, private_key_path, command}"| agent
|
|
88
|
+
agent -->|"ssh -p port username@host"| vm["macOS VM"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The VM template must expose port forwarding for the SSH guest port (default `22`, e.g. a `port-forward-22` tag), or pass `addSshPortForward: true` to `controller_request_vm` to add a rule at start time.
|
|
92
|
+
|
|
93
|
+
## Use-case 2: Local laptop
|
|
94
|
+
|
|
95
|
+
The agent manages VMs on the developer's own machine through a limited command set: list templates, start (which always clones the chosen template into a fresh VM so the original is never touched), show, prepare SSH access, delete. A running-VM limit (default 2) prevents exceeding the local Anka concurrency limit. The delete tool always requires a specific VM name and can never delete all VMs.
|
|
96
|
+
|
|
97
|
+
For SSH, `local_ssh_access` generates a throwaway ed25519 keypair on the host, copies the public key into the running VM with `anka cp`, installs it into the VM's `~/.ssh/authorized_keys` (via `anka run`), and returns the private key path plus a ready-to-use `ssh` command. The agent (on the same machine) then connects directly to the VM's shared-network IP. The VM template must have Remote Login (sshd) enabled.
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
All configuration is via environment variables.
|
|
102
|
+
|
|
103
|
+
### HTTP transport + auth
|
|
104
|
+
|
|
105
|
+
| Variable | Default | Description |
|
|
106
|
+
| --------------------- | --------- | --------------------------------------------------------------------- |
|
|
107
|
+
| `MCP_HTTP_PORT` | `9111` | Port the HTTP server listens on. |
|
|
108
|
+
| `MCP_HTTP_HOST` | `127.0.0.1` | Interface to bind to. Defaults to localhost; set `0.0.0.0` for remote access behind TLS. |
|
|
109
|
+
| `MCP_AUTH_TOKEN` | (none) | Legacy single bearer token for all MCP clients. Still supported. |
|
|
110
|
+
| `MCP_ADMIN_TOKEN` | (none) | Admin bearer token for `/admin/*` routes. Enables token management. |
|
|
111
|
+
| `MCP_DB_PATH` | `./anka-mcp.db` | SQLite database for client tokens and instance ownership. |
|
|
112
|
+
| `MCP_REVOKE_CLEANUP` | `on` | When a token is revoked, terminate its controller VMs (set `off` to skip). |
|
|
113
|
+
| `MCP_ALLOW_NO_AUTH` | `false` | Set to `1` to run unauthenticated (local dev only). |
|
|
114
|
+
| `MCP_ALLOWED_ORIGINS` | (none) | Comma-separated Origin allow-list for DNS-rebinding protection. |
|
|
115
|
+
| `MCP_LOG` | `on` | Request logging to stderr. Set to `off` (or `0`/`false`/`no`) to disable. |
|
|
116
|
+
| `MCP_AUDIT_LOG` | (none) | Optional append-only audit log file (duplicates stderr log lines). |
|
|
117
|
+
| `MCP_MAX_BODY_BYTES` | `1048576` | Max JSON request body size (1 MiB). |
|
|
118
|
+
| `MCP_RATE_LIMIT_RPM` | `120` | Max requests per client IP per minute (`0` = disabled). |
|
|
119
|
+
| `MCP_SESSION_IDLE_MS` | `3600000` | Idle MCP session eviction threshold (1 hour). |
|
|
120
|
+
| `MCP_MAX_SESSIONS` | `50` | Max concurrent MCP sessions. |
|
|
121
|
+
| `MCP_MAX_RESPONSE_CHARS` | `32768` | Max serialized tool response size (32 KiB). |
|
|
122
|
+
|
|
123
|
+
When logging is enabled, each MCP request is written to stderr with the client source (IP and user-agent), JSON-RPC method, tool name and arguments, tool response (with passwords and private keys redacted), and any underlying `anka` or controller API calls. Example:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
anka-mcp: 2026-06-22T16:52:15.021Z [127.0.0.1 (Cursor/1.x)] mcp tools/call {"tool":"local_list_templates","args":{}}
|
|
127
|
+
anka-mcp: 2026-06-22T16:52:15.059Z [127.0.0.1 (Cursor/1.x)] anka anka -j list -> ok
|
|
128
|
+
anka-mcp: 2026-06-22T16:52:15.059Z [127.0.0.1 (Cursor/1.x)] tool local_list_templates args={} -> {"vms":[...]}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Controller backend
|
|
132
|
+
|
|
133
|
+
| Variable | Default | Description |
|
|
134
|
+
| --------------------------------- | -------- | ------------------------------------------------------------------ |
|
|
135
|
+
| `ANKA_CONTROLLER_URL` | (none) | Base URL, e.g. `http://anka.controller:8090`. Enables the backend. |
|
|
136
|
+
| `ANKA_CONTROLLER_AUTH` | (none) | Raw `Authorization` header value (root token / UAK / Basic). |
|
|
137
|
+
| `ANKA_CONTROLLER_TLS_INSECURE` | `false` | Set to `1` to skip TLS certificate verification. |
|
|
138
|
+
| `ANKA_CONTROLLER_POLL_INTERVAL_MS`| `3000` | Interval between instance-status polls. |
|
|
139
|
+
| `ANKA_CONTROLLER_START_TIMEOUT_MS`| `180000` | Max time to wait for a VM to become SSH-ready. |
|
|
140
|
+
| `ANKA_CONTROLLER_SSH_PROBE` | `on` | When enabled, `controller_request_vm` runs an SSH auth probe with the generated key before returning (set to `0` to skip). |
|
|
141
|
+
|
|
142
|
+
### Local backend
|
|
143
|
+
|
|
144
|
+
| Variable | Default | Description |
|
|
145
|
+
| -------------------- | ------- | ------------------------------------------------------------ |
|
|
146
|
+
| `ANKA_LOCAL` | `auto`* | `auto` (detect the binary), `on`, or `off`. \*Defaults to `off` when `ANKA_CONTROLLER_URL` is set. |
|
|
147
|
+
| `ANKA_BIN` | `anka` | Path to (or name of) the anka binary. |
|
|
148
|
+
| `ANKA_TIMEOUT_MS` | `300000`| Max time a single anka invocation may run. |
|
|
149
|
+
| `ANKA_LOCAL_MAX_VMS` | `2` | Max running VMs allowed before start is refused. |
|
|
150
|
+
| `ANKA_LOCAL_POLL_INTERVAL_MS` | `2000` | Interval between status polls while waiting for a VM's IP. |
|
|
151
|
+
| `ANKA_LOCAL_IP_TIMEOUT_MS` | `120000` | Max time `local_start_vm`/`local_ssh_access` wait for an IP. |
|
|
152
|
+
|
|
153
|
+
### SSH connection details (returned to the agent)
|
|
154
|
+
|
|
155
|
+
| Variable | Default | Description |
|
|
156
|
+
| --------------------- | ------- | ---------------------------------------------------- |
|
|
157
|
+
| `ANKA_VM_SSH_USER` | `anka` | Username returned for SSHing into a VM. |
|
|
158
|
+
| `ANKA_VM_SSH_PASSWORD`| `admin` | Password returned for SSHing into a VM. |
|
|
159
|
+
| `ANKA_VM_SSH_GUEST_PORT` | `22` | Guest port that maps to SSH (matched in port forwarding). |
|
|
160
|
+
|
|
161
|
+
Returned `ssh` commands include `-o IdentitiesOnly=yes` so a local ssh-agent does not offer other keys and cause auth failures. If you build your own command, use that flag or prefix with `SSH_AUTH_SOCK=` to disable the agent.
|
|
162
|
+
|
|
163
|
+
For production, terminate TLS in front of this server so the bearer token and any returned credentials are not sent in cleartext.
|
|
164
|
+
|
|
165
|
+
See [SECURITY.md](SECURITY.md) for the full operator security guide.
|
|
166
|
+
|
|
167
|
+
### Remote deployment
|
|
168
|
+
|
|
169
|
+
By default the server binds to **localhost only** (`127.0.0.1`). To expose it on a network:
|
|
170
|
+
|
|
171
|
+
1. Set `MCP_HTTP_HOST=0.0.0.0` (or a specific interface).
|
|
172
|
+
2. Terminate **TLS** in a reverse proxy in front of anka-mcp.
|
|
173
|
+
3. Firewall to trusted clients only.
|
|
174
|
+
4. Issue **per-client tokens** via the admin API — avoid sharing `MCP_AUTH_TOKEN`.
|
|
175
|
+
5. Never use `MCP_ALLOW_NO_AUTH` on non-localhost hosts.
|
|
176
|
+
|
|
177
|
+
The server prints a warning at startup when bound to a non-loopback address.
|
|
178
|
+
|
|
179
|
+
### Multi-client tokens and VM isolation
|
|
180
|
+
|
|
181
|
+
When `MCP_ADMIN_TOKEN` is set, the server exposes an admin API to create and revoke per-client MCP bearer tokens. Tokens are stored in SQLite (`MCP_DB_PATH`); only hashed secrets are persisted.
|
|
182
|
+
|
|
183
|
+
| Method | Path | Auth | Purpose |
|
|
184
|
+
| -------- | -------------------- | ----------------------- | -------------------------------- |
|
|
185
|
+
| `POST` | `/admin/tokens` | `Bearer MCP_ADMIN_TOKEN` | Create a client token |
|
|
186
|
+
| `GET` | `/admin/tokens` | admin bearer | List tokens (no secrets) |
|
|
187
|
+
| `DELETE` | `/admin/tokens/:id` | admin bearer | Revoke token and clean up VMs |
|
|
188
|
+
|
|
189
|
+
Admin and MCP tokens are separate: the admin token never works on `/mcp`, and client tokens never work on `/admin/*`.
|
|
190
|
+
|
|
191
|
+
**Controller VM isolation:** each client token can only `controller_get_vm` / `controller_terminate_vm` instances it created via `controller_request_vm`. Revoking a token blocks MCP access immediately and, by default (`MCP_REVOKE_CLEANUP=on`), best-effort terminates all controller instances owned by that token. The revoke response includes `cleanup.terminated` and `cleanup.failed` arrays.
|
|
192
|
+
|
|
193
|
+
The legacy `MCP_AUTH_TOKEN` still works as a single shared client identity (`legacy`); all controller VMs created under it share one ownership bucket.
|
|
194
|
+
|
|
195
|
+
Back up `anka-mcp.db` for disaster recovery; it is created automatically on first start.
|
|
196
|
+
|
|
197
|
+
## Tools
|
|
198
|
+
|
|
199
|
+
### Controller
|
|
200
|
+
|
|
201
|
+
- `controller_list_templates` - list registry templates (`id`, `name`, `arch`) to find a `vmid`.
|
|
202
|
+
- `controller_request_vm` `{ vmid, tag?, name?, externalId?, addSshPortForward? }` - start one VM, install a temporary SSH key via the controller `startup_script`, wait until SSH auth succeeds over the forwarded port, return `{ instance_id, ssh: { host, port, username, private_key_path, command } }`. The controller `external_id` is auto-filled with MCP client, IP, user-agent, session, and credential id; pass `externalId` to append a custom `ref`.
|
|
203
|
+
- `controller_get_vm` `{ instance_id }` - current state and SSH details for an existing instance (must be owned by the caller's token).
|
|
204
|
+
- `controller_terminate_vm` `{ instance_id }` - terminate an instance (must be owned by the caller's token).
|
|
205
|
+
|
|
206
|
+
### Local
|
|
207
|
+
|
|
208
|
+
- `local_list_templates` - list the local VM library to find a template to clone from.
|
|
209
|
+
- `local_start_vm` `{ template, name?, wait?, timeoutSeconds? }` - clone the given template into a fresh, disposable VM and start it. The original template is never started or modified. `name` defaults to an auto-generated name (`mcp-<id>`). Subject to the running-VM limit. By default waits for the new VM to boot and obtain an IP, then returns `{ ok, name, source, ip }`; pass `wait: false` to return immediately. Delete with `local_delete_vm` when done.
|
|
210
|
+
- `local_show_vm` `{ name }` - get a VM's IP address.
|
|
211
|
+
- `local_ssh_access` `{ name }` - install a temporary SSH key into a running VM (waiting for a pending IP if needed); returns `{ ip, port, user, private_key_path, command }`.
|
|
212
|
+
- `local_delete_vm` `{ name }` - delete one specific VM.
|
|
213
|
+
|
|
214
|
+
VM/template names that start with `-` are rejected so a name can never be reinterpreted as a CLI flag.
|
|
215
|
+
|
|
216
|
+
## Connecting a client
|
|
217
|
+
|
|
218
|
+
Point any MCP client that supports streamable HTTP at `http://<host>:<port>/mcp` with the bearer token. For example, Cursor's `mcp.json`:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"mcpServers": {
|
|
223
|
+
"anka": {
|
|
224
|
+
"url": "http://your-host:9111/mcp",
|
|
225
|
+
"headers": {
|
|
226
|
+
"Authorization": "Bearer YOUR_TOKEN"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Publishing (maintainers)
|
|
234
|
+
|
|
235
|
+
Releases are published to npm by the [Publish](.github/workflows/publish.yml) workflow when a `v*` tag is pushed. Auth uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) — no long-lived npm tokens in GitHub secrets.
|
|
236
|
+
|
|
237
|
+
### First release (bootstrap)
|
|
238
|
+
|
|
239
|
+
Trusted publishing only works once `@veertu/anka-mcp` **already exists** on npm. The first version must be published once with token or interactive auth that satisfies the `@veertu` org's 2FA policy.
|
|
240
|
+
|
|
241
|
+
**Option A — interactive login (simplest):**
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm login # use an account with publish access to @veertu; complete 2FA when prompted
|
|
245
|
+
npm ci
|
|
246
|
+
npm run build
|
|
247
|
+
npm test
|
|
248
|
+
npm publish --access public
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Option B — granular access token (for CI-style bootstrap):**
|
|
252
|
+
|
|
253
|
+
1. On [npmjs.com](https://www.npmjs.com) → **Access Tokens** → **Generate New Token** → **Granular Access Token**
|
|
254
|
+
2. Permissions: **Read and write** for the `@veertu` scope (or this package)
|
|
255
|
+
3. Enable **Bypass two-factor authentication for automation** (required when the org mandates 2FA for publish)
|
|
256
|
+
4. Publish:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
export NODE_AUTH_TOKEN="npm_..."
|
|
260
|
+
npm ci && npm run build && npm test
|
|
261
|
+
npm publish --access public
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
If you see `403 ... Two-factor authentication or granular access token with bypass 2fa enabled is required`, your current login/token does not meet the org policy — use one of the options above.
|
|
265
|
+
|
|
266
|
+
Then on [npmjs.com](https://www.npmjs.com/package/@veertu/anka-mcp) → **Settings** → **Trusted publishing**, add a GitHub Actions publisher:
|
|
267
|
+
|
|
268
|
+
| Field | Value |
|
|
269
|
+
| ----- | ----- |
|
|
270
|
+
| Organization or user | `veertuinc` |
|
|
271
|
+
| Repository | `anka-mcp` |
|
|
272
|
+
| Workflow filename | `publish.yml` |
|
|
273
|
+
| Allowed actions | `npm publish` |
|
|
274
|
+
|
|
275
|
+
After the package exists and the trusted publisher is configured, push tags (e.g. `v0.1.1`) and CI publishes via OIDC. Provenance is generated automatically.
|
|
276
|
+
|
|
277
|
+
If publish fails with `404 Not Found` on PUT, check: (1) package bootstrapped on npm, (2) trusted publisher fields match exactly, (3) workflow uses Node 24+ (npm 11.5.1+).
|
|
278
|
+
|
|
279
|
+
## Testing
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npm test # run the suite once
|
|
283
|
+
npm run test:watch # watch mode
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The suite (Vitest) is hermetic - it needs neither a real Anka install nor a controller:
|
|
287
|
+
|
|
288
|
+
- Unit tests cover config parsing, token store, the controller client + `extractSsh`/`isSshReady` (against an in-process mock controller), and input validation / output shaping.
|
|
289
|
+
- End-to-end tests spawn the real server over HTTP and exercise tools through the MCP protocol, using a fake `anka` binary ([test/fixtures/fake-anka.mjs](test/fixtures/fake-anka.mjs)) and a mock controller ([test/helpers/controllerMock.ts](test/helpers/controllerMock.ts)). They verify auth, admin token API, per-token controller VM isolation, revoke cleanup, backend gating, per-backend tool exposure, the controller request->SSH flow, the local 2-VM guard, flag-injection rejection, and the SSH key-injection flow.
|
|
290
|
+
|
|
291
|
+
## Adding new tools
|
|
292
|
+
|
|
293
|
+
1. Create a file under `src/tools/controller/` or `src/tools/local/` exporting a tool via `defineTool({ name, config, handler })`.
|
|
294
|
+
2. Add it to the array in that backend's `index.ts` (`controllerTools` or `localTools`). It is then auto-registered whenever that backend is enabled.
|
|
295
|
+
|
|
296
|
+
## Project layout
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
src/
|
|
300
|
+
index.ts # entry: starts the HTTP server
|
|
301
|
+
auth.ts # MCP bearer token resolution
|
|
302
|
+
config.ts # env-driven config + backend detection
|
|
303
|
+
anka.ts # runAnka(): execFile wrapper + JSON-envelope parsing
|
|
304
|
+
controller.ts # Anka Build Cloud Controller API client
|
|
305
|
+
server.ts # createServer(): McpServer + registerTools
|
|
306
|
+
tokens/
|
|
307
|
+
store.ts # SQLite token + instance ownership store
|
|
308
|
+
schema.ts # DB migrations
|
|
309
|
+
ownership.ts # controller instance access helpers
|
|
310
|
+
cleanup.ts # revoke-time controller VM termination
|
|
311
|
+
tools/
|
|
312
|
+
define-tool.ts # defineTool helper + jsonResult
|
|
313
|
+
index.ts # registers enabled backends' tools
|
|
314
|
+
controller/ # controller_* tools
|
|
315
|
+
local/ # local_* tools (+ vms.ts: list/count/guard/name schema)
|
|
316
|
+
transports/
|
|
317
|
+
http.ts # streamable HTTP transport + auth/origin middleware
|
|
318
|
+
admin.ts # /admin/tokens routes
|
|
319
|
+
test/
|
|
320
|
+
fixtures/fake-anka.mjs # fake anka CLI for hermetic local-backend tests
|
|
321
|
+
helpers/ # mock controller + MCP-over-HTTP client
|
|
322
|
+
unit/ # config, controller client, validation/shaping
|
|
323
|
+
e2e/ # full server over HTTP (auth, controller, local)
|
|
324
|
+
```
|
package/dist/anka.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON envelope every `anka -j <command>` call returns, e.g.
|
|
3
|
+
* `{"status":"OK","body":[...]}` or `{"status":"ERROR","message":"..."}`.
|
|
4
|
+
*/
|
|
5
|
+
export interface AnkaEnvelope {
|
|
6
|
+
status: "OK" | "ERROR" | string;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
message?: string;
|
|
9
|
+
code?: number;
|
|
10
|
+
exception_type?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RunAnkaOptions {
|
|
13
|
+
/** Prepend `-j` so anka emits its JSON envelope and we parse it. Default true. */
|
|
14
|
+
machineReadable?: boolean;
|
|
15
|
+
/** Override the per-call timeout in ms. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Normalized result returned by {@link runAnka}. */
|
|
19
|
+
export interface AnkaResult {
|
|
20
|
+
/** True when the process exited 0 and (if parsed) the envelope status was OK. */
|
|
21
|
+
ok: boolean;
|
|
22
|
+
/** Parsed envelope status, when machine-readable output was parsed. */
|
|
23
|
+
status?: string;
|
|
24
|
+
/** Parsed `body` field from the envelope, when present. */
|
|
25
|
+
body?: unknown;
|
|
26
|
+
/** Parsed `message` field from the envelope, when present. */
|
|
27
|
+
message?: string;
|
|
28
|
+
/** Process exit code (0 on success). */
|
|
29
|
+
exitCode: number;
|
|
30
|
+
/** The exact argument vector passed to the anka binary. */
|
|
31
|
+
args: string[];
|
|
32
|
+
/** Raw stdout. */
|
|
33
|
+
stdout: string;
|
|
34
|
+
/** Raw stderr. */
|
|
35
|
+
stderr: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run the anka CLI with the given argument vector. Uses execFile (no shell) so
|
|
39
|
+
* arguments are passed verbatim and are not subject to shell injection.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runAnka(args: string[], options?: RunAnkaOptions): Promise<AnkaResult>;
|
package/dist/anka.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { config } from "./config.js";
|
|
3
|
+
import { logAnkaCommand } from "./log.js";
|
|
4
|
+
function isExecFailure(error) {
|
|
5
|
+
return typeof error === "object" && error !== null && "message" in error;
|
|
6
|
+
}
|
|
7
|
+
function tryParseEnvelope(stdout) {
|
|
8
|
+
const trimmed = stdout.trim();
|
|
9
|
+
if (!trimmed)
|
|
10
|
+
return undefined;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(trimmed);
|
|
13
|
+
if (parsed && typeof parsed === "object")
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Not JSON (e.g. a non machine-readable command); leave undefined.
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run the anka CLI with the given argument vector. Uses execFile (no shell) so
|
|
23
|
+
* arguments are passed verbatim and are not subject to shell injection.
|
|
24
|
+
*/
|
|
25
|
+
export function runAnka(args, options = {}) {
|
|
26
|
+
const { machineReadable = true, timeoutMs = config.ankaTimeoutMs } = options;
|
|
27
|
+
const finalArgs = machineReadable ? ["-j", ...args] : [...args];
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
execFile(config.ankaBin, finalArgs, { timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
30
|
+
const envelope = machineReadable ? tryParseEnvelope(stdout) : undefined;
|
|
31
|
+
if (error) {
|
|
32
|
+
const failure = isExecFailure(error)
|
|
33
|
+
? error
|
|
34
|
+
: { message: String(error) };
|
|
35
|
+
const result = {
|
|
36
|
+
ok: false,
|
|
37
|
+
status: envelope?.status,
|
|
38
|
+
body: envelope?.body,
|
|
39
|
+
message: envelope?.message ?? failure.message,
|
|
40
|
+
exitCode: typeof failure.code === "number" ? failure.code : 1,
|
|
41
|
+
args: finalArgs,
|
|
42
|
+
stdout,
|
|
43
|
+
stderr
|
|
44
|
+
};
|
|
45
|
+
logAnkaCommand(finalArgs, { ok: false, message: result.message });
|
|
46
|
+
resolve(result);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const status = envelope?.status;
|
|
50
|
+
const ok = status ? status === "OK" : true;
|
|
51
|
+
logAnkaCommand(finalArgs, { ok, message: envelope?.message });
|
|
52
|
+
resolve({
|
|
53
|
+
ok,
|
|
54
|
+
status,
|
|
55
|
+
body: envelope?.body,
|
|
56
|
+
message: envelope?.message,
|
|
57
|
+
exitCode: 0,
|
|
58
|
+
args: finalArgs,
|
|
59
|
+
stdout,
|
|
60
|
+
stderr
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=anka.js.map
|
package/dist/anka.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anka.js","sourceRoot":"","sources":["../src/anka.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ResolvedMcpCredential {
|
|
2
|
+
credentialId: string;
|
|
3
|
+
credentialLabel?: string;
|
|
4
|
+
}
|
|
5
|
+
/** Constant-time string comparison that tolerates differing lengths. */
|
|
6
|
+
export declare function safeEqual(a: string, b: string): boolean;
|
|
7
|
+
/** Resolve a bearer token to an MCP client credential identity, or null when invalid. */
|
|
8
|
+
export declare function resolveMcpCredential(bearerToken: string): ResolvedMcpCredential | null;
|
|
9
|
+
/** Whether the server has any configured MCP client authentication path. */
|
|
10
|
+
export declare function isMcpAuthConfigured(): boolean;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { config } from "./config.js";
|
|
3
|
+
import { getTokenStore, LEGACY_CREDENTIAL_ID } from "./tokens/store.js";
|
|
4
|
+
/** Constant-time string comparison that tolerates differing lengths. */
|
|
5
|
+
export function safeEqual(a, b) {
|
|
6
|
+
const bufA = Buffer.from(a);
|
|
7
|
+
const bufB = Buffer.from(b);
|
|
8
|
+
if (bufA.length !== bufB.length)
|
|
9
|
+
return false;
|
|
10
|
+
return timingSafeEqual(bufA, bufB);
|
|
11
|
+
}
|
|
12
|
+
/** Resolve a bearer token to an MCP client credential identity, or null when invalid. */
|
|
13
|
+
export function resolveMcpCredential(bearerToken) {
|
|
14
|
+
if (config.allowNoAuth) {
|
|
15
|
+
return { credentialId: "anonymous" };
|
|
16
|
+
}
|
|
17
|
+
if (!bearerToken)
|
|
18
|
+
return null;
|
|
19
|
+
if (config.authToken && safeEqual(bearerToken, config.authToken)) {
|
|
20
|
+
return { credentialId: LEGACY_CREDENTIAL_ID, credentialLabel: "legacy" };
|
|
21
|
+
}
|
|
22
|
+
const validated = getTokenStore().validateToken(bearerToken);
|
|
23
|
+
if (validated) {
|
|
24
|
+
return { credentialId: validated.id, credentialLabel: validated.label || undefined };
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** Whether the server has any configured MCP client authentication path. */
|
|
29
|
+
export function isMcpAuthConfigured() {
|
|
30
|
+
if (config.allowNoAuth)
|
|
31
|
+
return true;
|
|
32
|
+
if (config.authToken)
|
|
33
|
+
return true;
|
|
34
|
+
if (config.adminToken)
|
|
35
|
+
return true;
|
|
36
|
+
try {
|
|
37
|
+
return getTokenStore().hasActiveTokens();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAOxE,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,oBAAoB,CAAC,WAAmB;IACtD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAE9B,IAAI,MAAM,CAAC,SAAS,IAAI,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACjE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7D,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,EAAE,eAAe,EAAE,SAAS,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;IACvF,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB;IACjC,IAAI,MAAM,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,MAAM,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,MAAM,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,aAAa,EAAE,CAAC,eAAe,EAAE,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface AnkaMcpConfig {
|
|
2
|
+
/** Port the HTTP server listens on. */
|
|
3
|
+
httpPort: number;
|
|
4
|
+
/** Host/interface the HTTP server binds to. */
|
|
5
|
+
httpHost: string;
|
|
6
|
+
/** Bearer token clients must present. Empty only when auth is explicitly disabled. */
|
|
7
|
+
authToken: string;
|
|
8
|
+
/** Admin bearer token for /admin/* routes. Empty disables the admin API. */
|
|
9
|
+
adminToken: string;
|
|
10
|
+
/** SQLite database path for client tokens and instance ownership. */
|
|
11
|
+
dbPath: string;
|
|
12
|
+
/** When true, revoking a token terminates its owned controller instances. */
|
|
13
|
+
revokeCleanupEnabled: boolean;
|
|
14
|
+
/** When true, the server runs without authentication (local dev only). */
|
|
15
|
+
allowNoAuth: boolean;
|
|
16
|
+
/** Allowed Origin header values for DNS-rebinding protection. Empty = unrestricted. */
|
|
17
|
+
allowedOrigins: string[];
|
|
18
|
+
/** When false, request/tool/backend logging to stderr is suppressed. */
|
|
19
|
+
logEnabled: boolean;
|
|
20
|
+
/** Optional append-only audit log file path. Empty = stderr only. */
|
|
21
|
+
auditLogPath: string;
|
|
22
|
+
/** Max JSON request body size in bytes. */
|
|
23
|
+
maxBodyBytes: number;
|
|
24
|
+
/** Max requests per client IP per minute (0 = disabled). */
|
|
25
|
+
rateLimitRpm: number;
|
|
26
|
+
/** Idle MCP session eviction threshold in ms. */
|
|
27
|
+
sessionIdleMs: number;
|
|
28
|
+
/** Max concurrent MCP sessions. */
|
|
29
|
+
maxSessions: number;
|
|
30
|
+
/** Max serialized tool response size in characters. */
|
|
31
|
+
maxResponseChars: number;
|
|
32
|
+
/** True when the local anka CLI tool set should be exposed. */
|
|
33
|
+
localEnabled: boolean;
|
|
34
|
+
/** Path to (or name of) the anka CLI binary. */
|
|
35
|
+
ankaBin: string;
|
|
36
|
+
/** Max time in ms a single anka invocation may run before being killed. */
|
|
37
|
+
ankaTimeoutMs: number;
|
|
38
|
+
/** Max number of running VMs the local backend will allow. */
|
|
39
|
+
localMaxVms: number;
|
|
40
|
+
/** Interval between local VM status polls (waiting for an IP), in ms. */
|
|
41
|
+
localPollIntervalMs: number;
|
|
42
|
+
/** Max time to wait for a local VM to obtain an IP after starting, in ms. */
|
|
43
|
+
localIpTimeoutMs: number;
|
|
44
|
+
/** True when the controller tool set should be exposed. */
|
|
45
|
+
controllerEnabled: boolean;
|
|
46
|
+
/** Base URL of the Anka Build Cloud Controller (e.g. http://anka.controller:8090). */
|
|
47
|
+
controllerUrl: string;
|
|
48
|
+
/** Raw value for the Authorization header sent to the controller (optional). */
|
|
49
|
+
controllerAuth: string;
|
|
50
|
+
/** When true, TLS certificate verification against the controller is skipped. */
|
|
51
|
+
controllerTlsInsecure: boolean;
|
|
52
|
+
/** Interval between instance-status polls, in ms. */
|
|
53
|
+
controllerPollIntervalMs: number;
|
|
54
|
+
/** Max time to wait for a requested VM to become SSH-ready, in ms. */
|
|
55
|
+
controllerStartTimeoutMs: number;
|
|
56
|
+
/** When true, controller_request_vm verifies SSH key auth before returning. */
|
|
57
|
+
controllerSshProbeEnabled: boolean;
|
|
58
|
+
/** Username the agent should use to SSH into a VM. */
|
|
59
|
+
vmSshUser: string;
|
|
60
|
+
/** Password the agent should use to SSH into a VM. */
|
|
61
|
+
vmSshPassword: string;
|
|
62
|
+
/** Guest port that maps to SSH inside the VM (forwarded on the host). */
|
|
63
|
+
vmSshGuestPort: number;
|
|
64
|
+
serverName: string;
|
|
65
|
+
serverVersion: string;
|
|
66
|
+
}
|
|
67
|
+
/** Build the runtime config from environment variables (read once at startup). */
|
|
68
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv): AnkaMcpConfig;
|
|
69
|
+
export declare const config: AnkaMcpConfig;
|