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$allowedFieldsarrays.- CREATE allowlist (line 1014): does not include
dockerfile_target_build→ silently stripped on everycreate_*. - 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_buildso theupdateaction 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.mdso 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 forcreateApplicationPublic,createApplicationPrivateGH,createApplicationPrivateKey,createApplicationDockerImage, plus anupdateApplicationtest fordockerfile_target_build.src/__tests__/mcp-server.test.ts: a newdescribe('application tool handler')block exercising eachcreate_*action's forward via_registeredTools['application'].handler(...)+jest.spyOn(matches theenv_vars tool handlerpattern from #174). Includes a dedicated test assertingcreate_dockerimagedropsbase_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_publicwithbuild_pack: dockerfile,dockerfile_location, all 7 other build-config fields, and 4health_check_*fields → GET round-trips every field. (health_check_portis returned asstring, which is Coolify's column-level type coercion at the DB layer, not a client behaviour.)updatewithdockerfile_target_build: 'production'+ an updateddockerfile_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