Stu Mason
Stu Mason

Activity

Pull Request Merged

PR #185 merged: fix(application): wire build-config and health_check fields through create_* (#178)

Closes #178.

What this changes

Two related gaps in the application tool, both surfacing when a caller passes valid Coolify fields and finds them silently dropped:

1. Schema gap. Eight build-config fields were absent from the zod schema for the application tool: dockerfile_location, dockerfile_target_build, base_directory, publish_directory, install_command, build_command, start_command, watch_paths. Zod's default behavior strips unknown keys without error, so an MCP caller could set dockerfile_location on update, get no feedback, and see no effect on the app. Added all eight to the schema.

2. Handler gap. The 12 health_check_* fields were already declared in the schema, but the four create_* hand-pick handlers never forwarded them. Same story for the build-config fields (which are typed on CreateApplicationPublicRequest but never passed through). Result: create_key with health_check_enabled: true, health_check_path: '/health' produced an app with healthcheck off and default / path. Added explicit forwards in create_public, create_github, create_key, and create_dockerimage, keeping the existing hand-pick pattern so the schema stays authoritative for tool docs.

Two asymmetries confirmed against Coolify source (coollabsio/coolify main)

Posting these because they cut against the initial review summary in a meaningful way and shape this PR:

create_dockerimage only forwards health_check_* (not build-config). Coolify's /applications/dockerimage request-body schema in openapi.yaml does not accept base_directory/publish_directory/install_command/build_command/start_command/watch_paths/dockerfile_location — the endpoint is for pre-built registry images and has no build step. Wiring those into create_dockerimage would silently send dead weight. Handler create_dockerimage forwards healthcheck only.

dockerfile_target_build is UPDATE-only. You raised this in your comment — dockerfile_target_build isn't in openapi.yaml's request bodies or any Create*Request interface. Verified against app/Http/Controllers/Api/ApplicationsController.php directly:

  • removeUnnecessaryFieldsFromRequest() runs before validation, using two separate $allowedFields arrays.
  • CREATE allowlist (line 1014): does not include dockerfile_target_build → silently stripped on every create_*.
  • UPDATE allowlist (line 2497): does include dockerfile_target_build → accepted on PATCH.

The shared sharedDataApplications() validator declares a rule for the field, but on create paths the request never reaches it. So:

  • The zod schema includes dockerfile_target_build so the update action can forward it via the existing spread.
  • No create_* handler forwards it.
  • Its presence in the schema for create paths is harmless (zod accepts; handler hand-pick simply doesn't reference it). Documented in CLAUDE.md so future maintainers don't add the forward.

Coolify's openapi.yaml request bodies are an incomplete projection of the real allowlists; full truth lives in the controller $allowedFields arrays. Captured this in CLAUDE.md as a gotcha so it doesn't bite the next contributor.

Tests

Following the shape of #174 / #162:

  • src/__tests__/coolify-client.test.ts: per-method forwarding tests for createApplicationPublic, createApplicationPrivateGH, createApplicationPrivateKey, createApplicationDockerImage, plus an updateApplication test for dockerfile_target_build.
  • src/__tests__/mcp-server.test.ts: a new describe('application tool handler') block exercising each create_* action's forward via _registeredTools['application'].handler(...) + jest.spyOn (matches the env_vars tool handler pattern from #174). Includes a dedicated test asserting create_dockerimage drops base_directory/install_command/dockerfile_location (they should NOT reach the client).

npm test: 307 passed. npx tsc --noEmit: clean. npm run lint: clean (no new warnings). npx prettier --check: clean.

Live smoke test

Built the dist and ran against a live Coolify v4 instance:

  • create_public with build_pack: dockerfile, dockerfile_location, all 7 other build-config fields, and 4 health_check_* fields → GET round-trips every field. (health_check_port is returned as string, which is Coolify's column-level type coercion at the DB layer, not a client behaviour.)
  • update with dockerfile_target_build: 'production' + an updated dockerfile_location → GET confirms both persisted.
  • DELETE cleanup succeeded.

Out of scope (happy to send as follow-up PRs)

A few other application-tool gaps surfaced during the same investigation. Same shape, smaller diffs each:

  • Static-site flags (is_static, is_spa, static_image)
  • HTTPS/domain flags (is_force_https_enabled, autogenerate_domain, force_domain_override, redirect)
  • HTTP basic auth (is_http_basic_auth_enabled, http_basic_auth_username, http_basic_auth_password)
  • Resource limits (limits_memory, limits_memory_swap, limits_cpus, etc.)
  • Pre/post-deploy hooks (post_deployment_command(_container), pre_deployment_command(_container))
  • Per-provider manual webhook secrets (manual_webhook_secret_github/gitlab/bitbucket/gitea)
  • Build-server toggles (use_build_server, connect_to_docker_network)
  • Container label preservation flags (is_container_label_escape_enabled, is_preserve_repository_enabled)
  • Docker Compose extras (docker_compose_location, docker_compose_custom_*_command, docker_compose_domains)
  • Read-side normalization of health_check_port (string from Coolify → number on MCP layer)

🤖 Generated with Claude Code

+457
additions
-0
deletions
5
files changed