Это руководство пока доступно только на английском.

MCP tool reference

What it's for

The complete list of what an AI agent connected to DADA Cloud can actually do: every tool the MCP server exposes, what each one takes, whether it changes anything, and what it returns. Use it when you want to know if a task is possible before you ask an agent to try, or when you are writing your own client against the server.

Setup lives in Control DADA Cloud from an AI agent (MCP). Worked end-to-end flows live in MCP recipes.

The surface: 61 tools

The server is generated by reflection over the platform's own REST API and then cut down by an allowlist, so a tool exists here only if it was deliberately chosen. That is why the number is 61 and not the 216 operations the API has. Anything not on this page is not reachable from an agent — the last section explains what was left out and why.

Two flags come from the protocol itself and your client may show them:

  • read-only — the tool cannot change anything. All 25 list* / get* tools are read-only.
  • destructive — the tool removes something. Exactly four are: deleteBox, deleteEnvVar, deleteAgent and deleteServiceCache. Well-behaved clients ask you before running these.

Every other tool is a create or an update: additive, and (except extendBox) asynchronous.

How arguments work

All tools take one flat object. There are no nested paths, no request bodies to assemble — you pass projectId, name, image and so on at the top level and the server routes each one into the URL, the query string or the body.

Three argument names carry almost every call:

ArgumentWhat it isWhere you get it
projectIdProject UUID, not the slug you see in the console URLlistProjects, resolveRef
envIdEnvironment UUIDgetProject, resolveRef (or default_environment_id from createProject)
appNameApp name as a stringlistApps, resolveRef

You do not have to use UUIDs

Every tool that takes projectId, envId or appName also accepts names, and the server resolves them before the call runs:

{ "project": "internal", "env": "prod", "app": "telemost-bot", "key": "PGHOST", "value": "..." }

or, the same thing as one address:

{ "ref": "internal/prod/telemost-bot", "key": "PGHOST", "value": "..." }

If the project has exactly one environment, env may be left out. Names are resolved under your own permissions — nothing becomes reachable that was not already. An ambiguous project name (the same name visible to you in two orgs) comes back as a 409 that lists the candidates rather than a guess.

This is the whole point: one write is one call. The old listProjectsgetProjectlistApps → write walk is no longer needed, and listApps in particular used to return the console's full snapshot of every app in the environment — enough, on a busy project, to fill an agent's context window before the actual work began.

Every response that carries an id now carries the matching project / env / name beside it, so there is no return trip to turn an id back into an address.

Async: what "Committed" means

Most mutations return HTTP 202 and an operation id rather than a finished result. The tool result says so explicitly, and the flow is always the same:

  1. Call the mutation. It returns an operation id.
  2. Poll getOperation until the status is terminal — Committed or Failed.
  3. Committed does not mean running. It means the desired state was written. Poll listApps until the app's phase is Healthy to know it actually came up.

If the phase never reaches Healthy, searchLogs is the next call.

extendBox is the only synchronous mutation (it moves a timestamp). boxUp is also synchronous but for the opposite reason: it holds the request open until a command has actually run inside the box.

Addressing

ToolRequiredOptionalNotes
resolveRefone of ref / projectenv, appread-only. Turns internal/prod/telemost-bot into ids in one call. Resolving a project also lists its environments; resolving an environment lists its app names; resolving an app returns its current image, phase, URL, the env-var keys the console manages and the env-var keys actually present in the running workload.

resolveRef is the cheapest way into a session, and the only tool that shows the two env pictures side by side — see the warning under Environment variables.

Projects and environments

ToolRequiredOptionalNotes
listProjectsread-only. Start here. Returns every project you are a member of with your role in each.
getProjectprojectIdread-only. Returns the project plus its environments, each with an id — the only way to obtain an envId for a project that already exists.
createProjectslugdisplay_name, org_id, default_environmentCreates the project and its first environment, and returns default_environment_id. slug must be DNS-label-safe and is unique platform-wide. Without org_id the project lands in your personal org.

