@mks2508/coolify-mks-cli-mcp 0.5.0 → 0.6.1

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 (88) hide show
  1. package/dist/cli/coolify-state.d.ts +51 -0
  2. package/dist/cli/coolify-state.d.ts.map +1 -0
  3. package/dist/cli/index.js +2862 -631
  4. package/dist/coolify/config.d.ts +1 -1
  5. package/dist/coolify/config.d.ts.map +1 -1
  6. package/dist/coolify/index.d.ts +626 -12
  7. package/dist/coolify/index.d.ts.map +1 -1
  8. package/dist/coolify/types.d.ts +87 -3
  9. package/dist/coolify/types.d.ts.map +1 -1
  10. package/dist/dist-C4hIkHif.js +66 -0
  11. package/dist/dist-C4hIkHif.js.map +1 -0
  12. package/dist/dist-DEPvJhbP.js +3 -0
  13. package/dist/index.cjs +8511 -28542
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +32 -8
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +8470 -28506
  18. package/dist/index.js.map +1 -1
  19. package/dist/network.d.ts +75 -0
  20. package/dist/network.d.ts.map +1 -0
  21. package/dist/sdk.d.ts +356 -0
  22. package/dist/sdk.d.ts.map +1 -0
  23. package/dist/server/index.d.ts +9 -0
  24. package/dist/server/index.d.ts.map +1 -0
  25. package/dist/server/sse.js +3 -1
  26. package/dist/server/stdio.d.ts +0 -2
  27. package/dist/server/stdio.d.ts.map +1 -1
  28. package/dist/server/stdio.js +3307 -1618
  29. package/dist/tools/definitions.d.ts +1 -1
  30. package/dist/tools/definitions.d.ts.map +1 -1
  31. package/dist/tools/handlers.d.ts +6 -7
  32. package/dist/tools/handlers.d.ts.map +1 -1
  33. package/dist/tools/index.d.ts +8 -0
  34. package/dist/tools/index.d.ts.map +1 -0
  35. package/dist/trace.d.ts +71 -0
  36. package/dist/trace.d.ts.map +1 -0
  37. package/dist/utils/format.d.ts +1 -1
  38. package/dist/utils/format.d.ts.map +1 -1
  39. package/package.json +15 -9
  40. package/src/cli/actions.ts +162 -0
  41. package/src/cli/commands/active-deployments.ts +24 -0
  42. package/src/cli/commands/build-logs.ts +25 -22
  43. package/src/cli/commands/cancel-deploy.ts +35 -0
  44. package/src/cli/commands/config.ts +53 -47
  45. package/src/cli/commands/create.ts +74 -53
  46. package/src/cli/commands/databases.ts +63 -0
  47. package/src/cli/commands/db.ts +68 -0
  48. package/src/cli/commands/delete.ts +41 -29
  49. package/src/cli/commands/deploy.ts +42 -21
  50. package/src/cli/commands/deployments.ts +41 -31
  51. package/src/cli/commands/destinations.ts +19 -27
  52. package/src/cli/commands/diagnose.ts +139 -0
  53. package/src/cli/commands/env.ts +66 -41
  54. package/src/cli/commands/environments.ts +36 -32
  55. package/src/cli/commands/exec.ts +39 -0
  56. package/src/cli/commands/keys.ts +46 -0
  57. package/src/cli/commands/list.ts +29 -27
  58. package/src/cli/commands/logs.ts +33 -18
  59. package/src/cli/commands/network.ts +145 -0
  60. package/src/cli/commands/projects.ts +51 -39
  61. package/src/cli/commands/restart.ts +34 -18
  62. package/src/cli/commands/server-resources.ts +71 -0
  63. package/src/cli/commands/servers.ts +23 -23
  64. package/src/cli/commands/service-logs.ts +24 -16
  65. package/src/cli/commands/services.ts +63 -0
  66. package/src/cli/commands/show.ts +72 -41
  67. package/src/cli/commands/start.ts +34 -18
  68. package/src/cli/commands/stop.ts +34 -18
  69. package/src/cli/commands/svc.ts +68 -0
  70. package/src/cli/commands/teams.ts +60 -0
  71. package/src/cli/commands/update.ts +73 -49
  72. package/src/cli/commands/version.ts +37 -0
  73. package/src/cli/coolify-state.ts +88 -0
  74. package/src/cli/index.ts +383 -151
  75. package/src/coolify/config.ts +29 -27
  76. package/src/coolify/index.ts +1829 -123
  77. package/src/coolify/types.ts +217 -124
  78. package/src/index.ts +82 -868
  79. package/src/network.ts +298 -0
  80. package/src/sdk.ts +597 -0
  81. package/src/server/index.ts +13 -0
  82. package/src/server/sse.ts +33 -25
  83. package/src/server/stdio.ts +24 -27
  84. package/src/tools/definitions.ts +893 -264
  85. package/src/tools/handlers.ts +556 -748
  86. package/src/tools/index.ts +8 -0
  87. package/src/trace.ts +116 -0
  88. package/src/utils/format.ts +36 -33
@@ -1,771 +1,579 @@
1
1
  /**
2
2
  * MCP Tool handlers for Coolify.
3
3
  *
4
- * Handles execution of all 21 Coolify MCP tools.
4
+ * All handlers route through the Coolify SDK facade.
5
+ * Uses a generic mcpCall wrapper to eliminate boilerplate.
5
6
  *
6
7
  * @module
7
8
  */
8
9
 
9
- import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
10
- import { isOk, isErr } from '@mks2508/no-throw'
11
- import type { CoolifyService } from '../coolify/index.js'
10
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
11
+ import { Coolify } from "../sdk.js";
12
+
13
+ /** Singleton SDK for MCP usage (reads env vars / config file). */
14
+ let _sdk: Coolify | null = null;
15
+ function getSdk(): Coolify {
16
+ if (!_sdk) _sdk = Coolify.fromEnv();
17
+ return _sdk;
18
+ }
12
19
 
