PR #242 merged: fix: project deployment get through essential projection when logs requested (#232)
Problem
deployment {action: get, uuid, lines: N} bypassed the toDeploymentEssential() projection: the handler called getDeployment(uuid, { includeLogs: true }), which returned the raw upstream Coolify payload and spread it directly into the tool response.
That raw payload embeds the entire application graph (rendered docker_compose, custom_labels, webhook secrets) plus the destination server object, including server.settings.logdrain_custom_config (a live bearer token for the log drain) and sentinel_token.
Observed in production: deployment {action: get, uuid, lines: 5} returned a ~78KB payload for 5 log lines, with the secrets above embedded in the body — undoing the webhook-secret masking added in v2.12.0 for list_resources, and blowing typical MCP response token budgets.
The same bypass existed in deployment {action: list_for_app, include_logs: true} via listApplicationDeployments(uuid, { includeLogs: true }).
Fix
CoolifyClient.getDeployment()andCoolifyClient.listApplicationDeployments()now always project throughtoDeploymentEssential(). When logs are requested, the raw log string is attached as alogsfield on the projection — the raw upstream object (application graph, server/destination graph, secrets) is never returned.- Added
logs?: stringas an optional field onDeploymentEssential(src/types/coolify.ts). src/lib/mcp-server.ts'sdeploymenttool handler needed no structural changes beyond dropping a now-unneededas Deploymentcast — it already just spreads whatever the client returns, and the client now only ever returns the essential shape. Pagination (logs_meta, next/prev) andmax_charshandling viatruncateLogs()are unchanged.- Did not route the output through
maskResourceItemFull/SENSITIVE_RESOURCE_FIELDS(the v2.12.0list_resourcesmasking helper) — that helper targets the flatResourceListItemFullshape from/resourcesand doesn't apply cleanly here.toDeploymentEssential()is already an allowlist projection (it only ever copies named-safe fields across), so there's no raw-field surface left for that denylist to guard. Forcing it in would be contorting the code for no real protection.
Tests
src/__tests__/coolify-client.test.ts: updated the two tests that previously assertedincludeLogs: truereturns the raw deployment/list — they now assert the essential projection withlogsattached, and that raw fields (application,destination,id) never appear.src/__tests__/mcp-server.test.ts: newdeployment tool handler (#232 essential projection)suite. Mocks the raw HTTP layer (not the client) with a realistic bloated upstream payload embeddinglogdrain_custom_config,sentinel_token,manual_webhook_secret_*, anddocker_compose, then drives the realdeploymenttool end-to-end:- asserts the serialized response contains none of those leaked keys and no nested
application/server/destinationobjects, - asserts the response stays under 20KB even with a bloated upstream payload.
- asserts the serialized response contains none of those leaked keys and no nested
Verification
npm test— 365 passednpm run lint— 0 errors (4 pre-existingno-explicit-anywarnings, unrelated to this change)npx prettier --check .— cleannpm run build— clean
Closes #232
🤖 Generated with Claude Code