Apps

An app is a container workload: an image plus a port. Everything else the platform assembles.

ToolRequiredOptionalNotes
listAppsprojectId, envIdname, viewread-only. Every app in the environment with its live phase. This is the tool that answers "did it come up". Defaults to view=summary for agents — one line per app (ref, name, project, env, ids, phase, image, url). Pass name to narrow it to one app, or view=full for the console's whole snapshot.
createAppprojectId, envIdname, image, port, replicas, profile, framework, worker, volume, workload_typeAsync. For a cloud (container) environment image is required in practice and port/replicas/profile apply. For a VM environment the app deploys as a Compose stack onto the environment's app server, which must already be Ready.
updateAppImageprojectId, envId, appNameimageAsync. Rolls an existing app to a new image tag. This is how you deploy a new version of something already running.
updateAppStorageprojectId, envId, appNamepath, size, storage_classAsync. Attaches a shared persistent directory, or grows an existing one. Grow-only, and the storage class is fixed once created.

There is no deleteApp and no restartApp. Both omissions are deliberate: delete is destructive enough to belong in the console with a human in front of it, and restart is unnecessary because env and image changes reconcile on their own.

Environment variables

ToolRequiredOptionalNotes
listEnvVarsprojectId, envId, appNameread-only. Returns two lists. env_vars is what the console manages: non-secret values in plaintext, secret values masked. cluster_env is what the running workload actually carries — including variables from secretKeyRef, configMapKeyRef and .env files, which the console does not model — as keys only, never values.

An empty env_vars does not mean an empty app. An app assembled by hand in git has no rows in the console's table while its pods carry a dozen variables; reading env_vars: [] as "nothing here, safe to write" is how configuration gets erased. Check cluster_env, and check cluster_env.observed before believing an empty cluster list — observed: false means the platform could not look, not that there is nothing there.

ToolRequiredOptionalNotes
setEnvVarprojectId, envId, appName, keyvalue, is_secret, scope, dry_runCreates or updates one key. The value is stored AES-GCM encrypted either way; is_secret controls whether it is ever readable back.
deleteEnvVarprojectId, envId, appName, keydry_rundestructive. Removes one key.

Changes reconcile without a restart. There is no bulk-set tool on the agent surface — one key per call, which keeps a partial failure legible.

Writing an env var re-renders the app's values.yaml from the console's database. Anything hand-written in git that the console does not model would be dropped by that render, so the deploy is refused instead: the error names the exact paths at risk (common.extraEnv.PGHOST, common.servicePort, …). Import those keys into the console, or edit values.yaml directly, and retry.

Ask before you write: dry_run

Both writes accept dry_run: true. Nothing is saved and nothing is committed; the call returns 202 with an operation_id, and the operation's validation_result holds the plan for the values.yaml the write would produce:

{
  "dry_run": true,
  "values_path": "apps/internal/prod/telemost-bot/values.yaml",
  "added": ["common.extraEnv.NEW_KEY"],
  "changed": ["common.servicePort"],
  "removed": ["common.extraEnv.PGHOST", "common.useDotEnv"],
  "would_block": ["common.extraEnv.PGHOST", "common.useDotEnv"],
  "verdict": "THE REAL DEPLOY WOULD BE REFUSED: it deletes configuration that exists only in git (…)"
}

Read it with getOperation. removed is the list to look at first, and would_block is the subset that makes the real write fail. added includes the key you are about to set, so the plan describes the write you are considering rather than the state before it — values never travel, only key names.

Databases