13
20
  /**
14
- * Handler function for tool calls.
21
+ * Wraps an SDK call into an MCP tool result.
22
+ * On success → JSON response. On error → isError response.
23
+ *
24
+ * @param fn - Async function that calls the SDK (throws on error)
25
+ * @param label - Error context label
26
+ * @returns MCP CallToolResult
27
+ */
28
+ async function mcpCall(
29
+ fn: (sdk: Coolify) => Promise<unknown>,
30
+ label: string,
31
+ ): Promise<CallToolResult> {
32
+ try {
33
+ const data = await fn(getSdk());
34
+ return {
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: JSON.stringify(
39
+ typeof data === "object" && data !== null
40
+ ? { success: true, ...data }
41
+ : { success: true, result: data },
42
+ null,
43
+ 2,
44
+ ),
45
+ },
46
+ ],
47
+ };
48
+ } catch (error) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `${label}: ${error instanceof Error ? error.message : String(error)}`,
54
+ },
55
+ ],
56
+ isError: true,
57
+ };
58
+ }
59
+ }
60
+
61
+ /** Wraps SDK call returning an array into { count, items } format. */
62
+ async function mcpList(
63
+ fn: (sdk: Coolify) => Promise<unknown[]>,
64
+ itemKey: string,
65
+ label: string,
66
+ ): Promise<CallToolResult> {
67
+ return mcpCall(async (sdk) => {
68
+ const items = await fn(sdk);
69
+ return { count: items.length, [itemKey]: items };
70
+ }, label);
71
+ }
72
+
73
+ /**
74
+ * Main handler — routes tool calls to SDK methods.
15
75
  *
16
76
  * @param name - Tool name
17
77
  * @param args - Tool arguments
18
- * @param coolify - CoolifyService instance
19
- * @returns Tool call result
78
+ * @returns MCP tool result
20
79
  */
