@selfhost.dev/mcp-server 0.2.0 → 0.4.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.
Files changed (36) hide show
  1. package/README.md +123 -22
  2. package/dist/index.js +31 -3
  3. package/dist/index.js.map +1 -1
  4. package/dist/tools/auth.js +0 -11
  5. package/dist/tools/auth.js.map +1 -1
  6. package/dist/tools/deployment-domains.d.ts +2 -0
  7. package/dist/tools/deployment-domains.js +128 -0
  8. package/dist/tools/deployment-domains.js.map +1 -0
  9. package/dist/tools/deployment-notifications.d.ts +2 -0
  10. package/dist/tools/deployment-notifications.js +74 -0
  11. package/dist/tools/deployment-notifications.js.map +1 -0
  12. package/dist/tools/deployments.d.ts +2 -0
  13. package/dist/tools/deployments.js +302 -0
  14. package/dist/tools/deployments.js.map +1 -0
  15. package/dist/tools/env-vars.d.ts +2 -0
  16. package/dist/tools/env-vars.js +121 -0
  17. package/dist/tools/env-vars.js.map +1 -0
  18. package/dist/tools/github.d.ts +2 -0
  19. package/dist/tools/github.js +106 -0
  20. package/dist/tools/github.js.map +1 -0
  21. package/dist/tools/hetzner.d.ts +2 -0
  22. package/dist/tools/hetzner.js +263 -0
  23. package/dist/tools/hetzner.js.map +1 -0
  24. package/dist/tools/instances.js +1 -1
  25. package/dist/tools/project-databases.d.ts +2 -0
  26. package/dist/tools/project-databases.js +169 -0
  27. package/dist/tools/project-databases.js.map +1 -0
  28. package/dist/tools/project-services.d.ts +2 -0
  29. package/dist/tools/project-services.js +94 -0
  30. package/dist/tools/project-services.js.map +1 -0
  31. package/dist/tools/projects.d.ts +2 -0
  32. package/dist/tools/projects.js +141 -0
  33. package/dist/tools/projects.js.map +1 -0
  34. package/dist/types/tiers.js +37 -1
  35. package/dist/types/tiers.js.map +1 -1
  36. package/package.json +10 -3
