---
title: MCP tools and resources
description: Reference for the resources, tools, prompts, and widgets Cetacean's MCP server exposes to an agent.
category: reference
tags: [mcp, ai, agents, tools, reference]
canonical: https://cetacean.mazetti.me/mcp-tools
---

# MCP tools and resources

This page is the catalog an agent sees over Cetacean's MCP server: 14 resources, 27 tools, 6 prompts, and 5
widgets. For enabling the server, authorization, and configuration, see [MCP server][mcp].

Everything here is filtered per identity. A tool above the configured [operations level][operations-level] isn't
registered at all; a tool or resource the identity holds no grant for is hidden from the listing and refused at
call time.

## Tools

Tools are gated by operations level ([`mcp.operations_level`][mcp.operations_level], inheriting `server.operations_level`)
and by per-resource write grants. Every MCP tool requires the same operations level as the API route for
the same operation, so there's no MCP-specific scheme to learn.

Each tool advertises the behavioral hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`,
`openWorldHint`) clients use to gate confirmation prompts and an output schema the server validates results
against. A mutating tool returns an error when someone else changed the resource first, so re-read it and retry.

> [!CAUTION]
> Removals are irreversible: no spec history is retained after a service removal, and a config, secret, or
> volume payload can't be recovered.

### Orientation

Where to start, and how to turn a name into something you can act on. `find` and `describe` attach links to the
resources they returned, so a host can offer somewhere to go next.

#### `get_cluster_status`

Answers whether the cluster is healthy and, when it's not, names the services not in their desired state or
    restarting in a loop, the nodes down or draining, the rollouts in flight, and reserved against total CPU and memory. Start here.

**Arguments** — none

**Level** — 0 (read-only)

#### `find`

Locates resources. With `type` it enumerates that type, paged by `offset` and capped at the same 200 the default
    sets. Without `type` it searches every type at once: `offset` is ignored, and the result carries `counts`, the
    per-type breakdown behind `total`.

**Arguments** — `type` (plural: `nodes`, `services`, `tasks`, `stacks`, `configs`, `secrets`, `networks`,
        `volumes`), `query`, `state`, `stack`, `node`, `image`, `label`, `limit` (200 typed, 3 per type otherwise),
        `offset`, `raw`

**Level** — 0 (read-only)

#### `describe`

Returns one resource as a `Digest`. `id` takes an ID or a name—a hostname for a node, the rendered
    `{service}.{slot}` for a task, the plain name for everything else—and a name matching more than one
    resource
    is refused, naming the matching IDs.

**Arguments** — `type` (singular, required: `service`, `node`, `task`, `stack`,
        `config`, `secret`, `network`, `volume`), `id` (required), `raw`

**Level** — 0 (read-only)

#### `get_topology`

Projects the cluster as a graph: `network` joins services to the overlay networks they attach to, `placement`
    joins nodes to the services they run, and `drain-impact` joins the services on one node to the nodes that could
    take them.

    In `drain-impact` a service with no edges is stranded, and its `detail` names the placement constraint that
    blocked it; a global service reports as `global`, since draining stops its task rather than relocating it.
    Constraints are evaluated the way Swarm enforces them; spare capacity isn't considered, since
    [`get_cluster_status`](#get_cluster_status) already reports reserved against total. Where grants hid candidate
    nodes, the graph carries
    a `note` saying what the assessment was narrowed to.

**Arguments** — `view` (`network`, `placement`, `drain-impact`), `node` (for `drain-impact`)

**Level** — 0 (read-only)

### Observation

What the cluster is doing now, and what it did recently.

#### `get_logs`

Fetches recent log lines from one scope.

    A task reads one replica and is the only way to reach one that has already exited—the form to reach for after
    a crash, and the most perishable, since Swarm discards a task's output with the task record, five per replica
    slot by default. A service in a restart loop may hold seconds of history, so read it first. `stack` and
    `cluster` merge server-side and attribute each line to its service, so grepping the cluster with `contains`
    costs one call; a wide read covers at most 25 services and reports what it couldn't reach in `errors` rather
    than failing.

**Arguments** — exactly one of `service`, `task`, `stack`, `cluster`; plus `contains`, `tail` (100 per
        replica), `since`, `level` (`debug`, `info`, `warn`, `error`, `fatal`)

**Level** — 0 (read-only)

#### `get_events`

The change timeline, newest first. `resource` takes a name as readily as an ID, resolved against `types` when
    exactly one is given and otherwise only where no other type claims the name. A service's timeline includes its
    tasks' entries unless `types` leaves out `task`.

**Arguments** — `since`, `until`, `types`, `resource`, `limit`

**Level** — 0 (read-only)

#### `get_metrics`

Charts or ranks CPU, memory, or network use. It takes a target and a metric rather than PromQL, resolves the
    service or node against Cetacean's cache, and checks the caller's read grant before querying.

    Asking for `top` turns it into a ranking: `target: "cluster"` ranks the busiest services, or nodes with
    `by: "node"`, and `target: "node"` with an `id` ranks the services on that host, which answers why a node is
    hot.

    Metrics need Prometheus, plus cAdvisor for service metrics and node-exporter for node metrics; without them the
    tool says metrics are unavailable rather than returning empty series.

**Arguments** — `target` (required: `service`, `node`, `cluster`), `id`, `metric` (`cpu`, `memory`,
        `network`), `range` (`1h`, `6h`, `24h`, `7d`), `top` (5, capped at 10), `by`

**Level** — 0 (read-only)

#### `get_recommendations`

The recommendation engine's current findings, the same ones [`cetacean://recommendations`][mcp.resources]
    returns, with totals counting what the caller may read. A finding with a remedy carries it as `fix`, naming the
    tool to call, for example `{"tool": "update_service", "section": "resources"}`. A finding whose REST `fixAction`
    route has no MCP tool behind it reports no `fix`.