21
80
  export async function handleToolCall(
22
81
  name: string,
23
82
  args: Record<string, unknown>,
24
- coolify: CoolifyService
25
83
  ): Promise<CallToolResult> {
26
- const initResult = await coolify.init()
27
- if (isErr(initResult)) {
28
- return {
29
- content: [{ type: 'text', text: `Coolify not configured: ${initResult.error.message}` }],
30
- isError: true
31
- }
32
- }
84
+ const a = args as any;
33
85
 
34
86
  switch (name) {
35
- case 'deploy':
36
- return handleDeploy(coolify, args as unknown as DeployArgs)
37
- case 'get_env_vars':
38
- return handleGetEnvVars(coolify, args as unknown as GetEnvVarsArgs)
39
- case 'set_env_vars':
40
- return handleSetEnvVars(coolify, args as unknown as SetEnvVarsArgs)
41
- case 'get_deployment_status':
42
- return handleGetDeploymentStatus(coolify, args as unknown as GetDeploymentStatusArgs)
43
- case 'list_applications':
44
- return handleListApplications(coolify, args as unknown as ListApplicationsArgs)
45
- case 'delete_application':
46
- return handleDeleteApplication(coolify, args as unknown as DeleteApplicationArgs)
47
- case 'get_application_logs':
48
- return handleGetApplicationLogs(coolify, args as unknown as GetApplicationLogsArgs)
49
- case 'start_application':
50
- return handleStartApplication(coolify, args as unknown as StartApplicationArgs)
51
- case 'stop_application':
52
- return handleStopApplication(coolify, args as unknown as StopApplicationArgs)
53
- case 'restart_application':
54
- return handleRestartApplication(coolify, args as unknown as RestartApplicationArgs)
55
- case 'get_deployment_history':
56
- return handleGetDeploymentHistory(coolify, args as unknown as GetDeploymentHistoryArgs)
57
- case 'update_application':
58
- return handleUpdateApplication(coolify, args as unknown as UpdateApplicationArgs)
59
- case 'set_domains':
60
- return handleSetDomains(coolify, args as unknown as SetDomainsArgs)
61
- case 'list_servers':
62
- return handleListServers(coolify)
63
- case 'get_server':
64
- return handleGetServer(coolify, args as unknown as GetServerArgs)
65
- case 'list_projects':
66
- return handleListProjects(coolify)
67
- case 'list_teams':
68
- return handleListTeams(coolify)
69
- case 'get_server_destinations':
70
- return handleGetServerDestinations(coolify, args as unknown as GetServerDestinationsArgs)
71
- case 'create_application':
72
- return handleCreateApplication(coolify, args as unknown as CreateApplicationArgs)
73
- case 'create_project':
74
- return handleCreateProject(coolify, args as unknown as CreateProjectArgs)
75
- case 'get_resource_usage':
76
- return handleGetResourceUsage(coolify, args as unknown as GetResourceUsageArgs)
77
- case 'health_check':
78
- return handleHealthCheck(coolify)
79
- case 'get_application_details':
80
- return handleGetApplicationDetails(coolify, args as unknown as GetApplicationDetailsArgs)
81
- case 'list_deployments':
82
- return handleListDeployments(coolify)
83
- case 'get_deployment':
84
- return handleGetDeployment(coolify, args as unknown as GetDeploymentArgs)
85
- case 'get_application_deployments':
86
- return handleGetApplicationDeployments(coolify, args as unknown as GetApplicationDeploymentsArgs)
87
- default:
88
- return {
89
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
90
- isError: true
91
- }
92
- }
93
- }
94
-
95
- // Tool argument types
96
- interface DeployArgs {
97
- uuid: string
98
- force?: boolean
99
- tag?: string
100
- }
101
-
102
- interface GetEnvVarsArgs {
103
- uuid: string
104
- }
105
-
106
- interface SetEnvVarsArgs {
107
- uuid: string
108
- envVars: Record<string, string>
109
- }
110
-
111
- interface GetDeploymentStatusArgs {
112
- uuid: string
113
- }
114
-
115
- interface ListApplicationsArgs {
116
- teamId?: string
117
- projectId?: string
118
- }
119
-
120
- interface DeleteApplicationArgs {
121
- uuid: string
122
- }
123
-
124
- interface GetApplicationLogsArgs {
125
- uuid: string
126
- tail?: number
127
- }
128
-
129
- interface StartApplicationArgs {
130
- uuid: string
131
- }
132
-
133
- interface StopApplicationArgs {
134
- uuid: string
135
- }
136
-
137
- interface RestartApplicationArgs {
138
- uuid: string
139
- }
140
-
141
- interface GetDeploymentHistoryArgs {
142
- uuid: string
143
- }
144
-
145
- interface UpdateApplicationArgs {
146
- uuid: string
147
- name?: string
148
- description?: string
149
- buildPack?: 'dockerfile' | 'nixpacks' | 'static' | 'dockercompose'
150
- gitBranch?: string
151
- portsExposes?: string
152
- installCommand?: string
153
- buildCommand?: string
154
- startCommand?: string
155
- domains?: string
156
- isForceHttpsEnabled?: boolean
157
- isAutoDeployEnabled?: boolean
158
- }
159
-
160
- interface SetDomainsArgs {
161
- uuid: string
162
- domains: string
163
- forceHttps?: boolean
164
- }
165
-
166
- interface GetServerArgs {
167
- serverUuid: string
168
- }
169
-
170
- interface GetServerDestinationsArgs {
171
- serverUuid: string
172
- }
173
-
174
- interface CreateApplicationArgs {
175
- name: string
176
- serverUuid: string
177
- projectUuid: string
178
- environmentUuid: string
179
- githubRepoUrl: string
180
- description?: string
181
- branch?: string
182
- buildPack?: 'dockerfile' | 'nixpacks' | 'static' | 'dockercompose'
183
- type?: string
184
- dockerComposeLocation?: string
185
- dockerfileLocation?: string
186
- baseDirectory?: string
187
- }
188
-
189
- interface CreateProjectArgs {
190
- name: string
191
- description?: string
192
- }
193
-
194
- interface GetResourceUsageArgs {
195
- uuid: string
196
- }
197
-
198
- interface GetApplicationDetailsArgs {
199
- uuid: string
200
- }
201
-
202
- interface GetDeploymentArgs {
203
- deploymentUuid: string
204
- }
205
-
206
- interface GetApplicationDeploymentsArgs {
207
- uuid: string
208
- skip?: number
209
- take?: number
210
- }
211
-
212
- // Handler implementations
213
- async function handleDeploy(coolify: CoolifyService, args: DeployArgs): Promise<CallToolResult> {
214
- const result = await coolify.deploy({
215
- uuid: args.uuid,
216
- force: args.force ?? false,
217
- tag: args.tag
218
- })
87
+ // ─── Applications ──────────────────────────────────────────────────
88
+ case "deploy":
89
+ return mcpCall(
90
+ (s) =>
91
+ s.applications
92
+ .deploy(a.uuid, { force: a.force, tag: a.tag })
93
+ .then((r) => ({
94
+ deploymentUuid: r.deploymentUuid,
95
+ resourceUuid: r.resourceUuid,
96
+ message: "Deployment started",
97
+ })),
98
+ "Deployment failed",
99
+ );
100
+ case "list_applications":
101
+ return mcpList(
102
+ (s) => s.applications.list(),
103
+ "applications",
104
+ "Failed to list applications",
105
+ );
106
+ case "get_application_details":
107
+ return mcpCall(
108
+ (s) =>
109
+ s.applications.resolve(a.uuid).then((app) => ({ application: app })),
110
+ "Failed to get application",
111
+ );
112
+ case "start_application":
113
+ return mcpCall(
114
+ (s) =>
115
+ s.applications
116
+ .start(a.uuid, { force: a.force, instantDeploy: a.instantDeploy })
117
+ .then(() => ({
118
+ message: `Application ${a.uuid} started`,
119
+ })),
120
+ "Failed to start application",
121
+ );
122
+ case "stop_application":
123
+ return mcpCall(
124
+ (s) =>
125
+ s.applications.stop(a.uuid).then(() => ({
126
+ message: `Application ${a.uuid} stopped`,
127
+ })),
128
+ "Failed to stop application",
129
+ );
130
+ case "restart_application":
131
+ return mcpCall(
132
+ (s) =>
133
+ s.applications.restart(a.uuid).then(() => ({
134
+ message: `Application ${a.uuid} restarted`,
135
+ })),
136
+ "Failed to restart application",
137
+ );
138
+ case "delete_application":
139
+ return mcpCall(
140
+ (s) =>
141
+ s.applications.delete(a.uuid).then(() => ({
142
+ message: `Application ${a.uuid} deleted`,
143
+ })),
144
+ "Failed to delete application",
145
+ );
146
+ case "update_application":
147
+ return mcpCall(
148
+ (s) =>
149
+ s.applications
150
+ .update(a.uuid, {
151
+ name: a.name,
152
+ description: a.description,
153
+ buildPack: a.buildPack,
154
+ gitBranch: a.gitBranch,
155
+ portsExposes: a.portsExposes,
156
+ installCommand: a.installCommand,
157
+ buildCommand: a.buildCommand,
158
+ startCommand: a.startCommand,
159
+ domains: a.domains,
160
+ isForceHttpsEnabled: a.isForceHttpsEnabled,
161
+ isAutoDeployEnabled: a.isAutoDeployEnabled,
162
+ })
163
+ .then((app) => ({
164
+ message: `Application ${a.uuid} updated`,
165
+ application: app,
166
+ })),
167
+ "Failed to update application",
168
+ );
169
+ case "get_application_logs":
170
+ return mcpCall(
171
+ (s) =>
172
+ s.applications
173
+ .logs(a.uuid, { tail: a.tail, serviceName: a.serviceName })
174
+ .then((logs) => ({
175
+ timestamp: logs.timestamp,
176
+ logCount: logs.logs.length,
177
+ logs: logs.logs,
178
+ })),
179
+ "Failed to get logs",
180
+ );
181
+ case "get_deployment_history":
182
+ return mcpList(
183
+ (s) => s.applications.deployments(a.uuid),
184
+ "deployments",
185
+ "Failed to get deployment history",
186
+ );
187
+ case "get_application_deployments":
188
+ return mcpList(
189
+ (s) => s.applications.deployments(a.uuid),
190
+ "deployments",
191
+ "Failed to get deployments",
192
+ );
193
+ case "execute_command":
194
+ return mcpCall(
195
+ (s) => s.applications.exec(a.uuid, a.command),
196
+ "Failed to execute command",
197
+ );
198
+
199
+ // ─── Environment Variables ─────────────────────────────────────────
200
+ case "get_env_vars":
201
+ return mcpCall(
202
+ (s) =>
203
+ s.applications.envVars(a.uuid).then((vars) => ({
204
+ total: vars.length,
205
+ runtime: vars.filter((v) => v.is_runtime),
206
+ buildtime: vars.filter((v) => v.is_buildtime),
207
+ })),
208
+ "Failed to get env vars",
209
+ );
210
+ case "set_env_vars":
211
+ return mcpCall((s) => {
212
+ const entries = Object.entries(a.envVars as Record<string, string>);
213
+ return s.applications
214
+ .bulkSetEnv(
215
+ a.uuid,
216
+ entries.map(([key, value]) => ({ key, value })),
217
+ )
218
+ .then(() => ({
219
+ message: `Set ${entries.length} environment variable(s)`,
220
+ }));
221
+ }, "Failed to set env vars");
222
+ case "bulk_update_env_vars":
223
+ return mcpCall(
224
+ (s) =>
225
+ s.applications.bulkSetEnv(a.uuid, a.envVars).then(() => ({
226
+ message: `Bulk updated ${a.envVars.length} variable(s)`,
227
+ })),
228
+ "Failed to bulk update env vars",
229
+ );
230
+
231
+ // ─── Domains ───────────────────────────────────────────────────────
232
+ case "set_domains":
233
+ return mcpCall(
234
+ (s) =>
235
+ s.applications
236
+ .update(a.uuid, {
237
+ domains: a.domains,
238
+ isForceHttpsEnabled: a.forceHttps ?? true,
239
+ })
240
+ .then(() => ({
241
+ message: `Domains set for ${a.uuid}`,
242
+ domains: a.domains.split(",").map((d: string) => d.trim()),
243
+ })),
244
+ "Failed to set domains",
245
+ );
246
+
247
+ // ─── Deployment Status ─────────────────────────────────────────────
248
+ case "get_deployment_status":
249
+ return mcpCall(
250
+ (s) =>
251
+ s.applications
252
+ .resolve(a.uuid)
253
+ .then((app) => ({ status: app.status })),
254
+ "Failed to get status",
255
+ );
256
+
257
+ // ─── Deployments ───────────────────────────────────────────────────
258
+ case "list_deployments":
259
+ return mcpList(
260
+ (s) => s.deployments.active(),
261
+ "deployments",
262
+ "Failed to list deployments",
263
+ );
264
+ case "get_deployment":
265
+ return mcpCall(
266
+ (s) =>
267
+ s.deployments.get(a.deploymentUuid).then((d) => ({ deployment: d })),
268
+ "Failed to get deployment",
269
+ );
270
+ case "cancel_deployment":
271
+ return mcpCall(
272
+ (s) =>
273
+ s.deployments
274
+ .cancel(a.deploymentUuid)
275
+ .then(() => ({ message: "Deployment cancelled" })),
276
+ "Failed to cancel deployment",
277
+ );
278
+
279
+ // ─── Servers ───────────────────────────────────────────────────────
280
+ case "list_servers":
281
+ return mcpList(
282
+ (s) => s.servers.list(),
283
+ "servers",
284
+ "Failed to list servers",
285
+ );
286
+ case "get_server":
287
+ return mcpCall(
288
+ (s) => s.servers.get(a.serverUuid).then((srv) => ({ server: srv })),
289
+ "Failed to get server",
290
+ );
291
+ case "get_server_destinations":
292
+ return mcpList(
293
+ (s) => s.servers.destinations(a.serverUuid),
294
+ "destinations",
295
+ "Failed to get destinations",
296
+ );
297
+ case "get_server_resources":
298
+ return mcpList(
299
+ (s) => s.servers.resources(a.serverUuid),
300
+ "resources",
301
+ "Failed to get server resources",
302
+ );
303
+ case "get_server_domains":
304
+ return mcpList(
305
+ (s) => s.servers.domains(a.serverUuid),
306
+ "domains",
307
+ "Failed to get server domains",
308
+ );
309
+ case "validate_server":
310
+ return mcpCall(
311
+ (s) =>
312
+ s.servers
313
+ .validate(a.serverUuid)
314
+ .then(() => ({ message: "Server validated" })),
315
+ "Failed to validate server",
316
+ );
317
+
318
+ // ─── Projects ──────────────────────────────────────────────────────
319
+ case "list_projects":
320
+ return mcpList(
321
+ (s) => s.projects.list(),
322
+ "projects",
323
+ "Failed to list projects",
324
+ );
325
+ case "create_project":
326
+ return mcpCall(
327
+ (s) =>
328
+ s.projects.create(a.name, a.description).then((p) => ({
329
+ message: `Project "${a.name}" created`,
330
+ uuid: p.uuid,
331
+ })),
332
+ "Failed to create project",
333
+ );
334
+ case "create_application":
335
+ return mcpCall(
336
+ (s) =>
337
+ s.applications
338
+ .create({
339
+ name: a.name,
340
+ description: a.description,
341
+ projectUuid: a.projectUuid,
342
+ environmentUuid: a.environmentUuid,
343
+ serverUuid: a.serverUuid,
344
+ type: a.type || "public",
345
+ githubAppUuid: a.githubAppUuid,
346
+ githubRepoUrl: a.githubRepoUrl,
347
+ branch: a.branch,
348
+ buildPack: a.buildPack,
349
+ portsExposes: a.portsExposes,
350
+ dockerComposeLocation: a.dockerComposeLocation,
351
+ dockerfileLocation: a.dockerfileLocation,
352
+ baseDirectory: a.baseDirectory,
353
+ })
354
+ .then((r) => ({
355
+ message: `Application "${a.name}" created`,
356
+ uuid: r.uuid,
357
+ })),
358
+ "Failed to create application",
359
+ );
360
+
361
+ // ─── Teams ─────────────────────────────────────────────────────────
362
+ case "list_teams":
363
+ return mcpList((s) => s.teams.list(), "teams", "Failed to list teams");
364
+ case "get_current_team":
365
+ return mcpCall(
366
+ (s) => s.teams.current().then((t) => ({ team: t })),
367
+ "Failed to get current team",
368
+ );
369
+ case "get_team_members":
370
+ return mcpList(
371
+ (s) => s.teams.members(a.teamId),
372
+ "members",
373
+ "Failed to get team members",
374
+ );
375
+
376
+ // ─── Databases ─────────────────────────────────────────────────────
377
+ case "list_databases":
378
+ return mcpList(
379
+ (s) => s.databases.list(),
380
+ "databases",
381
+ "Failed to list databases",
382
+ );
383
+ case "get_database":
384
+ return mcpCall(
385
+ (s) => s.databases.get(a.uuid).then((db) => ({ database: db })),
386
+ "Failed to get database",
387
+ );
388
+ case "create_database":
389
+ return mcpCall(
390
+ (s) =>
391
+ s.databases.create(a.dbType, a.data).then((r) => ({
392
+ message: `${a.dbType} database created`,
393
+ uuid: r.uuid,
394
+ })),
395
+ "Failed to create database",
396
+ );
397
+ case "delete_database":
398
+ return mcpCall(
399
+ (s) =>
400
+ s.databases
401
+ .delete(a.uuid)
402
+ .then(() => ({ message: "Database deleted" })),
403
+ "Failed to delete database",
404
+ );
405
+ case "start_database":
406
+ return mcpCall(
407
+ (s) =>
408
+ s.databases
409
+ .start(a.uuid)
410
+ .then(() => ({ message: "Database started" })),
411
+ "Failed to start database",
412
+ );
413
+ case "stop_database":
414
+ return mcpCall(
415
+ (s) =>
416
+ s.databases
417
+ .stop(a.uuid)
418
+ .then(() => ({ message: "Database stopped" })),
419
+ "Failed to stop database",
420
+ );
421
+ case "restart_database":
422
+ return mcpCall(
423
+ (s) =>
424
+ s.databases
425
+ .restart(a.uuid)
426
+ .then(() => ({ message: "Database restarted" })),
427
+ "Failed to restart database",
428
+ );
429
+
430
+ // ─── Services ──────────────────────────────────────────────────────
431
+ case "list_services":
432
+ return mcpList(
433
+ (s) => s.services.list(),
434
+ "services",
435
+ "Failed to list services",
436
+ );
437
+ case "get_service":
438
+ return mcpCall(
439
+ (s) => s.services.get(a.uuid).then((svc) => ({ service: svc })),
440
+ "Failed to get service",
441
+ );
442
+ case "start_service":
443
+ return mcpCall(
444
+ (s) =>
445
+ s.services.start(a.uuid).then(() => ({ message: "Service started" })),
446
+ "Failed to start service",
447
+ );
448
+ case "stop_service":
449
+ return mcpCall(
450
+ (s) =>
451
+ s.services.stop(a.uuid).then(() => ({ message: "Service stopped" })),
452
+ "Failed to stop service",
453
+ );
454
+ case "restart_service":
455
+ return mcpCall(
456
+ (s) =>
457
+ s.services
458
+ .restart(a.uuid)
459
+ .then(() => ({ message: "Service restarted" })),
460
+ "Failed to restart service",
461
+ );
462
+ case "delete_service":
463
+ return mcpCall(
464
+ (s) =>
465
+ s.services
466
+ .delete(a.uuid)
467
+ .then(() => ({ message: "Service deleted" })),
468
+ "Failed to delete service",
469
+ );
470
+
471
+ // ─── Private Keys ──────────────────────────────────────────────────
472
+ case "list_private_keys":
473
+ return mcpList(
474
+ (s) => s.keys.list(),
475
+ "keys",
476
+ "Failed to list private keys",
477
+ );
478
+ case "get_private_key":
479
+ return mcpCall(
480
+ (s) => s.keys.get(a.uuid).then((k) => ({ privateKey: k })),
481
+ "Failed to get private key",
482
+ );
483
+
484
+ // ─── Smart Resolution ──────────────────────────────────────────────
485
+ case "resolve_application":
486
+ return mcpCall(
487
+ (s) =>
488
+ s.applications.resolve(a.query).then((app) => ({
489
+ application: {
490
+ uuid: app.uuid,
491
+ name: app.name,
492
+ status: app.status,
493
+ fqdn: app.fqdn,
494
+ },
495
+ })),
496
+ "Failed to resolve application",
497
+ );
498
+ case "resolve_server":
499
+ return mcpCall(
500
+ (s) =>
501
+ s.servers.resolve(a.query).then((srv) => ({
502
+ server: {
503
+ uuid: srv.uuid,
504
+ name: srv.name,
505
+ ip: srv.ip,
506
+ is_reachable: srv.is_reachable,
507
+ },
508
+ })),
509
+ "Failed to resolve server",
510
+ );
511
+
512
+ // ─── Diagnostics ───────────────────────────────────────────────────
513
+ case "diagnose_application":
514
+ return mcpCall((s) => s.diagnose.app(a.query), "Diagnosis failed");
515
+ case "diagnose_server":
516
+ return mcpCall((s) => s.diagnose.server(a.query), "Diagnosis failed");
517
+ case "find_infrastructure_issues":
518
+ return mcpCall(
519
+ (s) => s.diagnose.infrastructure(),
520
+ "Infrastructure scan failed",
521
+ );
522
+
523
+ // ─── Batch Operations ──────────────────────────────────────────────
524
+ case "restart_project_apps":
525
+ return mcpCall(
526
+ (s) => s.batch.restartProject(a.projectUuid),
527
+ "Batch restart failed",
528
+ );
529
+ case "redeploy_project_apps":
530
+ return mcpCall(
531
+ (s) => s.batch.redeployProject(a.projectUuid, a.force),
532
+ "Batch redeploy failed",
533
+ );
534
+ case "stop_all_apps":
535
+ return mcpCall((s) => s.batch.stopAll(), "Stop all failed");
536
+
537
+ // ─── Network Diagnostics ─────────────────────────────────────────
538
+ case "inspect_network":
539
+ return mcpCall(
540
+ (s) => s.network.inspect(a.uuid, a.servicesToTest),
541
+ "Network inspection failed",
542
+ );
543
+ case "analyze_deploy_failure":
544
+ return mcpCall(
545
+ (s) => s.diagnose.deployFailure(a.deploymentUuid),
546
+ "Deploy analysis failed",
547
+ );
548
+
549
+ // ─── Version / Health ──────────────────────────────────────────────
550
+ case "get_version":
551
+ return mcpCall(
552
+ (s) => s.version().then((v) => ({ version: v.version })),
553
+ "Failed to get version",
554
+ );
555
+ case "health_check":
556
+ return mcpCall(
557
+ (s) =>
558
+ s.servers.list().then(() => ({
559
+ status: "healthy",
560
+ message: "Coolify API is accessible",
561
+ })),
562
+ "Health check failed",
563
+ );
564
+ case "get_resource_usage":
565
+ return mcpCall(
566
+ (s) =>
567
+ s.applications
568
+ .resolve(a.uuid)
569
+ .then((app) => ({ status: app.status })),
570
+ "Failed to get resource usage",
571
+ );
219
572
 
220
- if (isOk(result)) {
221
- return {
222
- content: [{
223
- type: 'text',
224
- text: JSON.stringify({
225
- success: true,
226
- deploymentUuid: result.value.deploymentUuid,
227
- resourceUuid: result.value.resourceUuid,
228
- message: 'Deployment started'
229
- }, null, 2)
230
- }]
231
- }
232
- }
233
-
234
- return {
235
- content: [{ type: 'text', text: `Deployment failed: ${result.error.message}` }],
236
- isError: true
237
- }
238
- }
239
-
240
- async function handleGetEnvVars(coolify: CoolifyService, args: GetEnvVarsArgs): Promise<CallToolResult> {
241
- const result = await coolify.getEnvironmentVariables(args.uuid)
242
-
243
- if (isOk(result)) {
244
- const runtimeVars = result.value.filter(ev => ev.is_runtime)
245
- const buildtimeVars = result.value.filter(ev => ev.is_buildtime)
246
- return {
247
- content: [{
248
- type: 'text',
249
- text: JSON.stringify({
250
- success: true,
251
- total: result.value.length,
252
- runtimeCount: runtimeVars.length,
253
- buildtimeCount: buildtimeVars.length,
254
- runtime: runtimeVars,
255
- buildtime: buildtimeVars
256
- }, null, 2)
257
- }]
258
- }
259
- }
260
-
261
- return {
262
- content: [{ type: 'text', text: `Failed to get env vars: ${result.error.message}` }],
263
- isError: true
264
- }
265
- }
266
-
267
- async function handleSetEnvVars(coolify: CoolifyService, args: SetEnvVarsArgs): Promise<CallToolResult> {
268
- const result = await coolify.setEnvironmentVariables(args.uuid, args.envVars)
269
-
270
- if (isOk(result)) {
271
- return {
272
- content: [{
273
- type: 'text',
274
- text: `Set ${Object.keys(args.envVars).length} environment variable(s). Use deploy tool to apply changes.`
275
- }]
276
- }
277
- }
278
-
279
- return {
280
- content: [{ type: 'text', text: `Failed to set env vars: ${result.error.message}` }],
281
- isError: true
282
- }
283
- }
284
-
285
- async function handleGetDeploymentStatus(coolify: CoolifyService, args: GetDeploymentStatusArgs): Promise<CallToolResult> {
286
- const result = await coolify.getApplicationStatus(args.uuid)
287
-
288
- if (isOk(result)) {
289
- return {
290
- content: [{ type: 'text', text: JSON.stringify({ status: result.value }, null, 2) }]
291
- }
292
- }
293
-
294
- return {
295
- content: [{ type: 'text', text: `Failed to get status: ${result.error.message}` }],
296
- isError: true
297
- }
298
- }
299
-
300
- async function handleListApplications(coolify: CoolifyService, args: ListApplicationsArgs): Promise<CallToolResult> {
301
- const result = await coolify.listApplications(args.teamId, args.projectId)
302
-
303
- if (isOk(result)) {
304
- return {
305
- content: [{
306
- type: 'text',
307
- text: JSON.stringify({ success: true, count: result.value.length, applications: result.value }, null, 2)
308
- }]
309
- }
310
- }
311
-
312
- return {
313
- content: [{ type: 'text', text: `Failed to list applications: ${result.error.message}` }],
314
- isError: true
315
- }
316
- }
317
-
318
- async function handleDeleteApplication(coolify: CoolifyService, args: DeleteApplicationArgs): Promise<CallToolResult> {
319
- const result = await coolify.deleteApplication(args.uuid)
320
-
321
- if (isOk(result)) {
322
- return {
323
- content: [{
324
- type: 'text',
325
- text: JSON.stringify({ success: true, message: `Application ${args.uuid} deleted successfully` }, null, 2)
326
- }]
327
- }
328
- }
329
-
330
- return {
331
- content: [{ type: 'text', text: `Failed to delete application: ${result.error.message}` }],
332
- isError: true
333
- }
334
- }
335
-
336
- async function handleGetApplicationLogs(coolify: CoolifyService, args: GetApplicationLogsArgs): Promise<CallToolResult> {
337
- const result = await coolify.getApplicationLogs(args.uuid, { tail: args.tail })
338
-
339
- if (isOk(result)) {
340
- return {
341
- content: [{
342
- type: 'text',
343
- text: JSON.stringify({
344
- success: true,
345
- timestamp: result.value.timestamp,
346
- logCount: result.value.logs.length,
347
- logs: result.value.logs
348
- }, null, 2)
349
- }]
350
- }
351
- }
352
-
353
- return {
354
- content: [{ type: 'text', text: `Failed to get logs: ${result.error.message}` }],
355
- isError: true
356
- }
357
- }
358
-
359
- async function handleStartApplication(coolify: CoolifyService, args: StartApplicationArgs): Promise<CallToolResult> {
360
- const result = await coolify.startApplication(args.uuid)
361
-
362
- if (isOk(result)) {
363
- return {
364
- content: [{
365
- type: 'text',
366
- text: JSON.stringify({ success: true, message: `Application ${args.uuid} started`, application: result.value }, null, 2)
367
- }]
368
- }
369
- }
370
-
371
- return {
372
- content: [{ type: 'text', text: `Failed to start application: ${result.error.message}` }],
373
- isError: true
374
- }
375
- }
376
-
377
- async function handleStopApplication(coolify: CoolifyService, args: StopApplicationArgs): Promise<CallToolResult> {
378
- const result = await coolify.stopApplication(args.uuid)
379
-
380
- if (isOk(result)) {
381
- return {
382
- content: [{
383
- type: 'text',
384
- text: JSON.stringify({ success: true, message: `Application ${args.uuid} stopped`, application: result.value }, null, 2)
385
- }]
386
- }
387
- }
388
-
389
- return {
390
- content: [{ type: 'text', text: `Failed to stop application: ${result.error.message}` }],
391
- isError: true
392
- }
393
- }
394
-
395
- async function handleRestartApplication(coolify: CoolifyService, args: RestartApplicationArgs): Promise<CallToolResult> {
396
- const result = await coolify.restartApplication(args.uuid)
397
-
398
- if (isOk(result)) {
399
- return {
400
- content: [{
401
- type: 'text',
402
- text: JSON.stringify({ success: true, message: `Application ${args.uuid} restarted`, application: result.value }, null, 2)
403
- }]
404
- }
405
- }
406
-
407
- return {
408
- content: [{ type: 'text', text: `Failed to restart application: ${result.error.message}` }],
409
- isError: true
410
- }
411
- }
412
-
413
- async function handleGetDeploymentHistory(coolify: CoolifyService, args: GetDeploymentHistoryArgs): Promise<CallToolResult> {
414
- const result = await coolify.getApplicationDeploymentHistory(args.uuid)
415
-
416
- if (isOk(result)) {
417
- return {
418
- content: [{
419
- type: 'text',
420
- text: JSON.stringify({ success: true, count: result.value.length, deployments: result.value }, null, 2)
421
- }]
422
- }
423
- }
424
-
425
- return {
426
- content: [{ type: 'text', text: `Failed to get deployment history: ${result.error.message}` }],
427
- isError: true
428
- }
429
- }
430
-
431
- async function handleUpdateApplication(coolify: CoolifyService, args: UpdateApplicationArgs): Promise<CallToolResult> {
432
- const result = await coolify.updateApplication(args.uuid, {
433
- name: args.name,
434
- description: args.description,
435
- buildPack: args.buildPack,
436
- gitBranch: args.gitBranch,
437
- portsExposes: args.portsExposes,
438
- installCommand: args.installCommand,
439
- buildCommand: args.buildCommand,
440
- startCommand: args.startCommand,
441
- domains: args.domains,
442
- isForceHttpsEnabled: args.isForceHttpsEnabled,
443
- isAutoDeployEnabled: args.isAutoDeployEnabled
444
- })
445
-
446
- if (isOk(result)) {
447
- return {
448
- content: [{
449
- type: 'text',
450
- text: JSON.stringify({
451
- success: true,
452
- message: `Application ${args.uuid} updated. Use deploy tool to apply changes.`,
453
- application: result.value
454
- }, null, 2)
455
- }]
456
- }
457
- }
458
-
459
- return {
460
- content: [{ type: 'text', text: `Failed to update application: ${result.error.message}` }],
461
- isError: true
462
- }
463
- }
464
-
465
- async function handleSetDomains(coolify: CoolifyService, args: SetDomainsArgs): Promise<CallToolResult> {
466
- const result = await coolify.updateApplication(args.uuid, {
467
- domains: args.domains,
468
- isForceHttpsEnabled: args.forceHttps ?? true
469
- })
470
-
471
- if (isOk(result)) {
472
- const domainList = args.domains.split(',').map(d => d.trim())
473
- return {
474
- content: [{
475
- type: 'text',
476
- text: JSON.stringify({
477
- success: true,
478
- message: `Domains configured for application ${args.uuid}`,
479
- domains: domainList,
480
- forceHttps: args.forceHttps ?? true,
481
- nextSteps: [
482
- 'Ensure DNS records point to your server IP',
483
- 'Use deploy tool to apply domain changes',
484
- 'SSL certificates will be auto-generated via Let\'s Encrypt'
485
- ]
486
- }, null, 2)
487
- }]
488
- }
489
- }
490
-
491
- return {
492
- content: [{ type: 'text', text: `Failed to set domains: ${result.error.message}` }],
493
- isError: true
494
- }
495
- }
496
-
497
- async function handleListServers(coolify: CoolifyService): Promise<CallToolResult> {
498
- const result = await coolify.listServers()
499
-
500
- if (isOk(result)) {
501
- return {
502
- content: [{
503
- type: 'text',
504
- text: JSON.stringify({ success: true, count: result.value.length, servers: result.value }, null, 2)
505
- }]
506
- }
507
- }
508
-
509
- return {
510
- content: [{ type: 'text', text: `Failed to list servers: ${result.error.message}` }],
511
- isError: true
512
- }
513
- }
514
-
515
- async function handleGetServer(coolify: CoolifyService, args: GetServerArgs): Promise<CallToolResult> {
516
- const result = await coolify.getServer(args.serverUuid)
517
-
518
- if (isOk(result)) {
519
- return {
520
- content: [{ type: 'text', text: JSON.stringify({ success: true, server: result.value }, null, 2) }]
521
- }
522
- }
523
-
524
- return {
525
- content: [{ type: 'text', text: `Failed to get server: ${result.error.message}` }],
526
- isError: true
527
- }
528
- }
529
-
530
- async function handleListProjects(coolify: CoolifyService): Promise<CallToolResult> {
531
- const result = await coolify.listProjects()
532
-
533
- if (isOk(result)) {
534
- return {
535
- content: [{
536
- type: 'text',
537
- text: JSON.stringify({ success: true, count: result.value.length, projects: result.value }, null, 2)
538
- }]
539
- }
540
- }
541
-
542
- return {
543
- content: [{ type: 'text', text: `Failed to list projects: ${result.error.message}` }],
544
- isError: true
545
- }
546
- }
547
-
548
- async function handleListTeams(coolify: CoolifyService): Promise<CallToolResult> {
549
- const result = await coolify.listTeams()
550
-
551
- if (isOk(result)) {
552
- return {
553
- content: [{
554
- type: 'text',
555
- text: JSON.stringify({ success: true, count: result.value.length, teams: result.value }, null, 2)
556
- }]
557
- }
558
- }
559
-
560
- return {
561
- content: [{ type: 'text', text: `Failed to list teams: ${result.error.message}` }],
562
- isError: true
563
- }
564
- }
565
-
566
- async function handleGetServerDestinations(coolify: CoolifyService, args: GetServerDestinationsArgs): Promise<CallToolResult> {
567
- const result = await coolify.getServerDestinations(args.serverUuid)
568
-
569
- if (isOk(result)) {
570
- return {
571
- content: [{
572
- type: 'text',
573
- text: JSON.stringify({
574
- success: true,
575
- serverUuid: args.serverUuid,
576
- count: result.value.length,
577
- destinations: result.value
578
- }, null, 2)
579
- }]
580
- }
581
- }
582
-
583
- return {
584
- content: [{ type: 'text', text: `Failed to get destinations: ${result.error.message}` }],
585
- isError: true
586
- }
587
- }
588
-
589
- async function handleCreateApplication(coolify: CoolifyService, args: CreateApplicationArgs): Promise<CallToolResult> {
590
- const result = await coolify.createApplication({
591
- name: args.name,
592
- description: args.description,
593
- projectUuid: args.projectUuid,
594
- environmentUuid: args.environmentUuid,
595
- serverUuid: args.serverUuid,
596
- type: (args.type as any) || 'public',
597
- githubRepoUrl: args.githubRepoUrl,
598
- branch: args.branch,
599
- buildPack: args.buildPack,
600
- dockerComposeLocation: args.dockerComposeLocation,
601
- dockerfileLocation: args.dockerfileLocation,
602
- baseDirectory: args.baseDirectory,
603
- })
604
-
605
- if (isOk(result)) {
606
- return {
607
- content: [{
608
- type: 'text',
609
- text: JSON.stringify({
610
- success: true,
611
- message: `Application "${args.name}" created successfully`,
612
- uuid: result.value.uuid,
613
- nextSteps: [
614
- 'Use set_env_vars to configure environment variables',
615
- 'Use deploy to start the first deployment'
616
- ]
617
- }, null, 2)
618
- }]
619
- }
620
- }
621
-
622
- return {
623
- content: [{ type: 'text', text: `Failed to create application: ${result.error.message}` }],
624
- isError: true
625
- }
626
- }
627
-
628
- async function handleCreateProject(coolify: CoolifyService, args: CreateProjectArgs): Promise<CallToolResult> {
629
- const result = await coolify.createProject(args.name, args.description)
630
-
631
- if (isOk(result)) {
632
- return {
633
- content: [{
634
- type: 'text',
635
- text: JSON.stringify({
636
- success: true,
637
- message: `Project "${args.name}" created successfully`,
638
- uuid: result.value.uuid,
639
- }, null, 2)
640
- }]
641
- }
642
- }
643
-
644
- return {
645
- content: [{ type: 'text', text: `Failed to create project: ${result.error.message}` }],
646
- isError: true
647
- }
648
- }
649
-
650
- async function handleGetResourceUsage(coolify: CoolifyService, args: GetResourceUsageArgs): Promise<CallToolResult> {
651
- // For now, use getApplicationStatus - Coolify API doesn't have a separate resource usage endpoint
652
- const result = await coolify.getApplicationStatus(args.uuid)
653
-
654
- if (isOk(result)) {
655
- return {
656
- content: [{ type: 'text', text: JSON.stringify({ success: true, status: result.value }, null, 2) }]
657
- }
658
- }
659
-
660
- return {
661
- content: [{ type: 'text', text: `Failed to get resource usage: ${result.error.message}` }],
662
- isError: true
663
- }
664
- }
665
-
666
- async function handleHealthCheck(coolify: CoolifyService): Promise<CallToolResult> {
667
- // Try to list servers as a health check
668
- const result = await coolify.listServers()
669
-
670
- if (isOk(result)) {
671
- return {
672
- content: [{
673
- type: 'text',
674
- text: JSON.stringify({ success: true, status: 'healthy', message: 'Coolify API is accessible' }, null, 2)
675
- }]
676
- }
677
- }
678
-
679
- return {
680
- content: [{ type: 'text', text: `Health check failed: ${result.error.message}` }],
681
- isError: true
682
- }
683
- }
684
-
685
- async function handleGetApplicationDetails(coolify: CoolifyService, args: GetApplicationDetailsArgs): Promise<CallToolResult> {
686
- // List all applications and filter by UUID to get full details
687
- const result = await coolify.listApplications()
688
-
689
- if (isOk(result)) {
690
- const app = result.value.find((a) => a.uuid === args.uuid)
691
- if (app) {
573
+ default:
692
574
  return {
693
- content: [{ type: 'text', text: JSON.stringify({ success: true, application: app }, null, 2) }]
694
- }
695
- }
696
- return {
697
- content: [{ type: 'text', text: `Application ${args.uuid} not found` }],
698
- isError: true
699
- }
700
- }
701
-
702
- return {
703
- content: [{ type: 'text', text: `Failed to get application details: ${result.error.message}` }],
704
- isError: true
705
- }
706
- }
707
-
708
- async function handleListDeployments(coolify: CoolifyService): Promise<CallToolResult> {
709
- const result = await coolify.listDeployments()
710
-
711
- if (isOk(result)) {
712
- return {
713
- content: [{
714
- type: 'text',
715
- text: JSON.stringify({
716
- success: true,
717
- count: result.value.length,
718
- deployments: result.value
719
- }, null, 2)
720
- }]
721
- }
722
- }
723
-
724
- return {
725
- content: [{ type: 'text', text: `Failed to list deployments: ${result.error.message}` }],
726
- isError: true
727
- }
728
- }
729
-
730
- async function handleGetDeployment(coolify: CoolifyService, args: GetDeploymentArgs): Promise<CallToolResult> {
731
- const result = await coolify.getDeployment(args.deploymentUuid)
732
-
733
- if (isOk(result)) {
734
- return {
735
- content: [{
736
- type: 'text',
737
- text: JSON.stringify({
738
- success: true,
739
- deployment: result.value
740
- }, null, 2)
741
- }]
742
- }
743
- }
744
-
745
- return {
746
- content: [{ type: 'text', text: `Failed to get deployment: ${result.error.message}` }],
747
- isError: true
748
- }
749
- }
750
-
751
- async function handleGetApplicationDeployments(coolify: CoolifyService, args: GetApplicationDeploymentsArgs): Promise<CallToolResult> {
752
- const result = await coolify.getApplicationDeployments(args.uuid, args.skip, args.take)
753
-
754
- if (isOk(result)) {
755
- return {
756
- content: [{
757
- type: 'text',
758
- text: JSON.stringify({
759
- success: true,
760
- count: result.value.length,
761
- deployments: result.value
762
- }, null, 2)
763
- }]
764
- }
765
- }
766
-
767
- return {
768
- content: [{ type: 'text', text: `Failed to get application deployments: ${result.error.message}` }],
769
- isError: true
575
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
576
+ isError: true,
577
+ };
770
578
  }
771
579
  }