# Available Tools

> What the ZopNight MCP server's read and write tools cover, how the Write access setting gates them, and the required arguments, pagination, and filter values.

Source: https://zop.dev/developer-docs/integrations/mcp-server/available-tools

---

The MCP server exposes two classes of tool: **reads**, available to every connected agent, and **writes**, which change something and are governed by your organisation’s [Write access setting](https://zop.dev/docs/integrations/mcp-server/how-to-connect). The first tool, `list_organisations`, requires no arguments. All other tools require an `org_id` argument — call `list_organisations` first to get the org IDs.

**Info**

The [full catalogue below](#every-tool) is every tool the server can expose — generated from the same manifest the gateway authorizes against, so it is complete and current. But what a given agent sees is narrower: call `tools/list` (or ask your AI tool what ZopNight tools it has) and what comes back is **your** list — a tool is advertised only if it would actually be allowed for your token, your role, and your organisation's Write access setting. Nothing that appears there will fail with a permission error, and nothing is hidden that you could have called. The exact write tools each level turns on are also listed in **Settings → Organisation**.

## Authentication

Every request must include a valid PAT in the Authorization header:

```text
Authorization: Bearer MCP_PAT
```

Key points about authentication:

- Tokens are tied to your email, not a specific organisation
- Permissions are checked dynamically per request, against your **live** role
- If you are removed from an organisation, access is denied immediately
- If MCP is disabled for an organisation, tool calls for that org are blocked
- Tokens have configurable expiry — create a new one when it expires
- A token can do exactly what you can do — no more, and no less

## What the read tools cover

Reads are available at every level of write access. Each one is still checked against your role: you see through MCP exactly what you would see in the UI.

| Area | Covers |
| --- | --- |
| **Explore** | Organisations, cloud accounts, resources, teams, and the discovery status behind them |
| **Cost** | Cost and savings summaries, per-resource and per-provider breakdowns, trends, history, snapshots, budgets, billing sync |
| **Optimize** | Recommendations and their savings, schedules, overrides, resource groups |
| **Operate** | Start/stop history, actions, scheduler events, provisioning jobs, Services and their deploys |
| **Govern** | Tagging policies, smart tags, roles, users, audit logs, notification channels |
| **Diagnose** | Metrics, error detail, and the state behind a failed deploy or job |
| **Ship** | Projects, Environments, Services, Infrastructure, and deploy status |
| **Introspect** | What your own token can do — including `get_my_permissions` |

**Warning**

`get_service_config` returns a Service's shape — port, replicas, health checks, host, resources — and its environment variable **keys with every value redacted**. No tool reveals an env var value, at any level of write access, for any role. The eye-icon in the UI is a deliberate human action; it has no agent equivalent.

## What the write tools cover

A write tool changes something. Which ones your organisation admits is set by one dial, **Settings → Organisation → Write access**:

| Setting | Admits | Examples of what it turns on |
| --- | --- | --- |
| **Read-only** | Nothing | — |
| **Metadata only** | Changes to ZopNight's own records. No cloud state, no cost. | Create/update/delete a budget, set a recommendation's status, accept or revoke a smart tag |
| **Reversible changes** | Bounded blast radius, may affect cost. | Create/update/delete schedules and overrides, attach and detach resources, tagging policies, deploy / redeploy / roll back a Service, change Service config and env vars, retry a Provisioning job |
| **Irreversible changes** | No undo, or incurs cost. | Start and stop resources, create a Provisioning job, Kubernetes manifest writes and workload restarts, cancel a workflow, every delete |

The settings are cumulative — Reversible changes admits the metadata-only tools too. The picker in Settings enumerates the exact tools each setting turns on, generated from the same manifest the gateway authorizes against.

Some surfaces are excluded at every setting and nothing adds them: roles and permissions, user management, organisation deletion, cloud-account deletion, credentials, and bulk actions.

**Warning**

**Enabling MCP write means any content in your cloud environment becomes a potential instruction to your agent.** Resource names, tags, descriptions, and audit entries are attacker-influenceable strings that read tools hand to a model that can call write tools. See [How to Connect](https://zop.dev/docs/integrations/mcp-server/how-to-connect) for the full disclosure before you raise it.

### Retries are only partly deduplicated

Every write tool accepts an optional `idempotency_key`, up to 128 printable ASCII characters. It does two real things: a repeat call carrying the same key replays the original response for 24 hours instead of executing the write again, and two concurrent calls sharing a key are collapsed into a single execution. Reusing a key with **different** arguments is rejected rather than silently replayed, so a genuine retry is safe and a key collision is loud rather than silent.

**Warning**

That protection is held **in the server instance that handled the first call**, and no backend behind these tools deduplicates on the key. A retry routed to a different replica finds no entry and executes normally.

So treat a retried write as capable of running twice: a retried `deploy_service` can create a **second** deploy, and a retried `create_provisioning_job` a **second** job — real, billable cloud infrastructure you did not ask for. `idempotency_key` narrows the window; it does not close it.

Read back before you retry a write.

### Changes are not visible instantly

A write returns when ZopNight has recorded your intent, which is earlier than the world agreeing:

- **Schedules and overrides** are applied by the scheduler on its next pass — allow about a minute before reading back.
- **Deploys and Provisioning jobs** are asynchronous. They return an id; poll for status. A deploy routinely runs 20 minutes and can take up to about 35. `wait_for_deploy` returns after at most 60 seconds with `callAgain: true` if the deploy is still going — that is normal, and the agent should keep calling.
- **Start and stop** are confirmed against real cloud state rather than assumed, so a resource can read `starting` or `stopping` for a while.

An immediate read-back that shows the old value usually means the change has not landed yet, not that it failed.

## Every tool

The complete surface, generated from the gateway's tool manifest. **Reads** are grouped by area; **writes** by the level that admits them (see [above](#what-the-write-tools-cover)). The one-line purpose is each tool's own summary — call `tools/list` for the full argument schema, and remember your own list is filtered to what your token, role, and Write access setting allow.

{(() => {
  const readAreas = [
    ['explore', 'Explore', 'Organisations, cloud accounts, resources, teams, discovery'],
    ['cost', 'Cost', 'Spend, savings, budgets, billing, trends'],
    ['optimize', 'Optimize', 'Recommendations, schedules, overrides, resource groups'],
    ['operate', 'Operate', 'State history, actions, scheduler events, provisioning, Services'],
    ['ship', 'Ship', 'Projects, Environments, Services, Infrastructure, deploys'],
    ['govern', 'Govern', 'Tagging policies, smart tags, roles, users, audit logs, notifications'],
    ['diagnose', 'Diagnose', 'Metrics, errors, and the state behind a failed deploy or job'],
    ['introspect', 'Introspect', 'What your own token can do'],
  ];
  const writeTiers = [
    [1, 'Metadata only', "Changes to ZopNight's own records. No cloud state, no cost."],
    [2, 'Reversible changes', 'Bounded blast radius, may affect cost.'],
    [3, 'Irreversible changes', 'No undo, or incurs cost.'],
  ];
  const reads = mcpTools.filter((t) => !t.mutating);
  const writes = mcpTools.filter((t) => t.mutating);
  const Table = (rows) => (
    <table>
      <thead><tr><th>Tool</th><th>What it's for</th></tr></thead>
      <tbody>
        {rows.map((t) => (
          <tr key={t.name}><td><code>{t.name}</code></td><td>{t.purpose}</td></tr>
        ))}
      </tbody>
    </table>
  );
  return (
    <>
      <h3>Reads <span style={{ fontWeight: 400 }}>({reads.length}) — available at every tier</span></h3>
      {readAreas.map(([cat, label, blurb]) => {
        const rows = reads.filter((t) => t.category === cat).sort((a, b) => a.name.localeCompare(b.name));
        if (!rows.length) return null;
        return (
          <div key={cat}>
            <h4>{label} <span style={{ fontWeight: 400, color: 'var(--muted, #888)' }}>· {blurb}</span></h4>
            {Table(rows)}
          </div>
        );
      })}
      <h3>Writes <span style={{ fontWeight: 400 }}>({writes.length}) — gated by the Write access setting</span></h3>
      {writeTiers.map(([tier, label, blurb]) => {
        const rows = writes.filter((t) => t.tier === tier).sort((a, b) => a.name.localeCompare(b.name));
        if (!rows.length) return null;
        return (
          <div key={tier}>
            <h4>{label} <span style={{ fontWeight: 400, color: 'var(--muted, #888)' }}>· {blurb}</span></h4>
            {Table(rows)}
          </div>
        );
      })}
    </>
  );
})()}

## Pagination

List tools return results in pages — `tools/list` says which, in each tool's own
description. Pass a `page` argument (starting at 1) to navigate through results.
The response includes:

```json
{
  "items": [...],
  "total": 42,
  "page": 1,
  "limit": 10,
  "hasMore": true
}
```

When `hasMore` is `true`, increment the page number to fetch the next set.
If you omit the `page` argument, the server returns page 1 by default.

## Filters Reference

Many tools accept optional filter arguments to narrow results. Pass them
in the `arguments` object alongside `org_id`. All filters are optional
unless noted otherwise.

**Tip**

The `schedulable` filter accepts multiple formats: `true`, `false`,
`1`, `0`, `yes`, or `no`. They are all normalized
automatically.

{(() => {
  const filterSections = [
    {
      tool: 'list_resources',
      filters: [
        ['provider', 'aws, gcp, azure', 'Cloud provider'],
        ['resource_type', 'compute, disk, kms-key, gcp-subnet, ...', 'Resource type'],
        ['status', 'running, stopped, active, available, ...', 'Resource status (case-insensitive)'],
        ['region', 'us-central1, ap-south-1, us-east-1, ...', 'Cloud region'],
        ['search', 'Any string', 'Substring match on resource name'],
        ['cloud_account_id', 'Account ID', 'Filter by cloud account'],
        ['schedulable', 'true, false (also accepts 1, 0, yes, no)', 'Whether resource can be scheduled'],
        ['instance_type', 'e2-micro, STANDARD, ...', 'Instance/machine type'],
        ['sort_by', 'name', 'Sort field'],
        ['sort_order', 'asc, desc', 'Sort direction'],
        ['view', 'all, parents, children', 'Resource hierarchy view'],
        ['parent_uid', 'Resource UID', 'Parent resource — requires view=children'],
        ['tag', 'Tag key name', 'Filter by tag key presence'],
        ['limit', 'Integer', 'Results per page'],
      ],
    },
    {
      tool: 'get_recommendations',
      note: 'list_recommendation_resources supports the same filters.',
      filters: [
        ['status', 'open, optimised', 'Recommendation status'],
        ['provider', 'aws, gcp, azure', 'Cloud provider'],
        ['category', 'compliance, discount, idle, orphan, rightsizing, schedule', 'Recommendation category'],
        ['resource_type', 'disk, compute, gke-cluster, s3, ecr, ec2, ebs-volume', 'Resource type'],
        ['severity', 'high, medium, low', 'Recommendation severity'],
        ['cloud_account_id', 'Account ID', 'Filter by cloud account'],
        ['search', 'Any string', 'Matches resource name or UID'],
        ['resource_uid', 'Full resource UID', 'Exact match on resource'],
        ['rule_id', 'e.g. RC-022, RC-111', 'Specific recommendation rule'],
        ['sort_by', 'updated_at, generated_at, savings_usd, severity, title', 'Sort field'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'list_schedules',
      filters: [
        ['search', 'Any string', 'Matches schedule name'],
        ['team_id', 'Team UUID', 'Returns team-linked + standalone schedules'],
      ],
    },
    {
      tool: 'list_resource_groups',
      filters: [
        ['search', 'Any string', 'Matches group name'],
        ['team_id', 'Team UUID', 'Returns team-matching + unassigned groups'],
      ],
    },
    {
      tool: 'list_overrides',
      filters: [
        ['group_id', 'Resource group UUID', 'Filter by resource group'],
        ['resource_uid', 'Resource UID', 'Filter by resource'],
        ['status', 'active, expired', 'Override status'],
        ['sort_by', 'created_at, expires_at', 'Sort field'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'list_cloud_accounts',
      filters: [
        ['search', 'Any string', 'Matches account name'],
        ['provider', 'aws, gcp, azure', 'Cloud provider'],
      ],
    },
    {
      tool: 'get_cost_trends',
      filters: [
        ['from', 'YYYY-MM-DD', 'Start date (defaults to the backend range)'],
        ['to', 'YYYY-MM-DD', 'End date (defaults to the backend range)'],
        ['granularity', 'daily, weekly, monthly', 'Time granularity (defaults to daily)'],
      ],
    },
    {
      tool: 'get_cost_by_resources',
      filters: [
        ['resource_uids', 'Comma-separated UIDs', 'Filter to specific resources'],
        ['sort_by', 'cost', 'Sort field'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'list_audit_logs',
      filters: [
        ['method', 'POST, DELETE, PUT, PATCH', 'HTTP method'],
        ['resource', 'Any string', 'Path substring match'],
        ['status', 'HTTP status code', 'Filter by response status'],
        ['user', 'Email address', 'Filter by user email'],
        ['search', 'Any string', 'General search'],
        ['start_date', 'YYYY-MM-DD', 'Start date'],
        ['end_date', 'YYYY-MM-DD', 'End date'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'get_state_history',
      filters: [
        ['state', 'stopped, started, failed', 'Action state'],
        ['resource_uid', 'Resource UID', 'Filter by resource'],
        ['changed_by_type', 'schedule, manual, override, bulk', 'How the action was triggered'],
        ['from', 'YYYY-MM-DD', 'Start date'],
        ['to', 'YYYY-MM-DD', 'End date'],
        ['sort_by', 'changed_at', 'Sort field'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'list_events',
      filters: [
        ['schedule_id', 'Schedule UUID', 'Filter by schedule'],
        ['resource_uid', 'Resource UID', 'Filter by resource'],
        ['sort_order', 'asc, desc', 'Sort direction'],
      ],
    },
    {
      tool: 'list_budgets',
      filters: [
        ['resource_group_id', 'Resource group UUID', 'Filter by resource group'],
        ['resource_uid', 'Resource UID', 'Filter by resource'],
      ],
    },
  ];
  return filterSections.map(({ tool, note, filters }) => (
    <div key={tool}>
      <h3><code>{tool}</code></h3>
      {note && <p><em>{note}</em></p>}
      <table>
        <thead>
          <tr>
            <th>Filter</th>
            <th>Values</th>
            <th>Notes</th>
          </tr>
        </thead>
        <tbody>
          {filters.map(([name, values, desc]) => (
            <tr key={name}>
              <td><code>{name}</code></td>
              <td>{values}</td>
              <td>{desc}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  ));
})()}

### Filter Example

To list only running AWS compute resources sorted by name:

```json
{
  "method": "tools/call",
  "params": {
    "name": "list_resources",
    "arguments": {
      "org_id": "your-org-id",
      "provider": "aws",
      "resource_type": "compute",
      "status": "running",
      "sort_by": "name",
      "sort_order": "asc",
      "page": 1
    }
  }
}
```

## Example Prompts

Once connected, try asking your AI assistant:

| What to ask | What happens |
| --- | --- |
| "List my organisations" | Shows all orgs you have access to |
| "What are my cloud costs this month?" | Shows cost summary and savings |
| "Show me optimization recommendations" | Lists recommendations with savings estimates |
| "How many resources do I have?" | Shows total resource count and summary by provider |
| "What schedules are active?" | Shows start/stop schedules |
| "Show my cloud accounts" | Lists connected AWS/GCP/Azure accounts |
| "Show cost trends over the last month" | Shows daily cost trends |
| "What is the CPU utilization of my EC2 instances?" | Shows CPU/memory metrics |
| "Are my budgets on track?" | Shows budget health summary |
| "Show billing sync status" | Shows if billing data is syncing correctly |
| "What teams exist in my org?" | Lists all teams and members |
| "Show audit logs" | Shows recent activity in the organisation |
| "What actions were executed recently?" | Shows scheduler events and action history |
