Repository navigation
Releases
v3.10.1
What's Changed
Fixed
storagescan now create a storage on a service (#478). Coolify'sPOST /services/{uuid}/storagesrequiresresource_uuid, the service application or database the mount belongs to, and the tool had no way to send it.storagestakesresource_uuidand refuses a servicecreatewithout it, pointing atget_serviceto find it. Thanks @meditatingsurgeon for the report.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.10.0
What's Changed
Added
COOLIFY_MCP_REQUIRE_HUMAN=onmakes the stdio server fail closed like HTTP mode (#471). Over stdio, a client without elicitation (including an MCP proxy or agent harness that does not forward it) runs destructive operations on the model's ownconfirm: true. With the opt-in they are refused unless a human can be asked; read-only tools are unaffected. Unset, stdio behaves exactly as before. The README anddocs/security.mdnow say plainly what the stdio fallback is.
Security
- Client registration in HTTP mode is bounded and can be restricted.
client_nameis capped at 200 characters, and a client may register at most 10 redirect URIs of up to 2048 characters each. Before this, one unauthenticated request could persist megabytes. Registration has a limit of 30 an hour across all callers on top of the per-IP one, so a forgedX-Forwarded-Forno longer gets round it. A registered client nobody approves within a day is removed. Clients from state files written by earlier versions are kept. MCP_REDIRECT_HOSTSrestricts non-loopback redirect URIs to the listed hostnames, at registration, on client metadata documents, and on every authorization request, so clients registered before it was set obey it too. Unset, registration stays open.- The consent page says where approving sends access (the redirect host, or "an app on this computer" for loopback), not only the name the client gave itself.
MCP_CLIENT_IP_HEADERnames a header such ascf-connecting-ipfor the per-IP limits to key on, for deployments where the firstX-Forwarded-Forhop is forgeable or is the proxy's own address.- Identifiers can no longer change which endpoint a call reaches. Tools passed uuids and names into Coolify request paths as given, so a crafted value could point a call, including one from a read-only tool, at a different endpoint. Every uuid argument is now checked to be a Coolify uuid (letters, digits,
-,_), and the client encodes every identifier it puts in a path and refuses one that would still be read as.or... Nothing changes ontools/list.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.9.0
What's Changed
Added
- Tool errors point at
doctorwhen it can diagnose them (#384). A failed connection to Coolify and a 401 or 403 now end with "To diagnose, runnpx @masonator/coolify-mcp doctorwith this server's environment." The wording names the server's environment because in HTTP mode the person reading the error is not the one who configured it.get_version, which reads the plain-text/versionoutside the usual request path, gets the same pointer on the same two failures. Other errors are unchanged. The unusedCoolifyClient#validateConnection, which built its own copy of the connection message, is removed.
Changed
- Coolify calls no longer follow redirects (#453). An
http://base URL that redirects tohttps://used to work and now fails with "Use https://… as the base URL"; any other redirect fails naming its host.doctorfails a redirect to match (it used to warn on ones that were not Cloudflare Access).
Fixed
- A Cloudflare Access login page is no longer returned as a successful result (#453). Coolify calls followed redirects, so when Access intercepted one, fetch landed on the login page (200, HTML) and the client handed it back as the endpoint's data. Calls now refuse redirects and fail with an error naming what answered: Cloudflare Access (with the service-token fix), an
http://tohttps://redirect (with the address to use), or any other host. Access is recognised on its*.cloudflareaccess.comteam domain or a custom domain (the/cdn-cgi/access/path), by one check shared withdoctor. Not following redirects also stops our custom headers, the Access service token among them, from going on to whatever host a redirect names, since fetch only stripsAuthorization; HTTP mode's token check on the authorize page no longer follows them either. list_instancescannot hang on one instance (#454). It fanned outget_versionwith no deadline, so an instance that accepted the connection and never answered held it for fetch's 300 s headers timeout. It now shares the 10 s listing deadline from #393, and an instance that misses it is reported as "no answer within 10 s", whichresources/listnow logs too.resources/listno longer waits out a hung instance (#393). Nothing bounded a Coolify call, so in a fleet with one instance that accepts connections but never answers, every listing waited for fetch's own timeout (300 s for a missing response, 10 s for an unroutable host) before returning the others. The listing now gives each instance ten seconds (the same as HTTP mode's token probe), aborts the request at that point, and leaves the instance out as it already did for one that refused the connection; stderr names each instance left out and why. Thelist*client methods take an optionalsignalfor this. The listing stays uncached on purpose: it is meant to be current.- Log
linesis a whole number from 1 to 10000 onlogsandapplication_logs(#386). It took any number, and Coolify reads-1as "every line" from the release that addedall, so one call could pull an entire container log into context. Coolify caps the count at 10000 itself, silently, since 4.3.0; the limit is now in the schema where the model can see it, and on 4.0–4.2, which had no cap, it is a deliberate limit of our own.deployment'slines(our own truncation of stored build logs) becomes a positive whole number too:0showed an empty window and offered a next page forever.allis deliberately not offered: before the release that added it, Coolify answers it with the default 100 lines and no error. About 30 tokens on the tool list. COOLIFY_INSTANCESentries get the startup checks (#383). The single-instance variables were checked at startup for a doubled/api/v1, an unusable URL, leading whitespace or a line break in the token, line breaks in header values, an unexpanded${VAR}, and an internal URL with no dashboard URL. A fleet entry was only checked for a scheme and a non-empty token, so the rest failed at that instance's first call. Each entry now gets the same checks, labelled by index and name (COOLIFY_INSTANCES[1] ("staging") url ends with /api/v1), all reported at once.doctorreports them on afleet-configline of their own, so a bad entry no longer fails the default instance'sconfigcheck and skips its token probe. Header names that are not valid HTTP header names are caught too; custom header values may contain${, as a--headerflag may.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.8.0
What's Changed
Added
- Rollback (#442, Coolify 4.3+).
deploymentgainsrollback_images(the image tags Coolify can roll an application back to) anddeploygainsrollback_to(queue a rollback to one of them;wait: truefollows it like any deploy). A tag that is not in a non-empty images list is refused locally with the tags that are; an empty list, or the check itself failing, means Coolify could not inspect the server, so it does not block (only a 404, meaning no rollback on this Coolify, stops it).rollback_imagesreturns at most 20 images and counts the rest. Likedeploy, it asks for no confirmation: a rollback is a deploy of an earlier image and is undone by deploying again. On an older Coolify, the 404 now says rollback needs 4.3. coolify_url: dashboard links built, not guessed (#342). Takes a resource type (application, database, service, deployment, server, project, environment, private key) and a uuid, and returns the page in the Coolify dashboard, using the web routes (stable since v4.0.0). Application, database, service and deployment pages need the project and environment uuids, which the API does not put on the resource, so they are looked up once per instance from the project list and cached; a miss rebuilds the index once.get_applicationoffers it as an "Open in Coolify" action. New optionalCOOLIFY_UI_URL(orui_urlon aCOOLIFY_INSTANCESentry) sets the dashboard address whenCOOLIFY_BASE_URLis internal, as on a server in the Coolify docker network. When the base URL is internal (a single-label host such ascoolify) andCOOLIFY_UI_URLis unset, the link says it will not open in a browser and the startup check (and so doctor) warns; aCOOLIFY_UI_URLthat is not a URL fails the startup check. One project the token cannot read no longer breaks every link: its resources miss, and the error names it. 46 tools; the tool list goes from about 8,700 to about 8,900 tokens.
Changed
- Vendored Coolify OpenAPI spec re-synced with upstream
main(#416): 275 → 287 operations. 18 are new (preview update and logs, database imports, SQLite databases, per-server registries, application secret managers, email settings, integration tokens,PATCH /team) and 6 are gone (the server transfer routes). Every route the client calls is still present, and none of them gained a required field. storageshost_pathis documented as ending at Coolify 4.3.21. Upstream removed host paths in 4.3.22, and the storage endpoints now reject the field with 422. It is kept for older instances; the field's description anddocs/tools.mdsay where it stops.- Cache hints on 2026-07-28 list results (#337). Without them the SDK sent
ttlMs: 0, telling a client to re-list on every use. The tool, prompt and template lists are fixed for the life of the process and now say five minutes (the revision is stateless, so the TTL is the only bound on a stale list after an upgrade);resources/listnames live applications and says a minute;resources/readstays 0. Allprivate. 2025-era responses are unchanged. The interop test now asserts both eras against one HTTP server, including that a pinned 2026-07-28 client gets throughserver/discover, and a new test pins the tool order as stable. - MCP SDK 2.2.0 (#448). Bug fixes only for this server: no stray unhandled rejection when a notification is sent on a closed connection, and a
subscriptions/listenstream that honours none of the requested types now ends after its acknowledgement instead of staying open.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.7.0
What's Changed
Added
deployacceptspr(#425) to redeploy an existing pull-request preview, the other half of preview support after #434. It needs an application uuid; a tag or name withpris refused before Coolify is called, with a pointer tolist_applications.wait: truefollows the preview deployment. Coolify only redeploys a preview its GitHub webhook already created: with none for that PR it answers "Pull request N not found for this resource." and queues nothing, and the tool passes that answer through.
Changed
- Unknown argument keys are dropped out loud (#438). Every tool used to strip a key its schema did not declare, silently. That is how #434 happened: an
applicationupdate carrying only an unsupported flag became an empty body ("Invalid request"), and one carrying it next to a known field returned 200 with nothing changed. The schemas are now loose, so the key survives parsing; it is dropped before any handler can forward it to Coolify, and the result gains a note naming it ("Note: ignoredfoo, which application does not accept, so it was not sent."). Item keys in arrays of objects are checked too, so a typo such asis_build_timein anenv_varsdataentry is named rather than lost (upstream's bulk endpoint ignores it and answers 201). Anapplicationupdate with nothing left to send now answers "Error: nothing to update" instead of making the empty PATCH, matchingdatabaseandservice. The audit log records the call as sent. Wire-visible: every tool'sinputSchemanow carries"additionalProperties": {}, which costs about 290 tokens ontools/list, so the published figure moves from ~8,300 to ~8,700.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.6.0
What's Changed
Added
- Desktop extension icon.
manifest.jsonnow declaresicon.png(512 x 512, the site's ferry mark drawn as paths) so the.mcpbshows an icon in Claude Desktop and meets the Connectors Directory submission form (#406). The bundle also stops shippingevals/, which.mcpbignorenever excluded. applicationaccepts Coolify's preview settings (#434, reported by @KasperHonore):is_preview_deployments_enabled,is_pr_deployments_public_enabledandpreview_url_template, onupdateand on everycreate_*action. Coolify creates each app with previews off, and until now no tool could turn them on. The schema stripped the unknown keys, so anupdatecarrying only the flag sent Coolify an empty body ("Invalid request"), and one carrying the flag alongside a known field returned 200 with previews still off. The first two fields need Coolify 4.2,preview_url_template4.3.
Security
- Lockfile bumped past three
brace-expansionadvisories (#439).npm audit fixmoves the transitivebrace-expansionfrom 5.0.9 to 5.0.12 (high: CPU and stack-exhaustion DoS, GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p). Dev tooling only; the server never expands untrusted brace patterns.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.5.1
What's Changed
Added
- Outcome-scored task evals (
evals/src/tasks,npm run evals:tasks). The tool-selection score counts a hit when an expected tool name appears anywhere in the transcript, so a model that calls many tools scores well: a 3B model reached 15/15 while restarting services on a read request (evals/FINDINGS.md#7). The new suite runs 16 multi-step requests and passes one only when the exact request landed or the exact arguments were sent, the answer carries the fact that was asked for, and nothing else was written, including after a declined confirmation. An ambiguous request ("restart my app") must be answered with a question, and reaching for a bulk restart fails it even when the declined confirmation stops the call. It also counts schema-invalid arguments and invented ids, repeats cases withEVALS_TRIALS, and is not part ofnpm run evals, so CI cost is unchanged.
Fixed
- HTTP mode outside the container no longer dies on the first client registration (#417, reported by @artgas1). The OAuth state file defaults to
/data/oauth-state.json, a directory that only exists in the image. Runcoolify-mcp-httpon a workstation withoutMCP_OAUTH_STATE_FILEand the server started, answered/healthz, accepted the firstPOST /registerwith a 201, then exited 1 about 250ms later when the debounced state write threw from a timer. A plain SIGTERM did the same through the shutdown flush. The server now checks at startup that the state file's directory exists or can be created and is writable, and lists the failure with the other reasons it cannot start, naming the path and how to set one; the probe is a real write of the temp file the provider uses, so a stale.tmpleft by another user is caught too. A write that fails later, say a volume that goes read-only under a running server, logs one line on the way in and one on the way out, reports"persistence": "degraded"on/healthzin between, and the server keeps serving from memory instead of ending. - The eval fixture reported no deployments for any app. The client pages
/deployments/applications/{uuid}and reads a{ count, deployments }envelope; the fixture matched only the bare path and returned an array, sodeployment list_for_appcame back empty in every eval.
Upgrading from 3.5
- Container deployments are unaffected. The image's
/datavolume passes the new check. - One exception: a bind mount the daemon created.
./data:/dataon a
host where Docker made the directory root-owned never worked either, but it
used to start, go green and die at the first registration. It now refuses to
start and names the directory. Fix the ownership (the image runs asnode,
uid 1000) rather than the config. - A write that fails after boot is visible in two places: one stderr line
on the transition, and/healthzgains"persistence": "degraded"until a
write succeeds again. Anything that restarts on health can now see it before
the restart drops every registration and token. - Running
coolify-mcp-httpoutside a container? SetMCP_OAUTH_STATE_FILE
to a path your user can write, e.g../oauth-state.json. Until now that
omission crashed the server after its first registration; it is now a
startup error that says so.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.5.0
What's Changed
Added
MCP_HOSTsets the HTTP listen address (#418, @artgas1). HTTP mode has always listened on every interface. That is right in a container and wrong on a workstation, where the server answered on whatever network the machine was attached to. Unset keeps today's behaviour, so existing deployments are untouched;MCP_HOST=127.0.0.1keeps it on loopback. When set, the startup line names the bound address, and an unexpanded${VAR}in it is a startup error like the other HTTP variables.
Changed
search_docscovers the restructured Coolify docs (#420). Coolify reorganised its documentation, and the bundled index grew from 271 pages to 845, including the full API endpoint reference and the CLI. Results now link to current paths rather than to URLs that only redirect.
Upgrading from 3.4
- Drop-in. No configuration changes. HTTP mode still listens on every
interface unless you setMCP_HOST, so container deployments behave exactly
as before. - Running
coolify-mcp-httpoutside a container? SetMCP_HOST=127.0.0.1
so the server stops answering on every network your machine joins.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.4.0
What's Changed
Added
- Destructive confirmation works on protocol revision
2026-07-28(#341). That revision removed server-initiated requests mid-call, so the elicitation prompt this server has always used simply stopped working: every guarded operation on a current claude.ai client aborted withcould not confirm with the user, and no setting recovered it. Confirmation is now a two round-trip exchange on that era, unchanged on the 2025 era, chosen per call from the request itself. The summary you were shown is digested into signed state that the client echoes back, so an approval only authorises the blast radius it described — an emergency stop confirmed at 12 applications is refused rather than widened if 14 are running by the time you answer. Ten-minute expiry, fails closed,MCP_REQUEST_STATE_KEYto survive restarts. Blast-radius summaries now render in a stable order, so a reordering by Coolify cannot read as a changed blast radius and refuse an approval that was legitimately given.
Fixed
- The audit log no longer reports a failed confirmation as a human decline (#408). The refusal category was recovered by matching a substring of the error text, so a timeout, a cancelled call or a client that could not be asked all recorded as
declined. On the current protocol revision that was every destructive refusal, meaning the log answered "did someone approve this?" wrongly in exactly the case it exists for. The branch that knows why now says so, andcancelled,unavailableandstale_confirmationare distinguishable from a person saying no. - A raised confirmation is no longer audited as a successful destructive call (#341). On the two round-trip era one guarded operation writes two lines, and the first is a question. It recorded as
ok, so anything counting successful destructive operations doubled every one. It now records asawaiting_confirmation, which is worth counting in its own right. - HTTP audit lines carry the OAuth client id they always claimed to (#408). It was read from
authInfoon the handler context when the SDK hangs it offhttp.authInfo, so the field was silently absent from every line this server has ever written in HTTP mode.
Upgrading from 3.3
- Fixes a total outage of destructive operations on current clients. If
your client speaks protocol revision2026-07-28— claude.ai does today —
every guarded operation on the remote server was aborting withcould not confirm with the user, and no setting recovered it. This release is the fix.
Nothing changes for clients on the 2025 revisions, including the local stdio
server, which was never affected. - Drop-in. No required configuration.
MCP_REQUEST_STATE_KEYis optional
and only matters if you run more than one replica, or would rather a redeploy
did not interrupt someone mid-confirmation. - The audit log gained an outcome.
awaiting_confirmationmarks the first
of the two lines a guarded operation now writes on that revision. Anything
counting successful destructive calls should skip it, and anything counting
refusals should know thatdeclinednow means a person actually said no. - Blast-radius summaries are sorted. The same resources always render in
the same order. If you diff these prompts, expect a one-off reshuffle.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.
v3.3.0
What's Changed
Added
- Move a resource between environments (#299).
application,databaseandservicetake amoveaction, taking the targetenvironment_uuid. "Promote this app from staging to production" now has an answer that is one call instead of recreating the resource and copying env vars across. Requires Coolify v4.2+; an older instance is told so by name rather than returning a bare 404. The confirmation says what a move actually does: containers keep running, and from the next deployment the resource uses the target environment's shared environment variables. - Scheduled volume backups (#305).
storagesgainsbackup_set,backup_deleteandbackup_runfor application, database and service volumes (Coolify v4.2+).backup_deleteremoves the archives as well as the schedule, so it asks first, and points atbackup_setwithenabled: falsefor the "stop backing up but keep what I have" case. Note two upstream limits: Coolify has no endpoint to read a schedule back, sofind_issuesstill cannot flag a volume with no backup; andbackup_setreplaces the whole schedule rather than merging, so omitted fields revert to their defaults. COOLIFY_ACCESS_TOKEN_FILE: rotate the Coolify token without restarting (#398). A stdio server is spawned once per client session and a subprocess never sees a later change to its parent's environment, so rotatingCOOLIFY_ACCESS_TOKENcould not reach a running server. Point the new variable at a file and it is re-read whenever the file changes, taking effect on the next tool call. A401triggers exactly one retry, and only when a re-read actually produced a different token, so a rotation landing mid-call recovers while a genuinely bad token still fails on the first attempt.doctorreports the source, and for a file its path and age, never the value. Raised by a user who retired this server partly for this reason.- Audit log for every tool call and every refusal (#370). One JSON line per call carrying the tool, action, resource uuids, outcome, duration, and in HTTP mode the OAuth client id. On by default in HTTP mode, off over stdio,
COOLIFY_MCP_AUDIToverrides either way. Refusals are distinguished from errors and carry a reason category, so "a human said no" reads differently from "the client could not be asked". Arguments and responses are never logged: identifiers come from a closed allowlist of key names, so a future argument carrying a secret cannot reach a log line without a deliberate edit. See the audit section indocs/http-mode.md. - Privacy policy (PRIVACY.md). Written against the code, stating what the server collects (nothing), where the token goes (your Coolify instance and nowhere else), the one other outbound request it makes, and what the audit log does and does not record. Linked from the README and declared in
manifest.json, both of which the Connectors Directory requires of a local connector. - A display title on every tool (#406). Tools now carry the spec's top-level
titlealongsidename, so a client can show a person "Emergency stop" where the model readsstop_all_apps. Titles live in a table typedRecord<ToolName, string>, so a new tool without one is a compile error rather than a blank label. The Connectors Directory requires a title on every submitted tool.
Changed
- HTTP mode's audit line now reports what happened rather than what was asked. It previously peeked at the request body before dispatch, which could only name the tool; it is now written after the call completes and carries the outcome and duration.
- The Client ID Metadata Document fetch is bounded at 3 seconds (#340), rather than inheriting the 10 second default. Claude allows 10 seconds for the whole of discovery, registration and token exchange, so one hop cannot be allowed to spend all of it. The
client_idis a URL the caller chooses, so a host that connects and then stalls is entirely under their control. - The published tool-list token figure is corrected from ~6,600 to ~8,300 (#406). The surface grew through 3.x and the README did not follow, because the budget gate sat ~1,400 tokens above the number the docs advertised and so never tripped. The gate no longer holds a number of its own: it reads the figure out of the README and derives its ceiling from it, bounded in both directions so neither the payload nor the published claim can drift away from the other. Titles account for ~300 of the increase; the rest was already there and unreported.
Fixed
-
A loopback client can pick its own callback port (#340).
redirect_uriwas matched by exact string equality, so a native client that registershttp://127.0.0.1/callbackand then calls back on the ephemeral port it actually bound was rejected mid-redirect with a genericinvalid_request. RFC 8252 requires the port to be free at request time for loopback redirects, and it now is. The relaxation applies only to loopback: a remote https callback differing by port is still a different endpoint and is still refused. -
README answers "why not Coolify's own MCP server?" directly, with a comparison against the built-in
/mcpand the official CLI, and recommends the built-in outright for single-instance read-only use. -
Test fixtures, docs examples and one README example prompt now use IANA-reserved
example.comnames instead of live third-party domains, and the blast-radius example reads the same way in the changelog as it does indocs/security.md. No behaviour change.
Upgrading from 3.2
- Drop-in for stdio users. No config changes and no new required
variables. The tool count is unchanged at 45; three existing tools gained
actions rather than new tools appearing. - HTTP mode now writes an audit line per tool call by default. It is one
JSON line on stderr carrying the outcome, and it replaces the older
pre-dispatch line that could only name the tool. Anything parsing on the
auditkey still matches, but the timing and the field set changed. Set
COOLIFY_MCP_AUDIT=falseto turn it off. moveand the volume backup actions need Coolify v4.2+. On an older
instance they name the version requirement rather than returning a bare 404. Everything else works from v4.0.- Tools now carry a display
title. Clients that show one will start
showing it; clients that do not are unaffected. This is why the published
tool-list figure moved.
🖱️ One-click Claude Desktop install: download coolify-mcp.mcpb below and drag it into Settings → Extensions.