@@ -0,0 +1,302 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client.js";
3
+ import { session } from "../session.js";
4
+ /**
5
+ * Deployments (GithubRepoDeployment) - a deployed app from a GitHub repo,
6
+ * living inside a project (CoolifyProject). Each deployment has a build/run
7
+ * history; runs are returned INLINE on the deployment payload (`deployment_runs`,
8
+ * newest 50), there is no separate runs-list endpoint.
9
+ *
10
+ * Endpoints (base /api/v1/platform/github_repo_deployments):
11
+ * - GET / list (apps for the org)
12
+ * - GET /:pid show (single deployment + inline runs)
13
+ * - POST / create (provision Coolify app + first deploy)
14
+ * - PATCH /:pid update status/auto_deploy/ignore_paths/max_concurrent_previews
15
+ * - PATCH /:pid/build_config update build_pack/base_directory/port/dockerfile_location/env_vars
16
+ * - DELETE /:pid delete (also removes the Coolify app)
17
+ * - POST /:pid/deploy trigger a manual deploy (new run)
18
+ * - GET /:pid/logs app/container logs
19
+ * - GET /:pid/health health probe
20
+ * - GET /:pid/runs/:run_pid/logs build + container logs for one run
21
+ * - POST /:pid/runs/:run_pid/redeploy re-run the same commit
22
+ * - POST /:pid/runs/:run_pid/rollback re-deploy a prior successful run's commit
23
+ *
24
+ * `organization_id` is auto-injected from the active org by the HTTP client.
25
+ */
26
+ const DEPLOYMENTS_BASE = "/api/v1/platform/github_repo_deployments";
27
+ const BUILD_PACKS = ["nixpacks", "dockerfile", "dockercompose", "static"];
28
+ const DEPLOY_STATUSES = ["active", "paused"];
29
+ const NO_ORG = "No active organization. Call `list_organizations` then `select_organization` first.";
30
+ function hasOrg() {
31
+ return session.getActiveOrgId() !== null;
32
+ }
33
+ const RUN_STATUS_NOTE = "Run status values: `enqueued`/`building`/`deploying` are in-progress (transient); `active` = deployed successfully; `failed`/`aborted`/`removed`/`skipped` are terminal. Poll until the latest run reaches a terminal status.";
34
+ export function registerDeploymentTools(server) {
35
+ server.tool("list_deployments", `List deployments (apps) in the active organization. Each entry includes repo_full_name, branch, build_pack, port, status (active | paused - the container lifecycle, distinct from a run's status), deploy_url, custom domains, and an inline \`deployment_runs\` array (newest 50). ${RUN_STATUS_NOTE}`, {
36
+ is_preview: z.boolean().optional().describe("If set, filter by preview-deployment flag"),
37
+ }, async ({ is_preview }) => {
38
+ if (!hasOrg())
39
+ return { content: [{ type: "text", text: NO_ORG }] };
40
+ const result = await apiRequest(DEPLOYMENTS_BASE, {
41
+ query: { is_preview: is_preview === undefined ? undefined : String(is_preview) },
42
+ toolName: "list_deployments",
43
+ });
44
+ if (!result.success) {
45
+ return { content: [{ type: "text", text: `Failed to list deployments (${result.statusCode}): ${result.message}` }] };
46
+ }
47
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
48
+ });
49
+ server.tool("get_deployment", `Get one deployment by pid, including its inline build/run history (\`deployment_runs\`, newest 50) and the \`current_deployment_run_pid\`. Use this to POLL deploy progress after \`trigger_deploy\`/\`redeploy_run\`/\`rollback_run\`: re-call every ~5s until the latest run reaches a terminal status. ${RUN_STATUS_NOTE}`, {
50
+ deployment_pid: z.string().min(1),
51
+ }, async ({ deployment_pid }) => {
52
+ if (!hasOrg())
53
+ return { content: [{ type: "text", text: NO_ORG }] };
54
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}`, {
55
+ toolName: "get_deployment",
56
+ });
57
+ if (!result.success) {
58
+ return { content: [{ type: "text", text: `Failed to get deployment (${result.statusCode}): ${result.message}` }] };
59
+ }
60
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
61
+ });
62
+ server.tool("create_deployment", "Create a deployment (app) from a GitHub repo inside an existing project. The project (`coolify_project_pid`) MUST already be `active` (provisioned) - check `get_project` first. Provisions a Coolify app and enqueues the first build automatically. For private repos you must pass `installation_id` (from `list_github_installations`). If build_pack/port are omitted they're auto-detected (you can pre-run `detect_repo_config`). Returns the deployment + its first run; poll `get_deployment` for build progress.", {
63
+ repo_url: z.string().min(1).describe("https://github.com/owner/repo (optionally /tree/<branch>/<subdir>)"),
64
+ coolify_project_pid: z.string().min(1).describe("Target project pid; must be status `active`"),
65
+ branch: z.string().optional().describe("Branch to deploy; defaults to the repo's default branch"),
66
+ installation_id: z.number().int().optional().describe("GitHub App installation numeric id; required for private repos"),
67
+ environment: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase alphanumeric + hyphens").max(50).optional().describe("Coolify environment name (default production)"),
68
+ build_pack: z.enum(BUILD_PACKS).optional().describe("Build strategy; auto-detected if omitted"),
69
+ base_directory: z.string().optional().describe("Monorepo sub-directory to build from"),
70
+ dockerfile_location: z.string().optional().describe("Path to Dockerfile (when build_pack=dockerfile and it's nested)"),
71
+ port: z.number().int().min(1).max(65535).optional().describe("Container port; auto-detected/defaults to 3000"),
72
+ env_vars: z.record(z.string()).optional().describe("Initial environment variables { KEY: value }. Keys must match ^[A-Za-z_][A-Za-z0-9_]*$; values must be non-empty."),
73
+ required_env_vars: z.array(z.string()).optional().describe("Keys that must be present and non-blank in env_vars"),
74
+ custom_domain: z.string().optional().describe("Custom domain to attach after creation"),
75
+ }, async (args) => {
76
+ if (!hasOrg())
77
+ return { content: [{ type: "text", text: NO_ORG }] };
78
+ const body = {
79
+ repo_url: args.repo_url,
80
+ coolify_project_pid: args.coolify_project_pid,
81
+ };
82
+ if (args.branch !== undefined)
83
+ body.branch = args.branch;
84
+ if (args.installation_id !== undefined)
85
+ body.installation_id = args.installation_id;
86
+ if (args.environment !== undefined)
87
+ body.environment = args.environment;
88
+ if (args.build_pack !== undefined)
89
+ body.build_pack = args.build_pack;
90
+ if (args.base_directory !== undefined)
91
+ body.base_directory = args.base_directory;
92
+ if (args.dockerfile_location !== undefined)
93
+ body.dockerfile_location = args.dockerfile_location;
94
+ if (args.port !== undefined)
95
+ body.port = args.port;
96
+ if (args.env_vars !== undefined)
97
+ body.env_vars = args.env_vars;
98
+ if (args.required_env_vars !== undefined)
99
+ body.required_env_vars = args.required_env_vars;
100
+ if (args.custom_domain !== undefined)
101
+ body.custom_domain = args.custom_domain;
102
+ const result = await apiRequest(DEPLOYMENTS_BASE, {
103
+ method: "POST",
104
+ body,
105
+ toolName: "create_deployment",
106
+ });
107
+ if (!result.success) {
108
+ return { content: [{ type: "text", text: `Failed to create deployment (${result.statusCode}): ${result.message}` }] };
109
+ }
110
+ return {
111
+ content: [
112
+ { type: "text", text: "Deployment created and first build enqueued. Poll `get_deployment` until the latest run reaches a terminal status." },
113
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
114
+ ],
115
+ };
116
+ });
117
+ server.tool("update_deployment", "Update a deployment's lifecycle/routing settings: pause/resume (`status`), toggle GitHub-push auto-deploy, set ignore_paths, or cap concurrent preview deployments. Pass at least one field. To change build settings (build_pack/port/base_directory) use `update_deployment_build_config` instead. Pausing blocks deploys until resumed.", {
118
+ deployment_pid: z.string().min(1),
119
+ status: z.enum(DEPLOY_STATUSES).optional().describe("`paused` suspends deploys; `active` resumes"),
120
+ auto_deploy: z.boolean().optional().describe("Auto-deploy on GitHub push"),
121
+ ignore_paths: z.array(z.string()).optional().describe("Paths excluded from auto-deploy triggers (replaces the list)"),
122
+ max_concurrent_previews: z.number().int().min(1).max(20).optional().describe("Max simultaneous preview deployments (1-20)"),
123
+ }, async ({ deployment_pid, status, auto_deploy, ignore_paths, max_concurrent_previews }) => {
124
+ if (!hasOrg())
125
+ return { content: [{ type: "text", text: NO_ORG }] };
126
+ const body = {};
127
+ if (status !== undefined)
128
+ body.status = status;
129
+ if (auto_deploy !== undefined)
130
+ body.auto_deploy = auto_deploy;
131
+ if (ignore_paths !== undefined)
132
+ body.ignore_paths = ignore_paths;
133
+ if (max_concurrent_previews !== undefined)
134
+ body.max_concurrent_previews = max_concurrent_previews;
135
+ if (Object.keys(body).length === 0) {
136
+ return { content: [{ type: "text", text: "No changes - pass at least one of status / auto_deploy / ignore_paths / max_concurrent_previews." }] };
137
+ }
138
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}`, {
139
+ method: "PATCH",
140
+ body,
141
+ toolName: "update_deployment",
142
+ });
143
+ if (!result.success) {
144
+ return { content: [{ type: "text", text: `Failed to update deployment (${result.statusCode}): ${result.message}` }] };
145
+ }
146
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
147
+ });
148
+ server.tool("update_deployment_build_config", "Update a deployment's build configuration: build_pack, base_directory, port, dockerfile_location, and/or env_vars. The deployment must be provisioned (have a Coolify app) - otherwise returns 422. WARNING: if `env_vars` is provided it FULLY REPLACES the variable set (keys not included are deleted); to add/edit without deleting, use `set_env_vars`/`merge_env_vars` instead. Pass at least one field.", {
149
+ deployment_pid: z.string().min(1),
150
+ build_pack: z.enum(BUILD_PACKS).optional(),
151
+ base_directory: z.string().optional(),
152
+ port: z.number().int().min(1).max(65535).optional(),
153
+ dockerfile_location: z.string().optional(),
154
+ env_vars: z.record(z.string()).optional().describe("FULL REPLACEMENT of all env vars { KEY: value }"),
155
+ }, async ({ deployment_pid, build_pack, base_directory, port, dockerfile_location, env_vars }) => {
156
+ if (!hasOrg())
157
+ return { content: [{ type: "text", text: NO_ORG }] };
158
+ const body = {};
159
+ if (build_pack !== undefined)
160
+ body.build_pack = build_pack;
161
+ if (base_directory !== undefined)
162
+ body.base_directory = base_directory;
163
+ if (port !== undefined)
164
+ body.port = port;
165
+ if (dockerfile_location !== undefined)
166
+ body.dockerfile_location = dockerfile_location;
167
+ if (env_vars !== undefined)
168
+ body.env_vars = env_vars;
169
+ if (Object.keys(body).length === 0) {
170
+ return { content: [{ type: "text", text: "No changes - pass at least one build-config field." }] };
171
+ }
172
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/build_config`, {
173
+ method: "PATCH",
174
+ body,
175
+ toolName: "update_deployment_build_config",
176
+ });
177
+ if (!result.success) {
178
+ return { content: [{ type: "text", text: `Failed to update build config (${result.statusCode}): ${result.message}` }] };
179
+ }
180
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
181
+ });
182
+ server.tool("delete_deployment", "Delete a deployment and its Coolify app, build history, env vars, and attached domains. Irreversible.", {
183
+ deployment_pid: z.string().min(1),
184
+ name: z.string().optional().describe("Repo/app name for the confirmation message (cosmetic)"),
185
+ confirm: z.boolean().describe("Must be true. Removes the app and all its data."),
186
+ }, async ({ deployment_pid, name, confirm }) => {
187
+ if (!hasOrg())
188
+ return { content: [{ type: "text", text: NO_ORG }] };
189
+ if (!confirm) {
190
+ const label = name ? `\`${name}\` (${deployment_pid})` : deployment_pid;
191
+ return { content: [{ type: "text", text: `⚠️ About to DELETE deployment ${label} - removes the Coolify app, build history, env vars, and domains. No undo.\n\nCall again with confirm=true to proceed.` }] };
192
+ }
193
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}`, {
194
+ method: "DELETE",
195
+ toolName: "delete_deployment",
196
+ });
197
+ if (!result.success) {
198
+ return { content: [{ type: "text", text: `Failed to delete deployment (${result.statusCode}): ${result.message}` }] };
199
+ }
200
+ return { content: [{ type: "text", text: "Deployment removed successfully." }] };
201
+ });
202
+ server.tool("trigger_deploy", "Trigger a manual deploy of a deployment's current branch (creates a new build run). Returns 409 if a deploy is already in progress (one deploy at a time), or 422 if the deployment is paused or not yet provisioned. Poll `get_deployment` for the new run's progress.", {
203
+ deployment_pid: z.string().min(1),
204
+ }, async ({ deployment_pid }) => {
205
+ if (!hasOrg())
206
+ return { content: [{ type: "text", text: NO_ORG }] };
207
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/deploy`, {
208
+ method: "POST",
209
+ toolName: "trigger_deploy",
210
+ });
211
+ if (!result.success) {
212
+ return { content: [{ type: "text", text: `Failed to trigger deploy (${result.statusCode}): ${result.message}` }] };
213
+ }
214
+ return {
215
+ content: [
216
+ { type: "text", text: "Deploy triggered (new run enqueued). Poll `get_deployment` or `get_run_logs` for progress." },
217
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
218
+ ],
219
+ };
220
+ });
221
+ server.tool("get_deployment_logs", "Get the deployment's current application/container logs (the running app's stdout/stderr). For per-build logs of a specific run, use `get_run_logs` instead. Returns 422 if the deployment isn't provisioned yet.", {
222
+ deployment_pid: z.string().min(1),
223
+ }, async ({ deployment_pid }) => {
224
+ if (!hasOrg())
225
+ return { content: [{ type: "text", text: NO_ORG }] };
226
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/logs`, {
227
+ toolName: "get_deployment_logs",
228
+ });
229
+ if (!result.success) {
230
+ return { content: [{ type: "text", text: `Failed to get deployment logs (${result.statusCode}): ${result.message}` }] };
231
+ }
232
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
233
+ });
234
+ server.tool("get_deployment_health", "Get a deployment's health: whether the deploy_url is reachable, when it was last checked, any error, and the attached domains.", {
235
+ deployment_pid: z.string().min(1),
236
+ }, async ({ deployment_pid }) => {
237
+ if (!hasOrg())
238
+ return { content: [{ type: "text", text: NO_ORG }] };
239
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/health`, {
240
+ toolName: "get_deployment_health",
241
+ });
242
+ if (!result.success) {
243
+ return { content: [{ type: "text", text: `Failed to get deployment health (${result.statusCode}): ${result.message}` }] };
244
+ }
245
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
246
+ });
247
+ server.tool("get_run_logs", `Get the full build + container logs for one deployment run, plus the run object. The returned log buffer is cumulative and idempotent - each call returns the whole buffer, so REPLACE (don't append) on each poll. To stream a live build, re-call every ~5-10s until \`deployment_run.status\` is terminal. A 422 "Deployment has not started yet" means the run is still enqueued - back off and retry. ${RUN_STATUS_NOTE}`, {
248
+ deployment_pid: z.string().min(1),
249
+ run_pid: z.string().min(1).describe("Run pid, e.g. run_01h... (from a deployment's deployment_runs)"),
250
+ }, async ({ deployment_pid, run_pid }) => {
251
+ if (!hasOrg())
252
+ return { content: [{ type: "text", text: NO_ORG }] };
253
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/runs/${run_pid}/logs`, {
254
+ toolName: "get_run_logs",
255
+ });
256
+ if (!result.success) {
257
+ return { content: [{ type: "text", text: `Failed to get run logs (${result.statusCode}): ${result.message}` }] };
258
+ }
259
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
260
+ });
261
+ server.tool("redeploy_run", "Re-run a previous build from the SAME commit it used. Useful to retry a failed build or rebuild without a new push. Requires the source run to have a commit SHA, the deployment to be active, and no in-progress run (422 otherwise). Creates a new run; poll `get_deployment`.", {
262
+ deployment_pid: z.string().min(1),
263
+ run_pid: z.string().min(1).describe("The run to re-run (its commit will be redeployed)"),
264
+ }, async ({ deployment_pid, run_pid }) => {
265
+ if (!hasOrg())
266
+ return { content: [{ type: "text", text: NO_ORG }] };
267
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/runs/${run_pid}/redeploy`, {
268
+ method: "POST",
269
+ toolName: "redeploy_run",
270
+ });
271
+ if (!result.success) {
272
+ return { content: [{ type: "text", text: `Failed to redeploy (${result.statusCode}): ${result.message}` }] };
273
+ }
274
+ return {
275
+ content: [
276
+ { type: "text", text: "Redeploy triggered (new run from the same commit)." },
277
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
278
+ ],
279
+ };
280
+ });
281
+ server.tool("rollback_run", "Roll back by re-deploying the commit from a PRIOR SUCCESSFUL run. Only runs with status `active` (deployed successfully) can be rolled back to - returns 422 otherwise. Requires the deployment to be active and no in-progress run. Creates a new run; poll `get_deployment`.", {
282
+ deployment_pid: z.string().min(1),
283
+ run_pid: z.string().min(1).describe("A previously-successful run to roll back to (status must be `active`)"),
284
+ }, async ({ deployment_pid, run_pid }) => {
285
+ if (!hasOrg())
286
+ return { content: [{ type: "text", text: NO_ORG }] };
287
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/runs/${run_pid}/rollback`, {
288
+ method: "POST",
289
+ toolName: "rollback_run",
290
+ });
291
+ if (!result.success) {
292
+ return { content: [{ type: "text", text: `Failed to roll back (${result.statusCode}): ${result.message}` }] };
293
+ }
294
+ return {
295
+ content: [
296
+ { type: "text", text: "Rollback triggered (re-deploying the selected run's commit)." },
297
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
298
+ ],
299
+ };
300
+ });
301
+ }
302
+ //# sourceMappingURL=deployments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployments.js","sourceRoot":"","sources":["../../src/tools/deployments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerEnvVarTools(server: McpServer): void;
@@ -0,0 +1,121 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client.js";
3
+ import { session } from "../session.js";
4
+ /**
5
+ * Deployment environment variables. Stored encrypted at rest but returned in
6
+ * cleartext by the API (treat values as secrets).
7
+ *
8
+ * Endpoints (base /api/v1/platform/github_repo_deployments/:pid/env_vars):
9
+ * - GET /env_vars list { key, value }
10
+ * - PUT /env_vars FULL REPLACE - deletes keys not in the payload
11
+ * - PATCH /env_vars merge/upsert - never deletes
12
+ * - DELETE /env_vars/:key remove one key
13
+ *
14
+ * Saving env vars triggers a redeploy on active deployments, rate-limited to
15
+ * one per 60s (the response `redeploy_triggered` flag tells you if it fired).
16
+ */
17
+ const DEPLOYMENTS_BASE = "/api/v1/platform/github_repo_deployments";
18
+ const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
19
+ const NO_ORG = "No active organization. Call `list_organizations` then `select_organization` first.";
20
+ function hasOrg() {
21
+ return session.getActiveOrgId() !== null;
22
+ }
23
+ /** Validate a { KEY: value } map against the backend rules. Returns an error string or null. */
24
+ function validateEnvVars(env) {
25
+ const keys = Object.keys(env);
26
+ if (keys.length === 0)
27
+ return "env_vars is empty - provide at least one { KEY: value } pair.";
28
+ for (const [k, v] of Object.entries(env)) {
29
+ if (!KEY_RE.test(k)) {
30
+ return `Invalid key "${k}": must match ^[A-Za-z_][A-Za-z0-9_]*$ (start with a letter or underscore).`;
31
+ }
32
+ if (v === undefined || v === null || v === "") {
33
+ return `Value for "${k}" is blank. Values must be non-empty - use delete_env_var to remove a variable.`;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ export function registerEnvVarTools(server) {
39
+ server.tool("list_env_vars", "List a deployment's environment variables as { key, value } pairs. NOTE: values are returned in cleartext - handle as secrets. Call this before `set_env_vars` so you know the full current set (since `set_env_vars` replaces everything).", {
40
+ deployment_pid: z.string().min(1),
41
+ }, async ({ deployment_pid }) => {
42
+ if (!hasOrg())
43
+ return { content: [{ type: "text", text: NO_ORG }] };
44
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/env_vars`, {
45
+ toolName: "list_env_vars",
46
+ });
47
+ if (!result.success) {
48
+ return { content: [{ type: "text", text: `Failed to list env vars (${result.statusCode}): ${result.message}` }] };
49
+ }
50
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
51
+ });
52
+ server.tool("set_env_vars", "REPLACE a deployment's ENTIRE set of environment variables with the provided map. ⚠️ Any existing key NOT included here is DELETED. To add or edit a few variables without removing the rest, use `merge_env_vars` instead - or call `list_env_vars` first and pass the full merged set here. Keys must match ^[A-Za-z_][A-Za-z0-9_]*$; values must be non-empty. Saving may trigger a rate-limited redeploy.", {
53
+ deployment_pid: z.string().min(1),
54
+ env_vars: z.record(z.string()).describe("The COMPLETE desired set { KEY: value }; replaces all existing vars"),
55
+ }, async ({ deployment_pid, env_vars }) => {
56
+ if (!hasOrg())
57
+ return { content: [{ type: "text", text: NO_ORG }] };
58
+ const err = validateEnvVars(env_vars);
59
+ if (err)
60
+ return { content: [{ type: "text", text: err }] };
61
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/env_vars`, {
62
+ method: "PUT",
63
+ body: { env_vars },
64
+ toolName: "set_env_vars",
65
+ });
66
+ if (!result.success) {
67
+ return { content: [{ type: "text", text: `Failed to set env vars (${result.statusCode}): ${result.message}` }] };
68
+ }
69
+ return {
70
+ content: [
71
+ { type: "text", text: "Environment variables replaced. If `redeploy_triggered` is false the change was throttled and applies within ~60s." },
72
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
73
+ ],
74
+ };
75
+ });
76
+ server.tool("merge_env_vars", "Add or update environment variables WITHOUT deleting any existing ones (upsert/merge). This is the safe way to set a few variables. Keys must match ^[A-Za-z_][A-Za-z0-9_]*$; values must be non-empty. To remove a variable use `delete_env_var`. Saving may trigger a rate-limited redeploy.", {
77
+ deployment_pid: z.string().min(1),
78
+ env_vars: z.record(z.string()).describe("The variables to add/update { KEY: value }; existing keys are preserved"),
79
+ }, async ({ deployment_pid, env_vars }) => {
80
+ if (!hasOrg())
81
+ return { content: [{ type: "text", text: NO_ORG }] };
82
+ const err = validateEnvVars(env_vars);
83
+ if (err)
84
+ return { content: [{ type: "text", text: err }] };
85
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/env_vars`, {
86
+ method: "PATCH",
87
+ body: { env_vars },
88
+ toolName: "merge_env_vars",
89
+ });
90
+ if (!result.success) {
91
+ return { content: [{ type: "text", text: `Failed to merge env vars (${result.statusCode}): ${result.message}` }] };
92
+ }
93
+ return {
94
+ content: [
95
+ { type: "text", text: "Environment variables merged. If `redeploy_triggered` is false the change was throttled and applies within ~60s." },
96
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
97
+ ],
98
+ };
99
+ });
100
+ server.tool("delete_env_var", "Delete a single environment variable by key. Returns 404 if the key doesn't exist. Saving may trigger a rate-limited redeploy.", {
101
+ deployment_pid: z.string().min(1),
102
+ key: z.string().min(1).describe("The variable key to remove"),
103
+ }, async ({ deployment_pid, key }) => {
104
+ if (!hasOrg())
105
+ return { content: [{ type: "text", text: NO_ORG }] };
106
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/env_vars/${encodeURIComponent(key)}`, {
107
+ method: "DELETE",
108
+ toolName: "delete_env_var",
109
+ });
110
+ if (!result.success) {
111
+ return { content: [{ type: "text", text: `Failed to delete env var (${result.statusCode}): ${result.message}` }] };
112
+ }
113
+ const parts = [
114
+ { type: "text", text: `Environment variable \`${key}\` removed.` },
115
+ ];
116
+ if (result.data)
117
+ parts.push({ type: "text", text: JSON.stringify(result.data, null, 2) });
118
+ return { content: parts };
119
+ });
120
+ }
121
+ //# sourceMappingURL=env-vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/tools/env-vars.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGithubTools(server: McpServer): void;
@@ -0,0 +1,106 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client.js";
3
+ import { session } from "../session.js";
4
+ /**
5
+ * GitHub integration - list connected App installations (+ their repos),
6
+ * list branches, auto-detect a repo's build config, and get the install URL.
7
+ *
8
+ * Endpoints:
9
+ * - GET /api/v1/platform/github_installations
10
+ * - GET /api/v1/platform/github_installations/:installation_pid/branches?repo_full_name=
11
+ * - POST /api/v1/platform/github_installations/detect_repo_config
12
+ * - GET /github/connect?organization_pid= (returns a browser install URL)
13
+ *
14
+ * NOTE: connecting a GitHub account is an interactive, browser-based GitHub App
15
+ * installation - it CANNOT be completed headlessly. `get_github_connect_url`
16
+ * returns a link for the human to open; afterwards poll `list_github_installations`
17
+ * to confirm the connection landed.
18
+ */
19
+ const INSTALLATIONS_BASE = "/api/v1/platform/github_installations";
20
+ const NO_ORG = "No active organization. Call `list_organizations` then `select_organization` first.";
21
+ function orgId() {
22
+ return session.getActiveOrgId();
23
+ }
24
+ export function registerGithubTools(server) {
25
+ server.tool("list_github_installations", "List the GitHub App installations connected to the active organization, each with its accessible repositories embedded (`repos`: name, full_name, url, private, default_branch, description). Use `isActive`/status to pick a usable installation, and the repo's `default_branch` when listing branches. If empty, the org hasn't connected GitHub yet - use `get_github_connect_url`.", {}, async () => {
26
+ if (!orgId())
27
+ return { content: [{ type: "text", text: NO_ORG }] };
28
+ const result = await apiRequest(INSTALLATIONS_BASE, {
29
+ toolName: "list_github_installations",
30
+ });
31
+ if (!result.success) {
32
+ return { content: [{ type: "text", text: `Failed to list GitHub installations (${result.statusCode}): ${result.message}` }] };
33
+ }
34
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
35
+ });
36
+ server.tool("list_repo_branches", "List branches of a repository accessible through a GitHub installation. Provide the installation pid (ghi_..., from `list_github_installations`) and the repo as owner/repo. The API does not flag the default branch - cross-reference the repo's `default_branch` from `list_github_installations`.", {
37
+ installation_pid: z.string().min(1).describe("Installation pid, e.g. ghi_01h..."),
38
+ repo_full_name: z.string().regex(/^[^/]+\/[^/]+$/, "owner/repo").describe("owner/repo"),
39
+ page: z.number().int().min(1).optional().describe("Page (default 1)"),
40
+ per_page: z.number().int().min(1).max(100).optional().describe("Per page (default 100, max 100)"),
41
+ }, async ({ installation_pid, repo_full_name, page, per_page }) => {
42
+ if (!orgId())
43
+ return { content: [{ type: "text", text: NO_ORG }] };
44
+ const result = await apiRequest(`${INSTALLATIONS_BASE}/${installation_pid}/branches`, {
45
+ query: {
46
+ repo_full_name,
47
+ page: page === undefined ? undefined : String(page),
48
+ per_page: per_page === undefined ? undefined : String(per_page),
49
+ },
50
+ toolName: "list_repo_branches",
51
+ });
52
+ if (!result.success) {
53
+ return { content: [{ type: "text", text: `Failed to list branches (${result.statusCode}): ${result.message}` }] };
54
+ }
55
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
56
+ });
57
+ server.tool("detect_repo_config", "Scan a GitHub repo and auto-detect how to build/deploy it: visibility (`private`), `build_pack` (nixpacks | dockerfile | dockercompose | static), `port`, `dockerfile_path`, monorepo `base_directories`, and detected environment variables (`detected_vars` from .env.example/CI/workflows - these are hints, not secrets). Run this before `create_deployment` to pre-fill build settings. For PRIVATE repos you must pass `installation_pid` (else 422).", {
58
+ repo_url: z.string().min(1).describe("https://github.com/owner/repo"),
59
+ installation_pid: z.string().optional().describe("Installation pid (ghi_...); required for private repos"),
60
+ branch: z.string().optional().describe("Branch to scan (defaults to the repo's default branch)"),
61
+ }, async ({ repo_url, installation_pid, branch }) => {
62
+ if (!orgId())
63
+ return { content: [{ type: "text", text: NO_ORG }] };
64
+ const body = { repo_url };
65
+ if (installation_pid !== undefined)
66
+ body.installation_pid = installation_pid;
67
+ if (branch !== undefined)
68
+ body.branch = branch;
69
+ const result = await apiRequest(`${INSTALLATIONS_BASE}/detect_repo_config`, {
70
+ method: "POST",
71
+ body,
72
+ toolName: "detect_repo_config",
73
+ });
74
+ if (!result.success) {
75
+ return { content: [{ type: "text", text: `Failed to detect repo config (${result.statusCode}): ${result.message}` }] };
76
+ }
77
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
78
+ });
79
+ server.tool("get_github_connect_url", "Get a URL for the human to open in their browser to connect/install the SelfHost GitHub App on their account or org. Connecting GitHub is interactive (GitHub requires a logged-in user to approve and pick repos) and CANNOT be done headlessly. After the user completes it in the browser, poll `list_github_installations` to confirm the new installation appears (status `active`).", {
80
+ redirect_to_frontend: z.string().url().optional().describe("Optional URL to return the browser to after install"),
81
+ }, async ({ redirect_to_frontend }) => {
82
+ const id = orgId();
83
+ if (!id)
84
+ return { content: [{ type: "text", text: NO_ORG }] };
85
+ // /github/connect lives outside /api/v1/platform and expects `organization_pid` (not organization_id).
86
+ const result = await apiRequest("/github/connect", {
87
+ query: { organization_pid: id, redirect_to_frontend },
88
+ toolName: "get_github_connect_url",
89
+ skipOrgInjection: true,
90
+ });
91
+ if (!result.success) {
92
+ return { content: [{ type: "text", text: `Failed to get GitHub connect URL (${result.statusCode}): ${result.message}` }] };
93
+ }
94
+ const url = (result.data && result.data.redirect_url) || null;
95
+ if (!url) {
96
+ return { content: [{ type: "text", text: `Could not read the connect URL from the response.\n${JSON.stringify(result.data, null, 2)}` }] };
97
+ }
98
+ return {
99
+ content: [{
100
+ type: "text",
101
+ text: `Open this link in a browser to connect GitHub, then approve the app and select repositories:\n\n${url}\n\nWhen done, call \`list_github_installations\` to confirm the installation appears.`,
102
+ }],
103
+ };
104
+ });
105
+ }
106
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/tools/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerHetznerTools(server: McpServer): void;