@mseep/mcp-swarmpit 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/CLAUDE.md +128 -0
- package/README.md +416 -0
- package/dist/client.d.ts +107 -0
- package/dist/client.js +297 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/sanitize.d.ts +41 -0
- package/dist/sanitize.js +165 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/test/config.test.d.ts +1 -0
- package/dist/test/config.test.js +103 -0
- package/dist/test/config.test.js.map +1 -0
- package/dist/test/file-ref.test.d.ts +1 -0
- package/dist/test/file-ref.test.js +163 -0
- package/dist/test/file-ref.test.js.map +1 -0
- package/dist/test/helpers.test.d.ts +1 -0
- package/dist/test/helpers.test.js +133 -0
- package/dist/test/helpers.test.js.map +1 -0
- package/dist/test/sanitize.test.d.ts +1 -0
- package/dist/test/sanitize.test.js +207 -0
- package/dist/test/sanitize.test.js.map +1 -0
- package/dist/tools/admin.d.ts +3 -0
- package/dist/tools/admin.js +64 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/configs.d.ts +4 -0
- package/dist/tools/configs.js +70 -0
- package/dist/tools/configs.js.map +1 -0
- package/dist/tools/dashboard.d.ts +3 -0
- package/dist/tools/dashboard.js +41 -0
- package/dist/tools/dashboard.js.map +1 -0
- package/dist/tools/helpers.d.ts +16 -0
- package/dist/tools/helpers.js +74 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/networks.d.ts +3 -0
- package/dist/tools/networks.js +70 -0
- package/dist/tools/networks.js.map +1 -0
- package/dist/tools/nodes.d.ts +3 -0
- package/dist/tools/nodes.js +59 -0
- package/dist/tools/nodes.js.map +1 -0
- package/dist/tools/register.d.ts +3 -0
- package/dist/tools/register.js +30 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/secrets.d.ts +4 -0
- package/dist/tools/secrets.js +70 -0
- package/dist/tools/secrets.js.map +1 -0
- package/dist/tools/services.d.ts +4 -0
- package/dist/tools/services.js +198 -0
- package/dist/tools/services.js.map +1 -0
- package/dist/tools/stacks.d.ts +4 -0
- package/dist/tools/stacks.js +196 -0
- package/dist/tools/stacks.js.map +1 -0
- package/dist/tools/tasks.d.ts +3 -0
- package/dist/tools/tasks.js +23 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/timeseries.d.ts +3 -0
- package/dist/tools/timeseries.js +41 -0
- package/dist/tools/timeseries.js.map +1 -0
- package/dist/tools/util.d.ts +3 -0
- package/dist/tools/util.js +10 -0
- package/dist/tools/util.js.map +1 -0
- package/dist/tools/volumes.d.ts +3 -0
- package/dist/tools/volumes.js +59 -0
- package/dist/tools/volumes.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
- package/src/client.ts +391 -0
- package/src/config.ts +57 -0
- package/src/index.ts +49 -0
- package/src/sanitize.ts +218 -0
- package/src/test/config.test.ts +118 -0
- package/src/test/file-ref.test.ts +191 -0
- package/src/test/helpers.test.ts +147 -0
- package/src/test/sanitize.test.ts +234 -0
- package/src/tools/admin.ts +93 -0
- package/src/tools/configs.ts +101 -0
- package/src/tools/dashboard.ts +65 -0
- package/src/tools/helpers.ts +91 -0
- package/src/tools/networks.ts +99 -0
- package/src/tools/nodes.ts +88 -0
- package/src/tools/register.ts +36 -0
- package/src/tools/secrets.ts +101 -0
- package/src/tools/services.ts +283 -0
- package/src/tools/stacks.ts +282 -0
- package/src/tools/tasks.ts +37 -0
- package/src/tools/timeseries.ts +65 -0
- package/src/tools/util.ts +20 -0
- package/src/tools/volumes.ts +88 -0
- package/src/types.ts +131 -0
- package/swagger.json +1 -0
- package/swarmpit-config.example.json +9 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SwarmpitClient } from "../client.js";
|
|
4
|
+
import { toolResult, toolError } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
export function registerNodeTools(
|
|
7
|
+
server: McpServer,
|
|
8
|
+
client: SwarmpitClient
|
|
9
|
+
): void {
|
|
10
|
+
server.tool(
|
|
11
|
+
"list_nodes",
|
|
12
|
+
"List all Docker Swarm nodes",
|
|
13
|
+
{},
|
|
14
|
+
async () => {
|
|
15
|
+
try {
|
|
16
|
+
const nodes = await client.listNodes();
|
|
17
|
+
return toolResult(nodes);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return toolError(e);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
server.tool(
|
|
25
|
+
"get_node",
|
|
26
|
+
"Get details of a specific Docker Swarm node",
|
|
27
|
+
{ id: z.string().describe("Node ID") },
|
|
28
|
+
async ({ id }) => {
|
|
29
|
+
try {
|
|
30
|
+
const node = await client.getNode(id);
|
|
31
|
+
return toolResult(node);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return toolError(e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
server.tool(
|
|
39
|
+
"get_node_tasks",
|
|
40
|
+
"List all tasks running on a specific node",
|
|
41
|
+
{ id: z.string().describe("Node ID") },
|
|
42
|
+
async ({ id }) => {
|
|
43
|
+
try {
|
|
44
|
+
const tasks = await client.getNodeTasks(id);
|
|
45
|
+
return toolResult(tasks);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return toolError(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
server.tool(
|
|
53
|
+
"edit_node",
|
|
54
|
+
"Edit a Docker Swarm node (e.g. change availability, role, labels)",
|
|
55
|
+
{
|
|
56
|
+
id: z.string().describe("Node ID"),
|
|
57
|
+
spec: z.record(z.unknown()).describe("Node specification updates"),
|
|
58
|
+
},
|
|
59
|
+
async ({ id, spec }) => {
|
|
60
|
+
try {
|
|
61
|
+
await client.editNode(id, spec);
|
|
62
|
+
return toolResult({ updated: true, id });
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return toolError(e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
server.tool(
|
|
70
|
+
"delete_node",
|
|
71
|
+
"Remove a Docker Swarm node. DESTRUCTIVE: requires confirm=true",
|
|
72
|
+
{
|
|
73
|
+
id: z.string().describe("Node ID"),
|
|
74
|
+
confirm: z.boolean().describe("Must be true to confirm deletion"),
|
|
75
|
+
},
|
|
76
|
+
async ({ id, confirm }) => {
|
|
77
|
+
if (!confirm) {
|
|
78
|
+
return toolError("Destructive operation: set confirm=true to remove this node");
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await client.deleteNode(id);
|
|
82
|
+
return toolResult({ deleted: true, id });
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return toolError(e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { SwarmpitConfig } from "../config.js";
|
|
3
|
+
import { SwarmpitClient } from "../client.js";
|
|
4
|
+
import { registerUtilTools } from "./util.js";
|
|
5
|
+
import { registerServiceTools } from "./services.js";
|
|
6
|
+
import { registerStackTools } from "./stacks.js";
|
|
7
|
+
import { registerNetworkTools } from "./networks.js";
|
|
8
|
+
import { registerNodeTools } from "./nodes.js";
|
|
9
|
+
import { registerTaskTools } from "./tasks.js";
|
|
10
|
+
import { registerVolumeTools } from "./volumes.js";
|
|
11
|
+
import { registerSecretTools } from "./secrets.js";
|
|
12
|
+
import { registerConfigTools } from "./configs.js";
|
|
13
|
+
import { registerAdminTools } from "./admin.js";
|
|
14
|
+
import { registerDashboardTools } from "./dashboard.js";
|
|
15
|
+
import { registerTimeseriesTools } from "./timeseries.js";
|
|
16
|
+
|
|
17
|
+
export function registerAllTools(
|
|
18
|
+
server: McpServer,
|
|
19
|
+
config: SwarmpitConfig
|
|
20
|
+
): void {
|
|
21
|
+
const client = new SwarmpitClient(config.url, config.token);
|
|
22
|
+
const redact = config.redact;
|
|
23
|
+
|
|
24
|
+
registerUtilTools(server, config);
|
|
25
|
+
registerServiceTools(server, client, redact);
|
|
26
|
+
registerStackTools(server, client, redact);
|
|
27
|
+
registerNetworkTools(server, client);
|
|
28
|
+
registerNodeTools(server, client);
|
|
29
|
+
registerTaskTools(server, client);
|
|
30
|
+
registerVolumeTools(server, client);
|
|
31
|
+
registerSecretTools(server, client, redact);
|
|
32
|
+
registerConfigTools(server, client, redact);
|
|
33
|
+
registerAdminTools(server, client);
|
|
34
|
+
registerDashboardTools(server, client);
|
|
35
|
+
registerTimeseriesTools(server, client);
|
|
36
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { RedactMode } from "../config.js";
|
|
4
|
+
import { SwarmpitClient } from "../client.js";
|
|
5
|
+
import { toolResult, toolError, resolveData } from "./helpers.js";
|
|
6
|
+
|
|
7
|
+
function redactSecret(secret: Record<string, unknown>, redact: RedactMode): Record<string, unknown> {
|
|
8
|
+
if (redact === "none") return secret;
|
|
9
|
+
const { data, ...rest } = secret;
|
|
10
|
+
return { ...rest, data: data ? "[REDACTED]" : undefined };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerSecretTools(
|
|
14
|
+
server: McpServer,
|
|
15
|
+
client: SwarmpitClient,
|
|
16
|
+
redact: RedactMode
|
|
17
|
+
): void {
|
|
18
|
+
server.tool(
|
|
19
|
+
"list_secrets",
|
|
20
|
+
"List all Docker Swarm secrets",
|
|
21
|
+
{},
|
|
22
|
+
async () => {
|
|
23
|
+
try {
|
|
24
|
+
const secrets = await client.listSecrets();
|
|
25
|
+
return toolResult(secrets.map((s) => redactSecret(s, redact)));
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return toolError(e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
server.tool(
|
|
33
|
+
"get_secret",
|
|
34
|
+
"Get details of a specific Docker Swarm secret",
|
|
35
|
+
{ id: z.string().describe("Secret ID or name") },
|
|
36
|
+
async ({ id }) => {
|
|
37
|
+
try {
|
|
38
|
+
const secret = await client.getSecret(id);
|
|
39
|
+
return toolResult(redactSecret(secret, redact));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return toolError(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
server.tool(
|
|
47
|
+
"create_secret",
|
|
48
|
+
"Create a Docker Swarm secret. Data can be plain, { $env: VAR_NAME } to resolve from env, or { $file: /path } to read from a local file (avoids sending large content through LLM context).",
|
|
49
|
+
{
|
|
50
|
+
secretName: z.string().describe("Secret name"),
|
|
51
|
+
data: z.union([
|
|
52
|
+
z.string(),
|
|
53
|
+
z.object({ $env: z.string() }).describe('Reference to local env var'),
|
|
54
|
+
z.object({ $file: z.string() }).describe('Read from local file path'),
|
|
55
|
+
]).describe("Secret data — string, { $env: VAR_NAME }, or { $file: /path }"),
|
|
56
|
+
},
|
|
57
|
+
async ({ secretName, data }) => {
|
|
58
|
+
try {
|
|
59
|
+
const resolved = resolveData(data);
|
|
60
|
+
await client.createSecret({ secretName, data: resolved });
|
|
61
|
+
return toolResult({ created: true, secretName });
|
|
62
|
+
} catch (e) {
|
|
63
|
+
return toolError(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
server.tool(
|
|
69
|
+
"delete_secret",
|
|
70
|
+
"Delete a Docker Swarm secret. DESTRUCTIVE: requires confirm=true",
|
|
71
|
+
{
|
|
72
|
+
id: z.string().describe("Secret ID or name"),
|
|
73
|
+
confirm: z.boolean().describe("Must be true to confirm deletion"),
|
|
74
|
+
},
|
|
75
|
+
async ({ id, confirm }) => {
|
|
76
|
+
if (!confirm) {
|
|
77
|
+
return toolError("Destructive operation: set confirm=true to delete this secret");
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await client.deleteSecret(id);
|
|
81
|
+
return toolResult({ deleted: true, id });
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return toolError(e);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
server.tool(
|
|
89
|
+
"get_secret_services",
|
|
90
|
+
"List services using a specific secret",
|
|
91
|
+
{ id: z.string().describe("Secret ID or name") },
|
|
92
|
+
async ({ id }) => {
|
|
93
|
+
try {
|
|
94
|
+
const services = await client.getSecretServices(id);
|
|
95
|
+
return toolResult(services);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return toolError(e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { RedactMode } from "../config.js";
|
|
4
|
+
import { SwarmpitClient } from "../client.js";
|
|
5
|
+
import { sanitizeService, sanitizeServices } from "../sanitize.js";
|
|
6
|
+
import { toolResult, toolError, resolveEnvRef, prepareServiceForUpdate } from "./helpers.js";
|
|
7
|
+
|
|
8
|
+
export function registerServiceTools(
|
|
9
|
+
server: McpServer,
|
|
10
|
+
client: SwarmpitClient,
|
|
11
|
+
redact: RedactMode
|
|
12
|
+
): void {
|
|
13
|
+
server.tool(
|
|
14
|
+
"list_services",
|
|
15
|
+
"List all Docker Swarm services",
|
|
16
|
+
{},
|
|
17
|
+
async () => {
|
|
18
|
+
try {
|
|
19
|
+
const services = await client.listServices();
|
|
20
|
+
return toolResult(redact === "none" ? services : sanitizeServices(services, redact === "all"));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return toolError(e);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
server.tool(
|
|
28
|
+
"get_service",
|
|
29
|
+
"Get detailed info for a specific Docker Swarm service",
|
|
30
|
+
{ id: z.string().describe("Service ID or name") },
|
|
31
|
+
async ({ id }) => {
|
|
32
|
+
try {
|
|
33
|
+
const service = await client.getService(id);
|
|
34
|
+
return toolResult(redact === "none" ? service : sanitizeService(service, redact === "all"));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return toolError(e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
server.tool(
|
|
42
|
+
"service_logs",
|
|
43
|
+
"Get logs for a Docker Swarm service",
|
|
44
|
+
{
|
|
45
|
+
id: z.string().describe("Service ID or name"),
|
|
46
|
+
since: z.string().optional().describe("Time window as Go duration (e.g. '30s', '5m', '1h', '24h'). Default: 5m"),
|
|
47
|
+
},
|
|
48
|
+
async ({ id, since }) => {
|
|
49
|
+
try {
|
|
50
|
+
const logs = await client.getServiceLogs(id, since);
|
|
51
|
+
return toolResult(logs);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return toolError(e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
server.tool(
|
|
59
|
+
"create_service",
|
|
60
|
+
"Create a new Docker Swarm service",
|
|
61
|
+
{ spec: z.record(z.unknown()).describe("Service specification object") },
|
|
62
|
+
async ({ spec }) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = await client.createService(spec);
|
|
65
|
+
return toolResult(result);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return toolError(e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
server.tool(
|
|
73
|
+
"update_service",
|
|
74
|
+
"Update an existing Docker Swarm service",
|
|
75
|
+
{
|
|
76
|
+
id: z.string().describe("Service ID or name"),
|
|
77
|
+
spec: z.record(z.unknown()).describe("Updated service specification"),
|
|
78
|
+
},
|
|
79
|
+
async ({ id, spec }) => {
|
|
80
|
+
try {
|
|
81
|
+
await client.updateService(id, spec);
|
|
82
|
+
return toolResult({ updated: true, id });
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return toolError(e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
server.tool(
|
|
90
|
+
"redeploy_service",
|
|
91
|
+
"Redeploy a Docker Swarm service, optionally with a new image tag",
|
|
92
|
+
{
|
|
93
|
+
id: z.string().describe("Service ID or name"),
|
|
94
|
+
tag: z.string().optional().describe("Optional new image tag to deploy"),
|
|
95
|
+
},
|
|
96
|
+
async ({ id, tag }) => {
|
|
97
|
+
try {
|
|
98
|
+
await client.redeployService(id, tag);
|
|
99
|
+
return toolResult({ redeployed: true, id, tag: tag ?? "current" });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return toolError(e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
server.tool(
|
|
107
|
+
"rollback_service",
|
|
108
|
+
"Rollback a Docker Swarm service to its previous version",
|
|
109
|
+
{ id: z.string().describe("Service ID or name") },
|
|
110
|
+
async ({ id }) => {
|
|
111
|
+
try {
|
|
112
|
+
await client.rollbackService(id);
|
|
113
|
+
return toolResult({ rolledBack: true, id });
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return toolError(e);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
server.tool(
|
|
121
|
+
"scale_service",
|
|
122
|
+
"Scale a Docker Swarm service to a specific number of replicas",
|
|
123
|
+
{
|
|
124
|
+
id: z.string().describe("Service ID or name"),
|
|
125
|
+
replicas: z.number().int().min(0).describe("Desired number of replicas"),
|
|
126
|
+
},
|
|
127
|
+
async ({ id, replicas }) => {
|
|
128
|
+
try {
|
|
129
|
+
const service = await client.getService(id);
|
|
130
|
+
await client.updateService(id, prepareServiceForUpdate({ ...service, replicas }));
|
|
131
|
+
return toolResult({ scaled: true, id, from: service.replicas, to: replicas });
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return toolError(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
server.tool(
|
|
139
|
+
"list_service_tasks",
|
|
140
|
+
"List tasks (containers) for a Docker Swarm service",
|
|
141
|
+
{ id: z.string().describe("Service ID or name") },
|
|
142
|
+
async ({ id }) => {
|
|
143
|
+
try {
|
|
144
|
+
const tasks = await client.getServiceTasks(id);
|
|
145
|
+
return toolResult(tasks);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return toolError(e);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
server.tool(
|
|
153
|
+
"stop_service",
|
|
154
|
+
"Stop a Docker Swarm service (scale to 0 replicas)",
|
|
155
|
+
{ id: z.string().describe("Service ID or name") },
|
|
156
|
+
async ({ id }) => {
|
|
157
|
+
try {
|
|
158
|
+
await client.stopService(id);
|
|
159
|
+
return toolResult({ stopped: true, id });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return toolError(e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
server.tool(
|
|
167
|
+
"delete_service",
|
|
168
|
+
"Delete a Docker Swarm service. DESTRUCTIVE: requires confirm=true",
|
|
169
|
+
{
|
|
170
|
+
id: z.string().describe("Service ID or name"),
|
|
171
|
+
confirm: z.boolean().describe("Must be true to confirm deletion"),
|
|
172
|
+
},
|
|
173
|
+
async ({ id, confirm }) => {
|
|
174
|
+
if (!confirm) {
|
|
175
|
+
return toolError("Destructive operation: set confirm=true to delete this service");
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await client.deleteService(id);
|
|
179
|
+
return toolResult({ deleted: true, id });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return toolError(e);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
server.tool(
|
|
187
|
+
"update_service_env",
|
|
188
|
+
"Set or remove environment variables on a service. Supports $env references for secret values.",
|
|
189
|
+
{
|
|
190
|
+
id: z.string().describe("Service ID or name"),
|
|
191
|
+
set: z
|
|
192
|
+
.record(
|
|
193
|
+
z.union([
|
|
194
|
+
z.string(),
|
|
195
|
+
z.object({ $env: z.string() }).describe('Reference to local env var, e.g. { "$env": "MY_SECRET" }'),
|
|
196
|
+
])
|
|
197
|
+
)
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("Env vars to set. Values can be plain strings or { $env: VAR_NAME } references"),
|
|
200
|
+
remove: z.array(z.string()).optional().describe("Env var names to remove"),
|
|
201
|
+
},
|
|
202
|
+
async ({ id, set, remove }) => {
|
|
203
|
+
try {
|
|
204
|
+
const service = await client.getService(id);
|
|
205
|
+
let variables = [...(service.variables ?? [])];
|
|
206
|
+
|
|
207
|
+
if (remove?.length) {
|
|
208
|
+
const removeSet = new Set(remove);
|
|
209
|
+
variables = variables.filter((v) => !removeSet.has(v.name));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const changedNames: string[] = [];
|
|
213
|
+
if (set) {
|
|
214
|
+
for (const [name, rawValue] of Object.entries(set)) {
|
|
215
|
+
const value = resolveEnvRef(rawValue);
|
|
216
|
+
const existing = variables.find((v) => v.name === name);
|
|
217
|
+
if (existing) {
|
|
218
|
+
existing.value = value;
|
|
219
|
+
} else {
|
|
220
|
+
variables.push({ name, value });
|
|
221
|
+
}
|
|
222
|
+
changedNames.push(name);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await client.updateService(id, prepareServiceForUpdate({ ...service, variables }));
|
|
227
|
+
return toolResult({ updated: true, id, set: changedNames, removed: remove ?? [] });
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return toolError(e);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
server.tool(
|
|
235
|
+
"get_service_env",
|
|
236
|
+
"Get specific environment variable values from a service by name",
|
|
237
|
+
{
|
|
238
|
+
id: z.string().describe("Service ID or name"),
|
|
239
|
+
names: z.array(z.string()).describe("Env var names to retrieve"),
|
|
240
|
+
},
|
|
241
|
+
async ({ id, names }) => {
|
|
242
|
+
try {
|
|
243
|
+
const service = await client.getService(id);
|
|
244
|
+
const found: Record<string, string | null> = {};
|
|
245
|
+
for (const name of names) {
|
|
246
|
+
const v = service.variables?.find((v) => v.name === name);
|
|
247
|
+
found[name] = v?.value ?? null;
|
|
248
|
+
}
|
|
249
|
+
return toolResult(found);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
return toolError(e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
server.tool(
|
|
257
|
+
"get_service_compose",
|
|
258
|
+
"Get the compose YAML for a specific service",
|
|
259
|
+
{ id: z.string().describe("Service ID or name") },
|
|
260
|
+
async ({ id }) => {
|
|
261
|
+
try {
|
|
262
|
+
const compose = await client.getServiceCompose(id);
|
|
263
|
+
return toolResult(compose);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return toolError(e);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
server.tool(
|
|
271
|
+
"get_service_networks",
|
|
272
|
+
"Get networks attached to a specific service",
|
|
273
|
+
{ id: z.string().describe("Service ID or name") },
|
|
274
|
+
async ({ id }) => {
|
|
275
|
+
try {
|
|
276
|
+
const networks = await client.getServiceNetworks(id);
|
|
277
|
+
return toolResult(networks);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return toolError(e);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|