**Arguments** — `severity`

**Level** — 0 (read-only)

#### `watch`

Blocks until a service has settled, then reports whether it did. It exposes the same convergence rule the
    converging mutations use, as a read; the wait can't be canceled once started.

**Arguments** — `service` (required), `timeout` (60 seconds, capped at 300)

**Level** — 0 (read-only)

### Service lifecycle

Running services: how many replicas, which image, and starting over. All four converging tools accept
[task augmentation][tasks] and return a summary of where the service ended up rather than its full spec:

```json
{
    "id": "web",
    "name": "web",
    "image": "nginx:1.27",
    "mode": "replicated",
    "replicas": 5,
    "running": 5,
    "state": "running",
    "version": 42
}
```

`running` is the live count, `state` is the same derivation the [dashboard][dashboard] and the [API][api] report,
and `version` is the Swarm version index for a caller doing its own concurrency checks. `replicas` is omitted for a
global service.

#### `scale_service`

Sets the desired replica count. `0` stops every task without removing the service.

**Arguments** — `id`, `replicas`

**Level** — 1 (operational)

#### `update_service_image`

Sets the container image, triggering a rolling deploy per the service's update policy.

**Arguments** — `id`, `image`

**Level** — 1 (operational)

#### `rollback_service`

Reverts to the previous spec. Only one previous spec is retained, so calling twice doesn't unwind further.

**Arguments** — `id`

**Level** — 1 (operational)

#### `restart_service`

Forces a rolling restart by bumping `ForceUpdate`. No other spec field changes.

**Arguments** — `id`

**Level** — 1 (operational)

#### `remove_task`

Deletes a task; Swarm reschedules a replacement, so this is a forced reschedule.

**Arguments** — `id`

**Level** — 3 (impactful)

#### `remove_service`

Deletes a service and stops every task. Doesn't delete the volumes, networks, configs, or secrets it
    referenced.

**Arguments** — `id`

**Level** — 3 (impactful)

### Service configuration

Editing a service's spec. These tools return the section they changed rather than the whole service:

```json
{
    "id": "web",
    "name": "web",
    "version": 42,
    "section": "resources",
    "details": {
        "cpuLimitCores": 2,
        "memoryLimitBytes": 1073741824
    }
}
```

`details` reports the edited section as `describe` would, naming environment variables and log driver options
without their values—so raising a CPU limit never hands back the service's credentials.

`update_service_secrets`, `update_service_configs`, and `update_service_mounts` replace their set wholesale:
pass every entry the service should end up with, because one left out is detached—and a container may lose
data it was writing to a dropped mount.

#### `update_service`

Changes one section of a service's spec. `section` takes one of ten names, and `value` that section's new value:

    | Section           | Value                  | Semantics                                                                                                                                                                                             |
    |-------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | `env`             | Merge-patch object     | A string sets a key, `null` deletes it, an omitted key is preserved                                                                                                                                   |
    | `labels`          | Merge-patch object     | Same as `env`; the only service section that doesn't trigger a rolling deploy, along with the two policies                                                                                            |
    | `resources`       | `ResourceRequirements` | CPU and memory limits and reservations                                                                                                                                                                |
    | `placement`       | `Placement`            | Constraints, preferences, max replicas per node, platforms; may reschedule tasks                                                                                                                      |
    | `ports`           | Array of `PortConfig`  | Drops connections to any port it removes or remaps                                                                                                                                                    |
    | `update-policy`   | `UpdateConfig`         | Applies to the next spec change                                                                                                                                                                       |
    | `rollback-policy` | `UpdateConfig`         | Applies to `rollback_service`                                                                                                                                                                         |
    | `log-driver`      | `Driver`               | Name plus options; routes subsequent log lines                                                                                                                                                        |
    | `healthcheck`     | Probe                  | `test` as an array (`["CMD-SHELL", "curl -f localhost \|\| exit 1"]`), plus `interval`, `timeout`, `startPeriod`, `retries`. Durations are strings such as `"10s"`. An empty `test` removes the check |
    | `command`         | `command` plus `args`  | The entrypoint and what follows it, the split Docker makes. Omitting one clears it                                                                                                                    |

    Every section other than `env` and `labels` replaces its section wholesale: a field you omit is cleared.

    Secrets, configs, and mounts aren't sections here. Each takes a list rather than a spec fragment, and
    attaching a secret checks a read grant on the secret as well as a write grant on the service, so each is a tool
    of its own. Passing one as a `section` is refused with the name of the tool to use.

**Arguments** — `id`, `section`, `value`

**Level** — 2 (configuration)

#### `update_service_secrets`

Replaces the complete set of secrets a service receives.

**Arguments** — `id`, `secrets`

**Level** — 2 (configuration)

#### `update_service_configs`

Replaces the complete set of configs a service receives.

**Arguments** — `id`, `configs`

**Level** — 2 (configuration)

#### `update_service_mounts`

Replaces the complete set of mounts: named volumes, host bind mounts, tmpfs.

    > [!CAUTION]
    > A bind mount hands the container the host's filesystem at that path, and binding `/var/run/docker.sock` gives
    > it control of the whole cluster, and a volume with driver options can bind the host too. Below level 3 a bind
    > mount the service doesn't already have is refused, and so is a named volume the manager doesn't hold as a plain
    > `local` volume; at level 3, `update_service_mounts` does it if asked.

    The tool can't set bind options, so a bind the service has with a propagation mode reads as changed: below level 3
    it's refused, and at level 3 resending it drops the mode.

**Arguments** — `id`, `mounts`

**Level** — 2 (configuration)

### Cluster resources

