@selfhost.dev/mcp-server
v0.11.1
Published
MCP server for SelfHost.dev - provision managed databases, deploy apps (PaaS) and 36 one-prompt app templates via natural language
Maintainers
Keywords
Readme
SelfHost MCP Server
SelfHost is the managed home for your whole backend, databases and apps, on servers you control and run for you. This Model Context Protocol server puts all of it one sentence away inside your AI coding assistant: spin up production databases, ship apps straight from GitHub, wire up domains and environment variables, and watch builds stream live, all in plain English without ever leaving your editor.
The power of your own server. The simplicity of a PaaS. Driven by AI.
One platform, two capabilities:
- Projects - your apps, shipped. Connect a GitHub repo, click deploy, and it's live: auto-detected builds (Nixpacks, Dockerfile, Docker Compose, or static), auto-deploy on every push, live streaming build logs, environment variables, custom domains, pause and resume to scale to zero, and databases right inside the project. Pick which of six locations across three continents the server runs in, add any of 36 one-prompt app templates (Supabase, n8n, WordPress, GitLab, Immich, JupyterLab, WireGuard and more) next to your own code, and turn on free daily server backups. The ease of Railway, Render, and Vercel, with a server you actually control.
- Managed Databases - production-grade database hosting, your way. Managed PostgreSQL, MySQL, Redis, ClickHouse, and OpenSearch on AWS, in our cloud or your own AWS account (BYOC), with an eleven-entry PostgreSQL extension catalog (pgvector, TimescaleDB, PostGIS, pg_cron and more). Automated backups, snapshots, replicas, and metrics across engines; PITR, client TLS and parameter tuning on PostgreSQL; parameter tuning on MySQL; connection pooling on PostgreSQL, MySQL, and ClickHouse; offsite data backups and zero-downtime cluster tier scaling on ClickHouse; built-in k-NN vector search and an opt-in Dashboards UI on OpenSearch. Read engine logs and sample the live query workload without SSH.
One account, one bill, one AI control plane. Pay only for what you run: prepaid credits from $5, no plans or tiers.
Works with Claude Code, Cursor, Windsurf, Cline, and any MCP-compatible client.
Quick Start
Prerequisites
- A SelfHost.dev account
- Node.js 18+ or Bun
Install
Claude Code:
# Using npm (Node.js)
claude mcp add selfhost -- npx @selfhost.dev/mcp-server
# Using Bun
claude mcp add selfhost -- bunx @selfhost.dev/mcp-serverCursor / Windsurf / Other MCP clients:
Add to your MCP configuration file:
{
"mcpServers": {
"selfhost": {
"command": "npx",
"args": ["@selfhost.dev/mcp-server"]
}
}
}Or with Bun:
{
"mcpServers": {
"selfhost": {
"command": "bunx",
"args": ["@selfhost.dev/mcp-server"]
}
}
}That's it. The server handles authentication, token refresh, and everything else automatically.
First Run
On first use, the MCP server opens your browser to sign in via SelfHost's console. After you authorize, credentials are saved locally (~/.selfhost/credentials.json) and auto-refreshed - you won't need to sign in again.
You: "Show me my databases"
Claude: Authenticates → fetches your orgs → selects org → lists instances
→ Shows you a summary of all your running databasesWhat Can You Do?
Talk to the whole platform in plain English.
Ship and run apps with Projects
Connect a repo, deploy, and it's live - SelfHost takes your code from GitHub to a running app.
| You say | What happens |
|---------|-------------|
| "Create a project and deploy my GitHub repo into it" | Provisions the project, waits until it's ready, auto-detects the build, and deploys the repo |
| "Deploy the main branch of my-org/api" | Creates the deployment, triggers the build, and streams live logs until it's running |
| "Turn on auto-deploy so every push ships" | Enables auto-deploy; future pushes to the branch redeploy automatically |
| "Put my project's server in Singapore" | Shows what each family costs there, with live stock, and provisions it where you picked. A server cannot be moved later, so this is asked up front |
| "Add a private Postgres database to my project" | Provisions a Postgres database inside the project, with the credentials and visibility you choose |
| "Spin up Supabase in this project" | Deploys the Supabase template alongside your app and hands you the Studio credentials once it is up |
| "I want n8n for my automations" | Deploys n8n (or n8n backed by Postgres, if it is more than light use) and warns you the first person to open the URL claims the owner account |
| "Give me a CRM" | Deploys Twenty, and tells you it takes up to 15 minutes rather than leaving you wondering if it hung |
| "Turn on backups for this project" | Enables daily server backups, seven kept. Free - there is no backup charge for projects |
| "Why did my project go down last night?" | Reads the merged feed of Hetzner server actions and platform events, so "we stopped it for billing" is distinguishable from "the machine was rebuilt" |
| "Roll this project's server back to yesterday's backup" | Lists the backups, tells you exactly what you lose, and rebuilds the disk once you confirm |
| "Set DATABASE_URL on my app and redeploy" | Updates the env var (without wiping the others) and rolls out the change |
| "Point app.example.com at my deployment" | Attaches the custom domain and returns the exact DNS records to add, then verifies them |
| "Show me the build logs for the last deploy" | Streams the build and container logs for that run |
| "Pause my staging app so I stop paying for it" | Pauses the deployment (scales to zero); resume instantly when you need it |
| "Roll back to the last working deploy" | Re-deploys the commit from the previous successful run |
| "Notify my team channel when a deploy fails" | Binds a notification channel to the app's deploy outcomes |
| "Stop this build, I pushed the wrong thing" | Aborts the run in flight, and confirms nothing was rolled back - what is deployed keeps serving |
| "Is my app actually up?" | Reads last_deploy_status, which catches the case a run says active but the container has since crashed |
| "Give me a GitLab / Nextcloud / Metabase / Odoo instance" | Any of 36 one-prompt app templates, with the right credential shape for each and a real boot-time estimate |
| "I want a CRM" | Offers Twenty, EspoCRM or Krayin, asks for the credentials that app actually accepts, and warns when the first visitor to the URL claims the owner account |
| "Why is my n8n container crash-looping?" | Captures the container's own stdout/stderr from the host - where a failed migration or bad env var actually shows |
| "Why is my project server full?" | Reads live per-container CPU and memory, so you learn which service is eating the box rather than guessing |
| "Give me a shell on my project server" | Installs your public key and hands you ssh shell@<ip> - a non-root user, and it says so rather than promising root |
| "Only let me SSH in from the office" | Restricts port 22 to the CIDRs you name, and warns that the platform sees the MCP's address, not your laptop's |
| "Snapshot this project before I upgrade it" | Takes an on-demand whole-VM image you keep until you delete it - the real undo a project has |
| "Point crm.example.com at my EspoCRM" | Checks DNS resolves to the project first (so the certificate request cannot fail), then attaches the domain |
Manage database infrastructure with Managed Databases
Production-grade, standalone database hosting.
| You say | What happens |
|---------|-------------|
| "Spin up a Postgres 18 instance in us-east-1" | Walks you through config, shows cost estimate, provisions it |
| "Spin up a Redis 7.2 instance for caching in us-east-1" | Provisions a managed Redis instance with a cost estimate (backups, replicas, and metrics; no SQL tuning or pooling) |
| "Stop my staging database" | Finds the instance, confirms with you, stops it |
| "Set up a CPU alert at 80% on my prod DB" | Creates an alert rule with email notification |
| "Show me the backups for my main instance" | Lists all backups with sizes and timestamps |
| "Fork my production database for testing" | Creates an independent clone with a new password |
| "What's my estimated monthly cost for an r6i.xlarge with 500GB gp3?" | Calculates itemized cost breakdown |
| "Tune shared_buffers on my instance" | Previews defaults, applies the override, syncs to replicas |
| "Enable PgBouncer on my prod DB with 200 max clients" | Configures connection pooling, clients connect on port 6432 |
| "Enable ProxySQL pooling on my MySQL database" | Configures MySQL connection pooling; clients connect on port 6033 |
| "Spin up a ClickHouse cluster for analytics with 2 replicas" | Provisions a 3-node Keeper-quorum cluster (min 4 GB RAM per node), priced up front |
| "Enable CHProxy on my ClickHouse instance" | Configures ClickHouse query pooling; clients connect over HTTP on port 9090 |
| "Spin up an OpenSearch cluster for log search" | Prices the REAL machine count first - HA adds 3 cluster-manager nodes, so 1 replica is 5 billed nodes, not 2 - then provisions it |
| "Give me a Postgres 17 instance with TimescaleDB for sensor data" | Reads the extension catalog for that version and installs the extensions during provisioning |
| "Turn on PostGIS and pg_cron on my existing prod Postgres" | Tells you which of them restart PostgreSQL, which this instance cannot run and why, then enables them together |
| "Can my database survive losing a node without losing a write?" | Explains the three durability modes honestly - and that required buys RPO 0 by refusing writes when it cannot be held |
| "Alert me if my hypertable compression ratio drops below 5" | Creates a rule on the timescaledb_compression_ratio metric |
| "Tune innodb_buffer_pool_size on my MySQL instance" | Reads the current MySQL config and applies the override |
| "Add a read-only database user for the reporting app" | Creates a DBMS-level credential with the right grants |
| "Restore my prod database to 14:30 yesterday into a new instance" | Sets up a point-in-time recovery from WAL + base backups |
| "How has my prod database been doing this week?" | Summarizes CPU / memory / disk / engine metrics from the agent heartbeats |
| "Why is my database slow right now?" | Samples the live query workload and reads the engine log, no SSH involved |
| "Show me the Postgres error log" | Fetches the tail of whichever log you name, from a list of what that engine actually has |
| "Encrypt connections to my Postgres" | Turns on client TLS across the primary and every replica, and reminds you to move clients to sslmode=require |
| "Back up my ClickHouse data offsite every night" | Creates a BACKUP ALL schedule streaming to its own S3 bucket, with the retention you pick |
| "Restore that ClickHouse backup" | Restores it, after telling you plainly that it MERGES rather than rolling back |
| "Let my ClickHouse cluster grow and shrink with load" | Sets the tier floor and ceiling, quotes what the ceiling costs, and adds the up and down triggers |
| "My cluster stopped autoscaling" | Finds the failed scale plan blocking it - which nothing else surfaces - and retries or discards it |
| "Make Priya an admin" | Changes the member's role, and explains it if the hierarchy refuses |
| "POST to my service whenever a deploy fails" | Registers a webhook endpoint and hands you the signing secret |
| "How long will my credit last?" | Reports the balance, the burn rate, the runway, and any credit about to expire |
| "Scale my instance to r6i.2xlarge and 1TB" | Issues a manual scale, polls the resize event, and warns if the storage cascades to cluster peers |
| "Grow storage automatically before the disk fills up" | Creates a doubling storage rule up to a ceiling you approve, priced per node |
| "Auto-stop my staging DB at 7pm weekdays" | Creates a scheduled scaling policy with start/stop times |
| "Top up my wallet with $25" | Checks the balance and opens Razorpay checkout; can save the card for one-tap top-ups afterwards |
| "Stop my resources being stopped when credit runs out" | Sets up auto-recharge against a saved card, with a monthly cap you approve, and shows the burn rate that makes the threshold meaningful |
| "Prove my ClickHouse backups actually restore" | Schedules restore drills - a scratch instance is provisioned, restored into, row-count checked and torn down - and tells you they bill while they run |
Safety Built In
Destructive operations (delete, stop, reboot) require explicit confirmation. The server will always ask before proceeding - you'll never accidentally delete a production database.
One-Prompt App Templates
The console calls them one-click apps. From your assistant each one is a single prompt: name the app, and it deploys into a project, asks only for what that app actually takes, and tells you how to get in once it is up. 36 templates, every one the platform serves.
| You say | What happens |
|---------|-------------|
| "Deploy Supabase in my project" | Deploys Supabase with the Studio login you choose, and hands over the URL, the anon key and the credentials once it is live |
| "Give me somewhere to run notebooks" | Offers JupyterLab (Python) or Jupyter Data Science (Python, R and Julia), sets the password, and tells you it goes in the "Password or token" box |
| "I want VS Code in the browser" | Deploys code-server; one password signs you in and unlocks sudo in its terminal |
| "Set up a private VPN" | Deploys WireGuard with an HTTPS admin panel, one per project; clients connect over UDP 51820 |
| "Give me a Linux box I can SSH into" | Deploys an OpenSSH server with your public key on its own port, and hands you the ssh command |
| "Host my static site" | Deploys Nginx and explains how to upload your files over SSH |
| "Start a new Laravel app" | Deploys a fresh Laravel install on its own MySQL database |
| "I want n8n for my automations" | Deploys n8n and warns that the first person to open the URL becomes the owner |
Every template
| For | Template | What it is | What you set | First boot |
|---|---|---|---|---|
| Backends and automation | Supabase supabase | Postgres, Auth, storage and auto-generated APIs. | A username and a password | ~7 min |
| | n8n n8n | Workflow automation with hundreds of integrations. | Nothing: the first visitor claims the owner account | ~4 min |
| | n8n (Postgres) n8n-with-postgresql | The same n8n, backed by its own Postgres instead of SQLite. | Nothing: the first visitor claims the owner account | ~5 min |
| CRM and business | Twenty (CRM) twenty | Open-source CRM for contacts, deals and pipelines. | Nothing: the first visitor claims the owner account | ~14 min |
| | EspoCRM espocrm | Contacts, accounts and deals in a CRM you own. | A username (letters, digits, - and _) and a password | ~12 min |
| | Krayin (CRM) krayin | Leads, quotes and deals in an open-source CRM you own. | An email address and a password | ~15 min |
| | Odoo odoo | The open-source ERP and business suite. | A password, no username | ~8 min |
| | Chatwoot chatwoot | Live chat and a shared support inbox. | Nothing: the first visitor claims the owner account | ~12 min |
| | Mautic mautic | Marketing automation and campaigns. | Nothing: the first visitor claims the owner account | ~8 min |
| Websites and content | WordPress wordpress | MariaDB-backed WordPress for publishing and sites. | Nothing: the first visitor claims the owner account | ~6 min |
| | Strapi strapi | A headless CMS with an admin panel and a content API. | Nothing: the first visitor claims the owner account | ~6 min |
| | Nginx (static site) nginx | A static website served over HTTPS, with custom domains. | Nothing: no login, nothing to claim | ~10 min |
| | Laravel laravel | A fresh Laravel app on its own MySQL database. | Nothing: no login, nothing to claim | ~15 min |
| Developer tools | GitLab CE gitlab | Repositories, reviews and CI. | A password of 12+ characters; the user is always root | ~25 min |
| | Jenkins jenkins | The CI server for your builds. | A username (letters, digits, - and _) and a password | ~15 min |
| | code-server code-server | VS Code in the browser, with a terminal and your extensions. | A password of 12+ characters, no username | ~10 min |
| | OpenSSH server openssh-server | A Linux container you reach over SSH, with sudo and a home that persists. | A password of 12+ characters; the username is optional (default dev, a Linux login name) | ~10 min |
| | JupyterLab jupyterlab | Python notebooks with numpy, pandas and scikit-learn installed. | A password of 12+ characters, no username | ~15 min |
| | Jupyter Data Science jupyter-datascience | JupyterLab with Python, R and Julia notebooks. | A password of 12+ characters, no username | ~20 min |
| Search, analytics and dashboards | Elasticsearch elasticsearch | Search and analytics over your own documents. | A password; the user is always elastic | ~10 min |
| | Elasticsearch + Kibana elasticsearch-with-kibana | Search and analytics with a UI in front of it. | A password; the user is always elastic | ~8 min |
| | Grafana grafana | Dashboards and alerts over your metrics. | A username (letters, digits, - and _) and a password | ~5 min |
| | Grafana (Postgres) grafana-with-postgresql | The same Grafana, with its dashboards and alerts in a bundled Postgres. | A username (letters, digits, - and _) and a password | ~5 min |
| | Metabase metabase | Dashboards and questions over your data. | Nothing: the first visitor claims the owner account | ~7 min |
| Files, photos and documents | Nextcloud nextcloud | Files, calendar and contacts you host yourself. | A username and a password | ~15 min |
| | Immich immich | Photo and video backup for your phone. | Nothing: the first visitor claims the owner account | ~20 min |
| | Paperless-ngx paperless | A searchable, OCR-indexed archive of your documents. | A username and a password | ~8 min |
| | MinIO minio | S3-compatible object storage. | An S3 access key (3 to 20 characters) and secret key | ~3 min |
| AI | Open WebUI + Ollama open-webui-with-ollama | Chat with local models on your own box. | Nothing: the first visitor claims the owner account | ~11 min |
| | LiteLLM litellm | One OpenAI-compatible endpoint in front of every model provider. | A password; the user is always admin | ~6 min |
| | AnythingLLM anythingllm | Chat over your own documents. | A password, no username | ~6 min |
| Email | Stalwart (mail server) stalwart | A full mail server, SMTP through JMAP. | A password; the user is always admin | ~4 min |
| VPN and networking | WireGuard (wg-easy) wireguard | A private WireGuard VPN with an HTTPS admin panel. | A password of 12+ characters; the username is optional (default admin) | ~10 min |
| | Mysterium (VPN node) mysterium | A VPN node that shares the server's bandwidth on the Mysterium network, and earns for it. | A password of 12+ characters; the user is always myst | ~4 min |
| | Mysterium VPN Gateway mysterium-gateway | An HTTP proxy that routes your apps through the Mysterium VPN network, paid for in MYST. | Nothing: no login, nothing to claim | ~10 min |
| | URnetwork (bandwidth node) urnetwork | A provider node that shares the server's bandwidth with the URnetwork network. | An auth code from ur.io/app | ~10 min |
What the assistant tells you, unprompted
- The first visitor claims it. n8n, Twenty, WordPress, Immich, Strapi, Metabase, Chatwoot, Mautic and Open WebUI have no login at deploy time: the first person to open the URL becomes the owner. The assistant says so and asks you to finish setup straight away.
- Some apps share your network or spend money. Mysterium and URnetwork share the server's bandwidth with other people, and the Mysterium VPN Gateway is paid for in MYST. Each needs your explicit agreement before it deploys, and Mysterium's panel and the gateway's proxy open only to the IP address you name.
- No web address. Mysterium, the Mysterium VPN Gateway, URnetwork and the OpenSSH server have no URL, so they take no custom domain. Every other template can.
- One per project. Stalwart, WireGuard, Mysterium and the Mysterium VPN Gateway bind fixed ports, so a project holds one of each.
- A project is one server. Heavy templates need room, and a project's server cannot be resized, so running out means a new project. The assistant checks the recommended size first.
The tools behind this are in PaaS - Project Services.
Features
216 tools across 32 modules
Every tool maps to a real API endpoint. No mock data, no shortcuts.
Authentication & Users
Automatic browser-based sign-in with Firebase. Tokens refresh transparently.
| Tool | Description |
|------|-------------|
| create_or_login_user | Create account or sign in |
| get_current_user | Your profile, admin status, org membership |
| get_my_memberships | List your orgs and pending invitations |
| select_organization | Switch the active org for subsequent calls |
| get_invite_credits | Check remaining platform invite credits |
| send_platform_invite | Invite someone to join SelfHost |
| list_my_platform_invites | See invitations you've sent |
| reauthenticate | Sign out and sign in again (switch accounts) |
Organizations & Team Management
Multi-tenant - everything is scoped to your org.
| Tool | Description |
|------|-------------|
| list_organizations | All orgs you belong to |
| create_organization | Create a new org (you become owner, get 5 invite credits) |
| get_organization | Org details (name, members, payment-channel state) |
| update_organization | Change name, description, or IAM role |
| delete_organization | Soft-delete org and all related resources |
| list_members | See team members and their roles |
| update_member_role | Promote or demote an existing member (the only way to change a role after they've joined) |
| remove_member | Remove a member (owner/admin only) |
| invite_to_org | Send org invitation at a role you pick, by name |
| list_org_invitations | Pending invitations |
| cancel_invitation | Revoke a pending invitation |
| get_my_invitations | Invitations sent to you |
| list_activity_logs | Audit trail of all actions in the org |
Five roles, highest first: Owner (100), Admin (80), Billing (60), Manager (40), Member (20). Both invite and role-change take the role by name, so you never have to hunt for an id.
The hierarchy is enforced and the refusals are worth knowing in advance: an owner manages everyone, but an admin can only manage strictly below their own level - never a peer admin, and never granting a role at or above their own. Nobody can change their own role (self-demotion locks you out, self-promotion is an escalation), so an owner stepping down has to be demoted by another owner, and the last owner cannot be demoted or removed at all.
Cloud Credentials
Bring your own AWS account (BYOC) or use SelfHost's managed infrastructure.
| Tool | Description |
|------|-------------|
| list_credentials | List cloud credentials (secrets never exposed) |
| add_credential | Add AWS access keys or IAM role |
| update_credential | Rotate keys or update metadata |
| delete_credential | Remove a credential (with confirmation) |
| set_default_credential | Set as default for the org |
| get_aws_account_info | AWS account ID and free tier status |
Networking
VPCs, subnets, and security groups. Optional - auto-created during provisioning if not specified.
| Tool | Description |
|------|-------------|
| list_vpcs | List VPCs in a region (grouped by public/private) |
| create_vpc | Create VPC with internet gateway and subnets |
| delete_vpc | Remove VPC and associated resources (with confirmation) |
| list_subnets | List subnets in a region |
| create_subnet | Create subnet in a VPC |
| list_security_groups | List security groups |
| create_security_group | Create SG with inbound rules |
| update_security_group | Replace inbound rules on a security group |
Database Instances
The core feature. Full lifecycle management for PostgreSQL, MySQL, Redis, ClickHouse, and OpenSearch.
What each engine supports:
| | PostgreSQL | MySQL | Redis | ClickHouse | OpenSearch | |---|---|---|---|---|---| | Versions (default) | 18, 17, 16 | 8.4, 8.0 | 7.2, 7.0 | 26.8 | 2.19, 2.16, 2.13 | | Default port | 5432 | 3306 | 6379 | 8123 HTTP / 9000 native | 9200 (HTTPS only) | | Backups, snapshots, replicas, metrics, resize, fork | ✅ | ✅ | ✅ | ✅ | ✅ | | Connection pooling | PgBouncer (6432) | ProxySQL (6033) | - | CHProxy (9090) | - | | Parameter tuning | ✅ | ✅ | - | - | - | | Point-in-time recovery | ✅ | ✅ | - | - | - | | HA durability modes (RPO) | ✅ | - | - | - | - | | Database users | ✅ | ✅ | - | ✅ | ✅ | | Extensions | 11-entry catalog | - | - | - | - | | Named databases | ✅ | ✅ | - | ✅ | - | | Multi-AZ toggle after create | ✅ | ✅ | ✅ | ✅ | - | | Minimum RAM | - | - | - | 4 GB per node | 4 GB per node |
Leaving db_version off create_instance provisions the bold default above. The backend's own fallback is the oldest major each adapter accepts, so the tools always send the default explicitly - these match the console's create wizard, so both surfaces hand out the same database.
There is no in-place version upgrade on any engine. ClickHouse had one; selfhost#2580 withdrew it and pinned the platform to the 26.8 LTS, so a version change now means a new instance plus a restore or migration into it.
PostgreSQL HA durability is a three-way RPO dial (selfhost#2582), set at create with data_durability and changed later with set_postgres_durability. async acks locally (RPO > 0). preferred waits while standbys are healthy and degrades to no-wait when none are, so it never blocks writes and never guarantees zero loss. required is RPO 0 honestly implemented: when the synchronous set cannot be held, the primary stops accepting writes rather than acking data no survivor holds. Only required while sla_held is true may be described to a user as zero-data-loss. A 2-node required cluster has no automated failover, so it needs explicit no_auto_failover_ack.
PostgreSQL extensions are a catalog, not two flags. timescaledb_enabled / vector_enabled were removed backend-side - pass extensions: ["pgvector", ...] to create_instance instead, or add them later with enable_instance_extensions. See the extensions section below.
Two engines provision nodes the pricing endpoint cannot see, so pass type_of_dbms to estimate_instance_cost or the quote is wrong:
- ClickHouse replicates through a Keeper/Raft quorum, so multi-AZ needs at least one replica (2 is the recommended default). At exactly 1 replica the platform adds a third, keeper-only node (
t4g.small+ 20 GB) to hold the quorum, and it bills. At 2+ replicas the quorum runs embedded on the data nodes with no extra node. - OpenSearch HA is a cluster topology:
replica_countbuys data nodes, and the moment redundancy is asked for, 3 dedicated cluster-manager nodes are added on top. Soreplica_count: 1is 5 billed machines, not 2. That topology is also permanent - OpenSearch is the one engine whose multi-AZ cannot be toggled after create.
| Tool | Description |
|------|-------------|
| list_regions | Available AWS regions |
| get_instance_types_for_region | EC2 types with specs and hourly pricing |
| list_storage_types | EBS volume types with pricing |
| estimate_instance_cost | Itemized monthly cost breakdown before provisioning (all-in prices) |
| create_instance | Provision a new database (guided flow with cost confirmation) |
| list_instances | All instances grouped by master + replicas |
| get_instance | Detailed instance info with latest metrics |
| get_instance_metrics | Historical metric series (summarized: latest/min/max/avg per metric) |
| update_instance | Change config, resize, toggle multi-AZ, update CIDR ranges |
| stop_instance | Stop instances (with confirmation) |
| start_instance | Start stopped instances |
| reboot_instance | Reboot instances (with confirmation) |
| delete_instance | Delete instances with optional final snapshot (with confirmation) |
| fork_instance | Clone a database into a new independent instance |
| refresh_instance_group | Refresh cached state for a master + replica group |
| set_postgres_durability | Change the PostgreSQL HA durability mode - the RPO dial. Must be sent alone, master only (with confirmation) |
| get_opensearch_dashboards | Dashboards state, URL, and the RAM verdicts that decide whether it can be enabled (OpenSearch only) |
| set_opensearch_dashboards | Turn the OpenSearch Dashboards UI on or off on the cluster's endpoint node (with confirmation) |
PostgreSQL Configuration
Tune database parameters with calculated defaults per instance type.
| Tool | Description |
|------|-------------|
| preview_pg_config | See calculated defaults for an instance type (before creating) |
| get_pg_config | Current config with defaults, overrides, and effective values |
| update_pg_config | Apply overrides (auto-syncs to replicas, set null to revert) |
PostgreSQL Extensions (an 11-entry catalog)
A server-side catalog, not a pair of booleans. timescaledb_enabled and vector_enabled were removed from the API (selfhost#2516) - and a request still sending them is ignored rather than rejected, which is why v0.9.1's enable_timescaledb / enable_pgvector tools reported success and did nothing. They are gone.
Today's catalog: pgvector, timescaledb, pgcrypto, uuid-ossp, postgres_fdw, pg_partman, pg_stat_statements, pg_trgm, pg_cron, postgis, pg_textsearch. Read it from the API rather than this list - compatibility is per entry (pg_textsearch needs PostgreSQL 17 or 18) and the set has grown twice.
Forward-only. There is no disable path on any endpoint, because dropping an extension can break objects that depend on it.
| Tool | Description |
|------|-------------|
| list_postgres_extension_catalog | The catalog with no instance in play - for create_instance and project databases. Carries coolify_supported, which you must filter on before offering anything for a project database |
| list_instance_extensions | Per-instance state, split into enabled / available / unavailable (each with a reason) |
| enable_instance_extensions | Enable one or more on a running instance; pre-flights which of them restart PostgreSQL, are already on, or this instance cannot run (with confirmation) |
Pass extensions: [...] to create_instance to have them installed during provisioning - restart-requiring entries cost nothing extra on that path. Project (Coolify) databases take extensions at creation only, and only entries whose coolify_supported is true.
Once TimescaleDB is installed, five extra metrics join the instance's postgresql.database alert set: timescaledb_hypertable_count, timescaledb_chunk_count, timescaledb_compressed_chunk_count, timescaledb_compression_ratio, and timescaledb_total_size_bytes. They're absent from heartbeats until then, so don't write rules against them on a plain Postgres instance - they'd never fire.
PostgreSQL Client TLS
Encryption in transit for client connections. Group-wide: one call covers the primary and every replica, and certificates are issued per group.
| Tool | Description |
|------|-------------|
| set_postgres_tls | Turn client TLS on or off across a Postgres instance group (turning it off asks for confirmation - it downgrades every client connection) |
Once it's on, move clients to sslmode=require or stricter; a client left on the default may keep connecting in the clear. And note that a restore or a fork starts with TLS off even when the source had it on, because the new group has no certificate yet - re-enable it explicitly rather than assuming it carried over.
MySQL Configuration
Tune MySQL server parameters on a managed instance. No preview step - read the current config, then apply overrides.
| Tool | Description |
|------|-------------|
| get_mysql_config | Current config with defaults, overrides, and effective values |
| update_mysql_config | Apply overrides for max_connections, innodb_buffer_pool_size, tmp_table_size, and wait_timeout |
Backups & Snapshots
Two different things, and the tools keep them apart because a user asking to "restore a backup" means very different outcomes depending on which they have:
- Disk snapshots (
aws_backup_job, DLM policies,/snapshots) - block-level EBS snapshots. Every engine. Restored by creating a new instance from asnapshot_id. - Data backups (
clickhouse_native) - a fullBACKUP ALLof a ClickHouse instance's databases, streamed to a dedicated offsite S3 bucket. Restored in place withrestore_backup, and that restore merges rather than rolling back.
s3_file_backup was a third type and is now rejected by the backend; it is no longer offered.
Snapshot storage is metered (ebs_snapshot_storage, per GB-month against wallet credit) and every create path runs through the wallet gate, so the tools flag the cost up front and a 402 means an empty wallet or no linked payment method. A DLM policy holds retain_rule_count snapshots per volume, so its standing cost is roughly retention x volume size; deleting a snapshot is what stops its charge.
One asymmetry worth knowing before deleting a schedule: pausing (state: DISABLED) always keeps existing backups, but deleting a ClickHouse data-backup schedule also deletes every backup that schedule took. Deleting a disk-snapshot policy does not.
| Tool | Description |
|------|-------------|
| list_backups | List all backups, both families (syncs with AWS); read backup_type to tell them apart |
| create_backup | Take a disk snapshot (aws_backup_job) or a ClickHouse data backup (clickhouse_native) now |
| restore_backup | Restore a ClickHouse data backup, into its own instance or another one (merges, does not roll back) |
| delete_backup | Delete backup and AWS snapshot (with confirmation) |
| list_backup_policies | List backup schedules, both kinds (details.policy_type distinguishes them) |
| create_backup_policy | Create an automated disk-snapshot (DLM) schedule |
| create_data_backup_schedule | Create a recurring ClickHouse data-backup schedule to offsite S3 |
| update_backup_policy | Pause/resume (state) or change the cadence/retention of either kind |
| delete_backup_policy | Remove a schedule (with confirmation; a data-backup schedule takes its backups with it) |
| list_snapshots | List EBS snapshots in a region |
| get_snapshot | Snapshot details |
| delete_snapshot | Delete EBS snapshot (with confirmation) |
| create_snapshot | Create EBS snapshot from instance volume |
Alerts & Notifications
Monitor your databases and get notified when things go wrong. Rules target either cross-cutting metrics (system, network, storage) or your engine's own metric set - postgresql.database, mysql.database, redis.database, or clickhouse.database.
| Tool | Description |
|------|-------------|
| list_alert_rules | Active alert rules |
| create_alert_rule | Create rule (CPU, memory, disk, replication lag, etc.) |
| get_alert_rule | Rule details |
| update_alert_rule | Modify thresholds or notification channels |
| delete_alert_rule | Delete rule and all instances (with confirmation) |
| list_alert_instances | Fired/pending/resolved alerts (paginated) |
| get_alert_instance | Alert instance details |
| acknowledge_alert | Acknowledge a firing alert |
| resolve_alert | Manually resolve an alert |
| list_notification_channels | Email notification endpoints |
| create_notification_channel | Add email channel |
| update_notification_channel | Modify channel config |
| delete_notification_channel | Remove channel (with confirmation) |
| test_notification_channel | Send test notification |
Connection Pooling (PgBouncer + ProxySQL + CHProxy)
Reduces connection overhead for high-concurrency workloads. PgBouncer pools PostgreSQL (clients connect on port 6432); ProxySQL pools MySQL (clients connect on port 6033, admin on port 6032); CHProxy pools ClickHouse over HTTP (clients connect on port 9090, native TCP 9000 unaffected). Enable the one that matches your engine.
| Tool | Description |
|------|-------------|
| get_pgbouncer | Get current PgBouncer (Postgres) pooling config (or null if never configured) |
| enable_pgbouncer | Enable Postgres pooling with pool mode, max client conn, pool sizes |
| reconfigure_pgbouncer | Apply new PgBouncer settings on an enabled instance (PATCH-style) |
| disable_pgbouncer | Drain the PgBouncer pool and disable (with confirmation) |
| get_proxysql | Get current ProxySQL (MySQL) pooling config (or null if never configured) |
| enable_proxysql | Enable MySQL pooling (clients on port 6033, admin on port 6032) |
| reconfigure_proxysql | Apply new ProxySQL settings on an enabled instance (PATCH-style) |
| disable_proxysql | Drain the ProxySQL pool and disable (with confirmation) |
| get_chproxy | Get current CHProxy (ClickHouse) pooling config (or null if never configured) |
| enable_chproxy | Enable ClickHouse query pooling (clients on HTTP port 9090) with concurrency + queue limits |
| reconfigure_chproxy | Apply new CHProxy settings; also retries a failed enable without disabling first |
| disable_chproxy | Disable CHProxy and send clients back to port 8123 (with confirmation) |
Database Users
DBMS-level credentials - distinct from the SelfHost platform login. Use for apps and people that need their own DB user. PostgreSQL, MySQL, and ClickHouse (Redis authenticates with a single shared password, so it has no per-user credentials).
| Tool | Description |
|------|-------------|
| list_database_users | List users with role, status, target DB, expiry metadata |
| create_database_user | Create user with role (read_only / read_write / admin) and optional connection limit + password expiry |
| update_database_user | Change role, connection limit, or password expiry |
| delete_database_user | Soft-delete user (with confirmation) - drops active connections |
| rotate_database_user_password | Rotate password (with confirmation) - new value shown once |
Scaling
Manual one-off resize plus auto-scaling policies (reactive metric-based, scheduled cron, and auto start/stop).
| Tool | Description |
|------|-------------|
| scale_instance | Manual resize - change instance type, volume type, size, IOPS, throughput |
| list_scaling_policies | List policies, optionally filtered to one instance |
| create_scaling_policy | Create policy - reactive metric breach, scheduled cron, or auto start/stop schedule |
| update_scaling_policy | Modify thresholds, schedule, or action details |
| delete_scaling_policy | Remove a policy (with confirmation) |
Storage auto-scaling is cluster-wide and doubles. An expand_storage policy takes a ceiling (details.cluster_max_storage_gb), not a target size: each time it fires the volume doubles, capped at that ceiling and the volume type's maximum. The threshold must sit between 40 and 85 with a > / >= operator, and the rule can only attach to a data, non-replica node. When the volume commits, every live data peer in the cluster is expanded to match and storage bills per node - so on a multi-node cluster the cost is multiplied. The same cascade applies to a manual scale_instance on a data node.
Policies written before this contract (carrying details.new_size_gb) can no longer be edited or disabled - every save is rejected. Delete and recreate them.
Point-in-Time Recovery (PITR)
PostgreSQL and MySQL (Redis and ClickHouse excluded - snapshots are their recovery path). Continuous transaction-log archiving to S3 + restore to any second within the retention window. Restore creates a brand-new instance - the source is untouched.
| Tool | Description |
|------|-------------|
| get_pitr | Current config + restore window (oldest / latest restorable moments) |
| enable_pitr | Set up transaction-log archiving to an S3 bucket with retention days |
| configure_pitr | Change the retention window (retention only - it never started or stopped anything; v0.9.1 sent a status the endpoint ignored) |
| pause_pitr | Suspend scheduled base backups and retention pruning (with confirmation). WAL archiving and the S3 bill continue |
| resume_pitr | Resume a paused config, optionally with a new window; triggers a catch-up base backup if the last one went stale |
| retry_pitr | Retry after an error state (e.g. fix IAM, then retry) |
| trigger_pitr_base_backup | Run a one-off base backup now |
| list_pitr_base_backups | List historical base backups with status + timestamps |
| restore_pitr | Restore to a target time into a new instance (with confirmation) |
Billing & Wallet (PAYG)
Prepaid credit wallet + auto-recharge. No plans or tiers. Credit is consumed per usage and gated by wallet balance. All endpoints scoped to the active org.
There is no separate "link a card" call any more. POST billing/link was removed backend-side, so v0.9.1's link_billing_payment_method had been 404ing. A card is now saved by passing save_card: true on a real create_topup; DELETE billing/link survives as the un-link.
| Tool | Description |
|------|-------------|
| get_wallet_balance | Wallet balance, currency, payment state, hourly burn rate + runway |
| list_skus | Priced resource catalog (hourly rates); estimate how long a top-up lasts |
| list_credit_ledger | Credit lots: topup / free_credit (welcome grant, has expiry) / usage_charge |
| list_wallet_transactions | Per-resource hourly charges (applied / skipped) |
| create_topup | Buy credit. save_card stores the card for later (the only way to save one now), use_saved_card charges it off-session with no browser, and idempotency_key stops a blind retry double-charging |
| unlink_billing_payment_method | Remove the saved card (with confirmation) - this also stops auto-recharge being able to run |
| get_auto_recharge | Auto-recharge config, the saved cards available, the per-charge cap, and the burn/runway that makes the numbers meaningful |
| configure_auto_recharge | Set threshold / amount / monthly cap and the card to charge |
| enable_auto_recharge | Switch it on - a standing authorisation to charge the card unprompted (with confirmation) |
| disable_auto_recharge | Switch it off, keeping the settings (with confirmation) |
| update_billing_contact | Update billing phone / tax ID / country / state for invoices |
PaaS - Projects (app containers)
A project is a per-app container: a provisioned server + Coolify install + wildcard DNS that deployments, databases, and services attach to. Creation is async (~2-5 min) - poll get_project until status is active before deploying into it.
The project's server size and location are the main cost of a project, both are chosen at create time, and neither can be changed afterwards - a Hetzner server cannot be moved, and projects have no scaling path. Nineteen sizes across three families: cx* Cost-Optimized (cost_optimized, shared CPU, sold in Europe only), cpx* Regular (regular_purpose, shared CPU, in two generations that never share a location - the cpx*2 line sells in Europe + Singapore, the cpx*1 line is listed everywhere but in practice only carries stock in the US), ccx* Dedicated CPU (general_purpose, sold in all six). Six locations: Falkenstein and Nuremberg (Germany), Helsinki (Finland), Ashburn and Hillsboro (USA), Singapore.
Prices differ per location for the same size, so call list_server_types for live prices and per-location availability first, and name the location alongside any price. The cheapest US or APAC option is several times the cheapest European one, because no Cost-Optimized size is sold outside Europe.
Availability is a tri-state and it flaps: offered: false means the size is not sold in that location at all, offered: true, available: false means it is sold there but out of stock right now. Re-read before acting - it is normal for most of the cheap Cost-Optimized line to be out of stock across all of Europe at once, leaving cx23 as the only cheap size that can actually be provisioned.
| Tool | Description |
|------|-------------|
| list_server_types | Server sizes per location with availability and all-in hourly pricing (no auth, no rate limit) |
| list_projects | List projects in the org (pid, name, status, public_ip, domain) |
| get_project | Get one project; poll this for provisioning progress (active / failed) |
| create_project | Create a project on a chosen server_type + location, optionally with backups, and start async provisioning (returns status pending) |
| update_project | Rename / re-describe a project |
| delete_project | Tear down the server and everything in the project (with confirmation; blocked while provisioning or with running deployments) |
| get_project_metrics | Time-series server + database + deployment metrics from the monitoring agent (start_date/end_date) |
list_projects / get_project carry location, server_type, backups_enabled and a server-computed ready + readiness_detail. ready is not status == "active": a project is ready only when it is active and every service and database inside it is ready too, so an active project can legitimately still be waiting on a child.
PaaS - Project Backups
Native Hetzner backups of a project's whole VM - the only backup a project has, since its databases and services all live on that server. Free: there is no backup SKU for projects, so the platform absorbs the provider charge and the customer is billed nothing extra.
Daily, seven kept, both fixed. Hetzner assigns the window shortly after backups are switched on, so a just-enabled project reporting a null window is "pending", not broken.
Two different things live here. The backups tools toggle Hetzner's automatic daily schedule, which Hetzner rotates and deletes with the server - you cannot choose when one is taken. The snapshots tools are manual point-in-time images the user asks for and keeps until they delete them, capped at 10, which is what you want before a risky change. Both restore by rebuilding the whole VM, and neither is billed to the customer.
| Tool | Description |
|------|-------------|
| list_project_backups | Backups + schedule, read live from Hetzner (works on a stopped project) |
| enable_project_backups | Turn on automatic daily backups (free) |
| disable_project_backups | Turn them off and delete every existing backup (with confirmation) |
| restore_project_backup | Rebuild the server's disk from a backup - a true rollback; everything written since is lost (image id + exact project name + confirmation) |
| list_project_snapshots | On-demand snapshots + the cap (10). Unlike the dailies these are never rotated - they persist until deleted |
| create_project_snapshot | Snapshot the whole server now. The tool to reach for before a risky change; also free |
| restore_project_snapshot | Rebuild the VM from a snapshot - the most destructive project operation (image id + exact project name + confirmation) |
| delete_project_snapshot | Delete one snapshot to free a slot (with confirmation) |
PaaS - Project Activity
| Tool | Description |
|------|-------------|
| list_project_activities | One chronological feed merging Hetzner server actions with platform events, each tagged source; paginated, works on a stopped project |
Reading both sources together is the point: a project that stopped answering shows the platform event and the server action side by side, which usually separates "we stopped it for billing" from "the machine was rebuilt". If Hetzner is unreachable the feed degrades to platform events and sets meta.server_activities_available: false - check that before concluding nothing happened.
PaaS - Deployments (GitHub apps)
A deployment is an app built from a GitHub repo, living inside an active project. Build/run history is returned inline on the deployment.
last_deploy_status is not a run status. It is the deployment's own rolled-up state and it carries one value no run has: crashed, meaning the build succeeded and the container has since died. So a deployment whose newest run is active can be crashed right now - read it before telling anyone their app is up. Org owners are emailed once if a crash-loop episode lasts past ~10 minutes.
| Tool | Description |
|------|-------------|
| list_deployments | List apps in the org, each with inline build runs (newest 50) |
| get_deployment | Get one app + runs; poll this for deploy/run progress |
| create_deployment | Deploy a GitHub repo into a project (repo_url, branch, build_pack, port, env_vars, …) |
| update_deployment | Pause/resume, toggle auto-deploy, set ignore_paths / max preview deployments |
| update_deployment_build_config | Change build_pack / base_directory / port / dockerfile / env_vars |
| delete_deployment | Remove the app and its Coolify resources (with confirmation) |
| trigger_deploy | Manually deploy the current branch (new run) |
| abort_deploy | Stop the in-flight build run (with confirmation). Nothing is rolled back - what is deployed keeps serving |
| get_deployment_health | Reachability of the deploy URL + attached domains |
| get_run_logs | The log surface: build and running-container output for one run (poll until terminal) |
| redeploy_run | Re-run a build. Same commit when the run recorded one; branch HEAD when it did not (manual runs never record a SHA, and those used to be refused) |
| rollback_run | Re-deploy the commit from a prior successful run |
PaaS - Custom Domains
| Tool | Description |
|------|-------------|
| add_deployment_domain | Attach a custom domain to a deployment (returns DNS records to add) |
| sync_deployment_domains | Set the full domain list for a deployment (set primary / remove several) |
| remove_deployment_domain | Remove one domain from a deployment |
| verify_custom_domain | Check DNS (TXT + CNAME) and advance verification → live HTTPS |
| list_custom_domains | List the org's custom-domain verification states |
PaaS - Environment Variables
| Tool | Description |
|------|-------------|
| list_env_vars | List a deployment's env vars (values returned in cleartext) |
| detect_env_vars | Scan the repo for the vars the app expects, flagging which are already set |
| set_env_vars | Replace the entire env-var set (omitted keys are deleted) |
| merge_env_vars | Add/update env vars without deleting others (safe upsert) |
| delete_env_var | Remove one variable by key |
PaaS - GitHub
| Tool | Description |
|------|-------------|
| list_github_installations | Connected GitHub App installations + their accessible repos |
| list_repo_branches | Branches for a repo via an installation |
| detect_repo_config | Auto-detect build_pack / port / monorepo dirs / env-var hints for a repo |
| get_github_connect_url | Get a browser URL to connect GitHub (interactive; then re-list installations) |
| disconnect_github_installation | Disconnect one installation (with confirmation; blocked while deployments use it) |
| disconnect_all_github_installations | Disconnect every installation on the org (with confirmation) |
PaaS - Project Databases
Postgres/Redis/MySQL/MongoDB provisioned inside a project (distinct from the managed AWS instances). Postgres here can also take TimescaleDB or pgvector, but at creation only - a project database can't gain an extension later, and only one of the two images can run (TimescaleDB wins if you ask for both).
| Tool | Description |
|------|-------------|
| list_project_databases | List databases attached to a project |
| get_project_database | One database, status re-read live from Coolify (plus provisioning stages) |
| create_project_database | Provision a Postgres/Redis/MySQL/MongoDB in a project (type-specific credentials; Postgres optionally with TimescaleDB or pgvector) |
| delete_project_database | Delete a project database (exact name match + confirmation) |
PaaS - Project Services (one-prompt app templates)
Thirty-six templates, every one the backend serves: supabase, n8n, n8n-with-postgresql, twenty, wordpress, gitlab, elasticsearch, elasticsearch-with-kibana, immich, nextcloud, jenkins, grafana, grafana-with-postgresql, strapi, metabase, anythingllm, stalwart, chatwoot, mautic, minio, open-webui-with-ollama, litellm, odoo, paperless, espocrm, krayin, mysterium, mysterium-gateway, urnetwork, nginx, wireguard, code-server, openssh-server, jupyterlab, jupyter-datascience, laravel. The backend auto-discovers them from its strategy files, so it ships templates ahead of its clients - an unrecognised template_type still lists, deletes, inspects and takes a domain.
Five credential shapes, and the wrong one is refused before the request goes out (the backend silently drops fields a template does not accept, which would leave a user believing they set a password):
- username + password - becomes the app's admin login. The accepted username form varies: a slug for Jenkins/Grafana/EspoCRM, an email for Krayin (a bare
admincreates an account nobody can sign in as), a Linux login name for the OpenSSH server, anything for the rest. WireGuard and the OpenSSH server default it (admin,dev). - password only - the app owns the username (GitLab is always
root, Elasticsearchelastic, Mysteriummyst) or has none (code-server, JupyterLab, Jupyter Data Science). - nothing - the first visitor to the public URL claims the owner account. Every create of one of these says so unprompted.
- no login and nothing to claim - a static site (Nginx), a fresh app with no accounts yet (Laravel) or an HTTP proxy with no UI (the Mysterium VPN Gateway).
- an auth code - URnetwork takes the token from ur.io/app, and it is never repeated back.
Template-specific fields, each refused on any other template: Mysterium's panel_source and the gateway's proxy_source (the IP or CIDR allowed in; never a world range), the gateway's country and service_type, and the OpenSSH server's public_key and password_access (key-only sign-in needs a key). Mysterium, the gateway and URnetwork share the server's network with other people or spend MYST outside the platform, so each needs acknowledge_network_terms: true from the user. Four templates have no web UI (Mysterium, the gateway, URnetwork, the OpenSSH server), so no URL and no custom domain, and four refuse is_public: false because the backend would never open the port they are reached on (Mysterium, the gateway, WireGuard, the OpenSSH server).
Capacity is a refusal at declare time, not slow degradation. A project is one VM: heavy templates must leave more headroom (65% memory / 75% disk vs 80% / 85%), some need a minimum total host RAM, at most 5 children exist per project, Stalwart, WireGuard, Mysterium and the Mysterium VPN Gateway are one-per-project (their ports are fixed on the host), and Krayin cannot run on an ARM (cax*) server at all. A project server cannot be resized, so exhausting it means a new project.
Boot windows run from ~3 min (MinIO) to ~25 (GitLab). The create response names the expected window so nothing gets called stuck early.
| Tool | Description |
|------|-------------|
| list_project_services | List deployed templates (status, provisioning stages, error_message) - no credentials |
| get_project_service | One service including its credentials - the only endpoint that returns them |
| create_project_service | Deploy any of the 36 templates into a project, with per-template credential validation |
| delete_project_service | Delete a project service (exact name match + confirmation) |
| set_project_service_custom_domain | Point a domain at a service's web UI (every template but the four without one). DNS must already resolve to the project IP - the backend refuses otherwise, to avoid burning Let's Encrypt quota on certain-to-fail issuance |
| remove_project_service_custom_domain | Re-pin the platform hostname (with confirmation) - breaks anything registered against the custom domain |
Container inspection (services and project databases)
Read-only inspection of the Docker containers on a project's box, shared by services and project databases. Request-then-read, like the managed-database log surface. Read-only by construction: the backend emits only allow-listed docker ps / logs / stats / inspect, scoped to that resource's containers - there is deliberately no start / stop / restart / remove / exec surface anywhere.
| Tool | Description |
|------|-------------|
| request_project_container_logs | Dispatch a log capture (default 200 lines, max 1000) |
| get_project_container_logs | Read the captured logs - where a crash loop or failed migration actually shows up |
| request_project_container_stats | Dispatch a docker stats snapshot (CPU, memory, network, block IO) |
| get_project_container_stats | Read it - this is the answer to "why is my project server full?" |
PaaS - SSH Access to Project Servers
Customer shell access to a project's own server: an org-wide public-key library, per-project installation, and the firewall rule deciding who may reach port 22. The backend shipped this on 2026-08-29 and nothing consumed it for a fortnight - no console surface and no MCP tool - so the only way in was us adding a key by hand.
It is not root. Keys land on a dedicated non-root shell user with no sudo and no docker group, so the command is ssh shell@<ip>. The box also runs platform components (the Coolify control plane and its keys, Traefik, our agent), which a root or docker grant would expose. Consequently the shell user cannot see containers - they live under root-owned /var/lib/docker - so container logs and stats come from the inspection tools above, not from SSH.
Public keys only on this path; a pasted private key is refused (and the tools refuse it before it crosses the wire). A platform-generated keypair is a different route: create_project with generate_ssh_key, which returns the private half exactly once and never stores it.
Scope is per server. authorized_keys is a per-box file, so a key on project A grants nothing on project B - and deleting a library key does not revoke access on servers it is already installed on.
| Tool | Description |
|------|-------------|
| list_org_ssh_keys | The org's public-key library (a key here grants nothing until installed) |
| add_org_ssh_key | Save a public key to the library |
| delete_org_ssh_key | Remove a library entry (with confirmation). Does not revoke - the key stays on servers it was installed on |
| list_project_ssh_keys | Installed and pending keys, the ready-to-copy ssh_command, and the source firewall |
| add_project_ssh_key | Install a key on one project. access_level: write is not installed - it queues for a SelfHost platform admin's approval |
| remove_project_ssh_key | Revoke one key on one project (with confirmation) - this is what actually removes it from authorized_keys |
| set_project_ssh_access | Restrict port 22 to anywhere / my_ip / custom CIDRs (with confirmation). my_ip captures the address this MCP server connects from, not the user's laptop |
PaaS - Deployment Notifications
| Tool | Description |
|------|-------------|
| list_deployment_notification_bindings | Channels notified on a deployment's deploy outcomes |
| bind_deployment_notification_channel | Notify an org channel (nchan_…) on deploy success/failure |
| unbind_deployment_notification_channel | Stop notifying a channel for a deployment |
Database Logs & Query Stats
On-demand diagnostics for a managed database, no SSH. Postgres, MySQL and ClickHouse. Request-then-read: the POST creates a pending fetch, the platform agent fills it in, and you read the result with the matching list tool. Both need a positive wallet balance; a POST needs cloud_instances:update. Replicas are allowed deliberately.
Don't poll: a pending fetch is returned as-is instead of starting a second one, and there is a short per-source cooldown after one finishes.
| Tool | Description |
|------|-------------|
| list_log_sources | Log sources available on this instance (engine, replication, backup/restore, and more on ClickHouse) |
| fetch_instance_logs | Request a tail of one source (default 200 lines, max 2000, output capped at 1 MB) |
| list_instance_log_fetches | Last 20 fetches with their output - this is where the log text arrives |
| capture_db_stats | Sample the query workload over 5-60s (default 10) - counters are read twice and differenced |
| list_db_stats | Last 20 samples with their results |
Cluster Capacity Tiers
Moving a whole instance group up and down a ladder of machine sizes, node by node, without downtime. Not the same as scale_instance or a single-instance resize_instance rule - a single-node switchover fired at a multi-node cluster desyncs its tier state, which is why this exists. AWS and the data plane in this version.
Two moving parts, both required. The capacity config is the guardrails (which ladder, min/max tier, on/off); the tier policies are the triggers, created through create_scaling_policy with scaling_action: resize_instance + group_id. The config must exist and be enabled before a policy can reference it, and up and down are separate policies with enforced hysteresis.
| Tool | Description |
|------|-------------|
| list_capacity_ladders | The ordered machine-size ladder per engine - every tier bound is an index into this |
| get_capacity_config | Bounds, current tier, in-flight plan, and last_failed_scale_plan |
| create_capacity_config | Set a group's tier floor and ceiling (the ceiling is a spend ceiling) |
| update_capacity_config | Change bounds/window, or pause with enabled: false |
| delete_capacity_config | Remove the guardrails (current size unchanged; 422 while policies reference it) |
| retry_cluster_scale_plan | Resume a failed plan from where it stopped |
| delete_cluster_scale_plan | Abandon a failed plan |
A failed scale plan blocks all further automatic scaling for its group, and get_capacity_config is the only place it surfaces. If a cluster's scaling has quietly stopped working, look at last_failed_scale_plan first.
Webhook Endpoints
Org-wide outgoing HTTPS callbacks. Distinct from deployment notification bindings, which push one deployment's outcome to Slack or email; these POST typed events to a receiver you run. HTTPS only; the signing secret is generated server-side at create.
| Tool | Description |
|------|-------------|
| list_webhook_endpoints | The org's endpoints and what each subscribes to |
| get_webhook_endpoint | One endpoint's URL and event list |
| create_webhook_endpoint | Register an HTTPS receiver for one or more event types |
| update_webhook_endpoint | Change the URL or replace the event list |
| delete_webhook_endpoint | Remove an endpoint and its delivery history (with confirmation) |
Events: instance.scaling_failed, deploy_run.succeeded / .failed / .aborted, preview_deployment.created / .destroyed, database_user.password_rotated. That last one is the signal a credential consumer needs when a database user has password_rotation_enabled - the payload never carries the password, so the receiver refetches with rotate_database_user_password. Subscribing to it is what makes automatic rotation safe instead of breaking.
Common Workflows
Deploy an app from GitHub (Projects)
You: "Deploy my-org/web to a new project"
Claude: Shows sizes and locations with live prices → you pick
→ creates the project → polls until provisioned and active
→ detects the build config from the repo (build pack, port)
→ creates the deployment; it comes back `provisioning` with no
URL yet, which is normal, so it keeps polling
→ streams the build log