diff --git a/CHANGELOG.md b/CHANGELOG.md index db28628def..3d7b23301f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed - Handle an existing Cloudflare DNS record with a different IP when Coolify adds the DNS record of a new domain automatically. Coolify now replaces the record when it created the record and no other resource uses it. Otherwise it opens the **Configure DNS** dialog, where you can replace the record, and the domain shows the conflict instead of "Not checked" +- Save deployments started through the MCP server as MCP deployments, not as API deployments. The deployment list shows them with the **MCP** source and filter, and the API and MCP deployment data have a new `is_mcp` field ## [v4.6.0] - 2026-11-02 diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index 40a9dc8e68..60ec084ca7 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -26,6 +26,7 @@ class Index extends Component 'commit_message', 'is_webhook', 'is_api', + 'is_mcp', 'rollback', 'server_id', 'server_name', @@ -297,6 +298,7 @@ class Index extends Component WHEN pull_request_id > 0 THEN 'pull-request' WHEN is_webhook THEN 'webhook' WHEN rollback THEN 'rollback' + WHEN is_mcp THEN 'mcp' WHEN is_api THEN 'api' ELSE 'manual' END AS source_key @@ -309,6 +311,7 @@ class Index extends Component 'pull-request' => 'Pull requests', 'webhook' => 'Webhooks', 'api' => 'API', + 'mcp' => 'MCP', 'rollback' => 'Rollbacks', ]; diff --git a/app/Mcp/Tools/Control.php b/app/Mcp/Tools/Control.php index 3abd1fd744..e65c64ebe9 100644 --- a/app/Mcp/Tools/Control.php +++ b/app/Mcp/Tools/Control.php @@ -125,7 +125,7 @@ class Control extends Tool deployment_uuid: $deploymentUuid, force_rebuild: false, restart_only: $action === 'restart', - is_api: true, + is_mcp: true, no_questions_asked: $action === 'start', ); diff --git a/app/Mcp/Tools/Deploy.php b/app/Mcp/Tools/Deploy.php index 061e29a985..4a2ea823f1 100644 --- a/app/Mcp/Tools/Deploy.php +++ b/app/Mcp/Tools/Deploy.php @@ -74,7 +74,7 @@ class Deploy extends Tool deployment_uuid: $deploymentUuid, pull_request_id: $pullRequestId, force_rebuild: $force, - is_api: true, + is_mcp: true, no_questions_asked: true, ); diff --git a/app/Mcp/Tools/GetDeployment.php b/app/Mcp/Tools/GetDeployment.php index b056cb760b..18eeee719a 100644 --- a/app/Mcp/Tools/GetDeployment.php +++ b/app/Mcp/Tools/GetDeployment.php @@ -72,6 +72,7 @@ class GetDeployment extends Tool 'force_rebuild' => $deployment->force_rebuild, 'is_webhook' => $deployment->is_webhook, 'is_api' => $deployment->is_api, + 'is_mcp' => $deployment->is_mcp, 'restart_only' => $deployment->restart_only, 'rollback' => $deployment->rollback, 'git_type' => $deployment->git_type, diff --git a/app/Mcp/Tools/ListDeployments.php b/app/Mcp/Tools/ListDeployments.php index e958cc0c32..b3148ee157 100644 --- a/app/Mcp/Tools/ListDeployments.php +++ b/app/Mcp/Tools/ListDeployments.php @@ -120,6 +120,7 @@ class ListDeployments extends Tool 'force_rebuild' => $deployment->force_rebuild, 'is_webhook' => $deployment->is_webhook, 'is_api' => $deployment->is_api, + 'is_mcp' => $deployment->is_mcp, 'deployment_url' => $deployment->deployment_url, 'created_at' => $deployment->created_at, 'updated_at' => $deployment->updated_at, diff --git a/app/Models/Application.php b/app/Models/Application.php index bc5782ec12..9b0b735950 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1378,12 +1378,14 @@ class Application extends BaseModel 'webhook', 'webhooks' => $query->orWhere('is_webhook', true), 'rollback', 'rollbacks' => $query->orWhere('rollback', true), 'api' => $query->orWhere('is_api', true), + 'mcp' => $query->orWhere('is_mcp', true), 'manual' => $query->orWhere(function ($sourceQuery) { $sourceQuery ->where('pull_request_id', '<=', 0) ->where('is_webhook', false) ->where('rollback', false) - ->where('is_api', false); + ->where('is_api', false) + ->where('is_mcp', false); }), default => null, }; @@ -1409,7 +1411,8 @@ class Application extends BaseModel 'webhook' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', true), 'rollback' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', false)->where('rollback', true), 'api' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', false)->where('rollback', false)->where('is_api', true), - 'manual' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', false)->where('rollback', false)->where('is_api', false), + 'mcp' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', false)->where('rollback', false)->where('is_mcp', true), + 'manual' => $sourceQuery->where('pull_request_id', '<=', 0)->where('is_webhook', false)->where('rollback', false)->where('is_api', false)->where('is_mcp', false), default => $sourceQuery->where('id', -1), }; }); diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index e7c7f69cb8..bf4ad54945 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -32,6 +32,7 @@ use OpenApi\Attributes as OA; 'status' => ['type' => 'string'], 'is_webhook' => ['type' => 'boolean'], 'is_api' => ['type' => 'boolean'], + 'is_mcp' => ['type' => 'boolean'], 'created_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'], 'logs' => ['type' => 'string'], @@ -89,7 +90,11 @@ class ApplicationDeploymentQueue extends Model } $application = $deployment->application; - $source = $deployment->is_api ? 'api' : 'ui'; + $source = match (true) { + (bool) $deployment->is_mcp => 'mcp', + (bool) $deployment->is_api => 'api', + default => 'ui', + }; auditLog("{$source}.application.rollback", [ 'team_id' => $application?->team()?->id, @@ -108,7 +113,11 @@ class ApplicationDeploymentQueue extends Model } $application = $deployment->application; - $source = $deployment->is_api ? 'api' : 'ui'; + $source = match (true) { + (bool) $deployment->is_mcp => 'mcp', + (bool) $deployment->is_api => 'api', + default => 'ui', + }; auditLog("{$source}.deployment.cancelled", [ 'team_id' => $application?->team()?->id, @@ -145,6 +154,7 @@ class ApplicationDeploymentQueue extends Model 'rollback', 'commit_message', 'is_api', + 'is_mcp', 'build_server_id', 'horizon_job_id', 'horizon_job_worker', diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index d8f2d27296..1ccee11bd5 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -16,7 +16,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Spatie\Url\Url; -function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, ?string $commit = null, bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false, ?string $docker_registry_image_tag = null, ?string $parent_deployment_uuid = null) +function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, ?string $commit = null, bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false, ?string $docker_registry_image_tag = null, ?string $parent_deployment_uuid = null, bool $is_mcp = false) { $commit = $commit ?: ($application->git_commit_sha ?: 'HEAD'); $commit = validateGitRef($commit, 'deployment commit'); @@ -35,7 +35,11 @@ function queue_application_deployment(Application $application, string $deployme $destination_id = $destination->id; } - $admission = DB::transaction(function () use ($application, $application_id, $commit, $deployment_uuid, $deployment_url, $destination_id, $docker_registry_image_tag, $force_rebuild, $git_type, $is_api, $is_webhook, $no_questions_asked, $only_this_server, $parent_deployment_uuid, $pull_request_id, $restart_only, $rollback, $server_id, $server_name) { + // MCP tools also call the REST API controllers through an internal request. + $is_mcp = $is_mcp || request()->attributes->get('mcp_rest_bridge') === true; + $is_api = $is_api && ! $is_mcp; + + $admission = DB::transaction(function () use ($application, $application_id, $commit, $deployment_uuid, $deployment_url, $destination_id, $docker_registry_image_tag, $force_rebuild, $git_type, $is_api, $is_mcp, $is_webhook, $no_questions_asked, $only_this_server, $parent_deployment_uuid, $pull_request_id, $restart_only, $rollback, $server_id, $server_name) { // Lock stable rows because an empty deployment queue has no row to lock. Application::query()->whereKey($application_id)->lockForUpdate()->firstOrFail(); $serverForQueueCheck = Server::query()->whereKey($server_id)->lockForUpdate()->firstOrFail(); @@ -80,6 +84,7 @@ function queue_application_deployment(Application $application, string $deployme 'force_rebuild' => $force_rebuild, 'is_webhook' => $is_webhook, 'is_api' => $is_api, + 'is_mcp' => $is_mcp, 'restart_only' => $restart_only, 'commit' => $commit, 'rollback' => $rollback, @@ -106,7 +111,7 @@ function queue_application_deployment(Application $application, string $deployme $deployment = $admission['deployment']; - if (auth()->check() && ! $is_webhook && ! $is_api && ! $rollback) { + if (auth()->check() && ! $is_webhook && ! $is_api && ! $is_mcp && ! $rollback) { auditLog($restart_only ? 'ui.application.restarted' : 'ui.application.deployed', [ 'application_uuid' => $application->uuid, 'application_name' => $application->name, diff --git a/database/migrations/2026_11_03_120133_add_is_mcp_to_application_deployment_queues_table.php b/database/migrations/2026_11_03_120133_add_is_mcp_to_application_deployment_queues_table.php new file mode 100644 index 0000000000..e4af0ad580 --- /dev/null +++ b/database/migrations/2026_11_03_120133_add_is_mcp_to_application_deployment_queues_table.php @@ -0,0 +1,30 @@ +boolean('is_mcp')->default(false); + }); + } + + public function down(): void + { + if (! Schema::hasColumn('application_deployment_queues', 'is_mcp')) { + return; + } + + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('is_mcp'); + }); + } +}; diff --git a/openapi.json b/openapi.json index 76ac085a4f..b0f4c7f312 100644 --- a/openapi.json +++ b/openapi.json @@ -27187,6 +27187,9 @@ "is_api": { "type": "boolean" }, + "is_mcp": { + "type": "boolean" + }, "created_at": { "type": "string" }, diff --git a/openapi.yaml b/openapi.yaml index 6ba839ae90..4307e949a3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -17360,6 +17360,8 @@ components: type: boolean is_api: type: boolean + is_mcp: + type: boolean created_at: type: string updated_at: diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 1bbdd9c467..ccbbb9b995 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -237,6 +237,7 @@ (bool) data_get($deployment, 'is_webhook') => 'Webhook', $pullRequestId > 0 => 'Pull request #' . $pullRequestId, (bool) data_get($deployment, 'rollback') => 'Rollback', + (bool) data_get($deployment, 'is_mcp') => 'MCP', (bool) data_get($deployment, 'is_api') => 'API', default => 'Manual', }; @@ -244,6 +245,7 @@ $pullRequestId > 0 => 'pull-request', (bool) data_get($deployment, 'is_webhook') => 'webhook', (bool) data_get($deployment, 'rollback') => 'rollback', + (bool) data_get($deployment, 'is_mcp') => 'mcp', (bool) data_get($deployment, 'is_api') => 'api', default => 'manual', };