Issue #203 closed: system list_resources returns full Coolify payload by default — same bug class as pre-#158 listApplicationDeployments
Summary
system({ action: 'list_resources' }) (introduced in v2.11.0 / #172) typed as Promise<ResourceListItem[]> where ResourceListItem is { uuid, name, type, status? }, but the underlying Coolify /api/v1/resources endpoint actually returns the full nested resource record (~95 fields per item including build/healthcheck/limits/git/docker-compose config). The TypeScript type is runtime-erased, so the client just casts the bloated response without projecting it. This is the same bug class that PR #158 fixed for listApplicationDeployments (was typed Deployment[], returned { count, deployments } envelope with embedded logs blobs).
Reproduction
Against a Coolify v4 instance with a modest 33 apps/services:
| Metric | Value |
|---|---|
| Tool call | system({ action: 'list_resources' }) |
| HTTP endpoint | GET /api/v1/resources |
| Item count | 33 |
| Response size | 537,930 bytes (~525 KB) |
| Avg bytes / item | ~16,300 |
| Keys per item | ~95 (vs. 4 promised by ResourceListItem) |
Sample of fields actually returned that the type does not declare (truncated):
additional_networks_count, additional_servers, additional_servers_count,
base_directory, build_command, build_pack, compose_parsing_version,
config_hash, created_at, custom_docker_run_options, custom_healthcheck_found,
custom_labels, custom_network_aliases, custom_nginx_configuration, deleted_at,
description, destination, destination_id, destination_type, docker_compose,
docker_compose_custom_build_command, docker_compose_custom_start_command,
docker_compose_domains, docker_compose_location, docker_compose_raw,
docker_registry_image_name, docker_registry_image_tag, dockerfile,
dockerfile_location, dockerfile_target_build, environment_id, fqdn,
git_branch, git_commit_sha, git_full_url, git_repository,
health_check_command, health_check_enabled, health_check_host,
health_check_interval, health_check_method, health_check_path,
health_check_port, health_check_response_text, health_check_retries,
health_check_return_code, health_check_scheme, health_check_start_period,
health_check_timeout, health_check_type, ...
Impact
- MCP token budget: 525 KB blows past typical MCP client/LLM context limits on a single tool call. The result has to be spilled to disk by the host, after which the LLM cannot meaningfully consume it. Effectively renders
list_resourcesunusable for any agentic workflow on instances with non-trivial resource counts. - Type lie: any TypeScript consumer destructuring
ResourceListItemfields works (lucky), but anyone relying on the typed surface to reason about response size or field set is wrong-at-runtime. - Scales linearly with instance size: 33 resources is small. A larger instance (100+ apps/services) easily exceeds 1.5 MB on this single call.
Proposed fix (mirrors PR #158)
-
Introduce a true runtime essential projection:
export interface ResourceListItemEssential { uuid: string; name: string; type: 'server' | 'application' | 'database' | 'service' | string; status?: string; }(i.e. what
ResourceListItemalready claims to be, but now actually enforced at the API boundary inlistResources()by mapping over the response.) -
Update
listResources()to project by default and accept an opt-in:async listResources( options?: { include_full?: boolean } ): Promise<ResourceListItemEssential[] | FullResourceListItem[]> -
Expose
include_full?: booleanon thesystemtool'slist_resourcesaction. Defaultfalse(essential projection). Settrueto opt back into the raw Coolify response. -
Add an integration test that asserts the default response shape contains only the essential keys, guarding against future regressions.
Notes
- Naming
include_full(rather thaninclude_logs) because the heavy data on/resourcesis not a single field but the full record. Pattern parity with PR #158'sinclude_logs: trueopt-in. - A separate issue will cover secret-masking on the
include_fullpath — the raw Coolify response containsmanual_webhook_secret_*andhttp_basic_auth_passwordfields that should be masked by default (analog of the v2.9.0env_varsmasking from #159/#182). - Happy to send the PR.