Configs, secrets, networks, and volumes. Swarm secrets and configs are immutable, so rotating one is three
calls in order: `create_secret` for the replacement, [`update_service_secrets`](#update_service_secrets) to
repoint each service that uses it (`describe` the secret first, its `related` array names them), then
`remove_secret` once nothing references it. Configs follow the same sequence, and unlike a secret a config's
content can be read back, so use a secret for anything sensitive.

#### `create_secret`

Creates a Swarm secret.

**Arguments** — `name`, `data`, `encoding` (`utf8`, `base64`), `labels`

**Level** — 2 (configuration)

#### `create_config`

Creates a Swarm config.

**Arguments** — `name`, `data`, `encoding` (`utf8`, `base64`), `labels`

**Level** — 2 (configuration)

#### `remove_secret`

Deletes a secret. Fails if any service still references it.

**Arguments** — `id`

**Level** — 3 (impactful)

#### `remove_config`

Deletes a config. Fails if any service still references it.

**Arguments** — `id`

**Level** — 3 (impactful)

#### `remove_network`

Deletes an overlay network. Fails if a service is attached; `ingress` and `docker_gwbridge` can't be removed.

**Arguments** — `id`

**Level** — 3 (impactful)

#### `remove_volume`

Deletes a volume by name. Fails if a container is using a local volume, even with `force`, which there only
    overrides an error from the volume driver. On a cluster volume, `force` deletes it even while it is in use.

**Arguments** — `name`, `force`

**Level** — 3 (impactful)

### Nodes

Where work is allowed to run, and who votes in Raft.

#### `update_node_labels`

Patches a node's labels (JSON Merge Patch).

**Arguments** — `id`, `labels`

**Level** — 2 (configuration)

#### `update_node`

Changes a node's availability or role.

    Draining a manager may destabilize the swarm, so prefer draining workers. Promoting a node adds a Raft voter
    and demoting removes one: never reduce the manager count below the quorum threshold, and on a three-manager
    cluster demote at most one at a time. Node labels stay a tool of their own,
    [`update_node_labels`](#update_node_labels), at level 2, so a deployment that lets an agent relabel a node doesn't also let it demote a manager.

**Arguments** — `id`, `section` (`availability`, `role`), `value` (`active`, `pause`, `drain`—or
        `worker`, `manager`)

**Level** — 3 (impactful)

## Resources

Resources are read-only views returned as `application/json`. Three are static, eleven are URI templates.

| URI                          | Contents                                                                                                                                                                                                                  |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cetacean://cluster`         | Node, service, task, and stack counts, node readiness, task counts by state, total and reserved CPU and memory, the largest node's capacity, converged and degraded service counts, last sync time                        |
| `cetacean://recommendations` | Current [recommendation][recommendations] engine findings, each with severity, category, target resource, and rationale                                                                                                   |
| `cetacean://history`         | The most recent create, update, and delete events across every resource type, newest first, up to 100. A task event is named after its service (`demo_flaky.3`) rather than by task ID; `resourceId` still carries the ID |

Every numeric field in `cetacean://cluster` names its unit: `totalCPUCores`, `reservedCPUCores`,
`maxNodeCPUCores`, `totalMemoryBytes`, `reservedMemoryBytes`, `maxNodeMemoryBytes`. Both CPU figures are in
cores.

Templated resources return the same compact digest the `describe` tool builds. The exceptions are
`services/{id}/logs`, a raw log stream, and the two `compose` documents, which are YAML rather than JSON.

| URI template                       | Contents                                                                        |
|------------------------------------|---------------------------------------------------------------------------------|
| `cetacean://nodes/{id}`            | Node digest, by ID or hostname                                                  |
| `cetacean://services/{id}`         | Service digest, by ID or name, with cross-references                            |
| `cetacean://services/{id}/logs`    | Service logs merged across replicas, subscribable; not a digest                 |
| `cetacean://services/{id}/compose` | The service as a one-service Compose file; everything it references is external |
| `cetacean://tasks/{id}`            | Task digest; the parent service and node are in `related`, not top-level fields |
| `cetacean://stacks/{name}`         | Stack digest, rolling up its member resources                                   |
| `cetacean://stacks/{name}/compose` | The stack as a Compose file; secrets and configs are referenced, never exported |
| `cetacean://configs/{id}`          | Config digest; the payload's size in bytes, never the payload                   |
| `cetacean://secrets/{id}`          | Secret digest; the payload is never read, not even its length                   |
| `cetacean://networks/{id}`         | Network digest, with the attached services                                      |
| `cetacean://volumes/{name}`        | Volume digest, keyed by name                                                    |

### Subscriptions

Clients call `resources/subscribe` with a URI. When the underlying cluster state changes, the server sends
`notifications/resources/updated` for that URI and the client re-reads.
`notifications/resources/list_changed` fires when resources are created or removed. Both are filtered by grants per
notification: a client is only notified about resources its identity can read.

### Argument completion

Templated resource URIs and prompt arguments complete, offering resource **names** rather than IDs. Matching is
a case-insensitive substring, so typing `prometheus` offers `monitoring_prometheus`. Completions are capped at
100 values and are filtered by the caller's grants like every other read.

## Compact resource shapes

Tools and resource reads never return raw Docker objects—they're enormous, and they don't carry the field a
caller actually wants, since a service's health is derived from its tasks. Every read returns one of two shapes.

**Row** is one entry in a list, returned by `find`. Every row carries `id`, `name`, and `type` (singular:
`service`, `node`, `task`, and so on). `stack`, `state`, and `detail` (the most identifying secondary fact,
such as a service's image, a node's role, or a network's driver) are omitted where they don't apply.
`desired` and `running` are populated only where a replica count means something:

- services set both, the desired replica count and how many are running
- stacks set `desired` alone, counting member services rather than replicas
- nodes, tasks, configs, secrets, networks, and volumes set neither

**Digest** is the detail view of one resource, returned by `describe` and by every templated
`cetacean://<type>/{id}` read. Alongside `id`, `name`, `type`, and `state` it adds:

| Field            | Contents                                                                                                                                                          |
|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `reason`         | The cause Swarm gave for a non-healthy `state`; omitted when the state is healthy                                                                                 |
| `since`          | When the current state began: the oldest still-live failing task's timestamp, or the resource's own last-updated time                                             |
| `details`        | A type-specific map of facts, such as a service's image, replica counts, reserved CPU and memory, ports, and placement constraints, or a node's role and capacity |
| `related`        | Always an array, even when empty: the resources this one references or is referenced by                                                                           |
| `recentFailures` | Always an array: the task failures behind a failing state, newest first, capped at 5. Only a service digest populates it                                          |
| `restarts`       | A service's involuntary task terminations as `lastHour` and `lastWeek` counts. Services only                                                                      |

`restarts` is the only field that reveals a restart loop. `state` reads `running` whenever a replica happens
to be up, and `recentFailures` drops tasks the orchestrator has already replaced, so a service crash-looping
every few seconds otherwise describes as healthy. `lastHour` answers whether it's failing now; the ratio
between the two windows answers whether the fault is new.

Every numeric field in `details` names its unit, such as `cpuLimitCores` (a float, in cores) and
`memoryLimitBytes`, or is reported as a duration string like `"10s"` (`healthcheckInterval`, an update
policy's `delay` and `monitor`). Docker's own types express these as unlabeled NanoCPU or nanosecond
integers. Environment variables are reported as `envNames`: names only, never values.

### The `raw: true` option

Both `find` (when `type` is given) and `describe` accept `raw: true`, adding the untouched Docker record to
the result under `raw`. Use it only when a specific field a row or digest omits is needed; the compact shapes
exist to avoid handing an agent a several-hundred-line object.

Raw records ride beside the compact shape rather than replacing it: `find` adds a `raw` array with one record
per row, `describe` a `raw` object beside the digest.

## Prompts

Prompts are named sequences a client offers from a menu. Picking one seeds the conversation with an
investigation or a runbook. A prompt only seeds the conversation; every read and write it then describes goes
through the normal tools, grant checks included.

A prompt's level is the highest level of the tools it walks, so it's never offered where one of its steps
would be refused. At the default level 0 you get the three diagnostic prompts; level 1 adds `roll_back_service`.

Prompts are filtered by grants too, all or nothing: you are offered a prompt only if you can use every tool it
walks and can read every resource type in its Reads column. A caller whose grants match nothing is offered
none.

<!-- cards -->

| Prompt                  | Level | Argument  | Reads         | What it does                                                                                                                                                |
|-------------------------|-------|-----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `diagnose_service`      | 0     | `service` | service       | Walks tasks, the failing task's logs, metrics, and recent changes to find why a service is unhealthy                                                        |
| `explain_unschedulable` | 0     | `service` | service, node | Separates the causes of an unplaced task: placement constraints, node labels and platform, node availability and state, per-node replica caps, reservations |
| `review_capacity`       | 0     | none      | node          | Joins node capacity, reservations, real usage, and sizing findings to say where the cluster is constrained                                                  |
| `roll_back_service`     | 1     | `service` | service       | Confirms a service is degraded, then rolls it back and waits for the replicas to run                                                                        |
| `right_size_service`    | 2     | `service` | service       | Checks a sizing recommendation against measured use, then corrects the reservations                                                                         |
| `drain_node`            | 3     | `node`    | node, service | Checks quorum and that the work can be placed elsewhere, then drains the node and confirms the tasks moved                                                  |

## Apps

A host that supports the MCP Apps extension can render Cetacean's data as an interactive view instead of JSON.
Cetacean advertises `io.modelcontextprotocol/ui` and serves each widget as a resource. Each tool names its
widget in `_meta`, so a host knows which view fits the result; the same tool called from a client without app
support returns ordinary JSON.

MCP Apps are self-contained: they load nothing from the network and read data only through Cetacean's MCP tools,
so a widget shows exactly what the calling identity's grants allow.

Picking a finding in the recommendations widget sends the model a message in the user's name, and that message
quotes resource names and finding text from the cluster. Whoever can name a service or node chooses part of it.
The widget passes those fields as one JSON value marked as cluster data, but the model still reads them, so treat
a follow-up from a widget as you would any output that quotes the cluster.

<!-- cards -->

| Resource                        | Renders                                                         | Behavior                                                                                                                            |
|---------------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `ui://cetacean/table`           | A [`find`][mcp.tools.find] result                               | Searchable, sortable table of one resource type, showing how many records it holds when the page is a subset                        |
| `ui://cetacean/topology`        | A [`get_topology`][mcp.tools.get_topology] result               | Graph to pan, zoom, and drag; switching view re-runs the tool under the same identity and grants                                    |
| `ui://cetacean/logs`            | A [`get_logs`][mcp.tools.get_logs] result                       | Live tail, re-reading from the cursor the previous read returned; level and search filtering happens over the lines already fetched |
| `ui://cetacean/metrics`         | A [`get_metrics`][mcp.tools.get_metrics] result                 | Line chart with a range picker; changing the range re-runs the tool. Every series is named in a legend with its latest value        |
| `ui://cetacean/recommendations` | A [`get_recommendations`][mcp.tools.get_recommendations] result | Findings grouped by severity, most serious first. Picking one asks the host to send the model a follow-up, which a host may decline |

[api]:  api
[authentication]:  authentication
[dashboard]:  dashboard
[mcp]:  mcp
[mcp.operations_level]:  configuration#mcp.operations_level
[mcp.resources]:  mcp-tools#resources
[mcp.tools.find]:  mcp-tools#find
[mcp.tools.get_logs]:  mcp-tools#get_logs
[mcp.tools.get_topology]:  mcp-tools#get_topology
[mcp.tools.get_metrics]:  mcp-tools#get_metrics
[mcp.tools.get_recommendations]:  mcp-tools#get_recommendations
[operations-level]:  configuration#operations-level
[recommendations]:  recommendations
[server.public_url]:  configuration#server.public_url
[tasks]:  mcp#wait-for-changes-to-take-effect
