ClusterControl MCP
The ClusterControl MCP Server is a bridge between your AI assistant and your databases. You type what you need in plain English or other languages, and it figures out the rest. No dashboards to click through, no commands to memorize. It works with AI tools like Claude Desktop, Claude Code, and OpenAI Codex, and offers 89 tools, 21 resources and 13 guided procedures, covering backups, cluster management, user accounts, performance monitoring, advisors that report findings, a digest of what changed across your fleet, guided recovery, and an account of what an agent asked to do. It works with MySQL, MariaDB, PostgreSQL, MongoDB, SQL Server, and TimescaleDB.
How It Works
The diagram below shows how everything connects. Your AI tool sits at the top. When you type a request, it goes through the MCP Server, which translates it into the right action and passes it to the CMON Controller. The controller then talks to whichever database cluster you are working with.
Before you get started, make sure you have the following in place:
- A running ClusterControl 2.3.1 or later installation with CMON Controller accessible over the network (CMON controller reachable on port 9501). See Controller Version
- A CMON user account with access to the clusters you want to query
- An MCP-compatible AI client such as Claude Desktop, Claude Code, or OpenAI Codex
Once you have confirmed all the requirements above are in place, you are ready to install and configure the ClusterControl MCP Server. Packages are published to the Severalnines repository alongside other ClusterControl components.
The binary installs to /usr/bin/cmon-mcp. A systemd service unit and default configuration file are also installed:
/etc/systemd/system/cmon-mcp.service: service unit/etc/default/cmon-mcp: environment file for credentials and transport options/etc/cmon-mcp/governed.env: written by a fresh install; turns the audit log on and names the digest key/etc/cmon-mcp/digest.key: the digest key a fresh install generates (mode0600)/usr/share/cmon-mcp/profiles/: the two deployment profiles/usr/share/cmon-mcp/docs: the documentation bundlesearch_docsreads/etc/logrotate.d/cmon-mcp: rotation for the audit log
Packages are built for x86_64 only.
Tool Annotations
Every tool the MCP Server exposes advertises the standard MCP annotations readOnlyHint and destructiveHint alongside its name and description. Of the 89 tools, 60 are pure reads, 26 change state on the CMON Controller, and 3 write files on the local filesystem of the MCP Server host; 22 tools carry destructiveHint: true. The annotations are generated at tool registration from a committed, human-reviewed manifest, so the hints a client sees cannot drift from what was reviewed.
MCP clients can use these annotations to auto-approve read-only tools and to require explicit confirmation before state-changing or destructive ones. Keep in mind that annotations are advisory metadata for the client: the server does not enforce them. On its own, the dry-run preview for write operations is a client-side safeguard too, but the server can enforce it: with the execution guard on, a destructive call runs only against a confirmation minted on a preview of the same call, and the approval tier can require an approval as well. See Confirmation and Approval.
If you need the server itself to refuse an action rather than rely on the client, see Access Control below.
Configuration
The ClusterControl MCP Server is configured through the /etc/default/cmon-mcp file on your CMON host. Open the file and edit the values to match your environment:
Where Settings Come From
Every setting can be given as a command-line flag or as an environment variable. On a server host, the environment comes from two files that systemd loads in order.
| Source | Used when | Notes |
|---|---|---|
Command-line flags, for example -endpoint or -read-only |
An AI client starts the server itself (stdio), for example claude mcp add clustercontrol -- cmon-mcp … |
A flag wins over the same setting in the environment. A password passed as a flag is briefly visible in the process list, so prefer CMON_PASSWORD or, better, CMON_KEY_FILE. |
| Environment variables in the client's configuration | An AI client starts the server itself, for example the env block in claude_desktop_config.json |
The same names as on a server host. |
/etc/default/cmon-mcp |
The cmon-mcp systemd service |
Your file. systemd loads it second, so a setting here wins over the same setting in governed.env. An upgrade does not overwrite it. |
/etc/cmon-mcp/governed.env |
The cmon-mcp systemd service |
Written by a fresh install: it turns the audit log on and names the generated digest key. Loaded first, so /etc/default/cmon-mcp can override it, for example with MCP_AUDIT_LOG=off. |
| A shipped deployment profile | MCP_PROFILE or -profile names one |
Read in place from /usr/share/cmon-mcp/profiles/, beneath every source above: a flag wins, then the environment, then the profile, then the built-in default. |
| Files a setting points to | When you name them | The identity map, the approver map, the digest key, an operator lifecycle table and the documentation bundle. |
The audit log, the execution guard, the approval mode, the approval window and the approver settings have no flag: set them in the environment.
Two Ways to Run It
| On the client (stdio) | As a service (HTTP) | |
|---|---|---|
| Who starts the server | Each AI client starts its own process | systemd, once, on a host that can reach the CMON Controller |
| Where settings live | Flags or the client's env block |
/etc/default/cmon-mcp |
| Turned on by | MCP_BIND_ADDRESS unset |
MCP_BIND_ADDRESS set, for example 127.0.0.1:3000 |
| ClusterControl identity | The one user in its settings | One user, or one per caller with an identity map |
| Needed for | A single user on their own machine | Team access, per-agent identities, approvals attributed to a user, the agentic-operate profile, independent approval |
Configuration Parameters
CMON Controller Connection
| Parameter | Description | Example |
|---|---|---|
CMON_ENDPOINT |
The URL of your CMON Controller API | https://127.0.0.1:9501 |
CMON_USERNAME |
The ClusterControl username | admin |
CMON_PASSWORD |
Password for the user (leave empty if using key auth) | severalNINES999 |
CMON_KEY_FILE |
Path to RSA private key (alternative to password) | /etc/clustercontrol/id_rsa |
You can authenticate using either a password or an RSA key file, not both at the same time. If CMON_PASSWORD is set, leave CMON_KEY_FILE empty, and vice versa.
The CMON user you specify here is the identity the MCP Server uses for every action it performs. We recommend creating a dedicated CMON user with only the privileges your team actually needs and please avoid reusing your main admin account.
MCP Server
| Parameter | Description | Example |
|---|---|---|
MCP_BIND_ADDRESS |
Address and port the MCP Server listens on | 127.0.0.1:3000 |
MCP_BASE_URL |
URL advertised to SSE clients, when they connect through a proxy | https://cc.example.com |
MCP_AUTH_TOKEN |
Bearer token to secure all incoming connections | your-strong-random-token |
MCP_TLS_BIND_ADDRESS |
Address and port of the optional TLS listener (1.0.0-246 or later) | 0.0.0.0:3443 |
MCP_TLS_CERT_FILE |
PEM certificate (chain) for the TLS listener | /etc/cmon-mcp/tls.crt |
MCP_TLS_KEY_FILE |
PEM private key for the TLS listener; not readable by group or others | /etc/cmon-mcp/tls.key |
The listener on MCP_BIND_ADDRESS speaks plain HTTP. Keep it at 127.0.0.1:3000 and let remote AI clients
reach the server through an SSH tunnel, the optional TLS listener, or a TLS reverse proxy, as
Reaching the Server Securely describes. 0.0.0.0:3000 is only for a network
you trust end to end.
The three MCP_TLS_* settings are set together or not at all, and only with MCP_BIND_ADDRESS: the TLS
listener is added beside the plain one, which keeps serving unchanged. Because the TLS listener is meant to
be reached from other hosts, it also needs authentication: MCP_AUTH_TOKEN or an
identity map. The server refuses to start (exit code 78, naming the setting) when
only some are set, when neither a token nor an identity map is configured, when a file is missing or
unreadable, when the certificate and key don't match, when the key file is readable by group or others or
not owned by the service's user, or when MCP_TLS_BIND_ADDRESS overlaps MCP_BIND_ADDRESS or
MCP_APPROVAL_BIND_ADDRESS. TLS 1.2 is the minimum. To rotate the certificate, replace the files and
restart the service.
If you are moving an existing server to 127.0.0.1:3000 and its configuration still sets MCP_BASE_URL to
the host's address, remove that line, unless you set it for a TLS reverse proxy. Otherwise SSE clients are
sent a /message address outside the SSH tunnel and cannot finish connecting. Unset, MCP_BASE_URL
defaults to http:// plus MCP_BIND_ADDRESS, which is the tunnel address.
MCP Transport Endpoints
The MCP Server exposes two transports on the same port:
/mcp is the Streamable HTTP transport, used by Claude Code and OpenAI Codex. /sse and /message are
the older SSE transport, used by Claude Desktop, and are not served when an
identity map is active.
Per-Caller Identity
| Parameter | Description | Example |
|---|---|---|
MCP_IDENTITY_MAP_FILE |
Maps each caller's bearer token to its own ClusterControl user (also -identity-map-file); HTTP only |
/etc/cmon-mcp/identity-map.yaml |
By default every session acts as the one CMON user configured above, so the audit log cannot tell two callers apart. An identity map binds each token to its own ClusterControl user. The file must be mode 0600, and an unmapped token is refused before anything runs; it never falls back to the configured user.
identities:
- token_label: agent-a # names this entry in errors and logs
token_hash: sha256:<64 hex> # or token: <a strong random token>
cmon_username: agent-a # what every audit record names
cmon_password: <password> # or cmon_key_file: /path/to/key
An identity map changes the HTTP endpoints
With a map active, MCP_AUTH_TOKEN is ignored (each token authenticates on its own) and /sse
and /message are not served. Clients that can only use SSE cannot be used with an identity map;
connect them to /mcp instead.
Access Control
These settings are enforced by the server, not by the AI client. Unlike tool annotations and
dry_run previews, a refusal here cannot be overridden by the client.
| Parameter | Description | Example |
|---|---|---|
MCP_READ_ONLY |
Refuses all 29 state-changing tools for the lifetime of the process | 1 |
MCP_TOOL_ALLOW |
Comma-separated list of the only tools to expose | list_clusters,list_alarms |
MCP_TOOL_DENY |
Comma-separated list of tools to refuse; wins over MCP_TOOL_ALLOW |
create_job,restore_backup |
Read-only mode both hides the write tools from the client's tool list and refuses a direct call on one, so a client that calls a tool it was never shown is still refused. Scoping works the same way, and the resource surfaces follow their tools automatically — a surface is served only when every tool it maps to is in scope.
A name that matches no tool stops the server
An unknown entry in either list is a startup error naming every offender, and the service exits non-zero. This is deliberate: a silently ignored deny entry would leave a tool you meant to remove fully exposed.
Prefer MCP_TOOL_ALLOW when the requirement is only these
A deny list names what to remove, so a tool added in a later release is exposed by default. An allow list names what to keep, so a new tool is out of scope until you add it.
Read the startup warnings. Scoping is by tool name, and a tool name is not a data class.
Several tools read the same data from the CMON Controller, so denying one can leave a sibling serving
the same rows. The server names every such case at startup as tool scope: WARNING: …. If you denied
a tool for a security reason, that line tells you whether you actually removed the data.
One case deserves singling out: create_job takes a free-form command, which reaches CMON job
commands that have no tool of their own and that no tool deny list can name — including
drop_cluster, remove_node and failover. Limit them with the job command policy
below. Without one, if create_job is in scope, treat the assistant as able to run any CMON job.
Job Command Policy
| Parameter | Description | Example |
|---|---|---|
MCP_JOB_COMMAND_ALLOW |
Comma-separated CMON job commands that create_job and the dedicated job tools may submit; unset permits every one |
verify_backup,stage_replication_slave |
MCP_JOB_COMMAND_DENY |
Comma-separated job commands to refuse; wins over the allow list | remove_cluster |
A command the policy does not permit is refused with JOB_COMMAND_NOT_PERMITTED before any preview,
confirmation, approval request or controller request, and is checked again just before submission. A
tool that submits one fixed command (delete_backup, restore_backup, generate_error_report,
create_backup_schedule, update_backup_schedule) is hidden when its command is not permitted. A name
that is not a known job command stops the server at startup. The policy is logged at every start as
job command policy: …, and get_session_info reports it.
The policy governs job submission, not effects
set_controller_config, set_node_config, unset_node_config and add_maintenance_window
reach some of the same effects as job commands through other requests. Scope those tools with
MCP_TOOL_ALLOW or MCP_TOOL_DENY as well; the server names each such case at startup.
Independently of the policy, create_job checks a job before anyone is asked to approve it: the
identity needs read and execute access on the cluster, job_data must be one JSON object with unique
keys, and replica rebuilds, upgrades, PostgreSQL promotions and point-in-time recovery are held to the
shapes the release notes describe.
Confirmation and Approval
| Parameter | Description | Example |
|---|---|---|
MCP_EXECUTION_GUARD |
enforced: a destructive call runs only against a single-use confirmation minted on a preview of the same call. Unset or off: off |
enforced |
MCP_HUMAN_APPROVAL |
elicitation: every destructive execution is put to the calling session for approval. out_of_band: a separately authenticated approver decides. Unset or off: off |
elicitation |
MCP_APPROVAL_BIND_ADDRESS |
For out_of_band: the separate loopback address approvers answer on |
127.0.0.1:8089 |
MCP_APPROVER_MAP_FILE |
For out_of_band: the operator-owned approver list, with cmon_username and token_sha256 entries |
/etc/cmon-mcp/approvers.yaml |
MCP_APPROVAL_TTL |
The approval window for both modes, a duration from 1m to 30m. Unset: 2m. Anything else stops the server at startup |
10m |
MCP_GALERA_BOOTSTRAP_REQUIRE_OOB |
true: a Galera bootstrap is refused under any approval mode but out_of_band, even with the guard off (also -galera-bootstrap-require-oob). Unset: false |
true |
With the guard on, a refused call gets CONFIRMATION_REQUIRED; preview it first and present the
confirmation it returns. Confirmations are single-use and held in memory, so a restart revokes every
outstanding one. A confirmation that admitted a call stays valid for as long as the call can wait for
its approval.
MCP_HUMAN_APPROVAL accepts on, enforced, 1 and true as older spellings of elicitation. Any
other value stops the server at startup.
elicitationsends the approval request to the calling session. That is a person's decision only if the client shows the request to a person the agent cannot answer for; so far the official MCP Python SDK with anelicitation_callbackis the only client shown approving. It needs an identity map: without per-session identity a destructive call is refused withHUMAN_APPROVAL_UNAVAILABLE.out_of_bandwaits for an approver whose ClusterControl user is not the requester's. Approver tokens must differ from every caller's, and a request expires when the approval window,MCP_APPROVAL_TTL, runs out. The window is measured from the request, so a late decision gets only what is left. It requires the identity map, the digest key and the audit log, and the server does not start without them. Approvers decide withcmon-mcp approve.
Point-in-time recovery always needs an approval model: with MCP_HUMAN_APPROVAL off it is refused
with PITR_APPROVAL_MODEL_REQUIRED. Either mode proves who answered, not that a person read the
request.
A Galera bootstrap, which sop_galera_recovery submits, also needs an approval model, and its approval
asks the approver to confirm, for each node by name, that mysqld is shut down on that host. Under
elicitation the requester's own client answers those confirmations, so the server cannot tell whether
a person checked any host. Set MCP_GALERA_BOOTSTRAP_REQUIRE_OOB=true to require out_of_band for it.
Governed mode needs a digest key
Turning on the audit log, the execution guard or the approval tier puts the server in a governed
mode, and a governed server needs the digest key. A fresh install creates
/etc/cmon-mcp/digest.key; on a host that was upgraded rather than installed, write a key and set
MCP_DIGEST_HMAC_KEY_FILE before you restart.
Approving from the Command Line
Under out_of_band, an approver decides with the cmon-mcp binary itself:
cmon-mcp approve list # one row per request pending for you
cmon-mcp approve show <id> # that request's whole envelope
cmon-mcp approve approve <id> # approve it
cmon-mcp approve decline <id> # decline it
list shows each request with an 8-character id, and the other commands take 4 or more characters of
it. Read a request with show before you decide: list shows only the first line of its summary, and
the lines the server adds, such as the primary a promotion stops and the standby it promotes, appear
only in show. A Galera bootstrap's approval takes --confirm-shutdown <host:port> once for each node
the request names; give each one only after checking that host yourself.
The command reads three settings and nothing else, and opens no listener:
| Parameter | Description |
|---|---|
MCP_APPROVAL_BIND_ADDRESS |
The server's loopback approval listener, for example 127.0.0.1:8089 |
MCP_APPROVER_TOKEN_FILE |
A file holding the approver's token: a regular file owned by the user running the command, mode 0600 |
MCP_APPROVER_TOKEN |
The token itself. Set this or the file, not both; there is no token flag, so the token never appears in a command line |
Give each approver their own token, never one an MCP caller holds, and add its SHA-256 to the approver map. Restart the server after adding an entry:
umask 077
openssl rand -hex 32 > "$HOME/.cmon-mcp-approver.token"
printf %s "$(cat "$HOME/.cmon-mcp-approver.token")" | sha256sum # the token_sha256 for the map
version: 1
approvers:
- cmon_username: bob # the approver's ClusterControl username
token_sha256: <the 64 hex characters printed above>
From another machine, keep the listener on loopback and forward a port to it over SSH:
ssh -N -L 127.0.0.1:18089:127.0.0.1:8089 operator@mcp-host &
MCP_APPROVAL_BIND_ADDRESS=127.0.0.1:18089 \
MCP_APPROVER_TOKEN_FILE="$HOME/.cmon-mcp-approver.token" cmon-mcp approve list
A decision records the approval; it does not run the job. The server still checks the call again, and spends the approval once, before anything reaches the controller.
Client Timeouts
Under out_of_band, the agent's tool call stays open until a decision arrives or MCP_APPROVAL_TTL
runs out, and it sends no progress messages while it waits. If the client gives up first, the call is
cancelled, the request is withdrawn, and a later approve is refused. So the client's own timeout for
this server must be longer than the approval window:
- Claude Code times out a request to an HTTP or SSE server after 60 seconds by default. Set the
server's
timeoutin.mcp.json, in milliseconds, aboveMCP_APPROVAL_TTL, orMCP_TOOL_TIMEOUTfor every server. A server's owntimeoutoverridesMCP_TOOL_TIMEOUT. The Claude Code plugin setstimeoutto 1860000, above the longest window the server accepts (30 minutes), so it needs no tuning. - Codex CLI: set
tool_timeout_secin[mcp_servers.<name>]aboveMCP_APPROVAL_TTL; the default is 60. The Codex plugin sets 1860. - Other clients often default to 60 seconds per request. Where the timeout cannot be raised, keep
MCP_APPROVAL_TTLwithin it and have approvers ready before the call is made. - A reverse proxy in front of
/mcpneeds a read timeout aboveMCP_APPROVAL_TTLtoo.
Auditing
| Parameter | Description | Example |
|---|---|---|
MCP_AUDIT_LOG |
Path to the JSONL audit log; off disables auditing |
/var/log/cmon-mcp/audit.jsonl |
MCP_DIGEST_HMAC_KEY_FILE |
Key file the argument digests are computed under | /etc/cmon-mcp/digest.key |
Every tool call and resource read is recorded twice — an intent record before the action runs
and a completion record after, correlated by call_id. Background tasks add a third, acceptance,
correlated by task_id. Two records rather than one, because a single record written at the end
cannot be told apart from an action that never happened, and the actions that matter most are the
ones that did not return.
Raw arguments are never written. What is recorded is a keyed digest of them, so that someone who can read the log cannot work backwards to a low-entropy argument such as a password.
A fresh install is audited by default; an upgrade changes nothing
A first install provisions /etc/cmon-mcp/digest.key and /etc/cmon-mcp/governed.env, and that
second file switches auditing on — so a newly installed host records from its first call. An
upgrade enables nothing, because a host that has never been audited has no key, and a server
that is told to audit without one refuses to start. To turn auditing off on a fresh install, set
MCP_AUDIT_LOG=off in /etc/default/cmon-mcp, which is read after the package's file and wins.
Auditing is fail-closed
If the intent record cannot be written, the call is refused with AUDIT_UNAVAILABLE and the
action never runs. This is the intended behaviour: an unauditable server does not act.
It also means the log's permissions matter. The server refuses any log file whose mode is wider
than 0600, on every record, so a rotation policy that recreated it world-readable would refuse
every later call. The shipped /etc/logrotate.d/cmon-mcp already handles this correctly — do not
loosen its create 0600 root root line.
To find failed actions you need both fields, because they mean different things:
outcome covers transport and task-level failure; tool_is_error covers a tool that ran and
returned an error. The absence of tool_is_error is not evidence that the tool succeeded.
Agent Activity
| Parameter | Description | Example |
|---|---|---|
MCP_ACTIVITY_OPERATOR_IDENTITIES |
Comma-separated ClusterControl users who may summarize another identity's activity, or everyone's (also -activity-operator-identities). Unset: nobody |
alice,bob |
summarize_agent_activity answers "what did the agent ask to do?" from the audit log above, with no
controller request. Given an interval, it counts unique calls, reads, previews and execution requests,
their outcomes, the refusals recorded, and the independent approval decisions. An execution request
includes refused attempts and does not show that the controller changed anything.
- Each identity sees only its own records. Only the users named in
MCP_ACTIVITY_OPERATOR_IDENTITIEScan ask for another identity orall, and no ClusterControl admin role implies it. Name them exactly; wildcards, duplicates and surrounding spaces stop the server at startup. - It never decodes arguments, digests, keys or tokens, and it reads only the configured log, never a
path a caller supplies. With auditing off it answers
ACTIVITY_UNAVAILABLE. - It reads the first 16 MiB or 100,000 records of the log, and always marks the summary partial: nothing proves that earlier rotations were kept, so its counts are never claimed as a complete day.
Neither deployment profile allows summarize_agent_activity; add it to your allow list deliberately.
File Exports
| Parameter | Description | Example |
|---|---|---|
MCP_EXPORT_DIR |
The one directory local file writes are confined to; off refuses them all |
/srv/cc-exports |
Three tools write to the MCP Server host's own filesystem: download_error_report,
export_cluster_log and unpack_error_report. Each takes a destination path from the client, and
the service runs as root, so without a boundary, granting an assistant export_cluster_log alone is
not meaningfully different from granting it root write access to the host. Every path the client
supplies is resolved inside this directory and may not leave it.
The default is /var/lib/cmon-mcp/exports, created at mode 0700 on start.
This can change behaviour on an existing host
The boundary is on by default, so a server whose configuration you never touched will begin
refusing paths such as output_path=/tmp/report.tar.gz that previously worked. Point
MCP_EXPORT_DIR at the directory those callers already use to keep their existing paths valid.
The boundary is only as strong as the directory's own permissions. If it is a symlink, group- or
world-writable, or owned by another user, the server says so at startup as export dir: WARNING: …
rather than refusing — a link onto a larger volume is a legitimate thing to want.
Documentation Search
| Parameter | Description | Example |
|---|---|---|
MCP_DOCS_DIR |
Installed ClusterControl documentation bundle used by search_docs |
/usr/share/cmon-mcp/docs |
The package ships a documentation bundle and the search_docs tool answers from it locally, with no
network calls, so it works in an air-gapped installation. The bundle is read once at startup; if it
cannot be loaded, only search_docs is disabled and the reason is written to the journal, leaving
every other tool working. Restart the service after repairing or upgrading it.
Advisors
| Parameter | Description | Example |
|---|---|---|
MCP_SUPPORT_ENTITLEMENT |
Your support entitlement, which check_version_eol judges end of life against: mysql= premier, extended or sustaining; mariadb= community, enterprise or extended; and cluster:<id>= overrides. Unset: each engine's free community stream |
mysql=extended |
MCP_EOL_TABLE_FILE |
Your own release lifecycle table instead of the bundled one, in the same format, for example to get newer end-of-life dates on an air-gapped host between packages | /etc/cmon-mcp/eol.yaml |
The bundled lifecycle table is never fetched from the network. A table you name that cannot be read or parsed stops the server at startup, and the refusal never quotes a value from the file.
Complete Configuration Example
The following shows the ClusterControl MCP Server example configuration after it has been set up through the /etc/default/cmon-mcp file on your CMON host.
# CMON Controller connection
CMON_ENDPOINT=https://127.0.0.1:9501
CMON_USERNAME=admin
CMON_PASSWORD='severalNINES999'
# RSA key auth alternative (leave CMON_PASSWORD empty if using this):
# CMON_KEY_FILE=/etc/clustercontrol/id_rsa
# MCP Server: loopback only; clients connect through an SSH tunnel or a TLS reverse proxy
MCP_BIND_ADDRESS=127.0.0.1:3000
MCP_AUTH_TOKEN=
# Only for SSE clients (Claude Desktop) behind a TLS reverse proxy:
# MCP_BASE_URL=https://cc.example.com
# Optional TLS listener beside the plain one (all three or none; key mode 0600;
# needs MCP_AUTH_TOKEN or an identity map):
# MCP_TLS_BIND_ADDRESS=0.0.0.0:3443
# MCP_TLS_CERT_FILE=/etc/cmon-mcp/tls.crt
# MCP_TLS_KEY_FILE=/etc/cmon-mcp/tls.key
# Or start an agent from a shipped deployment profile, read in place:
# MCP_PROFILE=agentic-observe
# Access control — enforced by the server, not the AI client.
# Start here if you are connecting an assistant to a production controller.
MCP_READ_ONLY=1
# MCP_TOOL_ALLOW=list_clusters,get_cluster,list_alarms,search_docs
# MCP_TOOL_DENY=create_job
# Auditing — set automatically on a fresh install; shown here for reference.
# MCP_AUDIT_LOG=/var/log/cmon-mcp/audit.jsonl
# MCP_DIGEST_HMAC_KEY_FILE=/etc/cmon-mcp/digest.key
# File exports — defaults to /var/lib/cmon-mcp/exports. Set this if callers
# already rely on a different directory.
# MCP_EXPORT_DIR=/srv/cc-exports
# Per-caller identity (HTTP only) — one ClusterControl user per token.
# MCP_IDENTITY_MAP_FILE=/etc/cmon-mcp/identity-map.yaml
# Job command policy — the job commands create_job may submit.
# MCP_JOB_COMMAND_ALLOW=verify_backup,stage_replication_slave
# Confirmation and approval for destructive calls.
# MCP_EXECUTION_GUARD=enforced
# MCP_HUMAN_APPROVAL=elicitation
# Or independent approval:
# MCP_HUMAN_APPROVAL=out_of_band
# MCP_APPROVAL_BIND_ADDRESS=127.0.0.1:8089
# MCP_APPROVER_MAP_FILE=/etc/cmon-mcp/approvers.yaml
# The approval window, 1m to 30m (default 2m); raise client timeouts above it.
# MCP_APPROVAL_TTL=10m
Start read-only
MCP_READ_ONLY=1 is the setting to reach for first when pointing an assistant at a production
controller. All 60 read tools keep working, so the assistant can still investigate, explain and
report — it simply cannot change anything. Turn it off deliberately, once you know which write
actions you actually want to allow.
Generating a Secure Token
Protect your MCP Server with a bearer token whenever AI clients connect to it from another machine. The Claude Code and Codex plugins require one. Generate one with:
Copy the output and set it as the value of MCP_AUTH_TOKEN.
Starting the Service
Save the configuration file, then restart the service:
Confirm it started successfully:
You should see output confirming the MCP Server is running and listening on the address defined in MCP_BIND_ADDRESS.
A malformed value in any setting stops the server at startup with exit code 78 and names the setting,
and systemd does not keep restarting it, so nothing runs half-configured. Each setting logs its
effective value at startup, so the journal shows what took effect. From a client, get_session_info
shows the session's transport, its ClusterControl identity, the job command policy, the deployment
profile the server started from, the controller's version, and whether an approval request can reach
the client.
Controller Version
Every tool works against ClusterControl 2.3.1 and later. The server reads the controller's version at
startup with one unauthenticated ping, which needs no login, and again every 15 minutes, or every
minute while the version is unknown. The check takes at most 5 seconds and never delays or stops
startup. The journal gets one line when the version or its status changes:
controller version 2.5.0.25177: at or above the floor 2.3.1
controller version 2.3.0.13840 is BELOW the floor 2.3.1: these tools will fail: list_publications (needs 2.3.1: /v2/logical_replication getpublications), list_subscriptions (needs 2.3.1: /v2/logical_replication getsubscriptions)
controller version unknown (<reason>): the floor 2.3.1 was not checked
A controller below the floor is a warning, never a refusal: the server starts, and a tool the
controller cannot serve fails when it is called. get_session_info reports the last answer as
controller_version.
Common Setups
| You want | Configure |
|---|---|
| One person on their own machine | stdio: the connection settings in the client's configuration. Add MCP_READ_ONLY=1 if it should only read. |
| A team sharing one server | The service with MCP_BIND_ADDRESS and an identity map, so each person acts as their own ClusterControl user. |
| An agent that only diagnoses and advises | MCP_PROFILE=agentic-observe, the read-only deployment profile. |
| An agent that fixes what a finding names, under approval | MCP_PROFILE=agentic-operate, and MCP_HUMAN_APPROVAL=out_of_band with cmon-mcp approve if a second person must approve. |
| An account of what an agent asked to do | Auditing on (the default on a fresh install), summarize_agent_activity in scope, and MCP_ACTIVITY_OPERATOR_IDENTITIES for whoever reviews other identities. |
| A host without internet access | Nothing extra: the lifecycle table and documentation bundle ship in the package. Use MCP_EOL_TABLE_FILE for newer end-of-life dates. |
Deployment Profiles
The package ships two ready-made configurations for running the server under an AI agent. Each is a short list of settings that switches on the controls an agent should run with and limits it to the tools its job needs. Start from one rather than writing the configuration yourself.
What a Profile Is
- Two files in
/usr/share/cmon-mcp/profiles/:agentic-observe.envandagentic-operate.env. The package owns them: they are readable by everyone (0644), hold no credentials, and are replaced on every upgrade. - Name one with
MCP_PROFILE=agentic-observeorMCP_PROFILE=agentic-operatein/etc/default/cmon-mcp(or-profile), and the server reads it in place at every start. An upgrade then brings the release's tool list and job commands with no edit. Your own settings win over the profile's: a flag, then the environment, then the profile, then the built-in default. A key you set wins even when you set it empty, soMCP_READ_ONLY=turnsagentic-observe's read-only off. - Or copy one: copy its assignments into
/etc/default/cmon-mcpand leaveMCP_PROFILEunset. The copy is yours, and an upgrade never changes it. - Any other
MCP_PROFILEvalue, a path or file name included, stops the server at startup. Do not include a profile file through a systemd drop-in withEnvironmentFile=: it would be read after/etc/default/cmon-mcpand silently override what you later change there.
Which One to Use
| Profile | The agent can | The agent cannot |
|---|---|---|
agentic-observe |
Run every advisor, the fleet view and the fleet digest, follow a finding's next action to the read it names, and make every read a guided procedure needs | Change anything: every write tool is hidden and refused with READ_ONLY_MODE, so a procedure stops at its submit step |
agentic-operate |
Everything agentic-observe can, plus run a guided procedure to its submit step and submit the job through create_job or restore_backup, after a confirmation and an approval |
Submit a job command its procedures do not use, such as remove_cluster, or use any other write tool |
Use agentic-observe for an assistant that diagnoses and advises, with nothing to approve. Use
agentic-operate for one that can fix what a finding names, under approval.
What Each Profile Sets
| Setting | agentic-observe |
agentic-operate |
|---|---|---|
MCP_READ_ONLY |
1 |
unset |
MCP_AUDIT_LOG |
/var/log/cmon-mcp/audit.jsonl |
/var/log/cmon-mcp/audit.jsonl |
MCP_EXECUTION_GUARD |
unset (nothing to confirm) | enforced |
MCP_HUMAN_APPROVAL |
unset (nothing to approve) | elicitation |
MCP_EXPORT_DIR |
unset | off |
MCP_BIND_ADDRESS, MCP_IDENTITY_MAP_FILE |
not needed | required; the file carries them as commented placeholders |
MCP_TOOL_ALLOW |
28 tools: every advisor, get_fleet_posture, get_fleet_digest, get_session_info, and the read tools that findings and procedures name |
the same 28 plus create_job and restore_backup |
MCP_JOB_COMMAND_ALLOW |
unset (create_job is refused anyway) |
bootstrap_cluster, find_most_advanced, promote_replication_slave, restore_backup, stage_replication_slave, upgrade_cluster, verify_backup |
Before You Start
- Both profiles turn the audit log on, which requires the digest key. A fresh install provisions one;
on an upgraded host, write a key and set
MCP_DIGEST_HMAC_KEY_FILEbefore you restart. agentic-operateneeds the HTTP transport and an identity map. Without them the server starts but refuses everycreate_jobandrestore_backupexecution withHUMAN_APPROVAL_UNAVAILABLE(adry_runpreview still works), and says so at startup.- Bring an approver client: under
elicitationthe approval request goes to the agent's own session. For an approval from a separate person, useout_of_band(see Confirmation and Approval). - Moving an agent from
agentic-observetoagentic-operate: on a host that names the profile, changeMCP_PROFILE. On a host that copied it, remove the copiedMCP_READ_ONLYline too, or everycreate_jobandrestore_backupis refused withREAD_ONLY_MODE.
Setting It Up
- In
/etc/default/cmon-mcp, setMCP_PROFILE=agentic-observeorMCP_PROFILE=agentic-operate, or copy the profile's assignments there instead. Foragentic-operate, keep theMCP_BIND_ADDRESSline already in that file and setMCP_IDENTITY_MAP_FILEto your identity map. - Restart:
systemctl restart cmon-mcp. - Read the startup lines:
journalctl -u cmon-mcp. A named profile logsprofile: <name> (shipped with <version>)and, only when you set one of its keys to another value,profile overrides:naming those keys, never their values. Thejob command policy:line lists the job commands allowed, and each session'sget_session_inforeports the profile and the same list.
Each profile prints some WARNING: lines at every start, and they are expected. The tool scope:
lines name data that an allowed tool still reads for a tool the profile leaves out. Under
agentic-observe, the sop prompts: line says no approval is enforced, which is true and harmless:
nothing can be submitted there. A warning of any other kind is the one to read.
What You Can Change
Set the key in /etc/default/cmon-mcp, where it wins over a named profile, or change your copy there.
Never edit the profile file: the next upgrade replaces it.
| Change | Effect |
|---|---|
Add a tool to MCP_TOOL_ALLOW |
The agent can call it. A non-destructive write tool you add is gated by neither the execution guard nor the approval tier, so prefer read tools. |
| Remove a tool | The agent can no longer call it. Other allowed tools may still read the same data; the tool scope: warnings at startup name them. |
Add a job command to MCP_JOB_COMMAND_ALLOW |
The agent can submit it through create_job, after its own preview and an approval. |
| Have a separate person approve | Set MCP_HUMAN_APPROVAL=out_of_band with MCP_APPROVAL_BIND_ADDRESS and MCP_APPROVER_MAP_FILE. |
Switch the guard or the approval off under agentic-operate |
Not recommended: destructive calls then run without a confirmation or an approval, and point-in-time recovery is refused until an approval model is set. |
After an Upgrade
A host that names its profile with MCP_PROFILE gets the new release's lists at the restart that
follows the upgrade, and a key you set yourself still wins.
A copy in /etc/default/cmon-mcp is never overwritten. Compare it with the new profile files after
each upgrade: a tool or job command that a release adds stays refused under your copy until you add
it, or until you replace the copy with MCP_PROFILE. In 1.0.0-213 both profiles add advise_workload
and list_available_upgrades, and agentic-operate adds the restore_backup tool and job command. In
1.0.0-241 both profiles add advise_galera_health, get_fleet_digest and list_alarms, and
agentic-operate adds the bootstrap_cluster and find_most_advanced job commands.
diff <(grep -E '^MCP_' /usr/share/cmon-mcp/profiles/agentic-operate.env | sort) \
<(grep -E '^MCP_' /etc/default/cmon-mcp | sort)
Limits
- A profile limits tools, not data. Under
agentic-observe,advise_workloadreads the database user and database names of long-running and idle-in-transaction sessions, and the database names and normalised statement patterns of busy or full-scanning queries.get_fleet_postureandget_fleet_digestrun the same advisor inside themselves, and leavingadvise_workloadout does not filter their findings. If an observing agent should not see this data, set your ownMCP_TOOL_ALLOWwithout all three. - A profile governs calls made through the MCP Server only; the ClusterControl GUI, the s9s CLI and other clients are not affected.
Connecting to AI Clients
Once the MCP Server is running, you need to register it with your AI client. For Claude Code and Codex, install the Severalnines plugin; for other clients, add the server to the client's configuration by hand.
Reaching the Server Securely
The listener on MCP_BIND_ADDRESS speaks plain HTTP, so a bearer token sent to it over a network crosses
that network unencrypted. Keep it listening on 127.0.0.1:3000 and reach the server in one of three ways:
-
SSH tunnel. On the machine that runs your AI client, open a tunnel to the CMON host and keep it open while you work:
The client then connects to
http://127.0.0.1:3000, and the token travels inside SSH. The examples on this page use this address. -
Native TLS listener (1.0.0-246 or later). Set
MCP_TLS_BIND_ADDRESS,MCP_TLS_CERT_FILEandMCP_TLS_KEY_FILE(see MCP Server), together withMCP_AUTH_TOKENor an identity map, which the TLS listener requires. The server then also serves/mcpover TLS on that address, with the same authentication, and/ssetoo unless an identity map is active (SSE is never served with a map), while the plain listener keeps serving SSH-tunnel clients. Clients connect tohttps://your-cc-host:3443/mcp. A client must trust the certificate: for a private CA, start Claude Code withNODE_EXTRA_CA_CERTS=/path/to/ca.pem, and add the CA to the operating system's trust store for other clients. -
TLS reverse proxy. Put a proxy with a certificate, such as nginx, on the CMON host in front of
127.0.0.1:3000, and connect to itshttps://address instead. Turn response buffering off for/mcpand/sse, and set the proxy's read timeout aboveMCP_APPROVAL_TTL. For Claude Desktop, also setMCP_BASE_URLto the proxy's address.
Claude Code and Codex Plugins
Severalnines publishes plugins for Claude Code and Codex in the
severalnines/ai-plugins marketplace. They connect to the MCP
Server running on the CMON host over the /mcp endpoint, so nothing is installed on your machine, and
they set a client timeout longer than the longest approval window the server allows
(see Client Timeouts).
The plugins do not switch approvals on. Approvals are configured on the server, and they need an
identity map, so each person connects with their own token as their own
ClusterControl user, plus the settings in Confirmation and Approval. With only
the shared MCP_AUTH_TOKEN, the server refuses destructive calls when approvals are on.
In Claude Code, add the marketplace and install the plugin:
Claude Code asks for the MCP URL and the bearer token. The URL defaults to the SSH tunnel address,
http://127.0.0.1:3000/mcp; with the native TLS listener or a TLS reverse proxy, enter its https://
address ending in /mcp.
The token is kept in your system's credential store, not in a settings file. To change either later,
run /plugin configure clustercontrol-mcp@severalnines.
Run /mcp to check that plugin:clustercontrol-mcp:clustercontrol is connected.
Put the bearer token in your environment, for example in your shell profile, then add the marketplace and install the plugin:
export CLUSTERCONTROL_MCP_TOKEN=your-auth-token
codex plugin marketplace add severalnines/ai-plugins
codex plugin add clustercontrol-mcp@severalnines
A Codex plugin cannot ask for a URL, so this one connects to the SSH tunnel address,
http://127.0.0.1:3000/mcp. With the native TLS listener or a TLS reverse proxy, define the server in ~/.codex/config.toml
as in OpenAI Codex below; an entry named clustercontrol replaces the plugin's.
Claude Desktop
Open your Claude Desktop configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add the following entry under mcpServers so that it looks like this :
{
"mcpServers": {
"clustercontrol": {
"type": "sse",
"url": "http://127.0.0.1:3000/sse",
"headers": {
"Authorization": "Bearer your-auth-token"
}
}
}
}
Alternatively, you can use mcp-remote with Node.js installed on your environment with the following JSON configuration:
{
"mcpServers": {
"clustercontrol": {
"command": "npx",
"args": [
"mcp-remote",
"http://127.0.0.1:3000/sse",
"--allow-http",
"--header",
"Authorization: Bearer your-auth-token"
]
}
}
}
With an identity map
When an identity map is active, /sse and /message are not served. Point
the mcp-remote form at http://127.0.0.1:3000/mcp with the agent's own token instead.
Save the file and restart Claude Desktop. You should see clustercontrol appear in the File → Settings → developer → Local MCP servers panel like this after restart Claude Desktop.
Claude Code
Use the Claude Code plugin. To connect to the MCP Server by hand instead,
add it to a project's .mcp.json, using the streamable HTTP endpoint:
{
"mcpServers": {
"clustercontrol": {
"type": "http",
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"Authorization": "Bearer ${CLUSTERCONTROL_MCP_TOKEN}"
},
"timeout": 1860000
}
}
}
Claude Code expands ${CLUSTERCONTROL_MCP_TOKEN} from your environment, so the token is not written to the
file. With an identity map, use the agent's own token. timeout is in milliseconds
and must exceed MCP_APPROVAL_TTL under out_of_band approval; see Client Timeouts.
To run the MCP Server on your own machine instead (stdio, with cmon-mcp installed there), let Claude
Code start it. Pass the credentials in the environment rather than as flags, which are briefly visible
in the process list:
claude mcp add \
--env CMON_ENDPOINT=https://your-cc-host:9501 \
--env CMON_USERNAME=admin \
--env CMON_KEY_FILE=/path/to/id_rsa \
clustercontrol -- cmon-mcp
To verify it was added:
OpenAI Codex
Use the Codex plugin. To connect to the MCP Server by hand instead, or to
point the plugin at a TLS reverse proxy, add the server to ~/.codex/config.toml, using the /mcp
endpoint:
[mcp_servers.clustercontrol]
url = "http://127.0.0.1:3000/mcp"
bearer_token_env_var = "CLUSTERCONTROL_MCP_TOKEN"
tool_timeout_sec = 1860
Codex reads the token from the CLUSTERCONTROL_MCP_TOKEN environment variable. An entry named
clustercontrol replaces the plugin's definition as a whole, so give every setting, the timeout included.
Approving tool calls
Whether Codex asks you before it calls a tool is a Codex setting. If you let it call tools without
asking, use MCP_READ_ONLY=1 or the agentic-observe profile, or switch on the server's
execution guard and approval tier so that destructive calls cannot
run unconfirmed.
Verifying the Connection
Once connected, ask your AI assistant the following to confirm everything is working:
If the setup is correct, it will return the list of clusters managed by your ClusterControl instance.