ToolRequiredOptionalNotes
listDatabasesprojectId, envIdread-only. Managed PostgreSQL instances in the environment with their live phase.
createDatabaseprojectId, envIdname, database, app_ref, backup_enabled, backup_schedule, backup_retentionAsync. Omit app_ref for a standalone environment-level database; set it to bind the database to an app.
getDatabaseCredentialsprojectId, envId, name, revealread-only, but reveal must be true and every reveal is written to the audit log. Returns host, port, database, username and password. Returns 404 while the database is still provisioning — that is "not ready yet", not "does not exist".
queryDatabaseprojectId, envId, name, queryparamsread-only, requires write access. Runs one SELECT/WITH-SELECT against the database's own tenant data, connecting as the database's own credential (the same one getDatabaseCredentials reveals) inside a READ ONLY transaction that always rolls back, capped at 1000 rows / 5 MB / 5 seconds. INSERT/UPDATE/DELETE/DDL and dangerous admin functions (pg_sleep, pg_read_file, dblink, set_config, ...) are rejected before reaching the database, at the SQL-grammar level — not by instruction — and PostgreSQL itself refuses any write inside the READ ONLY transaction regardless of the connecting role's own privileges. params are positional $1, $2, ... bind values; never build the query text by interpolating values yourself. Answers DATABASE_NOT_ACCESSIBLE while the database's connection secret does not exist yet (still provisioning). Every call is audited under both the calling user and the database role the statement actually ran as.
explainDatabaseQueryprojectId, envId, name, queryformatread-only. Returns the PostgreSQL query plan for a read-only query without running it — EXPLAIN ANALYZE/BUFFERS/WAL are never reachable through this tool, the server always wraps the statement as EXPLAIN (VERBOSE, COSTS, FORMAT <format>). format is text or json.

Managed Redis

A scoped ACL user on the platform's shared Redis instance — the Redis analogue of a Postgres database, not a Redis server of your own.

