lattice-mcp 1.1.0 → 1.1.2

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 (4) hide show
  1. package/AGENTS.md +49 -24
  2. package/README.md +171 -72
  3. package/index.js +39 -12
  4. package/package.json +3 -2
package/AGENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > `lattice-mcp` is the **Model Context Protocol server for Lattice**, the container
4
4
  > orchestration platform that runs every `appleby.cloud` service. It exposes the
5
- > `lattice-api` admin surface to Claude Code as **125 typed tools** — workers, stacks,
5
+ > `lattice-api` admin surface to Claude Code as **126 typed tools** — workers, stacks,
6
6
  > containers, deployments, databases, registries, networks, volumes and instance config.
7
7
  > This file orients any agent/worker before touching code in this repo.
8
8
  >
@@ -32,8 +32,9 @@ Those live in [`lattice-api`](https://github.com/aidenappl/lattice-api) and
32
32
  - **Runtime:** Node ≥18 (needs global `fetch` and `AbortSignal.timeout`). `"type": "module"` —
33
33
  ESM only, top-level `await` is used at the bottom of `index.js`.
34
34
  - **`@modelcontextprotocol/sdk` ^1.29.0** — `McpServer` + `StdioServerTransport`.
35
- - **`zod`** — argument schemas. Supplied transitively by the MCP SDK; it is *not* a declared
36
- dependency, which is a latent fragility (see Rules & guardrails).
35
+ - **`zod` ^4.4.3** — argument schemas. Declared explicitly as of **1.1.1** (it was previously
36
+ only resolved transitively through the MCP SDK, a latent fragility); the range matches the
37
+ version the SDK resolves.
37
38
  - No build step, no bundler, no tests, no lint config. `node --check index.js` is the only
38
39
  static gate.
39
40
 
@@ -41,7 +42,7 @@ Those live in [`lattice-api`](https://github.com/aidenappl/lattice-api) and
41
42
 
42
43
  | Path | Role |
43
44
  |------|------|
44
- | `index.js` | Everything: `--setup` flow, config read, `api()` HTTP helper, `text()`/`body()` helpers, all 125 `server.tool(...)` registrations, transport connect. |
45
+ | `index.js` | Everything: `--setup` flow, config read, `api()` HTTP helper, `text()`/`body()` helpers, all 126 `server.tool(...)` registrations, transport connect. |
45
46
  | `package.json` | npm metadata. `bin.lattice-mcp` → `index.js`, so `npx lattice-mcp` works. |
46
47
  | `README.md` | User-facing setup + full tool table. |
47
48
  | `AGENTS.md` | This file. |
@@ -90,9 +91,12 @@ and supply the real token.
90
91
  the noun (`lattice_list_database_instances`). The prefix is what disambiguates these from
91
92
  `monitor_*` and `forta_*` tools in a shared client.
92
93
  - **`api(method, path, params, body)`** — `params` become query string entries (undefined/null
93
- are dropped), `body` is JSON-encoded. Errors are swallowed and returned as
94
- `{success:false, error_message}` so a network failure surfaces as tool output rather than a
95
- transport crash.
94
+ are dropped), `body` is JSON-encoded. Failures are returned as tool output, never thrown, so
95
+ they surface to the agent rather than crashing the transport. A network-level failure returns
96
+ `{success:false, error_message: "API request failed: …"}`; a response that arrives but is not
97
+ JSON (e.g. a 502/504 HTML page from the TLS proxy) returns
98
+ `{success:false, error_code: <http status>, error_message: "API returned a non-JSON response …"}`
99
+ with a truncated body — bodies are never logged, only returned, since they can carry secrets.
96
100
  - **`body(obj)`** strips `undefined` keys. **Always use it on PUT/PATCH tools.** The API treats
97
101
  a present-but-null field as an explicit clear, so forwarding raw `{...fields}` on an update
98
102
  would wipe every field the caller didn't pass.
@@ -122,28 +126,48 @@ exits immediately if either is missing. In practice these come from the `env` bl
122
126
 
123
127
  **Tool groups**, in file order:
124
128
 
129
+ The counts below sum to **126**, matching the header and `grep -c 'server.tool(' index.js`. The
130
+ first rows are the original, pre-`1.1.0` tools (registered top-of-file with no banner comment);
131
+ every bolded row corresponds to a `// ───` banner group and matches its exact in-file name.
132
+
125
133
  | Group | Tools | Notes |
126
134
  |-------|-------|-------|
127
- | Overview & health | 2 | `lattice_overview`, `lattice_health` |
135
+ | Overview & health | 3 | `lattice_overview`, `lattice_health`, `lattice_get_version` |
128
136
  | Workers | 3 + 4 actions | list/get/metrics; reboot, upgrade, stop-all, start-all |
129
- | Stacks | 2 + 4 actions | list/get; deploy, restart, stop, start, update |
130
- | Containers | 5 + 8 actions | get/list/logs/lifecycle/metrics; start, stop, restart, kill, pause, unpause, remove, recreate |
131
- | Deployments | 4 | list/get/logs, rollback, approve |
132
- | Audit & API tokens | 4 | audit log; token list/create/delete |
133
- | **Database instances** | **11** | CRUD, `lattice_database_action`, credentials, snapshots, restore |
134
- | **Backup destinations** | **6** | CRUD + test |
135
- | **Registries** | **8** | CRUD, test, test-inline, repositories, tags |
136
- | **Discovery & diagnostics** | **7** | search, anomalies, fleet-metrics, versions, refresh-versions, container metrics, self |
137
- | **Stacks — compose & tokens** | **13** | create/delete, compose update/sync/import, export/import, save-template, deploy tokens |
138
- | **Containersdefinition CRUD** | **3** | create/update/delete definitions |
139
- | **Workersresources** | **16** | worker CRUD, tokens, volumes, networks, force-remove |
140
- | **Global env vars / templates / webhooks** | **12** | |
141
- | **Users & instance config** | **12** | users, SSO, SMTP, notification prefs |
137
+ | Stacks | 2 + 5 actions | list/get; deploy, restart, stop, start, update |
138
+ | Containers | 4 + 8 actions | list/get/logs/lifecycle; start, stop, restart, kill, pause, unpause, remove, recreate. (`lattice_get_container_metrics` is *not* here — it lives under **Discovery & diagnostics**.) |
139
+ | Deployments | 4 | list/get/logs, rollback. (`lattice_approve_deployment` is *not* here — it lives under **Stacks — lifecycle, compose & deploy tokens**.) |
140
+ | Instance self-update | 2 | `lattice_update_api`, `lattice_update_web` tell the API/web container to pull its latest image and redeploy itself |
141
+ | Audit & API tokens | 4 | `lattice_get_audit_log`; API token list/create/delete |
142
+ | **Database instances** | **11** | CRUD, `lattice_database_action` (start/stop/restart/remove enum), `lattice_get_database_credentials`, snapshot list/create/restore/delete |
143
+ | **Backup destinations** | **6** | list/get/create/update/delete + `lattice_test_backup_destination` |
144
+ | **Registries** | **8** | list/create/update/delete, `lattice_test_registry`, `lattice_test_registry_inline`, `lattice_list_registry_repositories`, `lattice_list_registry_tags` |
145
+ | **Discovery & diagnostics** | **7** | `lattice_search`, `lattice_get_anomalies`, `lattice_get_fleet_metrics`, `lattice_get_versions`, `lattice_refresh_versions`, `lattice_get_container_metrics`, `lattice_get_self` |
146
+ | **Stackslifecycle, compose & deploy tokens** | **13** | create/delete, `lattice_get_stack_containers`, compose update/sync/import, export/import, `lattice_save_stack_as_template`, deploy-token list/create/delete, `lattice_approve_deployment` |
147
+ | **Containersdefinition CRUD** | **3** | `lattice_create_container`, `lattice_update_container`, `lattice_delete_container` |
148
+ | **Workers registration, tokens, volumes, networks** | **16** | worker create/update/delete, `lattice_get_worker_container_stats`, worker-token ×3, volume ×3, worker-network ×3, `lattice_list_all_networks`, `lattice_delete_network`, `lattice_force_remove_container` |
149
+ | **Global env vars, templates, webhooks** | **12** | env-var CRUD (4), template list/create/delete (3), webhook list/create/update/delete/test (5) |
150
+ | **Users & instance configuration** | **11** | user CRUD (4); SSO get/update (2); SMTP get/update/test (3); notification-prefs get/update (2) |
142
151
 
143
152
  Bolded groups were added in **1.1.0**, closing a gap where the MCP had drifted roughly two
144
153
  months behind `lattice-api` — database instances shipped in the API in May 2026 and were
145
154
  entirely unreachable from the MCP until then.
146
155
 
156
+ **1.1.1** is a bug-fix release (no tool count change). It corrects three request-shape/route bugs
157
+ verified against `lattice-api` handlers: `lattice_get_self` now calls `GET /auth/self` (the old
158
+ `GET /admin/self` route never existed and always 404'd — the endpoint is `HandleAuthSelf` on the
159
+ bearer-authed `/auth` subrouter); `lattice_force_remove_container` now sends `{container_name}` in
160
+ the JSON body (`HandleForceRemoveContainer` reads it there, not from query params, so the old call
161
+ always 400'd); and `lattice_test_backup_destination` now marks `worker_id` as **required** (a query
162
+ param the handler 400s without — the test dispatches over the worker's WebSocket). It also hardens
163
+ `api()` against non-JSON responses (see *How code is written here*) and declares `zod` explicitly.
164
+
165
+ **1.1.2** adds `lattice_get_version` (`GET /version`, so agents can read the deployed API version
166
+ for deploy-drift checks without shelling out to `curl`), and widens two read tools to pass filter
167
+ params the handlers already accept but the tools were dropping: `lattice_get_audit_log` gains
168
+ `user_id` / `action` / `resource_type` / `offset` (answer "who deleted stack X" server-side instead
169
+ of scanning 50 rows), and `lattice_get_container_logs` gains `offset` / `worker_id`. Tool count 125 → 126.
170
+
147
171
  **Consolidations.** Where `lattice-api` exposes several paths served by one handler, this repo
148
172
  exposes one tool with an enum rather than N tools. `lattice_database_action` covers
149
173
  `/start`, `/stop`, `/restart` and `/remove`. Worker actions are the historical exception — they
@@ -187,9 +211,10 @@ predate this convention and remain separate tools.
187
211
  - **Do not break tool names.** They are a public contract: renaming one silently breaks any
188
212
  saved workflow or prompt that referenced it. Add a new tool and deprecate in the description
189
213
  instead.
190
- - **`zod` is used but not declared** in `package.json` it resolves transitively through the
191
- MCP SDK. If the SDK ever drops or hoists it differently, every tool schema breaks at startup.
192
- Adding it as an explicit dependency is the correct fix; do it in a standalone change.
214
+ - **`zod` is declared explicitly** in `package.json` (`^4.4.3`) as of **1.1.1**. It used to
215
+ resolve only transitively through the MCP SDK, which meant a SDK change that dropped or hoisted
216
+ it differently would break every tool schema at startup. Keep the declared range aligned with
217
+ the version the SDK actually resolves (check `package-lock.json`).
193
218
  - **Keep destructive descriptions honest.** If a tool destroys data, the description must say so
194
219
  and name the safer alternative.
195
220
  - Publishing is outward-facing and effectively irreversible (npm unpublish is restricted after
package/README.md CHANGED
@@ -1,18 +1,55 @@
1
1
  # lattice-mcp
2
2
 
3
- MCP server for the [Lattice](https://github.com/aidenappl/lattice-api) container orchestration platform. Gives Claude Code direct access to manage workers, stacks, containers, and deployments.
3
+ Model Context Protocol server for [Lattice](https://github.com/aidenappl/lattice-api), the container orchestration platform that runs every `appleby.cloud` service. Gives Claude Code direct, typed access to workers, stacks, containers, deployments, databases, registries, networks, volumes and instance configuration.
4
4
 
5
- ## Quick Start
5
+ > **appleby.cloud platform** · MCP server · published to npm as `lattice-mcp` · consumed via `npx -y lattice-mcp`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ `lattice-mcp` is a single-file Node ESM program (`index.js`) that speaks MCP over stdio and translates tool calls into HTTP requests against the `lattice-api` admin surface. It exposes **126 typed tools** and holds no business logic, caching or state of its own — every behaviour (pagination, validation, side effects) comes from `lattice-api`.
12
+
13
+ Once configured, ask Claude Code things like:
14
+
15
+ - "What's the status of all stacks?"
16
+ - "Show me logs for the forta-api container"
17
+ - "Which containers are unhealthy?" (`lattice_get_anomalies` is the best first call)
18
+ - "Deploy stack 5" / "Rollback the last deployment on stack 12"
19
+ - "What image tags can I deploy from the registry?"
20
+
21
+ ## Role in the appleby.cloud ecosystem
22
+
23
+ | Repo | Relationship |
24
+ |------|--------------|
25
+ | [`lattice-api`](https://github.com/aidenappl/lattice-api) | The API this wraps — its route table is the source of truth for tool coverage. |
26
+ | [`lattice-web`](https://github.com/aidenappl/lattice-web) | Next.js dashboard over the same API. |
27
+ | [`lattice-runner`](https://github.com/aidenappl/lattice-runner) | Agent on each worker VM; WebSocket back to `lattice-api`. |
28
+ | `monitor-mcp` / `forta-mcp` / `keyring-mcp` / `openbucket-mcp` | Sibling MCP servers, same single-file structure. |
29
+
30
+ ## Tech stack
31
+
32
+ - **Node ≥18** (needs global `fetch` and `AbortSignal.timeout`), ESM (`"type": "module"`).
33
+ - **`@modelcontextprotocol/sdk` ^1.29.0** — `McpServer` + `StdioServerTransport`.
34
+ - **`zod` ^4.4.3** for argument schemas (a declared dependency as of 1.1.1).
35
+ - No build step, no bundler. `node --check index.js` is the only static gate.
36
+
37
+ ## Getting started
38
+
39
+ ### Prerequisites
40
+
41
+ - Node ≥18.
42
+ - A Lattice API URL and API token. Generate a token from the Lattice web dashboard under **Settings > API Tokens**.
43
+
44
+ ### Setup
45
+
46
+ Quickest — interactive setup writes the `lattice` block into `~/.mcp.json`:
6
47
 
7
48
  ```bash
8
49
  npx lattice-mcp --setup
9
50
  ```
10
51
 
11
- This prompts for your Lattice API URL and API token, writes the config to `~/.mcp.json`, and you're ready to go. Restart Claude Code after setup.
12
-
13
- ## Manual Setup
14
-
15
- Add to `~/.mcp.json`:
52
+ Or configure it manually in `~/.mcp.json`:
16
53
 
17
54
  ```json
18
55
  {
@@ -29,21 +66,42 @@ Add to `~/.mcp.json`:
29
66
  }
30
67
  ```
31
68
 
32
- Generate an API token from the Lattice web dashboard under **Settings > API Tokens**.
69
+ Restart Claude Code after setup so the new server and tools are picked up.
70
+
71
+ ### Environment variables
72
+
73
+ | Variable | Required | Description |
74
+ |----------|----------|-------------|
75
+ | `LATTICE_API_URL` | Yes | Lattice API base URL |
76
+ | `LATTICE_API_TOKEN` | Yes | Bearer token for authentication (sent on every request) |
77
+
78
+ ## Development
79
+
80
+ | Command | What it does |
81
+ |---------|--------------|
82
+ | `node index.js --setup` | Interactive setup — writes the `lattice` block into `~/.mcp.json` |
83
+ | `npm install` | Install dependencies (not vendored) |
84
+ | `node --check index.js` | Syntax gate — the only static check that exists |
85
+ | `LATTICE_API_URL=… LATTICE_API_TOKEN=… node index.js` | Run the server on stdio |
86
+ | `grep -c 'server.tool(' index.js` | Confirm the tool count (should be 126) |
87
+ | `npm publish` | Publish to npm — **this is deployment** (requires 2FA passkey from an interactive terminal) |
33
88
 
34
89
  ## Tools
35
90
 
36
- ### Overview & Health
91
+ All 126 tools, grouped as they appear in `index.js`. ⚠️ marks destructive tools; their descriptions state the blast radius.
92
+
93
+ ### Overview & health
37
94
  | Tool | Description |
38
95
  |------|-------------|
39
- | `lattice_overview` | Fleet overview — worker counts, stack counts, failed stacks, CPU/memory |
96
+ | `lattice_overview` | Fleet overview — worker/stack/container counts, failed stacks, CPU/memory |
40
97
  | `lattice_health` | API health and database connectivity |
98
+ | `lattice_get_version` | Deployed lattice-api version string — check deploy drift against GitHub tags |
41
99
 
42
100
  ### Workers
43
101
  | Tool | Description |
44
102
  |------|-------------|
45
- | `lattice_list_workers` | List workers with status, IP, versions |
46
- | `lattice_get_worker` | Detailed worker info |
103
+ | `lattice_list_workers` | List workers with status, IP, versions, heartbeat |
104
+ | `lattice_get_worker` | Detailed worker info including metrics |
47
105
  | `lattice_get_worker_metrics` | CPU, memory, disk, network metrics |
48
106
  | `lattice_reboot_worker` | Reboot a worker machine |
49
107
  | `lattice_upgrade_worker` | Upgrade worker runner to latest |
@@ -55,18 +113,18 @@ Generate an API token from the Lattice web dashboard under **Settings > API Toke
55
113
  |------|-------------|
56
114
  | `lattice_list_stacks` | List stacks with status and worker assignment |
57
115
  | `lattice_get_stack` | Full stack details including compose YAML |
58
- | `lattice_update_stack` | Update stack configuration |
59
116
  | `lattice_deploy_stack` | Deploy a stack (all or specific containers) |
60
117
  | `lattice_restart_stack` | Restart all containers in a stack |
61
118
  | `lattice_stop_stack` | Stop all containers in a stack |
62
119
  | `lattice_start_stack` | Start all containers in a stack |
120
+ | `lattice_update_stack` | Update stack configuration |
63
121
 
64
122
  ### Containers
65
123
  | Tool | Description |
66
124
  |------|-------------|
67
125
  | `lattice_list_containers` | List containers with status, image, ports, health |
68
126
  | `lattice_get_container` | Full container details |
69
- | `lattice_get_container_logs` | Recent container logs (stdout/stderr) |
127
+ | `lattice_get_container_logs` | Recent container logs (stdout/stderr); `offset` paginates into older logs, filter by `stream`/`worker_id` |
70
128
  | `lattice_get_container_lifecycle` | Lifecycle events (start, stop, health changes) |
71
129
  | `lattice_start_container` | Start a stopped container |
72
130
  | `lattice_stop_container` | Stop a running container |
@@ -74,8 +132,8 @@ Generate an API token from the Lattice web dashboard under **Settings > API Toke
74
132
  | `lattice_kill_container` | Force kill a container |
75
133
  | `lattice_pause_container` | Pause a running container |
76
134
  | `lattice_unpause_container` | Unpause a paused container |
77
- | `lattice_remove_container` | Remove a container |
78
- | `lattice_recreate_container` | Remove and recreate a container |
135
+ | `lattice_remove_container` | Remove a container ⚠️ |
136
+ | `lattice_recreate_container` | Remove and recreate a container ⚠️ |
79
137
 
80
138
  ### Deployments
81
139
  | Tool | Description |
@@ -83,36 +141,21 @@ Generate an API token from the Lattice web dashboard under **Settings > API Toke
83
141
  | `lattice_list_deployments` | List deployments with status and timing |
84
142
  | `lattice_get_deployment` | Deployment details with container-level status |
85
143
  | `lattice_get_deployment_logs` | Pull, create, start, swap events with timing |
86
- | `lattice_rollback_deployment` | Rollback to previous state |
144
+ | `lattice_rollback_deployment` | Rollback to previous state ⚠️ |
145
+
146
+ ### Instance self-update
147
+ | Tool | Description |
148
+ |------|-------------|
149
+ | `lattice_update_api` | Trigger the Lattice API container to self-update |
150
+ | `lattice_update_web` | Trigger the Lattice web container to update |
87
151
 
88
- ### System
152
+ ### Audit & API tokens
89
153
  | Tool | Description |
90
154
  |------|-------------|
91
- | `lattice_get_audit_log` | Recent audit log entries |
92
- | `lattice_update_api` | Trigger API self-update |
93
- | `lattice_update_web` | Trigger web container update |
155
+ | `lattice_get_audit_log` | Audit log entries (who did what, when); filter by `user_id`/`action`/`resource_type`, `offset` paginates |
94
156
  | `lattice_list_api_tokens` | List API tokens |
95
157
  | `lattice_create_api_token` | Create a new API token |
96
- | `lattice_delete_api_token` | Delete an API token |
97
-
98
- ## Example Prompts
99
-
100
- - "What's the status of all stacks?"
101
- - "Show me logs for the forta-api container"
102
- - "Deploy stack 5"
103
- - "Which containers are unhealthy?"
104
- - "Rollback the last deployment on stack 12"
105
-
106
- ## Environment Variables
107
-
108
- | Variable | Required | Description |
109
- |----------|----------|-------------|
110
- | `LATTICE_API_URL` | Yes | Lattice API base URL |
111
- | `LATTICE_API_TOKEN` | Yes | API token for authentication |
112
-
113
- ## License
114
-
115
- MIT
158
+ | `lattice_delete_api_token` | Delete an API token ⚠️ |
116
159
 
117
160
  ### Database instances
118
161
  | Tool | Description |
@@ -132,18 +175,22 @@ MIT
132
175
  ### Backup destinations
133
176
  | Tool | Description |
134
177
  |------|-------------|
135
- | `lattice_list_backup_destinations` / `lattice_get_backup_destination` | Inventory |
136
- | `lattice_create_backup_destination` / `lattice_update_backup_destination` | Manage destinations |
137
- | `lattice_delete_backup_destination` | Delete ⚠️ |
138
- | `lattice_test_backup_destination` | Test connectivity without writing a backup |
178
+ | `lattice_list_backup_destinations` | List backup destinations |
179
+ | `lattice_get_backup_destination` | One destination's configuration |
180
+ | `lattice_create_backup_destination` | Create a destination |
181
+ | `lattice_update_backup_destination` | Update a destination |
182
+ | `lattice_delete_backup_destination` | Delete a destination ⚠️ |
183
+ | `lattice_test_backup_destination` | Test connectivity without writing a backup (requires a connected `worker_id`) |
139
184
 
140
185
  ### Registries
141
186
  | Tool | Description |
142
187
  |------|-------------|
143
- | `lattice_list_registries` | Configured registries |
144
- | `lattice_create_registry` / `lattice_update_registry` | Manage registries |
145
- | `lattice_delete_registry` | Delete ⚠️ |
146
- | `lattice_test_registry` / `lattice_test_registry_inline` | Test stored or unsaved credentials |
188
+ | `lattice_list_registries` | Configured container registries |
189
+ | `lattice_create_registry` | Add a registry |
190
+ | `lattice_update_registry` | Update a registry |
191
+ | `lattice_delete_registry` | Delete a registry ⚠️ |
192
+ | `lattice_test_registry` | Test a saved registry's stored credentials |
193
+ | `lattice_test_registry_inline` | Test registry credentials before saving |
147
194
  | `lattice_list_registry_repositories` | What images exist |
148
195
  | `lattice_list_registry_tags` | **What versions are deployable** |
149
196
 
@@ -153,53 +200,105 @@ MIT
153
200
  | `lattice_search` | Search workers, stacks and containers in one call |
154
201
  | `lattice_get_anomalies` | **Restart loops, unhealthy containers, offline workers — best first call** |
155
202
  | `lattice_get_fleet_metrics` | Aggregated fleet CPU/memory/disk/network |
156
- | `lattice_get_versions` / `lattice_refresh_versions` | Runner versions and what's outdated |
203
+ | `lattice_get_versions` | Runner versions and what's outdated |
204
+ | `lattice_refresh_versions` | Re-poll every worker for its current runner version |
157
205
  | `lattice_get_container_metrics` | Per-container metrics over time |
158
206
  | `lattice_get_self` | Which user the token authenticates as |
159
207
 
160
- ### Stacks — compose, export & deploy tokens
208
+ ### Stacks — lifecycle, compose & deploy tokens
161
209
  | Tool | Description |
162
210
  |------|-------------|
163
- | `lattice_create_stack` / `lattice_delete_stack` | Stack lifecycle ⚠️ |
211
+ | `lattice_create_stack` | Create an empty stack |
212
+ | `lattice_delete_stack` | Delete a stack and all its containers ⚠️ |
164
213
  | `lattice_get_stack_containers` | Containers in a stack |
165
- | `lattice_update_stack_compose` / `lattice_sync_stack_compose` | Compose YAML management |
214
+ | `lattice_update_stack_compose` | Replace a stack's compose YAML |
215
+ | `lattice_sync_stack_compose` | Reconcile container records against stored compose YAML |
166
216
  | `lattice_import_compose` | Create a stack from compose YAML |
167
- | `lattice_export_stack` / `lattice_import_stack_export` | Portable stack backup/restore |
217
+ | `lattice_export_stack` | Export a stack's full definition as portable JSON |
218
+ | `lattice_import_stack_export` | Recreate a stack from an export document |
168
219
  | `lattice_save_stack_as_template` | Save a stack as a reusable template |
169
220
  | `lattice_list_deploy_tokens` | CI deploy tokens — `last_used_at` shows whether CI reaches Lattice |
170
- | `lattice_create_deploy_token` / `lattice_delete_deploy_token` | Manage CI deploy tokens ⚠️ |
221
+ | `lattice_create_deploy_token` | Create a CI deploy token |
222
+ | `lattice_delete_deploy_token` | Delete a CI deploy token ⚠️ |
171
223
  | `lattice_approve_deployment` | Approve a deployment awaiting approval |
172
224
 
173
225
  ### Container definitions
174
226
  | Tool | Description |
175
227
  |------|-------------|
176
- | `lattice_create_container` / `lattice_update_container` | Manage container definitions |
177
- | `lattice_delete_container` | Delete definition and container ⚠️ |
228
+ | `lattice_create_container` | Add a container definition to a stack |
229
+ | `lattice_update_container` | Update a container definition |
230
+ | `lattice_delete_container` | Delete definition and its running container ⚠️ |
178
231
 
179
- ### Workers — resources
232
+ ### Workers — registration, tokens, volumes, networks
180
233
  | Tool | Description |
181
234
  |------|-------------|
182
- | `lattice_create_worker` / `lattice_update_worker` / `lattice_delete_worker` | Worker registration ⚠️ |
183
- | `lattice_get_worker_container_stats` | Live per-container stats |
184
- | `lattice_list_worker_tokens` / `lattice_create_worker_token` / `lattice_delete_worker_token` | Runner registration tokens ⚠️ |
185
- | `lattice_list_worker_volumes` / `lattice_create_worker_volume` / `lattice_delete_worker_volume` | Docker volumes ⚠️ |
186
- | `lattice_list_worker_networks` / `lattice_create_worker_network` / `lattice_delete_worker_network` | Docker networks ⚠️ |
187
- | `lattice_list_all_networks` / `lattice_delete_network` | Fleet-wide networks ⚠️ |
235
+ | `lattice_create_worker` | Register a worker |
236
+ | `lattice_update_worker` | Update a worker's name, hostname, IP, status, labels |
237
+ | `lattice_delete_worker` | Delete a worker ⚠️ |
238
+ | `lattice_get_worker_container_stats` | Live per-container stats from one worker |
239
+ | `lattice_list_worker_tokens` | Worker registration tokens |
240
+ | `lattice_create_worker_token` | Create a registration token for a worker |
241
+ | `lattice_delete_worker_token` | Delete a worker token ⚠️ |
242
+ | `lattice_list_worker_volumes` | Docker volumes on a worker |
243
+ | `lattice_create_worker_volume` | Create a Docker volume on a worker |
244
+ | `lattice_delete_worker_volume` | Delete a Docker volume ⚠️ |
245
+ | `lattice_list_worker_networks` | Docker networks on a worker |
246
+ | `lattice_create_worker_network` | Create a Docker network on a worker |
247
+ | `lattice_delete_worker_network` | Delete a Docker network ⚠️ |
248
+ | `lattice_list_all_networks` | Every tracked network across the fleet |
249
+ | `lattice_delete_network` | Delete a tracked network by Lattice ID ⚠️ |
188
250
  | `lattice_force_remove_container` | Force-remove a wedged container ⚠️ |
189
251
 
190
252
  ### Env vars, templates & webhooks
191
253
  | Tool | Description |
192
254
  |------|-------------|
193
- | `lattice_list_env_vars` / `lattice_create_env_var` / `lattice_update_env_var` / `lattice_delete_env_var` | Global `${VAR}` interpolation values ⚠️ |
194
- | `lattice_list_templates` / `lattice_create_template` / `lattice_delete_template` | Stack templates ⚠️ |
195
- | `lattice_list_webhooks` / `lattice_create_webhook` / `lattice_update_webhook` / `lattice_delete_webhook` / `lattice_test_webhook` | Outbound event webhooks ⚠️ |
255
+ | `lattice_list_env_vars` | Global `${VAR}` interpolation values (secrets masked) |
256
+ | `lattice_create_env_var` | Create a global environment variable |
257
+ | `lattice_update_env_var` | Update a global environment variable |
258
+ | `lattice_delete_env_var` | Delete a global environment variable ⚠️ |
259
+ | `lattice_list_templates` | Saved stack templates |
260
+ | `lattice_create_template` | Create a stack template |
261
+ | `lattice_delete_template` | Delete a stack template ⚠️ |
262
+ | `lattice_list_webhooks` | Outbound event webhooks |
263
+ | `lattice_create_webhook` | Create an outbound webhook |
264
+ | `lattice_update_webhook` | Update a webhook |
265
+ | `lattice_delete_webhook` | Delete a webhook ⚠️ |
266
+ | `lattice_test_webhook` | Send a test payload to a webhook |
196
267
 
197
268
  ### Users & instance configuration
198
269
  | Tool | Description |
199
270
  |------|-------------|
200
- | `lattice_list_users` / `lattice_create_user` / `lattice_update_user` / `lattice_delete_user` | User management ⚠️ |
201
- | `lattice_get_sso_config` / `lattice_update_sso_config` | Forta SSO ⚠️ |
202
- | `lattice_get_smtp_config` / `lattice_update_smtp_config` / `lattice_test_smtp` | Alert email |
203
- | `lattice_get_notification_prefs` / `lattice_update_notification_prefs` | Per-event notification prefs |
271
+ | `lattice_list_users` | List Lattice users with roles and status |
272
+ | `lattice_create_user` | Create a local Lattice user |
273
+ | `lattice_update_user` | Update a user's name, role or active flag ⚠️ |
274
+ | `lattice_delete_user` | Delete a Lattice user ⚠️ |
275
+ | `lattice_get_sso_config` | Get the Forta SSO configuration |
276
+ | `lattice_update_sso_config` | Update SSO config ⚠️ (can lock out SSO users) |
277
+ | `lattice_get_smtp_config` | Get the SMTP configuration |
278
+ | `lattice_update_smtp_config` | Update the SMTP configuration |
279
+ | `lattice_test_smtp` | Send a test email with the saved SMTP config |
280
+ | `lattice_get_notification_prefs` | Per-event notification preferences |
281
+ | `lattice_update_notification_prefs` | Update notification preferences |
282
+
283
+ ## Project structure
284
+
285
+ Everything lives in one file:
286
+
287
+ | Path | Role |
288
+ |------|------|
289
+ | `index.js` | The whole server: `--setup` flow, config read, `api()` HTTP helper, `text()`/`body()` helpers, all 126 `server.tool(...)` registrations, transport connect. |
290
+ | `package.json` | npm metadata; `bin.lattice-mcp` → `index.js`. |
291
+ | `AGENTS.md` | Contributor/agent guide — conventions, handler contracts, verification. |
292
+ | `README.md` | This file. |
293
+
294
+ ## Deployment
204
295
 
205
- ⚠️ = destructive. Tool descriptions state the blast radius.
296
+ Published to npm as `lattice-mcp` (public). Consumers run `npx -y lattice-mcp`, which resolves the latest published version — so **publishing is deployment**, and a running MCP server must be restarted to pick up a new version. `npm publish` requires 2FA via passkey from an interactive terminal.
297
+
298
+ ## Contributing & further reading
299
+
300
+ Read [`AGENTS.md`](./AGENTS.md) before changing code — it documents the one-shape tool pattern, the `body()`/`api()` helpers, the "read the handler before adding a tool" rule, and the verification steps. Related repos: [`lattice-api`](https://github.com/aidenappl/lattice-api), [`lattice-web`](https://github.com/aidenappl/lattice-web), [`lattice-runner`](https://github.com/aidenappl/lattice-runner).
301
+
302
+ ## License
303
+
304
+ MIT
package/index.js CHANGED
@@ -77,12 +77,27 @@ async function api(method, path, params, body) {
77
77
  opts.headers["Content-Type"] = "application/json";
78
78
  opts.body = JSON.stringify(body);
79
79
  }
80
+ let res;
80
81
  try {
81
- const res = await fetch(url.toString(), opts);
82
- return await res.json();
82
+ res = await fetch(url.toString(), opts);
83
83
  } catch (err) {
84
+ // Network-level failure (DNS, connection refused, timeout) — no response at all.
84
85
  return { success: false, error: err.message, error_message: `API request failed: ${err.message}` };
85
86
  }
87
+ // Response arrived. Read the body once as text, then try to parse it as JSON.
88
+ // On parse failure surface the HTTP status and a truncated text body rather than
89
+ // an opaque parse error — a 502/504 HTML page from the TLS proxy lands here.
90
+ // Never log bodies anywhere; responses can carry secrets.
91
+ const raw = await res.text();
92
+ try {
93
+ return JSON.parse(raw);
94
+ } catch {
95
+ return {
96
+ success: false,
97
+ error_code: res.status,
98
+ error_message: `API returned a non-JSON response (HTTP ${res.status})${raw ? `: ${raw.slice(0, 500)}` : ""}`,
99
+ };
100
+ }
86
101
  }
87
102
 
88
103
  function text(data) {
@@ -99,7 +114,7 @@ function body(obj) {
99
114
 
100
115
  const server = new McpServer({
101
116
  name: "lattice",
102
- version: "1.0.0",
117
+ version: "1.1.2",
103
118
  });
104
119
 
105
120
  // Overview
@@ -114,6 +129,12 @@ server.tool("lattice_health", "Check API health and database connectivity", {},
114
129
  return { content: text(res) };
115
130
  });
116
131
 
132
+ // Version
133
+ server.tool("lattice_get_version", "Get the deployed lattice-api version string. Use to check deploy drift against GitHub tags/commits (e.g. for /howfarbehind).", {}, async () => {
134
+ const res = await api("GET", "/version");
135
+ return { content: text(res) };
136
+ });
137
+
117
138
  // Workers
118
139
  server.tool("lattice_list_workers", "List all workers with status, IP, Docker version, runner version, last heartbeat", {
119
140
  status: z.enum(["online", "offline", "disconnected"]).optional().describe("Filter by worker status"),
@@ -174,9 +195,11 @@ server.tool("lattice_get_container", "Get full container details including confi
174
195
  server.tool("lattice_get_container_logs", "Get recent container logs (stdout/stderr)", {
175
196
  id: z.number().describe("Container ID"),
176
197
  limit: z.number().optional().describe("Number of log lines (default 50)"),
198
+ offset: z.number().optional().describe("Skip this many lines — paginate past the tail into older logs"),
177
199
  stream: z.enum(["stdout", "stderr"]).optional().describe("Filter by stream"),
178
- }, async ({ id, limit, stream }) => {
179
- const res = await api("GET", `/admin/containers/${id}/logs`, { limit, stream });
200
+ worker_id: z.number().optional().describe("Filter by worker ID"),
201
+ }, async ({ id, limit, offset, stream, worker_id }) => {
202
+ const res = await api("GET", `/admin/containers/${id}/logs`, { limit, offset, stream, worker_id });
180
203
  return { content: text(res) };
181
204
  });
182
205
 
@@ -213,10 +236,14 @@ server.tool("lattice_get_deployment_logs", "Get deployment logs: pull, create, s
213
236
  });
214
237
 
215
238
  // Audit log
216
- server.tool("lattice_get_audit_log", "Get recent audit log entries (who did what, when)", {
239
+ server.tool("lattice_get_audit_log", "Get recent audit log entries (who did what, when). Filter by user, action, or resource type to answer 'who deleted X' without scanning.", {
217
240
  limit: z.number().optional().describe("Number of entries (default 50)"),
218
- }, async ({ limit }) => {
219
- const res = await api("GET", "/admin/audit-log", { limit });
241
+ offset: z.number().optional().describe("Skip this many entries for pagination"),
242
+ user_id: z.number().optional().describe("Filter by the user who performed the action"),
243
+ action: z.string().optional().describe("Filter by action (e.g. create, update, delete, deploy)"),
244
+ resource_type: z.string().optional().describe("Filter by resource type (e.g. stack, container, worker, registry)"),
245
+ }, async ({ limit, offset, user_id, action, resource_type }) => {
246
+ const res = await api("GET", "/admin/audit-log", { limit, offset, user_id, action, resource_type });
220
247
  return { content: text(res) };
221
248
  });
222
249
 
@@ -559,9 +586,9 @@ server.tool("lattice_delete_backup_destination", "Delete a backup destination. I
559
586
  return { content: text(res) };
560
587
  });
561
588
 
562
- server.tool("lattice_test_backup_destination", "Test connectivity and credentials for a backup destination without writing a real backup", {
589
+ server.tool("lattice_test_backup_destination", "Test connectivity and credentials for a backup destination without writing a real backup. The test is dispatched over the worker's WebSocket, so worker_id is required and that worker must be connected", {
563
590
  id: z.number().describe("Backup destination ID"),
564
- worker_id: z.number().optional().describe("Worker to run the test from"),
591
+ worker_id: z.number().describe("Worker to run the test from — required; the test runs on this worker over its WebSocket, so it must be connected"),
565
592
  }, async ({ id, worker_id }) => {
566
593
  const res = await api("POST", `/admin/backup-destinations/${id}/test`, { worker_id });
567
594
  return { content: text(res) };
@@ -682,7 +709,7 @@ server.tool("lattice_get_container_metrics", "Get a single container's CPU, memo
682
709
  });
683
710
 
684
711
  server.tool("lattice_get_self", "Get the user this API token authenticates as, including role", {}, async () => {
685
- const res = await api("GET", "/admin/self");
712
+ const res = await api("GET", "/auth/self");
686
713
  return { content: text(res) };
687
714
  });
688
715
 
@@ -974,7 +1001,7 @@ server.tool("lattice_force_remove_container", "Force-remove a container on a wor
974
1001
  id: z.number().describe("Worker ID"),
975
1002
  name: z.string().describe("Container name to force-remove"),
976
1003
  }, async ({ id, name }) => {
977
- const res = await api("POST", `/admin/workers/${id}/force-remove`, { name });
1004
+ const res = await api("POST", `/admin/workers/${id}/force-remove`, null, { container_name: name });
978
1005
  return { content: text(res) };
979
1006
  });
980
1007
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lattice-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "MCP server for Lattice container orchestration platform",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -24,6 +24,7 @@
24
24
  "url": "git+https://github.com/aidenappl/lattice-mcp.git"
25
25
  },
26
26
  "dependencies": {
27
- "@modelcontextprotocol/sdk": "^1.29.0"
27
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
+ "zod": "^4.4.3"
28
29
  }
29
30
  }