lattice-mcp 1.0.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/index.js +357 -0
- package/package.json +29 -0
package/index.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const API_URL = process.env.LATTICE_API_URL;
|
|
7
|
+
const API_TOKEN = process.env.LATTICE_API_TOKEN;
|
|
8
|
+
|
|
9
|
+
if (!API_URL || !API_TOKEN) {
|
|
10
|
+
console.error("LATTICE_API_URL and LATTICE_API_TOKEN are required");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- HTTP helper ---
|
|
15
|
+
|
|
16
|
+
async function api(method, path, params, body) {
|
|
17
|
+
const url = new URL(path, API_URL);
|
|
18
|
+
if (params) {
|
|
19
|
+
for (const [k, v] of Object.entries(params)) {
|
|
20
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const opts = {
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${API_TOKEN}`,
|
|
27
|
+
},
|
|
28
|
+
signal: AbortSignal.timeout(30000),
|
|
29
|
+
};
|
|
30
|
+
if (body) {
|
|
31
|
+
opts.headers["Content-Type"] = "application/json";
|
|
32
|
+
opts.body = JSON.stringify(body);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(url.toString(), opts);
|
|
36
|
+
return await res.json();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return { success: false, error: err.message, error_message: `API request failed: ${err.message}` };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function text(data) {
|
|
43
|
+
return [{ type: "text", text: JSON.stringify(data, null, 2) }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- MCP Server ---
|
|
47
|
+
|
|
48
|
+
const server = new McpServer({
|
|
49
|
+
name: "lattice",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Overview
|
|
54
|
+
server.tool("lattice_overview", "Get fleet overview: worker counts, stack counts, container counts, failed stacks, recent deployments, fleet CPU/memory averages", {}, async () => {
|
|
55
|
+
const res = await api("GET", "/admin/overview");
|
|
56
|
+
return { content: text(res) };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Healthcheck
|
|
60
|
+
server.tool("lattice_health", "Check API health and database connectivity", {}, async () => {
|
|
61
|
+
const res = await api("GET", "/healthcheck");
|
|
62
|
+
return { content: text(res) };
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Workers
|
|
66
|
+
server.tool("lattice_list_workers", "List all workers with status, IP, Docker version, runner version, last heartbeat", {
|
|
67
|
+
status: z.enum(["online", "offline", "disconnected"]).optional().describe("Filter by worker status"),
|
|
68
|
+
}, async ({ status }) => {
|
|
69
|
+
const res = await api("GET", "/admin/workers", { status });
|
|
70
|
+
return { content: text(res) };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
server.tool("lattice_get_worker", "Get detailed worker info including metrics", {
|
|
74
|
+
id: z.number().describe("Worker ID"),
|
|
75
|
+
}, async ({ id }) => {
|
|
76
|
+
const res = await api("GET", `/admin/workers/${id}`);
|
|
77
|
+
return { content: text(res) };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
server.tool("lattice_get_worker_metrics", "Get recent worker metrics (CPU, memory, disk, network)", {
|
|
81
|
+
id: z.number().describe("Worker ID"),
|
|
82
|
+
range: z.string().optional().describe("Time range (e.g. '1h', '6h', '24h')"),
|
|
83
|
+
}, async ({ id, range }) => {
|
|
84
|
+
const res = await api("GET", `/admin/workers/${id}/metrics`, { range });
|
|
85
|
+
return { content: text(res) };
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Stacks
|
|
89
|
+
server.tool("lattice_list_stacks", "List all stacks with status, worker assignment, and deployment strategy", {
|
|
90
|
+
status: z.string().optional().describe("Filter by status (deployed, deploying, failed, error)"),
|
|
91
|
+
worker_id: z.number().optional().describe("Filter by worker ID"),
|
|
92
|
+
}, async ({ status, worker_id }) => {
|
|
93
|
+
const res = await api("GET", "/admin/stacks", { status, worker_id });
|
|
94
|
+
return { content: text(res) };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
server.tool("lattice_get_stack", "Get full stack details including compose YAML and env vars", {
|
|
98
|
+
id: z.number().describe("Stack ID"),
|
|
99
|
+
}, async ({ id }) => {
|
|
100
|
+
const res = await api("GET", `/admin/stacks/${id}`);
|
|
101
|
+
return { content: text(res) };
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Containers
|
|
105
|
+
server.tool("lattice_list_containers", "List containers with status, image, ports, health. Filter by stack or status", {
|
|
106
|
+
stack_id: z.number().optional().describe("Filter by stack ID"),
|
|
107
|
+
worker_id: z.number().optional().describe("Filter by worker ID"),
|
|
108
|
+
status: z.string().optional().describe("Filter by status (running, stopped, pending, paused)"),
|
|
109
|
+
name: z.string().optional().describe("Filter by container name"),
|
|
110
|
+
}, async ({ stack_id, worker_id, status, name }) => {
|
|
111
|
+
const res = await api("GET", "/admin/containers", { stack_id, worker_id, status, name });
|
|
112
|
+
return { content: text(res) };
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.tool("lattice_get_container", "Get full container details including config, health, env vars, ports", {
|
|
116
|
+
id: z.number().describe("Container ID"),
|
|
117
|
+
}, async ({ id }) => {
|
|
118
|
+
const res = await api("GET", `/admin/containers/${id}`);
|
|
119
|
+
return { content: text(res) };
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
server.tool("lattice_get_container_logs", "Get recent container logs (stdout/stderr)", {
|
|
123
|
+
id: z.number().describe("Container ID"),
|
|
124
|
+
limit: z.number().optional().describe("Number of log lines (default 50)"),
|
|
125
|
+
stream: z.enum(["stdout", "stderr"]).optional().describe("Filter by stream"),
|
|
126
|
+
}, async ({ id, limit, stream }) => {
|
|
127
|
+
const res = await api("GET", `/admin/containers/${id}/logs`, { limit, stream });
|
|
128
|
+
return { content: text(res) };
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
server.tool("lattice_get_container_lifecycle", "Get container lifecycle events (start, stop, restart, health changes)", {
|
|
132
|
+
id: z.number().describe("Container ID"),
|
|
133
|
+
limit: z.number().optional().describe("Number of events (default 50)"),
|
|
134
|
+
}, async ({ id, limit }) => {
|
|
135
|
+
const res = await api("GET", `/admin/containers/${id}/lifecycle`, { limit });
|
|
136
|
+
return { content: text(res) };
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Deployments
|
|
140
|
+
server.tool("lattice_list_deployments", "List deployments with status, strategy, timing. Filter by stack or status", {
|
|
141
|
+
stack_id: z.number().optional().describe("Filter by stack ID"),
|
|
142
|
+
status: z.string().optional().describe("Filter by status (pending, deploying, deployed, failed, rolled_back)"),
|
|
143
|
+
limit: z.number().optional().describe("Number of deployments (default 50)"),
|
|
144
|
+
}, async ({ stack_id, status, limit }) => {
|
|
145
|
+
const res = await api("GET", "/admin/deployments", { stack_id, status, limit });
|
|
146
|
+
return { content: text(res) };
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
server.tool("lattice_get_deployment", "Get deployment details including container-level status", {
|
|
150
|
+
id: z.number().describe("Deployment ID"),
|
|
151
|
+
}, async ({ id }) => {
|
|
152
|
+
const res = await api("GET", `/admin/deployments/${id}`);
|
|
153
|
+
return { content: text(res) };
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
server.tool("lattice_get_deployment_logs", "Get deployment logs: pull, create, start, swap, rollback events with timing", {
|
|
157
|
+
id: z.number().describe("Deployment ID"),
|
|
158
|
+
}, async ({ id }) => {
|
|
159
|
+
const res = await api("GET", `/admin/deployments/${id}/logs`);
|
|
160
|
+
return { content: text(res) };
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Audit log
|
|
164
|
+
server.tool("lattice_get_audit_log", "Get recent audit log entries (who did what, when)", {
|
|
165
|
+
limit: z.number().optional().describe("Number of entries (default 50)"),
|
|
166
|
+
}, async ({ limit }) => {
|
|
167
|
+
const res = await api("GET", "/admin/audit-log", { limit });
|
|
168
|
+
return { content: text(res) };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Stack actions
|
|
172
|
+
server.tool("lattice_deploy_stack", "Deploy a stack (all containers or specific ones)", {
|
|
173
|
+
id: z.number().describe("Stack ID"),
|
|
174
|
+
container_ids: z.array(z.number()).optional().describe("Specific container IDs to deploy (omit for all)"),
|
|
175
|
+
force: z.boolean().optional().describe("Force redeploy — removes all containers and recreates from scratch"),
|
|
176
|
+
}, async ({ id, container_ids, force }) => {
|
|
177
|
+
const body = {};
|
|
178
|
+
if (container_ids?.length) body.container_ids = container_ids;
|
|
179
|
+
if (force) body.force = true;
|
|
180
|
+
const res = await api("POST", `/admin/stacks/${id}/deploy`, null, body);
|
|
181
|
+
return { content: text(res) };
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
server.tool("lattice_restart_stack", "Restart all containers in a stack", {
|
|
185
|
+
id: z.number().describe("Stack ID"),
|
|
186
|
+
}, async ({ id }) => {
|
|
187
|
+
const res = await api("POST", `/admin/stacks/${id}/restart-all`);
|
|
188
|
+
return { content: text(res) };
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
server.tool("lattice_stop_stack", "Stop all containers in a stack", {
|
|
192
|
+
id: z.number().describe("Stack ID"),
|
|
193
|
+
}, async ({ id }) => {
|
|
194
|
+
const res = await api("POST", `/admin/stacks/${id}/stop-all`);
|
|
195
|
+
return { content: text(res) };
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
server.tool("lattice_start_stack", "Start all containers in a stack", {
|
|
199
|
+
id: z.number().describe("Stack ID"),
|
|
200
|
+
}, async ({ id }) => {
|
|
201
|
+
const res = await api("POST", `/admin/stacks/${id}/start-all`);
|
|
202
|
+
return { content: text(res) };
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
server.tool("lattice_update_stack", "Update stack configuration (name, description, strategy, worker, etc.)", {
|
|
206
|
+
id: z.number().describe("Stack ID"),
|
|
207
|
+
status: z.string().optional().describe("Stack status"),
|
|
208
|
+
name: z.string().optional().describe("Stack name"),
|
|
209
|
+
description: z.string().optional().describe("Stack description"),
|
|
210
|
+
deployment_strategy: z.string().optional().describe("Deployment strategy"),
|
|
211
|
+
worker_id: z.number().optional().describe("Assigned worker ID"),
|
|
212
|
+
auto_deploy: z.boolean().optional().describe("Enable auto-deploy on image push"),
|
|
213
|
+
active: z.boolean().optional().describe("Whether the stack is active"),
|
|
214
|
+
}, async ({ id, status, name, description, deployment_strategy, worker_id, auto_deploy, active }) => {
|
|
215
|
+
const body = {};
|
|
216
|
+
if (status !== undefined) body.status = status;
|
|
217
|
+
if (name !== undefined) body.name = name;
|
|
218
|
+
if (description !== undefined) body.description = description;
|
|
219
|
+
if (deployment_strategy !== undefined) body.deployment_strategy = deployment_strategy;
|
|
220
|
+
if (worker_id !== undefined) body.worker_id = worker_id;
|
|
221
|
+
if (auto_deploy !== undefined) body.auto_deploy = auto_deploy;
|
|
222
|
+
if (active !== undefined) body.active = active;
|
|
223
|
+
const res = await api("PUT", `/admin/stacks/${id}`, null, body);
|
|
224
|
+
return { content: text(res) };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Container actions
|
|
228
|
+
server.tool("lattice_start_container", "Start a stopped container", {
|
|
229
|
+
id: z.number().describe("Container ID"),
|
|
230
|
+
}, async ({ id }) => {
|
|
231
|
+
const res = await api("POST", `/admin/containers/${id}/start`);
|
|
232
|
+
return { content: text(res) };
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
server.tool("lattice_stop_container", "Stop a running container", {
|
|
236
|
+
id: z.number().describe("Container ID"),
|
|
237
|
+
}, async ({ id }) => {
|
|
238
|
+
const res = await api("POST", `/admin/containers/${id}/stop`);
|
|
239
|
+
return { content: text(res) };
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
server.tool("lattice_restart_container", "Restart a container", {
|
|
243
|
+
id: z.number().describe("Container ID"),
|
|
244
|
+
}, async ({ id }) => {
|
|
245
|
+
const res = await api("POST", `/admin/containers/${id}/restart`);
|
|
246
|
+
return { content: text(res) };
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
server.tool("lattice_kill_container", "Force kill a container", {
|
|
250
|
+
id: z.number().describe("Container ID"),
|
|
251
|
+
}, async ({ id }) => {
|
|
252
|
+
const res = await api("POST", `/admin/containers/${id}/kill`);
|
|
253
|
+
return { content: text(res) };
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
server.tool("lattice_pause_container", "Pause a running container", {
|
|
257
|
+
id: z.number().describe("Container ID"),
|
|
258
|
+
}, async ({ id }) => {
|
|
259
|
+
const res = await api("POST", `/admin/containers/${id}/pause`);
|
|
260
|
+
return { content: text(res) };
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
server.tool("lattice_unpause_container", "Unpause a paused container", {
|
|
264
|
+
id: z.number().describe("Container ID"),
|
|
265
|
+
}, async ({ id }) => {
|
|
266
|
+
const res = await api("POST", `/admin/containers/${id}/unpause`);
|
|
267
|
+
return { content: text(res) };
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
server.tool("lattice_remove_container", "Remove a container entirely", {
|
|
271
|
+
id: z.number().describe("Container ID"),
|
|
272
|
+
}, async ({ id }) => {
|
|
273
|
+
const res = await api("POST", `/admin/containers/${id}/remove`);
|
|
274
|
+
return { content: text(res) };
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
server.tool("lattice_recreate_container", "Recreate a container (remove and create fresh)", {
|
|
278
|
+
id: z.number().describe("Container ID"),
|
|
279
|
+
}, async ({ id }) => {
|
|
280
|
+
const res = await api("POST", `/admin/containers/${id}/recreate`);
|
|
281
|
+
return { content: text(res) };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Worker actions
|
|
285
|
+
server.tool("lattice_reboot_worker", "Reboot a worker machine", {
|
|
286
|
+
id: z.number().describe("Worker ID"),
|
|
287
|
+
}, async ({ id }) => {
|
|
288
|
+
const res = await api("POST", `/admin/workers/${id}/reboot`);
|
|
289
|
+
return { content: text(res) };
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
server.tool("lattice_upgrade_worker", "Upgrade worker runner to latest version", {
|
|
293
|
+
id: z.number().describe("Worker ID"),
|
|
294
|
+
}, async ({ id }) => {
|
|
295
|
+
const res = await api("POST", `/admin/workers/${id}/upgrade`);
|
|
296
|
+
return { content: text(res) };
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
server.tool("lattice_stop_all_worker", "Stop all containers on a worker", {
|
|
300
|
+
id: z.number().describe("Worker ID"),
|
|
301
|
+
}, async ({ id }) => {
|
|
302
|
+
const res = await api("POST", `/admin/workers/${id}/stop-all`);
|
|
303
|
+
return { content: text(res) };
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
server.tool("lattice_start_all_worker", "Start all containers on a worker", {
|
|
307
|
+
id: z.number().describe("Worker ID"),
|
|
308
|
+
}, async ({ id }) => {
|
|
309
|
+
const res = await api("POST", `/admin/workers/${id}/start-all`);
|
|
310
|
+
return { content: text(res) };
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Dashboard controls
|
|
314
|
+
server.tool("lattice_update_api", "Trigger Lattice API self-update", {}, async () => {
|
|
315
|
+
const res = await api("POST", "/admin/update/api");
|
|
316
|
+
return { content: text(res) };
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
server.tool("lattice_update_web", "Trigger Lattice web container update", {}, async () => {
|
|
320
|
+
const res = await api("POST", "/admin/update/web");
|
|
321
|
+
return { content: text(res) };
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
server.tool("lattice_rollback_deployment", "Rollback a deployment to its previous state", {
|
|
325
|
+
id: z.number().describe("Deployment ID"),
|
|
326
|
+
}, async ({ id }) => {
|
|
327
|
+
const res = await api("POST", `/admin/deployments/${id}/rollback`);
|
|
328
|
+
return { content: text(res) };
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// API token management
|
|
332
|
+
server.tool("lattice_list_api_tokens", "List all API tokens", {}, async () => {
|
|
333
|
+
const res = await api("GET", "/admin/api-tokens");
|
|
334
|
+
return { content: text(res) };
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
server.tool("lattice_create_api_token", "Create a new API token for AI tools or automation", {
|
|
338
|
+
name: z.string().describe("Token name"),
|
|
339
|
+
expires_in: z.string().optional().describe("Expiration: '30d', '90d', '365d', or 'never'. Defaults to 90d"),
|
|
340
|
+
}, async ({ name, expires_in }) => {
|
|
341
|
+
const body = { name };
|
|
342
|
+
if (expires_in) body.expires_in = expires_in;
|
|
343
|
+
const res = await api("POST", "/admin/api-tokens", null, body);
|
|
344
|
+
return { content: text(res) };
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
server.tool("lattice_delete_api_token", "Delete an API token", {
|
|
348
|
+
id: z.number().describe("API token ID"),
|
|
349
|
+
}, async ({ id }) => {
|
|
350
|
+
const res = await api("DELETE", `/admin/api-tokens/${id}`);
|
|
351
|
+
return { content: text(res) };
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// --- Start ---
|
|
355
|
+
|
|
356
|
+
const transport = new StdioServerTransport();
|
|
357
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lattice-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Lattice container orchestration platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"lattice-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"lattice",
|
|
16
|
+
"containers",
|
|
17
|
+
"orchestration",
|
|
18
|
+
"model-context-protocol"
|
|
19
|
+
],
|
|
20
|
+
"author": "Aiden Appleby",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/aidenappl/lattice-mcp.git"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
28
|
+
}
|
|
29
|
+
}
|