ToolRequiredOptionalNotes
listServiceCachesprojectId, envIdread-only. Managed Redis cache users in the environment with their live phase.
createServiceCacheprojectId, envIdname, app_ref, key_prefix, profileAsync. Omit app_ref for a standalone cache user or set it to bind to an app. profile defaults to redis-full-access (everything except server/cluster admin, confined to the user's key prefix); narrower profiles exist for callers that want less. key_prefix defaults to the resource's own name.
deleteServiceCacheprojectId, envId, namedestructive, async. Unlike deleteDatabase, this actually releases the resource — it is an ACL user, not a shard, so there is no orphaned data left behind.
getServiceCacheCredentialsprojectId, envId, name, revealread-only, but reveal must be true and every reveal is written to the audit log. Returns host, port, username, password and a ready-to-paste DSN. Returns 404 while the cache user is still provisioning.

Git repositories and builds

The path from a repository to a running app.

ToolRequiredOptionalNotes
getGitInstallUrlprojectId, providerread-only. Returns the URL a human must open to grant repository access. An agent cannot install the app for you; it can only hand you the link.
listGitInstallationsprojectIdread-only. Which GitHub/GitLab installations the project's org already has.
listInstallationReposprojectId, installationIdread-only. Repositories a given installation can see.
connectGitRepoprojectId, envIdrepo_full_name, clone_url, installation_id, provider, production_branch, auto_deploy, app_name, root_dir, framework_override, port, replicas, profile, tokenLinks a repo to an app so pushes build and deploy. port/replicas/profile describe the app the first successful build will create (defaults 8080 / 2 / small). token is GitLab-only and is stored encrypted.
triggerBuildprojectId, envId, appNameQueues a build of the linked repo at its production branch HEAD. Builds are imperative: this returns the queued build, not an operation, so track it with getBuild rather than getOperation.
getBuildprojectId, buildIdread-only. One build by id.

Domains

ToolRequiredOptionalNotes
addDomainAuthorizationprojectIdapex_domainRegisters an apex you own (acme.com) and returns a TXT challenge to publish. Authorizing the apex covers its subdomains too.
verifyDomainAuthorizationprojectId, idForces the DNS check immediately instead of waiting for the next scheduled one.

Attaching a verified hostname to a specific app is not on the agent surface — finish that step in the console. See Custom domains and HTTPS.

Logs

ToolRequiredOptionalNotes
searchLogsprojectIdapp, vm, q, since, sizeread-only. One tool, both runtimes: pass app for a cloud app or vm for a VM/Compose app. At least one of the two is required and must belong to the project — that check is what stops one tenant reading another's logs. since accepts 15m, 1h, 6h, 24h, 7d (default 1h); size is 1-1000 (default 200); q is free text.

Operations

ToolRequiredOptionalNotes
getOperationprojectId, operationIdread-only. The state of one async operation. Poll after any 202 until Committed or Failed; on Failed, read the message.

App servers (VMs)

ToolRequiredOptionalNotes
listAppServersprojectIdread-only. Provisioned and connected VMs, newest first, excluding deleted ones.
createAppServerprojectIdname, mode, flavor, region, os_image, ssh_key_name, vm_ip, ssh_private_key, ssh_user, ssh_portAsync. mode=terraform (the default) provisions a new VM; mode=manual connects a VM you already have and needs vm_ip and ssh_private_key.
discoverAppServerWorkloadprojectId, serverNameLists what is already running on the VM — containers, images, ports, named volumes — so you can decide what to adopt. Read-only against the VM; changes nothing.
importComposeStackprojectId, serverName, app_name, servicesenv, ack_secrets_in_gitAsync. Adopts the services you picked from discoverAppServerWorkload into managed apps, one app per service, keeping their existing named volumes. env lands in git in plaintext, so it requires ack_secrets_in_git: true.
attachAppServerHostnameprojectId, serverNameapp_name, hostname, target_port, host_loopbackAsync. Publishes the VM on https://<name>.dada-tuda.ru: mints the hostname, points its A record at the VM's IP, and installs (or extends) the platform's nginx + Let's Encrypt stack on the server. Works on a bare VM — no import needed first. host_loopback: true with target_port proxies to a service bound to 127.0.0.1 on the host, which is how you publish a workload the platform did not deploy. Otherwise the hostname routes to the managed app named by app_name.

Boxes

A box is an ephemeral sandbox with root inside it — the shape an agent actually wants to work in. It owns exactly one environment, which is what every later attachment and hostname hangs off, and which crystallization promotes in place.

Getting a box

ToolRequiredOptionalNotes
getBoxCatalogread-only. The frozen catalog of warm images and size profiles. A size is only real if the pool controller has pre-warmed bodies of that shape, so read this rather than guessing a profile name.
boxUpprojectIdname, image, profile, region, ttl_seconds, session_ttl_hours, spend_cap_rub, ssh_public_key, wait_secondsSynchronous and the one to reach for. Returns only once a command has actually executed inside the box and succeeded — not when the API answered, not when a port accepted. The response carries the connection coordinates, a one-time dadabox_ session token (shown once, never retrievable), a ready-to-paste mcpServers snippet pointing at the box's own endpoint, and the measured time to ready by phase. wait_seconds is a bound in [0,360] defaulting to 240, not a hint: exceeding it returns a classified failure. A pool hit answers in seconds; an empty pool builds a body on the spot, which takes about three minutes, and that case answers 504 cold_start_timeout — which is not pool_exhausted and does not mean the platform is full. Retry, or pass a larger wait_seconds. A failed box keeps its name until you deleteBox it.
createBoxprojectIdname, image, profile, region, ttl_seconds, spend_cap_rub, ssh_public_keyAsync form of the same thing, for a caller that does not want to hold a request open. Poll the operation, then getBoxState for coordinates.
listBoxesprojectIdread-only. Every non-deleted box in the project, newest first.

ssh_public_key is the public half. The platform never holds your private key, which is why there is nothing to leak on our side.

ttl_seconds is when the box goes to sleep, not when it is destroyed. spend_cap_rub, when reached, suspends the box — deliberately: a runaway should cost the customer money, never their data.

Living with a box

ToolRequiredOptionalNotes
getBoxStateprojectId, boxNameread-only. Phase, the coordinates the runtime reported, the age of the newest sample, and whether the TTL has passed. Coordinates are empty until the box reports Ready.
getBoxConnectionprojectId, boxNamenew_sessionread-only. SSH and MCP coordinates plus the paste-ready mcpServers snippet. new_session=true mints a fresh one-time token; the old one is not revealed because only its hash is stored. Minting does not revoke — suspend or delete for that.
extendBoxprojectId, boxNamettl_secondsSynchronous. Pushes out the sleep time, measured from now, capped at 24 hours.
suspendBoxprojectId, boxNameAsync. Freezes the box: compute billing stops, the disk survives, resume brings the same box back. Not a delete.
resumeBoxprojectId, boxNamessh_public_keyAsync. Wakes it and waits for the exec channel to accept again — same disk, same injected credentials. Optionally rebinds a fresh key so you need not keep one alive across the sleep.
deleteBoxprojectId, boxNamedestructive, async. Destroys the sandbox and its disk. Resources attached to the box (databases, buckets) live outside it and survive. The name becomes reusable once deletion completes.
getBoxUsageprojectId, boxNamefrom, toread-only. Billed minutes and money for one box, straight from the per-minute ledger. Kinds are active and suspended_disk. Idle minutes are absent entirely — an idle minute writes no row, which is why an idle box reports zero rather than a small charge. Window defaults to the current calendar month and may not span more than 92 days.

Making a box useful

ToolRequiredOptionalNotes
attachBoxDatabaseprojectId, boxNamename, env_prefixAnswers 501 Not Implemented on this installation, for every box. The box runtime that runs here has no attach path, so this is permanent and retrying never helps. Use createDatabase to provision the same managed Postgres outside the box, then set its connection string as env inside the box yourself. Keeping the database outside the box is the design, not a workaround: deleting or crystallizing the box never destroys it.
listBoxAttachmentsprojectId, boxNameread-only. What is attached and which env key names each one injected. Never values.
exposeBoxprojectId, boxNameportPublishes a port serving inside the box on a hostname the platform assigns under its wildcard. You cannot choose the hostname — custom domains are a crystallization feature, and a throwaway body with an arbitrary name is a phishing surface. Returns the hostname, the URL, and the measured time to the first real 200. Responses carry X-Robots-Tag: noindex.
crystallizeBoxprojectId, boxNameack_monthly_charge, app_server_name, domain, probe_pathPromotes the box's userland onto a real VM booted from a standard OS image: the VM keeps its own kernel and init, and the result runs under systemd — no Docker, no Compose, no agent, because a crystallized VM is not a container host. Then it is verified: file-manifest equality on (path, size, mode, sha256), the listening-socket set before and after, an sha256-per-key env comparison, and an end-to-end HTTP probe. Requires ack_monthly_charge — promotion turns a per-minute body into a monthly bill, so consent is a gate and the answer is 409 without it.
listBoxCrystallizationsprojectId, boxNameread-only. Every attempt with its stored verification report. verified is separate from status on purpose: an attempt can finish while verification failed.

Agents

A ManagedAgent is a kagent runtime the console renders as a Crossplane claim: name, system prompt, model, and which MCP servers it can call. Writing one goes through git, exactly like an app — saveAgent queues the commit, Argo applies it, and the cluster answers minutes later, not synchronously.

ToolRequiredOptionalNotes
listAgentsprojectId, envIdread-only. Every agent in the environment: ManagedAgent claims the console ordered and raw kagent Agent CRs maintained by hand in git. Live readiness comes from getAgentState, not this list.
saveAgentprojectId, envId, name, promptdisplay_name, description, prompt_version, model_config, runtime, tools, envAsync — returns 202 with an operation; poll getOperation until Committed. Re-posting the same name updates that agent. tools names MCP servers from listAgentTools; env is plain key/value, not secret-managed. Run validateAgent first — a name/prompt/tool problem otherwise surfaces as an Argo sync failure minutes later, attached to no call you can point at.
deleteAgentprojectId, envId, namedestructive, async. Queues the git write that removes the agent claim; Argo then prunes the agent, its prompt ConfigMap and its RemoteMCPServers.
validateAgentname, promptdisplay_name, description, allowed_headers, toolsread-only. Checks name, prompt and requested tool names against everything the cluster would refuse later — a tools entry with no matching MCP server is caught here as a 400 with a per-field error list, not after the agent ships and answers every question anyway, just without that tool.
getAgentStateagentNameread-only. Whether kagent accepted the claim, whether its pods actually serve, which prompt version those pods loaded, and the Langfuse traces link. An agent not yet synced is reported absent, not as an error.
listAgentToolsread-only. The RemoteMCPServer objects the agent runtime has, including whether each was accepted and which tools it actually discovered — the source of truth for the names saveAgent.tools and validateAgent.tools take. The cluster-internal URL is withheld from everyone but platform admins; the servers themselves are shared platform infra, not a tenant secret.
sendAgentMessageagentName, textPosts one message straight to the agent's cluster-internal A2A endpoint and returns its reply in the same call — no Telegram bot, no bound chat. Stateless: no conversation history carries between calls, each is a fresh Task. This is the loop for iterating on a prompt: send a probe message, read the reply, fix the prompt with saveAgent, send again. 400 for an unknown agent, 502 if the agent's A2A endpoint errors or times out (up to 90s).

Access grants

An agent authenticates as its own /agents-group machine identity, which owns nothing by default. A grant is how that identity gets scoped access to one of your projects.

ToolRequiredOptionalNotes
listAgentGrantsprojectIdread-only. Every grant on the project, newest first, including revoked and expired ones — the audit trail of which identity held which role, for which run, and when it ended.
createAgentGrantprojectId, agent_usernamerole (Developer or ReadOnly, default Developer), ttl_minutes, run_refRequires Owner/Admin on the project — this lends access you already administer, it does not mint new authority. No token is issued: the identity keeps authenticating as itself, and this row is what its role resolves from.
revokeAgentGrantprojectId, grantIdEnds the grant immediately; the identity's next request to the project resolves to no access (404). Idempotent — revoking an already-ended grant still succeeds. The row stays as the audit trail.

What is deliberately not exposed

Knowing what an agent cannot do is as useful as knowing what it can.

  • No command execution inside a box, ever. There is no boxExec and there will not be one. Our MCP surface is control plane only. The box hands out its own local MCP endpoint, which you add to your client as a second server — so your code and your model credentials never traverse our API. A command tool here would route every keystroke of your work through us, which is the opposite of the promise.
  • No deleteApp, deleteDatabase, deleteProject, deleteAppServer. Deleting a customer's deployment is not an agent's job. deleteBox is the exception because ending a disposable body is the normal end of its life, and an agent that cannot release a box it created leaks paid capacity you pay for.
  • No account or billing tools beyond getBoxUsage. An agent needs to see what it has spent, not your whole account.
  • No admin, monitoring-config, model-serving, DNS-record or file-manager tools. They exist in the REST API; they are noise in a tool list an agent carries in its context on every turn.
  • No bulk env-var set and no revealEnvVar. One key per call, and secret values stay unreadable.
  • No agentChat* or agent/git/* tools. They exist in the REST API but back the console's own built-in ops assistant (the admin auto-fix button) and its git access — not the ManagedAgent runtime saveAgent writes. Wiring them in here would hand a tenant's agent the platform's own git-write credentials.

Everything omitted here is still available over the REST API with the same token, so nothing is locked away — it is just not in the agent's hands by default.

Self-hosting the full surface

The allowlist is a file. Mounting your own at MCP_OVERRIDES_PATH with an empty keep list exposes every operation the API has. That is supported for private deployments and not something we do on the hosted platform, for the reasons above.