Model Context Protocol (MCP) server that exposes Pacemaker pcs commands as safe, guardrailed tools over stdio. It connects to a target cluster node via SSH using your OpenSSH config (Host alias; optional sudo) and runs read-only status queries and controlled operations. Ideal for using Pacemaker safely from MCP-aware clients like Cursor or Claude.
- Pacemaker tools:
pcs_cluster_status,pcs_node_status,pcs_resource_list. - Logs access:
pcs_logs_common,pcs_logs_tail,pcs_logs_journalctlfor Pacemaker/Corosync troubleshooting. - Key-based auth via OpenSSH config: uses your
~/.ssh/configHost alias; optionalsudo. - Configurable: JSON/YAML config file or environment variables for alias and options.
- Node.js >= 18
- Access to a Pacemaker cluster node over SSH
# 1) Install dependencies
npm install
# 2) Build the server (emits dist/index.js)
npm run build
# 3) (Optional) Verify locally with MCP Inspector
npx @modelcontextprotocol/inspector@latest node $(pwd)/dist/index.jsYou can also run directly with Node:
node dist/index.jsUse your OpenSSH config (e.g., ~/.ssh/config) with a Host alias, and reference that alias. This is the only connection method used by the server.
- Set a Host entry in your OpenSSH config file:
Host my-cluster
HostName cluster-node.example.com
User ec2-user
IdentityFile ~/.ssh/id_rsa
Port 22
- If you use a bastion, either define it with ProxyJump or a ProxyCommand (both are supported):
Host my-cluster
HostName 10.1.30.239
User root
IdentityFile ~/.ssh/aws-instance_rsa
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
# Option A (preferred): use a Host alias for the bastion
# ProxyJump bastion
# Option B: ProxyCommand (will be auto-translated)
ProxyCommand ssh -W %h:%p bastion -i ~/.ssh/aws-bastion_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
Host bastion
HostName bastion.example.com
User ec2-user
IdentityFile ~/.ssh/aws-bastion_rsa
- Then either:
- Provide alias per-call as args:
sshConfigHost: "my-cluster"(and optionallysshConfigPathif not~/.ssh/config) - Or put it in your Pacemaker MCP config file (JSON/YAML) via
PACEMAKER_MCP_CONFIG:
- Provide alias per-call as args:
default:
sshConfigHost: my-cluster
# sshConfigPath: /absolute/path/to/ssh_config # optional, defaults to ~/.ssh/config
sudo: trueNotes:
- This server reads connection parameters exclusively from the OpenSSH alias (HostName, User, Port, IdentityFile).
- If your ssh config has
StrictHostKeyChecking nofor the alias, unknown host keys will be accepted unless overridden. - ProxyCommand lines like
ssh -W %h:%p <jump> -i <key> ...are supported; they are treated as a single-hop ProxyJump automatically. - If
IdentityFileis not set, the SSH agent (SSH_AUTH_SOCK) is used if available. - If
Useris not set, your local username is used by default.
Configuration sources (last-wins per field):
- Config file from
PACEMAKER_MCP_CONFIG(or default search paths) - Environment variables (e.g.,
PACEMAKER_SSH_CONFIG_HOST,PACEMAKER_USE_SUDO) - Per-tool arguments (
sshConfigHost,sshConfigPath,sudo)
-
Build so
dist/index.jsexists:npm run build -
Add the server to your global Cursor MCP config (macOS:
~/.cursor/mcp.json). Use absolute paths.
{
"mcpServers": {
"pacemaker-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/pcs_mcp/dist/index.js"],
"env": {
"PACEMAKER_SSH_CONFIG_HOST": "my-cluster",
"PACEMAKER_USE_SUDO": "true"
}
}
}
}Restart Cursor after saving.
-
Build
dist/index.js:npm run build -
Add the server to
~/Library/Application Support/Claude/claude_desktop_config.json(macOS), then restart Claude. Use absolute paths.
{
"mcpServers": {
"pacemaker-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/pcs_mcp/dist/index.js"],
"env": {
"PACEMAKER_MCP_CONFIG": "/absolute/path/to/pacemaker.yaml",
"PACEMAKER_SSH_CONFIG_HOST": "my-cluster",
"PACEMAKER_USE_SUDO": "false",
"PACEMAKER_SSH_READY_TIMEOUT_MS": "30000"
}
}
}
}Notes:
- Prefer absolute paths in
argsand file-based env likePACEMAKER_MCP_CONFIG. - Configure connection via OpenSSH Host alias; set the alias through env or your MCP config file.
- For production, prefer key-based SSH and passwordless
sudoifsudois required.
- Handshake timeout:
- Set
PACEMAKER_SSH_DEBUG=trueand retry; inspect logs for where it stalls (jump vs target vs auth). - Increase
PACEMAKER_SSH_READY_TIMEOUT_MS(e.g.,30000). - Verify your alias works in a terminal:
ssh my-cluster 'echo ok'. - If using a bastion, ensure
ProxyJumpor a correctProxyCommandis defined and keys are accessible. - If host key checks block you in dev/test, set
StrictHostKeyChecking noandUserKnownHostsFile /dev/nullin your SSH config or setPACEMAKER_INSECURE_ACCEPT_UNKNOWN_HOST_KEYS=true.
- Set
pcs_cluster_status: returnspcs cluster statuspcs_node_status: returnspcs status nodespcs_resource_list: returnspcs resource configpcs_logs_common: tail common log files and optionally journal; e.g., last 200 lines of Pacemaker/Corosync logs- args:
{ "lines": 200, "includeJournal": true }
- args:
pcs_logs_tail: tail specific log files- args:
{ "paths": ["/var/log/pacemaker/pacemaker.log", "/var/log/cluster/corosync.log"], "lines": 500 }
- args:
pcs_logs_journalctl: read journal for units (defaults to pacemaker and corosync)- args:
{ "units": ["pacemaker", "corosync"], "lines": 300, "since": "2 hours ago", "priority": "warning", "grep": "fail|error" }
- args:
Each tool accepts a cluster name from config or sshConfigHost/sshConfigPath, and sudo.
- Prefer key-based SSH; avoid passwords when possible.
- Set
PACEMAKER_INSECURE_ACCEPT_UNKNOWN_HOST_KEYS=falsein production. - Only use
sudoif required by your environment.
npm run typecheck
npm run build
# Run from TS directly (dev):
npm run devOpen with MCP Inspector (from dist output):
npx @modelcontextprotocol/inspector@latest node $(pwd)/dist/index.jsSee CONTRIBUTING.md for PR guidelines.
MIT. See LICENSE.